forked from rosa/hakurei
Compare commits
75 Commits
19c76e0831
...
staging
| Author | SHA1 | Date | |
|---|---|---|---|
|
e34e3b917e
|
|||
|
b0ba165107
|
|||
|
351d6c5a35
|
|||
|
f23f73701c
|
|||
|
876917229a
|
|||
|
0558032c2d
|
|||
|
c61cdc505f
|
|||
|
062edb3487
|
|||
|
e4355279a1
|
|||
|
289fdebead
|
|||
|
9c9e190db9
|
|||
|
d7d42c69a1
|
|||
|
c758e762bd
|
|||
|
10f8b1c221
|
|||
|
6907700d67
|
|||
|
0243f3ffbd
|
|||
|
cd0beeaf8e
|
|||
|
a69273ab2a
|
|||
|
4cd0f57e48
|
|||
|
33a0e6c01b
|
|||
|
d58f5c7590
|
|||
|
1da992e342
|
|||
|
9641805ec2
|
|||
|
0738f4889a
|
|||
|
7de3cfe221
|
|||
|
8b0648dd5d
|
|||
|
4667fac76c
|
|||
|
52e5443b0e
|
|||
|
130e470b60
|
|||
|
ba5ee8e3ee
|
|||
|
d1cef30877
|
|||
|
0188a3f0c7
|
|||
|
04fe3b24ce
|
|||
|
93ad551054
|
|||
|
3d54d1f176
|
|||
|
9feac7738f
|
|||
|
591a60bac9
|
|||
|
5093a06026
|
|||
|
50c1d7f880
|
|||
|
9e63633fbc
|
|||
|
61f981a34a
|
|||
|
d717c41bbe
|
|||
|
b896eec9b7
|
|||
|
8ab99e5e40
|
|||
|
2b6160ef7d
|
|||
|
4dcac7f133
|
|||
|
966fd4df9e
|
|||
|
a2cf59b989
|
|||
|
e87f59c4e4
|
|||
|
3b221c3e77
|
|||
|
ff3b385b12
|
|||
|
c6920e6ab7
|
|||
|
59b25d45fe
|
|||
|
9b99650eb1
|
|||
|
15bff9e1a6
|
|||
|
b948525c07
|
|||
|
9acbd16e9a
|
|||
|
64e5a1068b
|
|||
|
b6cbd49d8c
|
|||
|
6913b9224a
|
|||
|
9584958ecc
|
|||
|
389844b1ea
|
|||
|
5b7ab35633
|
|||
|
52b1a5a725
|
|||
|
6b78df8714
|
|||
|
dadf170a46
|
|||
|
9594832302
|
|||
|
91a2d4d6e1
|
|||
|
a854719b9f
|
|||
|
f03c0fb249
|
|||
|
a6600be34a
|
|||
|
b5592633f5
|
|||
|
584e302168
|
|||
|
141958656f
|
|||
|
648079f42c
|
31
.gitignore
vendored
31
.gitignore
vendored
@@ -1,27 +1,7 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.pkg
|
||||
/hakurei
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
# produced by tools and text editors
|
||||
*.qcow2
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
@@ -30,8 +10,5 @@ go.work.sum
|
||||
/internal/pkg/testdata/testtool
|
||||
/internal/rosa/hakurei_current.tar.gz
|
||||
|
||||
# release
|
||||
/dist/hakurei-*
|
||||
|
||||
# interactive nixos vm
|
||||
nixos.qcow2
|
||||
# cmd/dist default destination
|
||||
/dist
|
||||
|
||||
6
all.sh
Executable file
6
all.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
TOOLCHAIN_VERSION="$(go version)"
|
||||
cd "$(dirname -- "$0")/"
|
||||
echo "# Building cmd/dist using ${TOOLCHAIN_VERSION}."
|
||||
go run -v --tags=dist ./cmd/dist
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -61,7 +61,7 @@ func (a *Absolute) Is(v *Absolute) bool {
|
||||
|
||||
// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute.
|
||||
func NewAbs(pathname string) (*Absolute, error) {
|
||||
if !path.IsAbs(pathname) {
|
||||
if !filepath.IsAbs(pathname) {
|
||||
return nil, AbsoluteError(pathname)
|
||||
}
|
||||
return unsafeAbs(pathname), nil
|
||||
@@ -76,13 +76,13 @@ func MustAbs(pathname string) *Absolute {
|
||||
}
|
||||
}
|
||||
|
||||
// Append calls [path.Join] with [Absolute] as the first element.
|
||||
// Append calls [filepath.Join] with [Absolute] as the first element.
|
||||
func (a *Absolute) Append(elem ...string) *Absolute {
|
||||
return unsafeAbs(path.Join(append([]string{a.String()}, elem...)...))
|
||||
return unsafeAbs(filepath.Join(append([]string{a.String()}, elem...)...))
|
||||
}
|
||||
|
||||
// Dir calls [path.Dir] with [Absolute] as its argument.
|
||||
func (a *Absolute) Dir() *Absolute { return unsafeAbs(path.Dir(a.String())) }
|
||||
// Dir calls [filepath.Dir] with [Absolute] as its argument.
|
||||
func (a *Absolute) Dir() *Absolute { return unsafeAbs(filepath.Dir(a.String())) }
|
||||
|
||||
// GobEncode returns the checked pathname.
|
||||
func (a *Absolute) GobEncode() ([]byte, error) {
|
||||
@@ -92,7 +92,7 @@ func (a *Absolute) GobEncode() ([]byte, error) {
|
||||
// GobDecode stores data if it represents an absolute pathname.
|
||||
func (a *Absolute) GobDecode(data []byte) error {
|
||||
pathname := string(data)
|
||||
if !path.IsAbs(pathname) {
|
||||
if !filepath.IsAbs(pathname) {
|
||||
return AbsoluteError(pathname)
|
||||
}
|
||||
a.pathname = unique.Make(pathname)
|
||||
@@ -110,7 +110,7 @@ func (a *Absolute) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &pathname); err != nil {
|
||||
return err
|
||||
}
|
||||
if !path.IsAbs(pathname) {
|
||||
if !filepath.IsAbs(pathname) {
|
||||
return AbsoluteError(pathname)
|
||||
}
|
||||
a.pathname = unique.Make(pathname)
|
||||
|
||||
237
cmd/dist/main.go
vendored
Normal file
237
cmd/dist/main.go
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
//go:build dist
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
_ "embed"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// getenv looks up an environment variable, and returns fallback if it is unset.
|
||||
func getenv(key, fallback string) string {
|
||||
if v, ok := os.LookupEnv(key); ok {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// mustRun runs a command with the current process's environment and panics
|
||||
// on error or non-zero exit code.
|
||||
func mustRun(ctx context.Context, name string, arg ...string) {
|
||||
cmd := exec.CommandContext(ctx, name, arg...)
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
//go:embed comp/_hakurei
|
||||
var comp []byte
|
||||
|
||||
func main() {
|
||||
fmt.Println()
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("# ")
|
||||
|
||||
version := getenv("HAKUREI_VERSION", "untagged")
|
||||
prefix := getenv("PREFIX", "/usr")
|
||||
destdir := getenv("DESTDIR", "dist")
|
||||
|
||||
if err := os.MkdirAll(destdir, 0755); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
s, err := os.MkdirTemp(destdir, ".dist.*")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
var code int
|
||||
|
||||
if err = os.RemoveAll(s); err != nil {
|
||||
code = 1
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
if r := recover(); r != nil {
|
||||
code = 1
|
||||
log.Println(r)
|
||||
}
|
||||
|
||||
os.Exit(code)
|
||||
}()
|
||||
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
log.Println("Building hakurei.")
|
||||
mustRun(ctx, "go", "generate", "./...")
|
||||
mustRun(
|
||||
ctx, "go", "build",
|
||||
"-trimpath",
|
||||
"-v", "-o", s,
|
||||
"-ldflags=-s -w "+
|
||||
"-buildid= -linkmode external -extldflags=-static "+
|
||||
"-X hakurei.app/internal/info.buildVersion="+version+" "+
|
||||
"-X hakurei.app/internal/info.hakureiPath="+prefix+"/bin/hakurei "+
|
||||
"-X hakurei.app/internal/info.hsuPath="+prefix+"/bin/hsu "+
|
||||
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
||||
"./...",
|
||||
)
|
||||
fmt.Println()
|
||||
|
||||
log.Println("Testing Hakurei.")
|
||||
mustRun(
|
||||
ctx, "go", "test",
|
||||
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
||||
"./...",
|
||||
)
|
||||
fmt.Println()
|
||||
|
||||
log.Println("Creating distribution.")
|
||||
const suffix = ".tar.gz"
|
||||
distName := "hakurei-" + version + "-" + runtime.GOARCH
|
||||
var f *os.File
|
||||
if f, err = os.OpenFile(
|
||||
filepath.Join(s, distName+suffix),
|
||||
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
|
||||
0644,
|
||||
); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer func() {
|
||||
if f == nil {
|
||||
return
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
|
||||
h := sha512.New()
|
||||
gw := gzip.NewWriter(io.MultiWriter(f, h))
|
||||
tw := tar.NewWriter(gw)
|
||||
|
||||
mustWriteHeader := func(name string, size int64, mode os.FileMode) {
|
||||
header := tar.Header{
|
||||
Name: filepath.Join(distName, name),
|
||||
Size: size,
|
||||
Mode: int64(mode),
|
||||
Uname: "root",
|
||||
Gname: "root",
|
||||
}
|
||||
|
||||
if mode&os.ModeDir != 0 {
|
||||
header.Typeflag = tar.TypeDir
|
||||
fmt.Printf("%s %s\n", mode, name)
|
||||
} else {
|
||||
header.Typeflag = tar.TypeReg
|
||||
fmt.Printf("%s %s (%d bytes)\n", mode, name, size)
|
||||
}
|
||||
|
||||
if err = tw.WriteHeader(&header); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
mustWriteFile := func(name string, data []byte, mode os.FileMode) {
|
||||
mustWriteHeader(name, int64(len(data)), mode)
|
||||
if mode&os.ModeDir != 0 {
|
||||
return
|
||||
}
|
||||
if _, err = tw.Write(data); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
mustWriteFromPath := func(dst, src string, mode os.FileMode) {
|
||||
var r *os.File
|
||||
if r, err = os.Open(src); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var fi os.FileInfo
|
||||
if fi, err = r.Stat(); err != nil {
|
||||
_ = r.Close()
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if mode == 0 {
|
||||
mode = fi.Mode()
|
||||
}
|
||||
|
||||
mustWriteHeader(dst, fi.Size(), mode)
|
||||
if _, err = io.Copy(tw, r); err != nil {
|
||||
_ = r.Close()
|
||||
panic(err)
|
||||
} else if err = r.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
mustWriteFile(".", nil, fs.ModeDir|0755)
|
||||
mustWriteFile("comp/", nil, os.ModeDir|0755)
|
||||
mustWriteFile("comp/_hakurei", comp, 0644)
|
||||
mustWriteFile("install.sh", []byte(`#!/bin/sh -e
|
||||
cd "$(dirname -- "$0")" || exit 1
|
||||
|
||||
install -vDm0755 "bin/hakurei" "${DESTDIR}`+prefix+`/bin/hakurei"
|
||||
install -vDm0755 "bin/sharefs" "${DESTDIR}`+prefix+`/bin/sharefs"
|
||||
|
||||
install -vDm4511 "bin/hsu" "${DESTDIR}`+prefix+`/bin/hsu"
|
||||
if [ ! -f "${DESTDIR}/etc/hsurc" ]; then
|
||||
install -vDm0400 "hsurc.default" "${DESTDIR}/etc/hsurc"
|
||||
fi
|
||||
|
||||
install -vDm0644 "comp/_hakurei" "${DESTDIR}`+prefix+`/share/zsh/site-functions/_hakurei"
|
||||
`), 0755)
|
||||
|
||||
mustWriteFromPath("README.md", "README.md", 0)
|
||||
mustWriteFile("hsurc.default", []byte("1000 0"), 0400)
|
||||
mustWriteFromPath("bin/hsu", filepath.Join(s, "hsu"), 04511)
|
||||
for _, name := range []string{
|
||||
"hakurei",
|
||||
"sharefs",
|
||||
} {
|
||||
mustWriteFromPath(
|
||||
filepath.Join("bin", name),
|
||||
filepath.Join(s, name),
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
if err = tw.Close(); err != nil {
|
||||
panic(err)
|
||||
} else if err = gw.Close(); err != nil {
|
||||
panic(err)
|
||||
} else if err = f.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
f = nil
|
||||
|
||||
if err = os.WriteFile(
|
||||
filepath.Join(destdir, distName+suffix+".sha512"),
|
||||
append(hex.AppendEncode(nil, h.Sum(nil)), " "+distName+suffix+"\n"...),
|
||||
0644,
|
||||
); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err = os.Rename(
|
||||
filepath.Join(s, distName+suffix),
|
||||
filepath.Join(destdir, distName+suffix),
|
||||
); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -102,13 +102,13 @@ func main() {
|
||||
log.Fatal("this program must not be started by root")
|
||||
}
|
||||
|
||||
if !path.IsAbs(hakureiPath) {
|
||||
if !filepath.IsAbs(hakureiPath) {
|
||||
log.Fatal("this program is compiled incorrectly")
|
||||
return
|
||||
}
|
||||
|
||||
var toolPath string
|
||||
pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
|
||||
pexe := filepath.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
|
||||
if p, err := os.Readlink(pexe); err != nil {
|
||||
log.Fatalf("cannot read parent executable path: %v", err)
|
||||
} else if strings.HasSuffix(p, " (deleted)") {
|
||||
|
||||
@@ -69,11 +69,12 @@ func main() {
|
||||
}()
|
||||
|
||||
var (
|
||||
flagQuiet bool
|
||||
flagCures int
|
||||
flagBase string
|
||||
flagTShift int
|
||||
flagIdle bool
|
||||
flagQuiet bool
|
||||
flagCures int
|
||||
flagBase string
|
||||
flagIdle bool
|
||||
|
||||
flagHostAbstract bool
|
||||
)
|
||||
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) {
|
||||
msg.SwapVerbose(!flagQuiet)
|
||||
@@ -89,19 +90,15 @@ func main() {
|
||||
} else if base, err = check.NewAbs(flagBase); err != nil {
|
||||
return
|
||||
}
|
||||
if cache, err = pkg.Open(ctx, msg, flagCures, base); err == nil {
|
||||
if flagTShift < 0 {
|
||||
cache.SetThreshold(0)
|
||||
} else if flagTShift > 31 {
|
||||
cache.SetThreshold(1 << 31)
|
||||
} else {
|
||||
cache.SetThreshold(1 << flagTShift)
|
||||
}
|
||||
}
|
||||
|
||||
var flags int
|
||||
if flagIdle {
|
||||
pkg.SetSchedIdle = true
|
||||
flags |= pkg.CSchedIdle
|
||||
}
|
||||
if flagHostAbstract {
|
||||
flags |= pkg.CHostAbstract
|
||||
}
|
||||
cache, err = pkg.Open(ctx, msg, flags, flagCures, base)
|
||||
|
||||
return
|
||||
}).Flag(
|
||||
@@ -116,14 +113,17 @@ func main() {
|
||||
&flagBase,
|
||||
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
||||
"Directory to store cured artifacts",
|
||||
).Flag(
|
||||
&flagTShift,
|
||||
"tshift", command.IntFlag(-1),
|
||||
"Dependency graph size exponent, to the power of 2",
|
||||
).Flag(
|
||||
&flagIdle,
|
||||
"sched-idle", command.BoolFlag(false),
|
||||
"Set SCHED_IDLE scheduling policy",
|
||||
).Flag(
|
||||
&flagHostAbstract,
|
||||
"host-abstract", command.BoolFlag(
|
||||
os.Getenv("MBF_HOST_ABSTRACT") != "",
|
||||
),
|
||||
"Do not restrict networked cure containers from connecting to host "+
|
||||
"abstract UNIX sockets",
|
||||
)
|
||||
|
||||
{
|
||||
@@ -618,6 +618,9 @@ func main() {
|
||||
z.Hostname = "localhost"
|
||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
if s, ok := os.LookupEnv("TERM"); ok {
|
||||
z.Env = append(z.Env, "TERM="+s)
|
||||
}
|
||||
|
||||
var tempdir *check.Absolute
|
||||
if s, err := filepath.Abs(os.TempDir()); err != nil {
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
#endif
|
||||
|
||||
#define SHAREFS_MEDIA_RW_ID (1 << 10) - 1 /* owning gid presented to userspace */
|
||||
#define SHAREFS_PERM_DIR 0700 /* permission bits for directories presented to userspace */
|
||||
#define SHAREFS_PERM_REG 0600 /* permission bits for regular files presented to userspace */
|
||||
#define SHAREFS_PERM_DIR 0770 /* permission bits for directories presented to userspace */
|
||||
#define SHAREFS_PERM_REG 0660 /* permission bits for regular files presented to userspace */
|
||||
#define SHAREFS_FORBIDDEN_FLAGS O_DIRECT /* these open flags are cleared unconditionally */
|
||||
|
||||
/* sharefs_private is populated by sharefs_init and contains process-wide context */
|
||||
|
||||
@@ -19,12 +19,11 @@ import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/cgo"
|
||||
"strconv"
|
||||
@@ -145,9 +144,9 @@ func sharefs_destroy(private_data unsafe.Pointer) {
|
||||
func showHelp(args *fuseArgs) {
|
||||
executableName := sharefsName
|
||||
if args.argc > 0 {
|
||||
executableName = path.Base(C.GoString(*args.argv))
|
||||
executableName = filepath.Base(C.GoString(*args.argv))
|
||||
} else if name, err := os.Executable(); err == nil {
|
||||
executableName = path.Base(name)
|
||||
executableName = filepath.Base(name)
|
||||
}
|
||||
|
||||
fmt.Printf("usage: %s [options] <mountpoint>\n\n", executableName)
|
||||
@@ -470,13 +469,14 @@ func _main(s ...string) (exitCode int) {
|
||||
os.NewFile(uintptr(C.fuse_session_fd(se)), "fuse"),
|
||||
))
|
||||
|
||||
var setupWriter io.WriteCloser
|
||||
if fd, w, err := container.Setup(&z.ExtraFiles); err != nil {
|
||||
var setupPipe [2]*os.File
|
||||
if r, w, err := os.Pipe(); err != nil {
|
||||
log.Println(err)
|
||||
return 5
|
||||
} else {
|
||||
z.Args = append(z.Args, "-osetup="+strconv.Itoa(fd))
|
||||
setupWriter = w
|
||||
z.Args = append(z.Args, "-osetup="+strconv.Itoa(3+len(z.ExtraFiles)))
|
||||
z.ExtraFiles = append(z.ExtraFiles, r)
|
||||
setupPipe[0], setupPipe[1] = r, w
|
||||
}
|
||||
|
||||
if err := z.Start(); err != nil {
|
||||
@@ -487,6 +487,9 @@ func _main(s ...string) (exitCode int) {
|
||||
}
|
||||
return 5
|
||||
}
|
||||
if err := setupPipe[0].Close(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := z.Serve(); err != nil {
|
||||
if m, ok := message.GetMessage(err); ok {
|
||||
log.Println(m)
|
||||
@@ -496,10 +499,10 @@ func _main(s ...string) (exitCode int) {
|
||||
return 5
|
||||
}
|
||||
|
||||
if err := gob.NewEncoder(setupWriter).Encode(&setup); err != nil {
|
||||
if err := gob.NewEncoder(setupPipe[1]).Encode(&setup); err != nil {
|
||||
log.Println(err)
|
||||
return 5
|
||||
} else if err = setupWriter.Close(); err != nil {
|
||||
} else if err = setupPipe[1].Close(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
|
||||
122
cmd/sharefs/test/raceattr.go
Normal file
122
cmd/sharefs/test/raceattr.go
Normal file
@@ -0,0 +1,122 @@
|
||||
//go:build raceattr
|
||||
|
||||
// The raceattr program reproduces vfs inode file attribute race.
|
||||
//
|
||||
// Even though libfuse high-level API presents the address of a struct stat
|
||||
// alongside struct fuse_context, file attributes are actually inherent to the
|
||||
// inode, instead of the specific call from userspace. The kernel implementation
|
||||
// in fs/fuse/xattr.c appears to make stale data in the inode (set by a previous
|
||||
// call) impossible or very unlikely to reach userspace via the stat family of
|
||||
// syscalls. However, when using default_permissions to have the VFS check
|
||||
// permissions, this race still happens, despite the resulting struct stat being
|
||||
// correct when overriding the check via capabilities otherwise.
|
||||
//
|
||||
// This program reproduces the failure, but because of its continuous nature, it
|
||||
// is provided independent of the vm integration test suite.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func newStatAs(
|
||||
ctx context.Context, cancel context.CancelFunc,
|
||||
n *atomic.Uint64, ok *atomic.Bool,
|
||||
uid uint32, pathname string,
|
||||
continuous bool,
|
||||
) func() {
|
||||
return func() {
|
||||
runtime.LockOSThread()
|
||||
defer cancel()
|
||||
|
||||
if _, _, errno := syscall.Syscall(
|
||||
syscall.SYS_SETUID, uintptr(uid),
|
||||
0, 0,
|
||||
); errno != 0 {
|
||||
cancel()
|
||||
log.Printf("cannot set uid to %d: %s", uid, errno)
|
||||
}
|
||||
|
||||
var stat syscall.Stat_t
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := syscall.Lstat(pathname, &stat); err != nil {
|
||||
// SHAREFS_PERM_DIR not world executable, or
|
||||
// SHAREFS_PERM_REG not world readable
|
||||
if !continuous {
|
||||
cancel()
|
||||
}
|
||||
ok.Store(true)
|
||||
log.Printf("uid %d: %v", uid, err)
|
||||
} else if stat.Uid != uid {
|
||||
// appears to be unreachable
|
||||
if !continuous {
|
||||
cancel()
|
||||
}
|
||||
ok.Store(true)
|
||||
log.Printf("got uid %d instead of %d", stat.Uid, uid)
|
||||
}
|
||||
n.Add(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("raceattr: ")
|
||||
|
||||
p := flag.String("target", "/sdcard/raceattr", "pathname of test file")
|
||||
u0 := flag.Int("uid0", 1<<10-1, "first uid")
|
||||
u1 := flag.Int("uid1", 1<<10-2, "second uid")
|
||||
count := flag.Int("count", 1, "threads per uid")
|
||||
continuous := flag.Bool("continuous", false, "keep running even after reproduce")
|
||||
flag.Parse()
|
||||
|
||||
if os.Geteuid() != 0 {
|
||||
log.Fatal("this program must run as root")
|
||||
}
|
||||
|
||||
ctx, cancel := signal.NotifyContext(
|
||||
context.Background(),
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGHUP,
|
||||
)
|
||||
|
||||
if err := os.WriteFile(*p, nil, 0); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
|
||||
n atomic.Uint64
|
||||
ok atomic.Bool
|
||||
)
|
||||
|
||||
if *count < 1 {
|
||||
*count = 1
|
||||
}
|
||||
for range *count {
|
||||
wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u0), *p, *continuous))
|
||||
if *u1 >= 0 {
|
||||
wg.Go(newStatAs(ctx, cancel, &n, &ok, uint32(*u1), *p, *continuous))
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
if !*continuous && ok.Load() {
|
||||
log.Printf("reproduced after %d calls", n.Load())
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,6 @@ const (
|
||||
// CancelSignal is the signal expected by container init on context cancel.
|
||||
// A custom [Container.Cancel] function must eventually deliver this signal.
|
||||
CancelSignal = SIGUSR2
|
||||
|
||||
// Timeout for writing initParams to Container.setup.
|
||||
initSetupTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -53,7 +50,7 @@ type (
|
||||
ExtraFiles []*os.File
|
||||
|
||||
// Write end of a pipe connected to the init to deliver [Params].
|
||||
setup *os.File
|
||||
setup [2]*os.File
|
||||
// Cancels the context passed to the underlying cmd.
|
||||
cancel context.CancelFunc
|
||||
// Closed after Wait returns. Keeps the spawning thread alive.
|
||||
@@ -287,14 +284,16 @@ func (p *Container) Start() error {
|
||||
}
|
||||
|
||||
// place setup pipe before user supplied extra files, this is later restored by init
|
||||
if fd, f, err := Setup(&p.cmd.ExtraFiles); err != nil {
|
||||
if r, w, err := os.Pipe(); err != nil {
|
||||
return &StartError{
|
||||
Fatal: true,
|
||||
Step: "set up params stream",
|
||||
Err: err,
|
||||
}
|
||||
} else {
|
||||
p.setup = f
|
||||
fd := 3 + len(p.cmd.ExtraFiles)
|
||||
p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, r)
|
||||
p.setup[0], p.setup[1] = r, w
|
||||
p.cmd.Env = []string{setupEnv + "=" + strconv.Itoa(fd)}
|
||||
}
|
||||
p.cmd.ExtraFiles = append(p.cmd.ExtraFiles, p.ExtraFiles...)
|
||||
@@ -324,9 +323,9 @@ func (p *Container) Start() error {
|
||||
}
|
||||
|
||||
if abi, err := LandlockGetABI(); err != nil {
|
||||
if p.HostAbstract {
|
||||
if p.HostAbstract || !p.HostNet {
|
||||
// landlock can be skipped here as it restricts access
|
||||
// to resources already covered by namespaces (pid)
|
||||
// to resources already covered by namespaces (pid, net)
|
||||
goto landlockOut
|
||||
}
|
||||
return &StartError{Step: "get landlock ABI", Err: err}
|
||||
@@ -428,24 +427,33 @@ func (p *Container) Start() error {
|
||||
// Serve serves [Container.Params] to the container init.
|
||||
//
|
||||
// Serve must only be called once.
|
||||
func (p *Container) Serve() error {
|
||||
if p.setup == nil {
|
||||
func (p *Container) Serve() (err error) {
|
||||
if p.setup[0] == nil || p.setup[1] == nil {
|
||||
panic("invalid serve")
|
||||
}
|
||||
|
||||
setup := p.setup
|
||||
p.setup = nil
|
||||
if err := setup.SetDeadline(time.Now().Add(initSetupTimeout)); err != nil {
|
||||
done := make(chan struct{})
|
||||
defer func() {
|
||||
if closeErr := p.setup[1].Close(); err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
p.cancel()
|
||||
}
|
||||
close(done)
|
||||
p.setup[0], p.setup[1] = nil, nil
|
||||
}()
|
||||
if err = p.setup[0].Close(); err != nil {
|
||||
return &StartError{
|
||||
Fatal: true,
|
||||
Step: "set init pipe deadline",
|
||||
Step: "close read end of init pipe",
|
||||
Err: err,
|
||||
Passthrough: true,
|
||||
}
|
||||
}
|
||||
|
||||
if p.Path == nil {
|
||||
p.cancel()
|
||||
return &StartError{
|
||||
Step: "invalid executable pathname",
|
||||
Err: EINVAL,
|
||||
@@ -461,18 +469,27 @@ func (p *Container) Serve() error {
|
||||
p.SeccompRules = make([]std.NativeRule, 0)
|
||||
}
|
||||
|
||||
err := gob.NewEncoder(setup).Encode(&initParams{
|
||||
t := time.Now().UTC()
|
||||
go func(f *os.File) {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
if cancelErr := f.SetWriteDeadline(t); cancelErr != nil {
|
||||
p.msg.Verbose(err)
|
||||
}
|
||||
|
||||
case <-done:
|
||||
p.msg.Verbose("setup payload took", time.Since(t))
|
||||
return
|
||||
}
|
||||
}(p.setup[1])
|
||||
|
||||
return gob.NewEncoder(p.setup[1]).Encode(&initParams{
|
||||
p.Params,
|
||||
Getuid(),
|
||||
Getgid(),
|
||||
len(p.ExtraFiles),
|
||||
p.msg.IsVerbose(),
|
||||
})
|
||||
_ = setup.Close()
|
||||
if err != nil {
|
||||
p.cancel()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait blocks until the container init process to exit and releases any
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/command"
|
||||
@@ -26,6 +25,8 @@ import (
|
||||
"hakurei.app/ext"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal/params"
|
||||
"hakurei.app/ldd"
|
||||
"hakurei.app/message"
|
||||
"hakurei.app/vfs"
|
||||
@@ -84,9 +85,9 @@ func TestStartError(t *testing.T) {
|
||||
{"params env", &container.StartError{
|
||||
Fatal: true,
|
||||
Step: "set up params stream",
|
||||
Err: container.ErrReceiveEnv,
|
||||
Err: params.ErrReceiveEnv,
|
||||
}, "set up params stream: environment variable not set",
|
||||
container.ErrReceiveEnv, syscall.EBADF,
|
||||
params.ErrReceiveEnv, syscall.EBADF,
|
||||
"cannot set up params stream: environment variable not set"},
|
||||
|
||||
{"params", &container.StartError{
|
||||
@@ -436,11 +437,8 @@ func TestContainer(t *testing.T) {
|
||||
wantOps, wantOpsCtx := tc.ops(t)
|
||||
wantMnt := tc.mnt(t, wantOpsCtx)
|
||||
|
||||
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
var libPaths []*check.Absolute
|
||||
c := helperNewContainerLibPaths(ctx, &libPaths, "container", strconv.Itoa(i))
|
||||
c := helperNewContainerLibPaths(t.Context(), &libPaths, "container", strconv.Itoa(i))
|
||||
c.Uid = tc.uid
|
||||
c.Gid = tc.gid
|
||||
c.Hostname = hostnameFromTestCase(tc.name)
|
||||
@@ -450,7 +448,6 @@ func TestContainer(t *testing.T) {
|
||||
} else {
|
||||
c.Stdout, c.Stderr = os.Stdout, os.Stderr
|
||||
}
|
||||
c.WaitDelay = helperDefaultTimeout
|
||||
*c.Ops = append(*c.Ops, *wantOps...)
|
||||
c.SeccompRules = tc.rules
|
||||
c.SeccompFlags = tc.flags | seccomp.AllowMultiarch
|
||||
@@ -458,6 +455,15 @@ func TestContainer(t *testing.T) {
|
||||
c.SeccompDisable = !tc.filter
|
||||
c.RetainSession = tc.session
|
||||
c.HostNet = tc.net
|
||||
if info.CanDegrade {
|
||||
if _, err := container.LandlockGetABI(); err != nil {
|
||||
if !errors.Is(err, syscall.ENOSYS) {
|
||||
t.Fatalf("LandlockGetABI: error = %v", err)
|
||||
}
|
||||
c.HostAbstract = true
|
||||
t.Log("Landlock LSM is unavailable, enabling HostAbstract")
|
||||
}
|
||||
}
|
||||
|
||||
c.
|
||||
Readonly(check.MustAbs(pathReadonly), 0755).
|
||||
@@ -553,11 +559,10 @@ func testContainerCancel(
|
||||
) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx, cancel := context.WithTimeout(t.Context(), helperDefaultTimeout)
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
|
||||
c := helperNewContainer(ctx, "block")
|
||||
c.Stdout, c.Stderr = os.Stdout, os.Stderr
|
||||
c.WaitDelay = helperDefaultTimeout
|
||||
if containerExtra != nil {
|
||||
containerExtra(c)
|
||||
}
|
||||
@@ -738,8 +743,7 @@ func init() {
|
||||
const (
|
||||
envDoCheck = "HAKUREI_TEST_DO_CHECK"
|
||||
|
||||
helperDefaultTimeout = 5 * time.Second
|
||||
helperInnerPath = "/usr/bin/helper"
|
||||
helperInnerPath = "/usr/bin/helper"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/ext"
|
||||
"hakurei.app/internal/netlink"
|
||||
"hakurei.app/internal/params"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
@@ -56,7 +57,7 @@ type syscallDispatcher interface {
|
||||
// isatty provides [Isatty].
|
||||
isatty(fd int) bool
|
||||
// receive provides [Receive].
|
||||
receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error)
|
||||
receive(key string, e any, fdp *int) (closeFunc func() error, err error)
|
||||
|
||||
// bindMount provides procPaths.bindMount.
|
||||
bindMount(msg message.Msg, source, target string, flags uintptr) error
|
||||
@@ -155,8 +156,8 @@ func (direct) capBoundingSetDrop(cap uintptr) error { return capBound
|
||||
func (direct) capAmbientClearAll() error { return capAmbientClearAll() }
|
||||
func (direct) capAmbientRaise(cap uintptr) error { return capAmbientRaise(cap) }
|
||||
func (direct) isatty(fd int) bool { return ext.Isatty(fd) }
|
||||
func (direct) receive(key string, e any, fdp *uintptr) (func() error, error) {
|
||||
return Receive(key, e, fdp)
|
||||
func (direct) receive(key string, e any, fdp *int) (func() error, error) {
|
||||
return params.Receive(key, e, fdp)
|
||||
}
|
||||
|
||||
func (direct) bindMount(msg message.Msg, source, target string, flags uintptr) error {
|
||||
@@ -179,7 +180,7 @@ func (direct) mustLoopback(ctx context.Context, msg message.Msg) {
|
||||
lo = ifi.Index
|
||||
}
|
||||
|
||||
c, err := netlink.DialRoute()
|
||||
c, err := netlink.DialRoute(0)
|
||||
if err != nil {
|
||||
msg.GetLogger().Fatalln(err)
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@ func (k *kstub) isatty(fd int) bool {
|
||||
return expect.Ret.(bool)
|
||||
}
|
||||
|
||||
func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) {
|
||||
func (k *kstub) receive(key string, e any, fdp *int) (closeFunc func() error, err error) {
|
||||
k.Helper()
|
||||
expect := k.Expects("receive")
|
||||
|
||||
@@ -408,10 +408,17 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// avoid changing test cases
|
||||
var fdpComp *uintptr
|
||||
if fdp != nil {
|
||||
fdpComp = new(uintptr(*fdp))
|
||||
}
|
||||
|
||||
err = expect.Error(
|
||||
stub.CheckArg(k.Stub, "key", key, 0),
|
||||
stub.CheckArgReflect(k.Stub, "e", e, 1),
|
||||
stub.CheckArgReflect(k.Stub, "fdp", fdp, 2))
|
||||
stub.CheckArgReflect(k.Stub, "fdp", fdpComp, 2))
|
||||
|
||||
// 3 is unused so stores params
|
||||
if expect.Args[3] != nil {
|
||||
@@ -426,7 +433,7 @@ func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error
|
||||
if expect.Args[4] != nil {
|
||||
if v, ok := expect.Args[4].(uintptr); ok && v >= 3 {
|
||||
if fdp != nil {
|
||||
*fdp = v
|
||||
*fdp = int(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/ext"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/params"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
@@ -147,35 +148,33 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
}
|
||||
|
||||
var (
|
||||
params initParams
|
||||
closeSetup func() error
|
||||
setupFd uintptr
|
||||
offsetSetup int
|
||||
param initParams
|
||||
closeSetup func() error
|
||||
setupFd int
|
||||
)
|
||||
if f, err := k.receive(setupEnv, ¶ms, &setupFd); err != nil {
|
||||
if f, err := k.receive(setupEnv, ¶m, &setupFd); err != nil {
|
||||
if errors.Is(err, EBADF) {
|
||||
k.fatal(msg, "invalid setup descriptor")
|
||||
}
|
||||
if errors.Is(err, ErrReceiveEnv) {
|
||||
if errors.Is(err, params.ErrReceiveEnv) {
|
||||
k.fatal(msg, setupEnv+" not set")
|
||||
}
|
||||
|
||||
k.fatalf(msg, "cannot decode init setup payload: %v", err)
|
||||
} else {
|
||||
if params.Ops == nil {
|
||||
if param.Ops == nil {
|
||||
k.fatal(msg, "invalid setup parameters")
|
||||
}
|
||||
if params.ParentPerm == 0 {
|
||||
params.ParentPerm = 0755
|
||||
if param.ParentPerm == 0 {
|
||||
param.ParentPerm = 0755
|
||||
}
|
||||
|
||||
msg.SwapVerbose(params.Verbose)
|
||||
msg.SwapVerbose(param.Verbose)
|
||||
msg.Verbose("received setup parameters")
|
||||
closeSetup = f
|
||||
offsetSetup = int(setupFd + 1)
|
||||
}
|
||||
|
||||
if !params.HostNet {
|
||||
if !param.HostNet {
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), CancelSignal,
|
||||
os.Interrupt, SIGTERM, SIGQUIT)
|
||||
defer cancel() // for panics
|
||||
@@ -188,7 +187,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
k.fatalf(msg, "cannot set SUID_DUMP_USER: %v", err)
|
||||
}
|
||||
if err := k.writeFile(fhs.Proc+"self/uid_map",
|
||||
append([]byte{}, strconv.Itoa(params.Uid)+" "+strconv.Itoa(params.HostUid)+" 1\n"...),
|
||||
append([]byte{}, strconv.Itoa(param.Uid)+" "+strconv.Itoa(param.HostUid)+" 1\n"...),
|
||||
0); err != nil {
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
@@ -198,7 +197,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
if err := k.writeFile(fhs.Proc+"self/gid_map",
|
||||
append([]byte{}, strconv.Itoa(params.Gid)+" "+strconv.Itoa(params.HostGid)+" 1\n"...),
|
||||
append([]byte{}, strconv.Itoa(param.Gid)+" "+strconv.Itoa(param.HostGid)+" 1\n"...),
|
||||
0); err != nil {
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
@@ -207,8 +206,8 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
}
|
||||
|
||||
oldmask := k.umask(0)
|
||||
if params.Hostname != "" {
|
||||
if err := k.sethostname([]byte(params.Hostname)); err != nil {
|
||||
if param.Hostname != "" {
|
||||
if err := k.sethostname([]byte(param.Hostname)); err != nil {
|
||||
k.fatalf(msg, "cannot set hostname: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -221,7 +220,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
state := &setupState{process: make(map[int]WaitStatus), Params: ¶ms.Params, Msg: msg, Context: ctx}
|
||||
state := &setupState{process: make(map[int]WaitStatus), Params: ¶m.Params, Msg: msg, Context: ctx}
|
||||
defer cancel()
|
||||
|
||||
/* early is called right before pivot_root into intermediate root;
|
||||
@@ -229,7 +228,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
difficult to obtain via library functions after pivot_root, and
|
||||
implementations are expected to avoid changing the state of the mount
|
||||
namespace */
|
||||
for i, op := range *params.Ops {
|
||||
for i, op := range *param.Ops {
|
||||
if op == nil || !op.Valid() {
|
||||
k.fatalf(msg, "invalid op at index %d", i)
|
||||
}
|
||||
@@ -272,7 +271,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
step sets up the container filesystem, and implementations are expected to
|
||||
keep the host root and sysroot mount points intact but otherwise can do
|
||||
whatever they need to. Calling chdir is allowed but discouraged. */
|
||||
for i, op := range *params.Ops {
|
||||
for i, op := range *param.Ops {
|
||||
// ops already checked during early setup
|
||||
if prefix, ok := op.prefix(); ok {
|
||||
msg.Verbosef("%s %s", prefix, op)
|
||||
@@ -328,7 +327,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
k.fatalf(msg, "cannot clear the ambient capability set: %v", err)
|
||||
}
|
||||
for i := uintptr(0); i <= lastcap; i++ {
|
||||
if params.Privileged && i == CAP_SYS_ADMIN {
|
||||
if param.Privileged && i == CAP_SYS_ADMIN {
|
||||
continue
|
||||
}
|
||||
if err := k.capBoundingSetDrop(i); err != nil {
|
||||
@@ -337,7 +336,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
}
|
||||
|
||||
var keep [2]uint32
|
||||
if params.Privileged {
|
||||
if param.Privileged {
|
||||
keep[capToIndex(CAP_SYS_ADMIN)] |= capToMask(CAP_SYS_ADMIN)
|
||||
|
||||
if err := k.capAmbientRaise(CAP_SYS_ADMIN); err != nil {
|
||||
@@ -351,13 +350,13 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
k.fatalf(msg, "cannot capset: %v", err)
|
||||
}
|
||||
|
||||
if !params.SeccompDisable {
|
||||
rules := params.SeccompRules
|
||||
if !param.SeccompDisable {
|
||||
rules := param.SeccompRules
|
||||
if len(rules) == 0 { // non-empty rules slice always overrides presets
|
||||
msg.Verbosef("resolving presets %#x", params.SeccompPresets)
|
||||
rules = seccomp.Preset(params.SeccompPresets, params.SeccompFlags)
|
||||
msg.Verbosef("resolving presets %#x", param.SeccompPresets)
|
||||
rules = seccomp.Preset(param.SeccompPresets, param.SeccompFlags)
|
||||
}
|
||||
if err := k.seccompLoad(rules, params.SeccompFlags); err != nil {
|
||||
if err := k.seccompLoad(rules, param.SeccompFlags); err != nil {
|
||||
// this also indirectly asserts PR_SET_NO_NEW_PRIVS
|
||||
k.fatalf(msg, "cannot load syscall filter: %v", err)
|
||||
}
|
||||
@@ -366,10 +365,10 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
msg.Verbose("syscall filter not configured")
|
||||
}
|
||||
|
||||
extraFiles := make([]*os.File, params.Count)
|
||||
extraFiles := make([]*os.File, param.Count)
|
||||
for i := range extraFiles {
|
||||
// setup fd is placed before all extra files
|
||||
extraFiles[i] = k.newFile(uintptr(offsetSetup+i), "extra file "+strconv.Itoa(i))
|
||||
extraFiles[i] = k.newFile(uintptr(setupFd+1+i), "extra file "+strconv.Itoa(i))
|
||||
}
|
||||
k.umask(oldmask)
|
||||
|
||||
@@ -447,7 +446,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
|
||||
// called right before startup of initial process, all state changes to the
|
||||
// current process is prohibited during late
|
||||
for i, op := range *params.Ops {
|
||||
for i, op := range *param.Ops {
|
||||
// ops already checked during early setup
|
||||
if err := op.late(state, k); err != nil {
|
||||
if m, ok := messageFromError(err); ok {
|
||||
@@ -468,14 +467,14 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
k.fatalf(msg, "cannot close setup pipe: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command(params.Path.String())
|
||||
cmd := exec.Command(param.Path.String())
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
cmd.Args = params.Args
|
||||
cmd.Env = params.Env
|
||||
cmd.Args = param.Args
|
||||
cmd.Env = param.Env
|
||||
cmd.ExtraFiles = extraFiles
|
||||
cmd.Dir = params.Dir.String()
|
||||
cmd.Dir = param.Dir.String()
|
||||
|
||||
msg.Verbosef("starting initial process %s", params.Path)
|
||||
msg.Verbosef("starting initial process %s", param.Path)
|
||||
if err := k.start(cmd); err != nil {
|
||||
k.fatalf(msg, "%v", err)
|
||||
}
|
||||
@@ -493,9 +492,9 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
for {
|
||||
select {
|
||||
case s := <-sig:
|
||||
if s == CancelSignal && params.ForwardCancel && cmd.Process != nil {
|
||||
if s == CancelSignal && param.ForwardCancel && cmd.Process != nil {
|
||||
msg.Verbose("forwarding context cancellation")
|
||||
if err := k.signal(cmd, os.Interrupt); err != nil {
|
||||
if err := k.signal(cmd, os.Interrupt); err != nil && !errors.Is(err, os.ErrProcessDone) {
|
||||
k.printf(msg, "cannot forward cancellation: %v", err)
|
||||
}
|
||||
continue
|
||||
@@ -525,7 +524,7 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
||||
cancel()
|
||||
|
||||
// start timeout early
|
||||
go func() { time.Sleep(params.AdoptWaitDelay); close(timeout) }()
|
||||
go func() { time.Sleep(param.AdoptWaitDelay); close(timeout) }()
|
||||
|
||||
// close initial process files; this also keeps them alive
|
||||
for _, f := range extraFiles {
|
||||
@@ -569,7 +568,7 @@ func TryArgv0(msg message.Msg) {
|
||||
msg = message.New(log.Default())
|
||||
}
|
||||
|
||||
if len(os.Args) > 0 && path.Base(os.Args[0]) == initName {
|
||||
if len(os.Args) > 0 && filepath.Base(os.Args[0]) == initName {
|
||||
Init(msg)
|
||||
msg.BeforeExit()
|
||||
os.Exit(0)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/internal/params"
|
||||
"hakurei.app/internal/stub"
|
||||
)
|
||||
|
||||
@@ -40,7 +41,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
call("lockOSThread", stub.ExpectArgs{}, nil, nil),
|
||||
call("getpid", stub.ExpectArgs{}, 1, nil),
|
||||
call("setPtracer", stub.ExpectArgs{uintptr(0)}, nil, nil),
|
||||
call("receive", stub.ExpectArgs{"HAKUREI_SETUP", new(initParams), new(uintptr)}, nil, ErrReceiveEnv),
|
||||
call("receive", stub.ExpectArgs{"HAKUREI_SETUP", new(initParams), new(uintptr)}, nil, params.ErrReceiveEnv),
|
||||
call("fatal", stub.ExpectArgs{[]any{"HAKUREI_SETUP not set"}}, nil, nil),
|
||||
},
|
||||
}, nil},
|
||||
|
||||
@@ -3,7 +3,7 @@ package container
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
. "syscall"
|
||||
|
||||
"hakurei.app/check"
|
||||
@@ -46,7 +46,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
}
|
||||
|
||||
for _, name := range []string{"null", "zero", "full", "random", "urandom", "tty"} {
|
||||
targetPath := path.Join(target, name)
|
||||
targetPath := filepath.Join(target, name)
|
||||
if err := k.ensureFile(targetPath, 0444, state.ParentPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
for i, name := range []string{"stdin", "stdout", "stderr"} {
|
||||
if err := k.symlink(
|
||||
fhs.Proc+"self/fd/"+string(rune(i+'0')),
|
||||
path.Join(target, name),
|
||||
filepath.Join(target, name),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -72,13 +72,13 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
{fhs.Proc + "kcore", "core"},
|
||||
{"pts/ptmx", "ptmx"},
|
||||
} {
|
||||
if err := k.symlink(pair[0], path.Join(target, pair[1])); err != nil {
|
||||
if err := k.symlink(pair[0], filepath.Join(target, pair[1])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
devShmPath := path.Join(target, "shm")
|
||||
devPtsPath := path.Join(target, "pts")
|
||||
devShmPath := filepath.Join(target, "shm")
|
||||
devPtsPath := filepath.Join(target, "pts")
|
||||
for _, name := range []string{devShmPath, devPtsPath} {
|
||||
if err := k.mkdir(name, state.ParentPerm); err != nil {
|
||||
return err
|
||||
@@ -92,7 +92,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
|
||||
if state.RetainSession {
|
||||
if k.isatty(Stdout) {
|
||||
consolePath := path.Join(target, "console")
|
||||
consolePath := filepath.Join(target, "console")
|
||||
if err := k.ensureFile(consolePath, 0444, state.ParentPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func (d *MountDevOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
}
|
||||
|
||||
if d.Mqueue {
|
||||
mqueueTarget := path.Join(target, "mqueue")
|
||||
mqueueTarget := filepath.Join(target, "mqueue")
|
||||
if err := k.mkdir(mqueueTarget, state.ParentPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package container
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
@@ -30,7 +30,7 @@ func (l *SymlinkOp) Valid() bool { return l != nil && l.Target != nil && l.LinkN
|
||||
|
||||
func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
|
||||
if l.Dereference {
|
||||
if !path.IsAbs(l.LinkName) {
|
||||
if !filepath.IsAbs(l.LinkName) {
|
||||
return check.AbsoluteError(l.LinkName)
|
||||
}
|
||||
if name, err := k.readlink(l.LinkName); err != nil {
|
||||
@@ -44,7 +44,7 @@ func (l *SymlinkOp) early(_ *setupState, k syscallDispatcher) error {
|
||||
|
||||
func (l *SymlinkOp) apply(state *setupState, k syscallDispatcher) error {
|
||||
target := toSysroot(l.Target.String())
|
||||
if err := k.mkdirAll(path.Dir(target), state.ParentPerm); err != nil {
|
||||
if err := k.mkdirAll(filepath.Dir(target), state.ParentPerm); err != nil {
|
||||
return err
|
||||
}
|
||||
return k.symlink(l.LinkName, target)
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Setup appends the read end of a pipe for setup params transmission and returns its fd.
|
||||
func Setup(extraFiles *[]*os.File) (int, *os.File, error) {
|
||||
if r, w, err := os.Pipe(); err != nil {
|
||||
return -1, nil, err
|
||||
} else {
|
||||
fd := 3 + len(*extraFiles)
|
||||
*extraFiles = append(*extraFiles, r)
|
||||
return fd, w, nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrReceiveEnv = errors.New("environment variable not set")
|
||||
)
|
||||
|
||||
// Receive retrieves setup fd from the environment and receives params.
|
||||
func Receive(key string, e any, fdp *uintptr) (func() error, error) {
|
||||
var setup *os.File
|
||||
|
||||
if s, ok := os.LookupEnv(key); !ok {
|
||||
return nil, ErrReceiveEnv
|
||||
} else {
|
||||
if fd, err := strconv.Atoi(s); err != nil {
|
||||
return nil, optionalErrorUnwrap(err)
|
||||
} else {
|
||||
setup = os.NewFile(uintptr(fd), "setup")
|
||||
if setup == nil {
|
||||
return nil, syscall.EDOM
|
||||
}
|
||||
if fdp != nil {
|
||||
*fdp = setup.Fd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return setup.Close, gob.NewDecoder(setup).Decode(e)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -29,16 +29,16 @@ const (
|
||||
|
||||
func toSysroot(name string) string {
|
||||
name = strings.TrimLeftFunc(name, func(r rune) bool { return r == '/' })
|
||||
return path.Join(sysrootPath, name)
|
||||
return filepath.Join(sysrootPath, name)
|
||||
}
|
||||
|
||||
func toHost(name string) string {
|
||||
name = strings.TrimLeftFunc(name, func(r rune) bool { return r == '/' })
|
||||
return path.Join(hostPath, name)
|
||||
return filepath.Join(hostPath, name)
|
||||
}
|
||||
|
||||
func createFile(name string, perm, pperm os.FileMode, content []byte) error {
|
||||
if err := os.MkdirAll(path.Dir(name), pperm); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(name), pperm); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(name, syscall.O_CREAT|syscall.O_EXCL|syscall.O_WRONLY, perm)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -61,7 +61,7 @@ func TestCreateFile(t *testing.T) {
|
||||
Path: "/proc/nonexistent",
|
||||
Err: syscall.ENOENT,
|
||||
}
|
||||
if err := createFile(path.Join(Nonexistent, ":3"), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||
if err := createFile(filepath.Join(Nonexistent, ":3"), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("createFile: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
})
|
||||
@@ -72,7 +72,7 @@ func TestCreateFile(t *testing.T) {
|
||||
Path: "/proc/nonexistent",
|
||||
Err: syscall.ENOENT,
|
||||
}
|
||||
if err := createFile(path.Join(Nonexistent), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||
if err := createFile(filepath.Join(Nonexistent), 0644, 0755, nil); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("createFile: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
})
|
||||
@@ -80,7 +80,7 @@ func TestCreateFile(t *testing.T) {
|
||||
|
||||
t.Run("touch", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
pathname := path.Join(tempDir, "empty")
|
||||
pathname := filepath.Join(tempDir, "empty")
|
||||
if err := createFile(pathname, 0644, 0755, nil); err != nil {
|
||||
t.Fatalf("createFile: error = %v", err)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ func TestCreateFile(t *testing.T) {
|
||||
|
||||
t.Run("write", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
pathname := path.Join(tempDir, "zero")
|
||||
pathname := filepath.Join(tempDir, "zero")
|
||||
if err := createFile(pathname, 0644, 0755, []byte{0}); err != nil {
|
||||
t.Fatalf("createFile: error = %v", err)
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func TestCreateFile(t *testing.T) {
|
||||
|
||||
func TestEnsureFile(t *testing.T) {
|
||||
t.Run("create", func(t *testing.T) {
|
||||
if err := ensureFile(path.Join(t.TempDir(), "ensure"), 0644, 0755); err != nil {
|
||||
if err := ensureFile(filepath.Join(t.TempDir(), "ensure"), 0644, 0755); err != nil {
|
||||
t.Errorf("ensureFile: error = %v", err)
|
||||
}
|
||||
})
|
||||
@@ -115,7 +115,7 @@ func TestEnsureFile(t *testing.T) {
|
||||
t.Run("stat", func(t *testing.T) {
|
||||
t.Run("inaccessible", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
pathname := path.Join(tempDir, "inaccessible")
|
||||
pathname := filepath.Join(tempDir, "inaccessible")
|
||||
if f, err := os.Create(pathname); err != nil {
|
||||
t.Fatalf("Create: error = %v", err)
|
||||
} else {
|
||||
@@ -150,7 +150,7 @@ func TestEnsureFile(t *testing.T) {
|
||||
|
||||
t.Run("ensure", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
pathname := path.Join(tempDir, "ensure")
|
||||
pathname := filepath.Join(tempDir, "ensure")
|
||||
if f, err := os.Create(pathname); err != nil {
|
||||
t.Fatalf("Create: error = %v", err)
|
||||
} else {
|
||||
@@ -195,12 +195,12 @@ func TestProcPaths(t *testing.T) {
|
||||
|
||||
t.Run("sample", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
if err := os.MkdirAll(path.Join(tempDir, "proc/self"), 0755); err != nil {
|
||||
if err := os.MkdirAll(filepath.Join(tempDir, "proc/self"), 0755); err != nil {
|
||||
t.Fatalf("MkdirAll: error = %v", err)
|
||||
}
|
||||
|
||||
t.Run("clean", func(t *testing.T) {
|
||||
if err := os.WriteFile(path.Join(tempDir, "proc/self/mountinfo"), []byte(`15 20 0:3 / /proc rw,relatime - proc /proc rw
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "proc/self/mountinfo"), []byte(`15 20 0:3 / /proc rw,relatime - proc /proc rw
|
||||
16 20 0:15 / /sys rw,relatime - sysfs /sys rw
|
||||
17 20 0:5 / /dev rw,relatime - devtmpfs udev rw,size=1983516k,nr_inodes=495879,mode=755`), 0644); err != nil {
|
||||
t.Fatalf("WriteFile: error = %v", err)
|
||||
@@ -243,8 +243,8 @@ func TestProcPaths(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("malformed", func(t *testing.T) {
|
||||
path.Join(tempDir, "proc/self/mountinfo")
|
||||
if err := os.WriteFile(path.Join(tempDir, "proc/self/mountinfo"), []byte{0}, 0644); err != nil {
|
||||
filepath.Join(tempDir, "proc/self/mountinfo")
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "proc/self/mountinfo"), []byte{0}, 0644); err != nil {
|
||||
t.Fatalf("WriteFile: error = %v", err)
|
||||
}
|
||||
|
||||
|
||||
1
dist/hsurc.default
vendored
1
dist/hsurc.default
vendored
@@ -1 +0,0 @@
|
||||
1000 0
|
||||
12
dist/install.sh
vendored
12
dist/install.sh
vendored
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
cd "$(dirname -- "$0")" || exit 1
|
||||
|
||||
install -vDm0755 "bin/hakurei" "${DESTDIR}/usr/bin/hakurei"
|
||||
install -vDm0755 "bin/sharefs" "${DESTDIR}/usr/bin/sharefs"
|
||||
|
||||
install -vDm4511 "bin/hsu" "${DESTDIR}/usr/bin/hsu"
|
||||
if [ ! -f "${DESTDIR}/etc/hsurc" ]; then
|
||||
install -vDm0400 "hsurc.default" "${DESTDIR}/etc/hsurc"
|
||||
fi
|
||||
|
||||
install -vDm0644 "comp/_hakurei" "${DESTDIR}/usr/share/zsh/site-functions/_hakurei"
|
||||
31
dist/release.sh
vendored
31
dist/release.sh
vendored
@@ -1,31 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
cd "$(dirname -- "$0")/.."
|
||||
VERSION="${HAKUREI_VERSION:-untagged}"
|
||||
pname="hakurei-${VERSION}-$(go env GOARCH)"
|
||||
out="${DESTDIR:-dist}/${pname}"
|
||||
|
||||
echo '# Preparing distribution files.'
|
||||
mkdir -p "${out}"
|
||||
cp -v "README.md" "dist/hsurc.default" "dist/install.sh" "${out}"
|
||||
cp -rv "dist/comp" "${out}"
|
||||
echo
|
||||
|
||||
echo '# Building hakurei.'
|
||||
go generate ./...
|
||||
go build -trimpath -v -o "${out}/bin/" -ldflags "-s -w
|
||||
-buildid= -linkmode external -extldflags=-static
|
||||
-X hakurei.app/internal/info.buildVersion=${VERSION}
|
||||
-X hakurei.app/internal/info.hakureiPath=/usr/bin/hakurei
|
||||
-X hakurei.app/internal/info.hsuPath=/usr/bin/hsu
|
||||
-X main.hakureiPath=/usr/bin/hakurei" ./...
|
||||
echo
|
||||
|
||||
echo '# Testing hakurei.'
|
||||
go test -ldflags='-buildid= -linkmode external -extldflags=-static' ./...
|
||||
echo
|
||||
|
||||
echo '# Creating distribution.'
|
||||
rm -f "${out}.tar.gz" && tar -C "${out}/.." -vczf "${out}.tar.gz" "${pname}"
|
||||
rm -rf "${out}"
|
||||
(cd "${out}/.." && sha512sum "${pname}.tar.gz" > "${pname}.tar.gz.sha512")
|
||||
echo
|
||||
@@ -137,11 +137,10 @@
|
||||
|
||||
CC="musl-clang -O3 -Werror -Qunused-arguments" \
|
||||
GOCACHE="$(mktemp -d)" \
|
||||
HAKUREI_TEST_SKIP_ACL=1 \
|
||||
PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
|
||||
DESTDIR="$out" \
|
||||
HAKUREI_VERSION="v${hakurei.version}" \
|
||||
./dist/release.sh
|
||||
./all.sh
|
||||
'';
|
||||
}
|
||||
);
|
||||
@@ -196,6 +195,7 @@
|
||||
./test/interactive/vm.nix
|
||||
./test/interactive/hakurei.nix
|
||||
./test/interactive/trace.nix
|
||||
./test/interactive/raceattr.nix
|
||||
|
||||
self.nixosModules.hakurei
|
||||
home-manager.nixosModules.home-manager
|
||||
|
||||
@@ -56,8 +56,10 @@ type Ops interface {
|
||||
|
||||
// ApplyState holds the address of [Ops] and any relevant application state.
|
||||
type ApplyState struct {
|
||||
// AutoEtcPrefix is the prefix for [FSBind] in autoetc [FSBind.Special] condition.
|
||||
// Prefix for [FSBind] in autoetc [FSBind.Special] condition.
|
||||
AutoEtcPrefix string
|
||||
// Whether to skip remounting root.
|
||||
NoRemountRoot bool
|
||||
|
||||
Ops
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package hst
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
@@ -28,7 +28,7 @@ func (l *FSLink) Valid() bool {
|
||||
if l == nil || l.Target == nil || l.Linkname == "" {
|
||||
return false
|
||||
}
|
||||
return !l.Dereference || path.IsAbs(l.Linkname)
|
||||
return !l.Dereference || filepath.IsAbs(l.Linkname)
|
||||
}
|
||||
|
||||
func (l *FSLink) Path() *check.Absolute {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/fhs"
|
||||
)
|
||||
|
||||
func init() { gob.Register(new(FSOverlay)) }
|
||||
@@ -69,9 +70,12 @@ func (o *FSOverlay) Apply(z *ApplyState) {
|
||||
return
|
||||
}
|
||||
|
||||
if o.Upper != nil && o.Work != nil { // rw
|
||||
if o.Upper != nil && o.Work != nil {
|
||||
z.Overlay(o.Target, o.Upper, o.Work, o.Lower...)
|
||||
} else { // ro
|
||||
if o.Target.Is(fhs.AbsRoot) {
|
||||
z.NoRemountRoot = true
|
||||
}
|
||||
} else {
|
||||
z.OverlayReadonly(o.Target, o.Lower...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,5 +49,18 @@ func TestFSOverlay(t *testing.T) {
|
||||
Lower: ms("/tmp/.src0", "/tmp/.src1"),
|
||||
}}, m("/mnt/src"), ms("/tmp/.src0", "/tmp/.src1"),
|
||||
"*/mnt/src:/tmp/.src0:/tmp/.src1"},
|
||||
|
||||
{"no remount root", &hst.FSOverlay{
|
||||
Target: m("/"),
|
||||
Lower: ms("/tmp/.src0", "/tmp/.src1"),
|
||||
Upper: m("/tmp/upper"),
|
||||
Work: m("/tmp/work"),
|
||||
}, true, container.Ops{&container.MountOverlayOp{
|
||||
Target: m("/"),
|
||||
Lower: ms("/tmp/.src0", "/tmp/.src1"),
|
||||
Upper: m("/tmp/upper"),
|
||||
Work: m("/tmp/work"),
|
||||
}}, m("/"), ms("/tmp/upper", "/tmp/work", "/tmp/.src0", "/tmp/.src1"),
|
||||
"w*/:/tmp/upper:/tmp/work:/tmp/.src0:/tmp/.src1"},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/acl"
|
||||
"hakurei.app/internal/info"
|
||||
)
|
||||
|
||||
const testFileName = "acl.test"
|
||||
@@ -24,11 +26,17 @@ var (
|
||||
)
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
if os.Getenv("HAKUREI_TEST_SKIP_ACL") == "1" {
|
||||
t.Skip("acl test skipped")
|
||||
if info.CanDegrade {
|
||||
name := filepath.Join(t.TempDir(), "check-degrade")
|
||||
if err := os.WriteFile(name, nil, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := acl.Update(name, os.Geteuid()); errors.Is(err, syscall.ENOTSUP) {
|
||||
t.Skip(err)
|
||||
}
|
||||
}
|
||||
|
||||
testFilePath := path.Join(t.TempDir(), testFileName)
|
||||
testFilePath := filepath.Join(t.TempDir(), testFileName)
|
||||
|
||||
if f, err := os.Create(testFilePath); err != nil {
|
||||
t.Fatalf("Create: error = %v", err)
|
||||
|
||||
7
internal/info/optional_skip.go
Normal file
7
internal/info/optional_skip.go
Normal file
@@ -0,0 +1,7 @@
|
||||
//go:build !noskip
|
||||
|
||||
package info
|
||||
|
||||
// CanDegrade is whether tests are allowed to transparently degrade or skip due
|
||||
// to required system features being denied or unavailable.
|
||||
const CanDegrade = true
|
||||
5
internal/info/optional_strict.go
Normal file
5
internal/info/optional_strict.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build noskip
|
||||
|
||||
package info
|
||||
|
||||
const CanDegrade = false
|
||||
90
internal/kobject/event.go
Normal file
90
internal/kobject/event.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package kobject
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/internal/uevent"
|
||||
)
|
||||
|
||||
// Event is a [uevent.Message] with known environment variables processed.
|
||||
type Event struct {
|
||||
// alloc_uevent_skb: action_string
|
||||
Action uevent.KobjectAction `json:"action"`
|
||||
// alloc_uevent_skb: devpath
|
||||
DevPath string `json:"devpath"`
|
||||
|
||||
// Uninterpreted environment variable pairs. An entry missing a separator
|
||||
// gains the value "\x00".
|
||||
Env map[string]string `json:"env"`
|
||||
|
||||
// SEQNUM value set by the kernel.
|
||||
Sequence uint64 `json:"seqnum"`
|
||||
// SYNTH_UUID value set on trigger, nil denotes a non-synthetic event.
|
||||
Synth *uevent.UUID `json:"synth_uuid,omitempty"`
|
||||
// SUBSYSTEM value set by the kernel.
|
||||
Subsystem string `json:"subsystem"`
|
||||
}
|
||||
|
||||
// Populate populates e with the contents of a [uevent.Message].
|
||||
//
|
||||
// The ACTION and DEVPATH environment variables are ignored and assumed to be
|
||||
// consistent with the header.
|
||||
func (e *Event) Populate(reportErr func(error), m *uevent.Message) {
|
||||
if reportErr == nil {
|
||||
reportErr = func(error) {}
|
||||
}
|
||||
|
||||
*e = Event{
|
||||
Action: m.Action,
|
||||
DevPath: m.DevPath,
|
||||
Env: make(map[string]string),
|
||||
}
|
||||
|
||||
for _, s := range m.Env {
|
||||
k, v, ok := strings.Cut(s, "=")
|
||||
if !ok {
|
||||
if _, ok = e.Env[s]; !ok {
|
||||
e.Env[s] = "\x00"
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch k {
|
||||
case "ACTION", "DEVPATH":
|
||||
continue
|
||||
|
||||
case "SEQNUM":
|
||||
seq, err := strconv.ParseUint(v, 10, 64)
|
||||
if err != nil {
|
||||
if _e := errors.Unwrap(err); _e != nil {
|
||||
err = _e
|
||||
}
|
||||
reportErr(err)
|
||||
|
||||
e.Env[k] = v
|
||||
continue
|
||||
}
|
||||
e.Sequence = seq
|
||||
|
||||
case "SYNTH_UUID":
|
||||
var uuid uevent.UUID
|
||||
err := uuid.UnmarshalText(unsafe.Slice(unsafe.StringData(v), len(v)))
|
||||
if err != nil {
|
||||
reportErr(err)
|
||||
|
||||
e.Env[k] = v
|
||||
continue
|
||||
}
|
||||
e.Synth = &uuid
|
||||
|
||||
case "SUBSYSTEM":
|
||||
e.Subsystem = v
|
||||
|
||||
default:
|
||||
e.Env[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
92
internal/kobject/event_test.go
Normal file
92
internal/kobject/event_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package kobject_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/kobject"
|
||||
"hakurei.app/internal/uevent"
|
||||
)
|
||||
|
||||
func TestEvent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
msg uevent.Message
|
||||
want kobject.Event
|
||||
errs []error
|
||||
}{
|
||||
{"sample coldboot qemu", uevent.Message{
|
||||
Action: uevent.KOBJ_ADD,
|
||||
DevPath: "/devices/LNXSYSTM:00/LNXPWRBN:00",
|
||||
Env: []string{
|
||||
"ACTION=add",
|
||||
"DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00",
|
||||
"SUBSYSTEM=acpi",
|
||||
"SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed",
|
||||
"MODALIAS=acpi:LNXPWRBN:",
|
||||
"SEQNUM=777",
|
||||
}}, kobject.Event{
|
||||
Action: uevent.KOBJ_ADD,
|
||||
DevPath: "/devices/LNXSYSTM:00/LNXPWRBN:00",
|
||||
Env: map[string]string{
|
||||
"MODALIAS": "acpi:LNXPWRBN:",
|
||||
},
|
||||
Sequence: 777,
|
||||
Synth: &uevent.UUID{
|
||||
0xfe, 0x4d, 0x7c, 0x9d,
|
||||
0xb8, 0xc6,
|
||||
0x4a, 0x70,
|
||||
0x9e, 0xf1,
|
||||
0x3d, 0x8a, 0x58, 0xd1, 0x8e, 0xed,
|
||||
},
|
||||
Subsystem: "acpi",
|
||||
}, []error{}},
|
||||
|
||||
{"nil reportErr", uevent.Message{Env: []string{
|
||||
"SEQNUM=\x00",
|
||||
}}, kobject.Event{Env: map[string]string{
|
||||
"SEQNUM": "\x00",
|
||||
}}, nil},
|
||||
|
||||
{"bad SEQNUM SYNTH_UUID", uevent.Message{Env: []string{
|
||||
"SEQNUM=\x00",
|
||||
"SYNTH_UUID=\x00",
|
||||
"SUBSYSTEM=\x00",
|
||||
}}, kobject.Event{Subsystem: "\x00", Env: map[string]string{
|
||||
"SEQNUM": "\x00",
|
||||
"SYNTH_UUID": "\x00",
|
||||
}}, []error{strconv.ErrSyntax, uevent.UUIDSizeError(1)}},
|
||||
|
||||
{"bad sep", uevent.Message{Env: []string{
|
||||
"SYNTH_UUID",
|
||||
}}, kobject.Event{Env: map[string]string{
|
||||
"SYNTH_UUID": "\x00",
|
||||
}}, []error{}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var f func(error)
|
||||
gotErrs := make([]error, 0)
|
||||
if tc.errs != nil {
|
||||
f = func(err error) {
|
||||
gotErrs = append(gotErrs, err)
|
||||
}
|
||||
}
|
||||
|
||||
var got kobject.Event
|
||||
got.Populate(f, &tc.msg)
|
||||
|
||||
if !reflect.DeepEqual(&got, &tc.want) {
|
||||
t.Errorf("Populate: %#v, want %#v", got, tc.want)
|
||||
}
|
||||
if tc.errs != nil && !reflect.DeepEqual(gotErrs, tc.errs) {
|
||||
t.Errorf("Populate: errs = %v, want %v", gotErrs, tc.errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,11 @@ type Conn struct {
|
||||
|
||||
// Dial returns the address of a newly connected generic netlink connection of
|
||||
// specified family and groups.
|
||||
func Dial(family int, groups uint32) (*Conn, error) {
|
||||
//
|
||||
// For a nonzero rcvbuf, the socket receive buffer size is set to its absolute
|
||||
// value via SO_RCVBUF for a positive value, or SO_RCVBUFFORCE for a negative
|
||||
// value.
|
||||
func Dial(family int, groups uint32, rcvbuf int64) (*Conn, error) {
|
||||
var c Conn
|
||||
if fd, err := syscall.Socket(
|
||||
syscall.AF_NETLINK,
|
||||
@@ -75,6 +79,23 @@ func Dial(family int, groups uint32) (*Conn, error) {
|
||||
return nil, syscall.ENOTRECOVERABLE
|
||||
}
|
||||
|
||||
if rcvbuf != 0 {
|
||||
opt := syscall.SO_RCVBUF
|
||||
if rcvbuf < 0 {
|
||||
opt = syscall.SO_RCVBUFFORCE
|
||||
rcvbuf = -rcvbuf
|
||||
}
|
||||
if err = syscall.SetsockoptInt(
|
||||
fd,
|
||||
syscall.SOL_SOCKET,
|
||||
opt,
|
||||
int(rcvbuf),
|
||||
); err != nil {
|
||||
_ = syscall.Close(fd)
|
||||
return nil, os.NewSyscallError("setsockopt", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.family = family
|
||||
c.f = os.NewFile(uintptr(fd), "netlink")
|
||||
if c.raw, err = c.f.SyscallConn(); err != nil {
|
||||
@@ -101,23 +122,40 @@ func (c *Conn) Close() error {
|
||||
return c.f.Close()
|
||||
}
|
||||
|
||||
// Recvfrom wraps recv(2) with nonblocking behaviour via the runtime network poller.
|
||||
// Recvmsg wraps recv(2) with nonblocking behaviour via the runtime network poller.
|
||||
//
|
||||
// The returned slice is valid until the next call to Recvfrom.
|
||||
func (c *Conn) Recvfrom(
|
||||
// The returned slice is valid until the next call to Recvmsg.
|
||||
func (c *Conn) Recvmsg(
|
||||
ctx context.Context,
|
||||
flags int,
|
||||
) (data []byte, from syscall.Sockaddr, err error) {
|
||||
) (data []byte, recvflags int, from syscall.Sockaddr, err error) {
|
||||
if err = c.f.SetReadDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var n int
|
||||
data = c.buf[:]
|
||||
|
||||
if ctx == nil {
|
||||
rcErr := c.raw.Control(func(fd uintptr) {
|
||||
n, _, recvflags, from, err = syscall.Recvmsg(int(fd), data, nil, flags)
|
||||
})
|
||||
if n >= 0 {
|
||||
data = data[:n]
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = os.NewSyscallError("recvmsg", err)
|
||||
} else {
|
||||
err = rcErr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
rcErr := c.raw.Read(func(fd uintptr) (done bool) {
|
||||
n, from, err = syscall.Recvfrom(int(fd), data, flags)
|
||||
n, _, recvflags, from, err = syscall.Recvmsg(int(fd), data, nil, flags)
|
||||
return err != syscall.EWOULDBLOCK
|
||||
})
|
||||
if n >= 0 {
|
||||
@@ -129,7 +167,7 @@ func (c *Conn) Recvfrom(
|
||||
select {
|
||||
case rcErr := <-done:
|
||||
if err != nil {
|
||||
err = os.NewSyscallError("recvfrom", err)
|
||||
err = os.NewSyscallError("recvmsg", err)
|
||||
} else {
|
||||
err = rcErr
|
||||
}
|
||||
@@ -147,12 +185,12 @@ func (c *Conn) Recvfrom(
|
||||
}
|
||||
}
|
||||
|
||||
// Sendto wraps send(2) with nonblocking behaviour via the runtime network poller.
|
||||
func (c *Conn) Sendto(
|
||||
// Sendmsg wraps send(2) with nonblocking behaviour via the runtime network poller.
|
||||
func (c *Conn) Sendmsg(
|
||||
ctx context.Context,
|
||||
p []byte,
|
||||
flags int,
|
||||
to syscall.Sockaddr,
|
||||
flags int,
|
||||
) (err error) {
|
||||
if err = c.f.SetWriteDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
@@ -161,7 +199,7 @@ func (c *Conn) Sendto(
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
done <- c.raw.Write(func(fd uintptr) (done bool) {
|
||||
err = syscall.Sendto(int(fd), p, flags, to)
|
||||
err = syscall.Sendmsg(int(fd), p, nil, to, flags)
|
||||
return err != syscall.EWOULDBLOCK
|
||||
})
|
||||
}()
|
||||
@@ -169,7 +207,7 @@ func (c *Conn) Sendto(
|
||||
select {
|
||||
case rcErr := <-done:
|
||||
if err != nil {
|
||||
err = os.NewSyscallError("sendto", err)
|
||||
err = os.NewSyscallError("sendmsg", err)
|
||||
} else {
|
||||
err = rcErr
|
||||
}
|
||||
@@ -278,7 +316,7 @@ type HandlerFunc func(resp []syscall.NetlinkMessage) error
|
||||
func (c *Conn) receive(ctx context.Context, f HandlerFunc, flags int) error {
|
||||
for {
|
||||
var resp []syscall.NetlinkMessage
|
||||
if data, _, err := c.Recvfrom(ctx, flags); err != nil {
|
||||
if data, _, _, err := c.Recvmsg(ctx, flags); err != nil {
|
||||
return err
|
||||
} else if len(data) < syscall.NLMSG_HDRLEN {
|
||||
return syscall.EBADE
|
||||
@@ -302,9 +340,9 @@ func (c *Conn) Roundtrip(ctx context.Context, f HandlerFunc) error {
|
||||
}
|
||||
defer func() { c.seq++ }()
|
||||
|
||||
if err := c.Sendto(ctx, c.pending(), 0, &syscall.SockaddrNetlink{
|
||||
if err := c.Sendmsg(ctx, c.pending(), &syscall.SockaddrNetlink{
|
||||
Family: syscall.AF_NETLINK,
|
||||
}); err != nil {
|
||||
}, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ type RouteConn struct{ conn *Conn }
|
||||
func (c *RouteConn) Close() error { return c.conn.Close() }
|
||||
|
||||
// DialRoute returns the address of a newly connected [RouteConn].
|
||||
func DialRoute() (*RouteConn, error) {
|
||||
c, err := Dial(syscall.NETLINK_ROUTE, 0)
|
||||
func DialRoute(rcvbuf int64) (*RouteConn, error) {
|
||||
c, err := Dial(syscall.NETLINK_ROUTE, 0, rcvbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"hakurei.app/ext"
|
||||
"hakurei.app/internal/dbus"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal/params"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
@@ -84,7 +85,7 @@ type syscallDispatcher interface {
|
||||
// setDumpable provides [container.SetDumpable].
|
||||
setDumpable(dumpable uintptr) error
|
||||
// receive provides [container.Receive].
|
||||
receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error)
|
||||
receive(key string, e any, fdp *int) (closeFunc func() error, err error)
|
||||
|
||||
// containerStart provides the Start method of [container.Container].
|
||||
containerStart(z *container.Container) error
|
||||
@@ -154,8 +155,8 @@ func (direct) prctl(op, arg2, arg3 uintptr) error { return ext.Prctl(op, arg2, a
|
||||
func (direct) overflowUid(msg message.Msg) int { return container.OverflowUid(msg) }
|
||||
func (direct) overflowGid(msg message.Msg) int { return container.OverflowGid(msg) }
|
||||
func (direct) setDumpable(dumpable uintptr) error { return ext.SetDumpable(dumpable) }
|
||||
func (direct) receive(key string, e any, fdp *uintptr) (func() error, error) {
|
||||
return container.Receive(key, e, fdp)
|
||||
func (direct) receive(key string, e any, fdp *int) (func() error, error) {
|
||||
return params.Receive(key, e, fdp)
|
||||
}
|
||||
|
||||
func (direct) containerStart(z *container.Container) error { return z.Start() }
|
||||
|
||||
@@ -401,12 +401,12 @@ func (k *kstub) setDumpable(dumpable uintptr) error {
|
||||
stub.CheckArg(k.Stub, "dumpable", dumpable, 0))
|
||||
}
|
||||
|
||||
func (k *kstub) receive(key string, e any, fdp *uintptr) (closeFunc func() error, err error) {
|
||||
func (k *kstub) receive(key string, e any, fdp *int) (closeFunc func() error, err error) {
|
||||
k.Helper()
|
||||
expect := k.Expects("receive")
|
||||
reflect.ValueOf(e).Elem().Set(reflect.ValueOf(expect.Args[1]))
|
||||
if expect.Args[2] != nil {
|
||||
*fdp = expect.Args[2].(uintptr)
|
||||
*fdp = int(expect.Args[2].(uintptr))
|
||||
}
|
||||
return func() error { return k.Expects("closeReceive").Err }, expect.Error(
|
||||
stub.CheckArg(k.Stub, "key", key, 0))
|
||||
@@ -690,38 +690,38 @@ func (panicMsgContext) Value(any) any { panic("unreachable") }
|
||||
// This type is meant to be embedded in partial syscallDispatcher implementations.
|
||||
type panicDispatcher struct{}
|
||||
|
||||
func (panicDispatcher) new(func(k syscallDispatcher, msg message.Msg)) { panic("unreachable") }
|
||||
func (panicDispatcher) getppid() int { panic("unreachable") }
|
||||
func (panicDispatcher) getpid() int { panic("unreachable") }
|
||||
func (panicDispatcher) getuid() int { panic("unreachable") }
|
||||
func (panicDispatcher) getgid() int { panic("unreachable") }
|
||||
func (panicDispatcher) lookupEnv(string) (string, bool) { panic("unreachable") }
|
||||
func (panicDispatcher) pipe() (*os.File, *os.File, error) { panic("unreachable") }
|
||||
func (panicDispatcher) stat(string) (os.FileInfo, error) { panic("unreachable") }
|
||||
func (panicDispatcher) open(string) (osFile, error) { panic("unreachable") }
|
||||
func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") }
|
||||
func (panicDispatcher) tempdir() string { panic("unreachable") }
|
||||
func (panicDispatcher) mkdir(string, os.FileMode) error { panic("unreachable") }
|
||||
func (panicDispatcher) removeAll(string) error { panic("unreachable") }
|
||||
func (panicDispatcher) exit(int) { panic("unreachable") }
|
||||
func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) prctl(uintptr, uintptr, uintptr) error { panic("unreachable") }
|
||||
func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) lookPath(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") }
|
||||
func (panicDispatcher) overflowUid(message.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) overflowGid(message.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) setDumpable(uintptr) error { panic("unreachable") }
|
||||
func (panicDispatcher) receive(string, any, *uintptr) (func() error, error) { panic("unreachable") }
|
||||
func (panicDispatcher) containerStart(*container.Container) error { panic("unreachable") }
|
||||
func (panicDispatcher) containerServe(*container.Container) error { panic("unreachable") }
|
||||
func (panicDispatcher) containerWait(*container.Container) error { panic("unreachable") }
|
||||
func (panicDispatcher) mustHsuPath() *check.Absolute { panic("unreachable") }
|
||||
func (panicDispatcher) dbusAddress() (string, string) { panic("unreachable") }
|
||||
func (panicDispatcher) setupContSignal(int) (io.ReadCloser, func(), error) { panic("unreachable") }
|
||||
func (panicDispatcher) getMsg() message.Msg { panic("unreachable") }
|
||||
func (panicDispatcher) fatal(...any) { panic("unreachable") }
|
||||
func (panicDispatcher) fatalf(string, ...any) { panic("unreachable") }
|
||||
func (panicDispatcher) new(func(k syscallDispatcher, msg message.Msg)) { panic("unreachable") }
|
||||
func (panicDispatcher) getppid() int { panic("unreachable") }
|
||||
func (panicDispatcher) getpid() int { panic("unreachable") }
|
||||
func (panicDispatcher) getuid() int { panic("unreachable") }
|
||||
func (panicDispatcher) getgid() int { panic("unreachable") }
|
||||
func (panicDispatcher) lookupEnv(string) (string, bool) { panic("unreachable") }
|
||||
func (panicDispatcher) pipe() (*os.File, *os.File, error) { panic("unreachable") }
|
||||
func (panicDispatcher) stat(string) (os.FileInfo, error) { panic("unreachable") }
|
||||
func (panicDispatcher) open(string) (osFile, error) { panic("unreachable") }
|
||||
func (panicDispatcher) readdir(string) ([]os.DirEntry, error) { panic("unreachable") }
|
||||
func (panicDispatcher) tempdir() string { panic("unreachable") }
|
||||
func (panicDispatcher) mkdir(string, os.FileMode) error { panic("unreachable") }
|
||||
func (panicDispatcher) removeAll(string) error { panic("unreachable") }
|
||||
func (panicDispatcher) exit(int) { panic("unreachable") }
|
||||
func (panicDispatcher) evalSymlinks(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) prctl(uintptr, uintptr, uintptr) error { panic("unreachable") }
|
||||
func (panicDispatcher) lookupGroupId(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) lookPath(string) (string, error) { panic("unreachable") }
|
||||
func (panicDispatcher) cmdOutput(*exec.Cmd) ([]byte, error) { panic("unreachable") }
|
||||
func (panicDispatcher) overflowUid(message.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) overflowGid(message.Msg) int { panic("unreachable") }
|
||||
func (panicDispatcher) setDumpable(uintptr) error { panic("unreachable") }
|
||||
func (panicDispatcher) receive(string, any, *int) (func() error, error) { panic("unreachable") }
|
||||
func (panicDispatcher) containerStart(*container.Container) error { panic("unreachable") }
|
||||
func (panicDispatcher) containerServe(*container.Container) error { panic("unreachable") }
|
||||
func (panicDispatcher) containerWait(*container.Container) error { panic("unreachable") }
|
||||
func (panicDispatcher) mustHsuPath() *check.Absolute { panic("unreachable") }
|
||||
func (panicDispatcher) dbusAddress() (string, string) { panic("unreachable") }
|
||||
func (panicDispatcher) setupContSignal(int) (io.ReadCloser, func(), error) { panic("unreachable") }
|
||||
func (panicDispatcher) getMsg() message.Msg { panic("unreachable") }
|
||||
func (panicDispatcher) fatal(...any) { panic("unreachable") }
|
||||
func (panicDispatcher) fatalf(string, ...any) { panic("unreachable") }
|
||||
|
||||
func (panicDispatcher) notifyContext(context.Context, ...os.Signal) (context.Context, context.CancelFunc) {
|
||||
panic("unreachable")
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"time"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/info"
|
||||
@@ -372,17 +371,18 @@ func (k *outcome) start(ctx context.Context, msg message.Msg,
|
||||
// shim runs in the same session as monitor; see shim.go for behaviour
|
||||
cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGCONT) }
|
||||
|
||||
var shimPipe *os.File
|
||||
if fd, w, err := container.Setup(&cmd.ExtraFiles); err != nil {
|
||||
var shimPipe [2]*os.File
|
||||
if r, w, err := os.Pipe(); err != nil {
|
||||
return cmd, nil, &hst.AppError{Step: "create shim setup pipe", Err: err}
|
||||
} else {
|
||||
shimPipe = w
|
||||
cmd.Env = []string{
|
||||
// passed through to shim by hsu
|
||||
shimEnv + "=" + strconv.Itoa(fd),
|
||||
shimEnv + "=" + strconv.Itoa(3+len(cmd.ExtraFiles)),
|
||||
// interpreted by hsu
|
||||
"HAKUREI_IDENTITY=" + k.state.identity.String(),
|
||||
}
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
|
||||
shimPipe[0], shimPipe[1] = r, w
|
||||
}
|
||||
|
||||
if len(k.supp) > 0 {
|
||||
@@ -393,12 +393,16 @@ func (k *outcome) start(ctx context.Context, msg message.Msg,
|
||||
|
||||
msg.Verbosef("setuid helper at %s", hsuPath)
|
||||
if err := cmd.Start(); err != nil {
|
||||
_, _ = shimPipe[0].Close(), shimPipe[1].Close()
|
||||
msg.Resume()
|
||||
return cmd, shimPipe, &hst.AppError{Step: "start setuid wrapper", Err: err}
|
||||
return cmd, nil, &hst.AppError{Step: "start setuid wrapper", Err: err}
|
||||
}
|
||||
if err := shimPipe[0].Close(); err != nil {
|
||||
msg.Verbose(err)
|
||||
}
|
||||
|
||||
*startTime = time.Now().UTC()
|
||||
return cmd, shimPipe, nil
|
||||
return cmd, shimPipe[1], nil
|
||||
}
|
||||
|
||||
// serveShim serves outcomeState through the shim setup pipe.
|
||||
@@ -411,11 +415,11 @@ func serveShim(msg message.Msg, shimPipe *os.File, state *outcomeState) error {
|
||||
msg.Verbose(err.Error())
|
||||
}
|
||||
if err := gob.NewEncoder(shimPipe).Encode(state); err != nil {
|
||||
_ = shimPipe.Close()
|
||||
msg.Resume()
|
||||
return &hst.AppError{Step: "transmit shim config", Err: err}
|
||||
}
|
||||
_ = shimPipe.Close()
|
||||
return nil
|
||||
return shimPipe.Close()
|
||||
}
|
||||
|
||||
// printMessageError prints the error message according to [message.GetMessage],
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"hakurei.app/ext"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/params"
|
||||
"hakurei.app/internal/pipewire"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
@@ -197,7 +198,7 @@ func shimEntrypoint(k syscallDispatcher) {
|
||||
if errors.Is(err, syscall.EBADF) {
|
||||
k.fatal("invalid config descriptor")
|
||||
}
|
||||
if errors.Is(err, container.ErrReceiveEnv) {
|
||||
if errors.Is(err, params.ErrReceiveEnv) {
|
||||
k.fatal(shimEnv + " not set")
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/hst"
|
||||
"hakurei.app/internal/env"
|
||||
"hakurei.app/internal/params"
|
||||
"hakurei.app/internal/stub"
|
||||
)
|
||||
|
||||
@@ -172,7 +173,7 @@ func TestShimEntrypoint(t *testing.T) {
|
||||
call("setDumpable", stub.ExpectArgs{uintptr(ext.SUID_DUMP_DISABLE)}, nil, nil),
|
||||
call("getppid", stub.ExpectArgs{}, 0xbad, nil),
|
||||
call("setupContSignal", stub.ExpectArgs{0xbad}, 0, nil),
|
||||
call("receive", stub.ExpectArgs{"HAKUREI_SHIM", outcomeState{}, nil}, nil, container.ErrReceiveEnv),
|
||||
call("receive", stub.ExpectArgs{"HAKUREI_SHIM", outcomeState{}, nil}, nil, params.ErrReceiveEnv),
|
||||
call("fatal", stub.ExpectArgs{[]any{"HAKUREI_SHIM not set"}}, nil, nil),
|
||||
|
||||
// deferred
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"syscall"
|
||||
@@ -165,9 +165,9 @@ func (s *spFilesystemOp) toSystem(state *outcomeStateSys) error {
|
||||
}
|
||||
for _, pair := range entry.Values {
|
||||
if pair[0] == "path" {
|
||||
if path.IsAbs(pair[1]) {
|
||||
if filepath.IsAbs(pair[1]) {
|
||||
// get parent dir of socket
|
||||
dir := path.Dir(pair[1])
|
||||
dir := filepath.Dir(pair[1])
|
||||
if dir == "." || dir == fhs.Root {
|
||||
state.msg.Verbosef("dbus socket %q is in an unusual location", pair[1])
|
||||
}
|
||||
@@ -290,7 +290,9 @@ func (s *spFilesystemOp) toContainer(state *outcomeStateParams) error {
|
||||
if state.Container.Flags&hst.FDevice == 0 {
|
||||
state.params.Remount(fhs.AbsDev, syscall.MS_RDONLY)
|
||||
}
|
||||
state.params.Remount(fhs.AbsRoot, syscall.MS_RDONLY)
|
||||
if !state.as.NoRemountRoot {
|
||||
state.params.Remount(fhs.AbsRoot, syscall.MS_RDONLY)
|
||||
}
|
||||
|
||||
state.params.Env = make([]string, 0, len(state.env))
|
||||
for key, value := range state.env {
|
||||
|
||||
42
internal/params/params.go
Normal file
42
internal/params/params.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Package params provides helpers for receiving setup payload from parent.
|
||||
package params
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// ErrReceiveEnv is returned by [Receive] if setup fd is not present in environment.
|
||||
var ErrReceiveEnv = errors.New("environment variable not set")
|
||||
|
||||
// Receive retrieves setup fd from the environment and receives params.
|
||||
//
|
||||
// The file descriptor written to the value pointed to by fdp must not be passed
|
||||
// to any system calls. It is made available for ordering file descriptor only.
|
||||
func Receive(key string, v any, fdp *int) (func() error, error) {
|
||||
var setup *os.File
|
||||
|
||||
if s, ok := os.LookupEnv(key); !ok {
|
||||
return nil, ErrReceiveEnv
|
||||
} else {
|
||||
if fd, err := strconv.Atoi(s); err != nil {
|
||||
if _err := errors.Unwrap(err); _err != nil {
|
||||
err = _err
|
||||
}
|
||||
return nil, err
|
||||
} else {
|
||||
setup = os.NewFile(uintptr(fd), "setup")
|
||||
if setup == nil {
|
||||
return nil, syscall.EDOM
|
||||
}
|
||||
if fdp != nil {
|
||||
*fdp = fd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return setup.Close, gob.NewDecoder(setup).Decode(v)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package container_test
|
||||
package params_test
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/params"
|
||||
)
|
||||
|
||||
func TestSetupReceive(t *testing.T) {
|
||||
@@ -30,8 +30,8 @@ func TestSetupReceive(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
if _, err := container.Receive(key, nil, nil); !errors.Is(err, container.ErrReceiveEnv) {
|
||||
t.Errorf("Receive: error = %v, want %v", err, container.ErrReceiveEnv)
|
||||
if _, err := params.Receive(key, nil, nil); !errors.Is(err, params.ErrReceiveEnv) {
|
||||
t.Errorf("Receive: error = %v, want %v", err, params.ErrReceiveEnv)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestSetupReceive(t *testing.T) {
|
||||
const key = "TEST_ENV_FORMAT"
|
||||
t.Setenv(key, "")
|
||||
|
||||
if _, err := container.Receive(key, nil, nil); !errors.Is(err, strconv.ErrSyntax) {
|
||||
if _, err := params.Receive(key, nil, nil); !errors.Is(err, strconv.ErrSyntax) {
|
||||
t.Errorf("Receive: error = %v, want %v", err, strconv.ErrSyntax)
|
||||
}
|
||||
})
|
||||
@@ -48,7 +48,7 @@ func TestSetupReceive(t *testing.T) {
|
||||
const key = "TEST_ENV_RANGE"
|
||||
t.Setenv(key, "-1")
|
||||
|
||||
if _, err := container.Receive(key, nil, nil); !errors.Is(err, syscall.EDOM) {
|
||||
if _, err := params.Receive(key, nil, nil); !errors.Is(err, syscall.EDOM) {
|
||||
t.Errorf("Receive: error = %v, want %v", err, syscall.EDOM)
|
||||
}
|
||||
})
|
||||
@@ -60,16 +60,22 @@ func TestSetupReceive(t *testing.T) {
|
||||
|
||||
encoderDone := make(chan error, 1)
|
||||
extraFiles := make([]*os.File, 0, 1)
|
||||
deadline, _ := t.Deadline()
|
||||
if fd, f, err := container.Setup(&extraFiles); err != nil {
|
||||
if r, w, err := os.Pipe(); err != nil {
|
||||
t.Fatalf("Setup: error = %v", err)
|
||||
} else if fd != 3 {
|
||||
t.Fatalf("Setup: fd = %d, want 3", fd)
|
||||
} else {
|
||||
if err = f.SetDeadline(deadline); err != nil {
|
||||
t.Fatal(err.Error())
|
||||
t.Cleanup(func() {
|
||||
if err = errors.Join(r.Close(), w.Close()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
extraFiles = append(extraFiles, r)
|
||||
if deadline, ok := t.Deadline(); ok {
|
||||
if err = w.SetDeadline(deadline); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
go func() { encoderDone <- gob.NewEncoder(f).Encode(payload) }()
|
||||
go func() { encoderDone <- gob.NewEncoder(w).Encode(payload) }()
|
||||
}
|
||||
|
||||
if len(extraFiles) != 1 {
|
||||
@@ -87,13 +93,13 @@ func TestSetupReceive(t *testing.T) {
|
||||
|
||||
var (
|
||||
gotPayload []uint64
|
||||
fdp *uintptr
|
||||
fdp *int
|
||||
)
|
||||
if !useNilFdp {
|
||||
fdp = new(uintptr)
|
||||
fdp = new(int)
|
||||
}
|
||||
var closeFile func() error
|
||||
if f, err := container.Receive(key, &gotPayload, fdp); err != nil {
|
||||
if f, err := params.Receive(key, &gotPayload, fdp); err != nil {
|
||||
t.Fatalf("Receive: error = %v", err)
|
||||
} else {
|
||||
closeFile = f
|
||||
@@ -103,7 +109,7 @@ func TestSetupReceive(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if !useNilFdp {
|
||||
if int(*fdp) != dupFd {
|
||||
if *fdp != dupFd {
|
||||
t.Errorf("Fd: %d, want %d", *fdp, dupFd)
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -973,23 +973,23 @@ func connectName(name string, manager bool) (conn Conn, err error) {
|
||||
return connectName(name+"-manager", false)
|
||||
}
|
||||
|
||||
if path.IsAbs(name) || (len(name) > 0 && name[0] == '@') {
|
||||
if filepath.IsAbs(name) || (len(name) > 0 && name[0] == '@') {
|
||||
return Dial(name)
|
||||
} else {
|
||||
runtimeDir, ok := os.LookupEnv("PIPEWIRE_RUNTIME_DIR")
|
||||
if !ok || !path.IsAbs(runtimeDir) {
|
||||
if !ok || !filepath.IsAbs(runtimeDir) {
|
||||
runtimeDir, ok = os.LookupEnv("XDG_RUNTIME_DIR")
|
||||
}
|
||||
if !ok || !path.IsAbs(runtimeDir) {
|
||||
if !ok || !filepath.IsAbs(runtimeDir) {
|
||||
// this is cargo culted from windows stuff and has no effect normally;
|
||||
// keeping it to maintain compatibility in case someone sets this
|
||||
runtimeDir, ok = os.LookupEnv("USERPROFILE")
|
||||
}
|
||||
|
||||
if !ok || !path.IsAbs(runtimeDir) {
|
||||
if !ok || !filepath.IsAbs(runtimeDir) {
|
||||
runtimeDir = DEFAULT_SYSTEM_RUNTIME_DIR
|
||||
}
|
||||
return Dial(path.Join(runtimeDir, name))
|
||||
return Dial(filepath.Join(runtimeDir, name))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,31 @@ func TestFlatten(t *testing.T) {
|
||||
fs.ModeCharDevice | 0400,
|
||||
)},
|
||||
|
||||
{"coldboot", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices": {Mode: fs.ModeDir | 0700},
|
||||
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
|
||||
"block": {Mode: fs.ModeDir | 0700},
|
||||
"block/uevent": {Mode: 0600, Data: []byte{}},
|
||||
}, []pkg.FlatEntry{
|
||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||
|
||||
{Mode: fs.ModeDir | 0700, Path: "block"},
|
||||
{Mode: 0600, Path: "block/uevent", Data: []byte{}},
|
||||
|
||||
{Mode: fs.ModeDir | 0700, Path: "devices"},
|
||||
{Mode: fs.ModeDir | 0700, Path: "devices/empty"},
|
||||
{Mode: fs.ModeDir | 0700, Path: "devices/sub"},
|
||||
{Mode: 0600, Path: "devices/sub/uevent", Data: []byte("add")},
|
||||
{Mode: 0600, Path: "devices/uevent", Data: []byte("add")},
|
||||
}, pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8"), nil},
|
||||
|
||||
{"empty", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
@@ -159,6 +184,32 @@ func TestFlatten(t *testing.T) {
|
||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||
}, pkg.MustDecode("WVpvsVqVKg9Nsh744x57h51AuWUoUR2nnh8Md-EYBQpk6ziyTuUn6PLtF2e0Eu_d"), nil},
|
||||
|
||||
{"sample no assume checksum", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M/check": {Mode: 0400, Data: []byte{}},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/_wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
||||
"identifier/_wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
||||
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []pkg.FlatEntry{
|
||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||
|
||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
||||
{Mode: fs.ModeDir | 0500, Path: "checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M"},
|
||||
{Mode: 0400, Path: "checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M/check", Data: []byte{}},
|
||||
|
||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
||||
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
||||
|
||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||
}, pkg.MustDecode("OC290t23aimNo2Rp2pPwan5GI2KRLRdOwYxXQMD9jw0QROgHnNXWodoWdV0hwu2w"), nil},
|
||||
|
||||
{"sample tar step unpack", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"syscall"
|
||||
@@ -40,9 +40,6 @@ type ExecPath struct {
|
||||
W bool
|
||||
}
|
||||
|
||||
// SetSchedIdle is whether to set [ext.SCHED_IDLE] scheduling priority.
|
||||
var SetSchedIdle bool
|
||||
|
||||
// GetArtifactFunc is the function signature of [FContext.GetArtifact].
|
||||
type GetArtifactFunc func(Artifact) (*check.Absolute, unique.Handle[Checksum])
|
||||
|
||||
@@ -189,7 +186,7 @@ func NewExec(
|
||||
paths ...ExecPath,
|
||||
) Artifact {
|
||||
if name == "" {
|
||||
name = "exec-" + path.Base(pathname.String())
|
||||
name = "exec-" + filepath.Base(pathname.String())
|
||||
}
|
||||
if timeout <= 0 {
|
||||
timeout = ExecTimeoutDefault
|
||||
@@ -400,6 +397,7 @@ const SeccompPresets = std.PresetStrict &
|
||||
func (a *execArtifact) makeContainer(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
flags int,
|
||||
hostNet bool,
|
||||
temp, work *check.Absolute,
|
||||
getArtifact GetArtifactFunc,
|
||||
@@ -426,8 +424,9 @@ func (a *execArtifact) makeContainer(
|
||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||
z.ParentPerm = 0700
|
||||
z.HostNet = hostNet
|
||||
z.HostAbstract = flags&CHostAbstract != 0
|
||||
z.Hostname = "cure"
|
||||
z.SetScheduler = SetSchedIdle
|
||||
z.SetScheduler = flags&CSchedIdle != 0
|
||||
z.SchedPolicy = ext.SCHED_IDLE
|
||||
if z.HostNet {
|
||||
z.Hostname = "cure-net"
|
||||
@@ -563,6 +562,7 @@ func (c *Cache) EnterExec(
|
||||
var z *container.Container
|
||||
z, err = e.makeContainer(
|
||||
ctx, c.msg,
|
||||
c.flags,
|
||||
hostNet,
|
||||
temp, work,
|
||||
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
||||
@@ -579,6 +579,11 @@ func (c *Cache) EnterExec(
|
||||
z.Stdin, z.Stdout, z.Stderr = stdin, stdout, stderr
|
||||
z.Path, z.Args = path, args
|
||||
z.RetainSession = retainSession
|
||||
if stdin == os.Stdin {
|
||||
if s, ok := os.LookupEnv("TERM"); ok {
|
||||
z.Env = append(z.Env, "TERM="+s)
|
||||
}
|
||||
}
|
||||
|
||||
if err = z.Start(); err != nil {
|
||||
return
|
||||
@@ -597,7 +602,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
||||
msg := f.GetMessage()
|
||||
var z *container.Container
|
||||
if z, err = a.makeContainer(
|
||||
ctx, msg, hostNet,
|
||||
ctx, msg, f.cache.flags, hostNet,
|
||||
f.GetTempDir(), f.GetWorkDir(),
|
||||
f.GetArtifact,
|
||||
f.cache.Ident,
|
||||
|
||||
@@ -33,8 +33,7 @@ func TestExec(t *testing.T) {
|
||||
)
|
||||
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"offline", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
c.SetStrict(true)
|
||||
{"offline", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
testtool, testtoolDestroy := newTesttool()
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
@@ -111,8 +110,7 @@ func TestExec(t *testing.T) {
|
||||
testtoolDestroy(t, base, c)
|
||||
}, pkg.MustDecode("Q5DluWQCAeohLoiGRImurwFp3vdz9IfQCoj7Fuhh73s4KQPRHpEQEnHTdNHmB8Fx")},
|
||||
|
||||
{"net", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
c.SetStrict(true)
|
||||
{"net", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
testtool, testtoolDestroy := newTesttool()
|
||||
|
||||
wantChecksum := pkg.MustDecode(
|
||||
@@ -146,8 +144,7 @@ func TestExec(t *testing.T) {
|
||||
testtoolDestroy(t, base, c)
|
||||
}, pkg.MustDecode("bPYvvqxpfV7xcC1EptqyKNK1klLJgYHMDkzBcoOyK6j_Aj5hb0mXNPwTwPSK5F6Z")},
|
||||
|
||||
{"overlay root", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
c.SetStrict(true)
|
||||
{"overlay root", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
testtool, testtoolDestroy := newTesttool()
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
@@ -172,8 +169,7 @@ func TestExec(t *testing.T) {
|
||||
testtoolDestroy(t, base, c)
|
||||
}, pkg.MustDecode("PO2DSSCa4yoSgEYRcCSZfQfwow1yRigL3Ry-hI0RDI4aGuFBha-EfXeSJnG_5_Rl")},
|
||||
|
||||
{"overlay work", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
c.SetStrict(true)
|
||||
{"overlay work", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
testtool, testtoolDestroy := newTesttool()
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
@@ -203,8 +199,7 @@ func TestExec(t *testing.T) {
|
||||
testtoolDestroy(t, base, c)
|
||||
}, pkg.MustDecode("iaRt6l_Wm2n-h5UsDewZxQkCmjZjyL8r7wv32QT2kyV55-Lx09Dq4gfg9BiwPnKs")},
|
||||
|
||||
{"multiple layers", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
c.SetStrict(true)
|
||||
{"multiple layers", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
testtool, testtoolDestroy := newTesttool()
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
@@ -256,8 +251,7 @@ func TestExec(t *testing.T) {
|
||||
testtoolDestroy(t, base, c)
|
||||
}, pkg.MustDecode("O2YzyR7IUGU5J2CADy0hUZ3A5NkP_Vwzs4UadEdn2oMZZVWRtH0xZGJ3HXiimTnZ")},
|
||||
|
||||
{"overlay layer promotion", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
c.SetStrict(true)
|
||||
{"overlay layer promotion", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
testtool, testtoolDestroy := newTesttool()
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
|
||||
@@ -11,9 +11,7 @@ func TestFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"file", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
c.SetStrict(true)
|
||||
|
||||
{"file", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
cureMany(t, c, []cureStep{
|
||||
{"short", pkg.NewFile("null", []byte{0}), base.Append(
|
||||
"identifier",
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestIRRoundtrip(t *testing.T) {
|
||||
testCasesCache := make([]cacheTestCase, len(testCases))
|
||||
for i, tc := range testCases {
|
||||
want := tc.a
|
||||
testCasesCache[i] = cacheTestCase{tc.name, nil,
|
||||
testCasesCache[i] = cacheTestCase{tc.name, 0, nil,
|
||||
func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
r, w := io.Pipe()
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestHTTPGet(t *testing.T) {
|
||||
}))
|
||||
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"direct", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
{"direct", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
var r pkg.RContext
|
||||
rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
|
||||
reflect.NewAt(
|
||||
@@ -94,7 +94,7 @@ func TestHTTPGet(t *testing.T) {
|
||||
}
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")},
|
||||
|
||||
{"cure", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
{"cure", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
var r pkg.RContext
|
||||
rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
|
||||
reflect.NewAt(
|
||||
|
||||
@@ -13,10 +13,8 @@ import (
|
||||
"hash"
|
||||
"io"
|
||||
"io/fs"
|
||||
"iter"
|
||||
"maps"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
@@ -332,23 +330,6 @@ type FloodArtifact interface {
|
||||
Artifact
|
||||
}
|
||||
|
||||
// Flood returns an iterator over the dependency tree of an [Artifact].
|
||||
func Flood(a Artifact) iter.Seq[Artifact] {
|
||||
return func(yield func(Artifact) bool) {
|
||||
for _, d := range a.Dependencies() {
|
||||
if !yield(d) {
|
||||
return
|
||||
}
|
||||
|
||||
for d0 := range Flood(d) {
|
||||
if !yield(d0) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TrivialArtifact refers to an [Artifact] that cures without requiring that
|
||||
// any other [Artifact] is cured before it. Its dependency tree is ignored after
|
||||
// computing its identifier.
|
||||
@@ -507,6 +488,49 @@ type pendingArtifactDep struct {
|
||||
*sync.WaitGroup
|
||||
}
|
||||
|
||||
const (
|
||||
// CValidateKnown arranges for [KnownChecksum] outcomes to be validated to
|
||||
// match its intended checksum.
|
||||
//
|
||||
// A correct implementation of [KnownChecksum] does not successfully cure
|
||||
// with output not matching its intended checksum. When an implementation
|
||||
// fails to perform this validation correctly, the on-disk format enters
|
||||
// an inconsistent state (correctable by [Cache.Scrub]).
|
||||
//
|
||||
// This flag causes [Cache.Cure] to always compute the checksum, and reject
|
||||
// a cure if it does not match the intended checksum.
|
||||
//
|
||||
// This behaviour significantly reduces performance and is not recommended
|
||||
// outside of testing a custom [Artifact] implementation.
|
||||
CValidateKnown = 1 << iota
|
||||
|
||||
// CSchedIdle arranges for the [ext.SCHED_IDLE] scheduling priority to be
|
||||
// set for [KindExec] and [KindExecNet] containers.
|
||||
CSchedIdle
|
||||
|
||||
// CAssumeChecksum enables the use of [KnownChecksum] for duplicate function
|
||||
// call suppression via the on-disk cache.
|
||||
//
|
||||
// This may cause incorrect cure outcome if an impossible checksum is
|
||||
// specified that matches an output already present in the on-disk cache.
|
||||
// This may be avoided by purposefully specifying a statistically
|
||||
// unattainable checksum, like the zero value.
|
||||
//
|
||||
// While this optimisation might seem appealing, it is almost never
|
||||
// applicable in real world use. Almost every time this path was taken, it
|
||||
// was caused by an incorrect checksum accidentally left behind while
|
||||
// bumping a package. Only enable this if you are really sure you need it.
|
||||
CAssumeChecksum
|
||||
|
||||
// CHostAbstract disables restriction of sandboxed processes from connecting
|
||||
// to an abstract UNIX socket created by a host process.
|
||||
//
|
||||
// This is considered less secure in some systems, but does not introduce
|
||||
// impurity due to [KindExecNet] being [KnownChecksum]. This flag exists
|
||||
// to support kernels without Landlock LSM enabled.
|
||||
CHostAbstract
|
||||
)
|
||||
|
||||
// Cache is a support layer that implementations of [Artifact] can use to store
|
||||
// cured [Artifact] data in a content addressed fashion.
|
||||
type Cache struct {
|
||||
@@ -526,12 +550,8 @@ type Cache struct {
|
||||
|
||||
// Directory where all [Cache] related files are placed.
|
||||
base *check.Absolute
|
||||
|
||||
// Whether to validate [FileArtifact.Cure] for a [KnownChecksum] file. This
|
||||
// significantly reduces performance.
|
||||
strict bool
|
||||
// Maximum size of a dependency graph.
|
||||
threshold uintptr
|
||||
// Immutable cure options set by [Open].
|
||||
flags int
|
||||
|
||||
// Artifact to [unique.Handle] of identifier cache.
|
||||
artifact sync.Map
|
||||
@@ -564,22 +584,6 @@ type Cache struct {
|
||||
inExec atomic.Bool
|
||||
}
|
||||
|
||||
// IsStrict returns whether the [Cache] strictly verifies checksums.
|
||||
func (c *Cache) IsStrict() bool { return c.strict }
|
||||
|
||||
// SetStrict sets whether the [Cache] strictly verifies checksums, even when
|
||||
// the implementation promises to validate them internally. This significantly
|
||||
// reduces performance and is not recommended outside of testing.
|
||||
//
|
||||
// This method is not safe for concurrent use with any other method.
|
||||
func (c *Cache) SetStrict(strict bool) { c.strict = strict }
|
||||
|
||||
// SetThreshold imposes a maximum size on the dependency graph, checked on every
|
||||
// call to Cure. The zero value disables this check entirely.
|
||||
//
|
||||
// This method is not safe for concurrent use with any other method.
|
||||
func (c *Cache) SetThreshold(threshold uintptr) { c.threshold = threshold }
|
||||
|
||||
// extIdent is a [Kind] concatenated with [ID].
|
||||
type extIdent [wordSize + len(ID{})]byte
|
||||
|
||||
@@ -894,7 +898,7 @@ func (c *Cache) Scrub(checks int) error {
|
||||
se.DanglingIdentifiers = append(se.DanglingIdentifiers, *want)
|
||||
seMu.Unlock()
|
||||
return false
|
||||
} else if err = Decode(got, path.Base(linkname)); err != nil {
|
||||
} else if err = Decode(got, filepath.Base(linkname)); err != nil {
|
||||
seMu.Lock()
|
||||
lnp := dir.Append(linkname)
|
||||
se.Errs[lnp.Handle()] = append(se.Errs[lnp.Handle()], err)
|
||||
@@ -1059,7 +1063,7 @@ func (c *Cache) finaliseIdent(
|
||||
// [FileArtifact] to the filesystem. If err is nil, the caller is responsible
|
||||
// for closing the resulting [io.ReadCloser].
|
||||
func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
|
||||
if kc, ok := f.(KnownChecksum); ok {
|
||||
if kc, ok := f.(KnownChecksum); c.flags&CAssumeChecksum != 0 && ok {
|
||||
c.checksumMu.RLock()
|
||||
r, err = os.Open(c.base.Append(
|
||||
dirChecksum,
|
||||
@@ -1230,14 +1234,6 @@ func (e InvalidArtifactError) Error() string {
|
||||
return "artifact " + Encode(e) + " cannot be cured"
|
||||
}
|
||||
|
||||
// DependencyError refers to an artifact with a dependency tree larger than the
|
||||
// threshold specified by a previous call to [Cache.SetThreshold].
|
||||
type DependencyError struct{ A Artifact }
|
||||
|
||||
func (e DependencyError) Error() string {
|
||||
return "artifact has too many dependencies"
|
||||
}
|
||||
|
||||
// Cure cures the [Artifact] and returns its pathname and [Checksum]. Direct
|
||||
// calls to Cure are not subject to the cures limit.
|
||||
func (c *Cache) Cure(a Artifact) (
|
||||
@@ -1253,18 +1249,6 @@ func (c *Cache) Cure(a Artifact) (
|
||||
default:
|
||||
}
|
||||
|
||||
if c.threshold > 0 {
|
||||
var n uintptr
|
||||
for range Flood(a) {
|
||||
if n == c.threshold {
|
||||
err = DependencyError{a}
|
||||
return
|
||||
}
|
||||
n++
|
||||
}
|
||||
c.msg.Verbosef("visited %d artifacts", n)
|
||||
}
|
||||
|
||||
return c.cure(a, true)
|
||||
}
|
||||
|
||||
@@ -1488,7 +1472,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
return
|
||||
}
|
||||
buf := c.getIdentBuf()
|
||||
err = Decode((*Checksum)(buf[:]), path.Base(name))
|
||||
err = Decode((*Checksum)(buf[:]), filepath.Base(name))
|
||||
if err == nil {
|
||||
checksum = unique.Make(Checksum(buf[:]))
|
||||
}
|
||||
@@ -1522,16 +1506,18 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
checksums,
|
||||
)
|
||||
|
||||
c.checksumMu.RLock()
|
||||
checksumFi, err = os.Stat(checksumPathname.String())
|
||||
c.checksumMu.RUnlock()
|
||||
if c.flags&CAssumeChecksum != 0 {
|
||||
c.checksumMu.RLock()
|
||||
checksumFi, err = os.Stat(checksumPathname.String())
|
||||
c.checksumMu.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return
|
||||
}
|
||||
|
||||
checksumFi, err = nil, nil
|
||||
}
|
||||
|
||||
checksumFi, err = nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1587,7 +1573,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
}
|
||||
r, err = f.Cure(&RContext{common{c}})
|
||||
if err == nil {
|
||||
if checksumPathname == nil || c.IsStrict() {
|
||||
if checksumPathname == nil || c.flags&CValidateKnown != 0 {
|
||||
h := sha512.New384()
|
||||
hbw := c.getWriter(h)
|
||||
_, err = io.Copy(w, io.TeeReader(r, hbw))
|
||||
@@ -1604,7 +1590,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
if checksumPathname == nil {
|
||||
checksum = unique.Make(Checksum(buf[:]))
|
||||
checksums = Encode(Checksum(buf[:]))
|
||||
} else if c.IsStrict() {
|
||||
} else if c.flags&CValidateKnown != 0 {
|
||||
if got := Checksum(buf[:]); got != checksum.Value() {
|
||||
err = &ChecksumMismatchError{
|
||||
Got: got,
|
||||
@@ -1842,10 +1828,10 @@ func (c *Cache) Close() {
|
||||
func Open(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
cures int,
|
||||
flags, cures int,
|
||||
base *check.Absolute,
|
||||
) (*Cache, error) {
|
||||
return open(ctx, msg, cures, base, true)
|
||||
return open(ctx, msg, flags, cures, base, true)
|
||||
}
|
||||
|
||||
// open implements Open but allows omitting the [lockedfile] lock when called
|
||||
@@ -1853,7 +1839,7 @@ func Open(
|
||||
func open(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
cures int,
|
||||
flags, cures int,
|
||||
base *check.Absolute,
|
||||
lock bool,
|
||||
) (*Cache, error) {
|
||||
@@ -1875,6 +1861,7 @@ func open(
|
||||
|
||||
c := Cache{
|
||||
cures: make(chan struct{}, cures),
|
||||
flags: flags,
|
||||
|
||||
msg: msg,
|
||||
base: base,
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/info"
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/stub"
|
||||
"hakurei.app/message"
|
||||
@@ -33,7 +34,7 @@ import (
|
||||
func unsafeOpen(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
cures int,
|
||||
flags, cures int,
|
||||
base *check.Absolute,
|
||||
lock bool,
|
||||
) (*pkg.Cache, error)
|
||||
@@ -228,7 +229,7 @@ func TestIdent(t *testing.T) {
|
||||
var cache *pkg.Cache
|
||||
if a, err := check.NewAbs(t.TempDir()); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if cache, err = pkg.Open(t.Context(), msg, 0, a); err != nil {
|
||||
} else if cache, err = pkg.Open(t.Context(), msg, 0, 0, a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(cache.Close)
|
||||
@@ -252,6 +253,7 @@ func TestIdent(t *testing.T) {
|
||||
// on test completion.
|
||||
type cacheTestCase struct {
|
||||
name string
|
||||
flags int
|
||||
early func(t *testing.T, base *check.Absolute)
|
||||
f func(t *testing.T, base *check.Absolute, c *pkg.Cache)
|
||||
want pkg.Checksum
|
||||
@@ -288,8 +290,20 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
msg := message.New(log.New(os.Stderr, "cache: ", 0))
|
||||
msg.SwapVerbose(testing.Verbose())
|
||||
|
||||
flags := tc.flags
|
||||
|
||||
if info.CanDegrade {
|
||||
if _, err := container.LandlockGetABI(); err != nil {
|
||||
if !errors.Is(err, syscall.ENOSYS) {
|
||||
t.Fatalf("LandlockGetABI: error = %v", err)
|
||||
}
|
||||
flags |= pkg.CHostAbstract
|
||||
t.Log("Landlock LSM is unavailable, setting CHostAbstract")
|
||||
}
|
||||
}
|
||||
|
||||
var scrubFunc func() error // scrub after hashing
|
||||
if c, err := pkg.Open(t.Context(), msg, 1<<4, base); err != nil {
|
||||
if c, err := pkg.Open(t.Context(), msg, flags, 1<<4, base); err != nil {
|
||||
t.Fatalf("Open: error = %v", err)
|
||||
} else {
|
||||
t.Cleanup(c.Close)
|
||||
@@ -468,9 +482,7 @@ func TestCache(t *testing.T) {
|
||||
}()
|
||||
|
||||
testCases := []cacheTestCase{
|
||||
{"file", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
c.SetStrict(true)
|
||||
|
||||
{"file", pkg.CValidateKnown | pkg.CAssumeChecksum, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
identifier := (pkg.ID)(bytes.Repeat([]byte{
|
||||
0x75, 0xe6, 0x9d, 0x6d, 0xe7, 0x9f,
|
||||
}, 8))
|
||||
@@ -593,7 +605,7 @@ func TestCache(t *testing.T) {
|
||||
if c0, err := unsafeOpen(
|
||||
t.Context(),
|
||||
message.New(nil),
|
||||
0, base, false,
|
||||
0, 0, base, false,
|
||||
); err != nil {
|
||||
t.Fatalf("open: error = %v", err)
|
||||
} else {
|
||||
@@ -627,7 +639,7 @@ func TestCache(t *testing.T) {
|
||||
}
|
||||
}, pkg.MustDecode("St9rlE-mGZ5gXwiv_hzQ_B8bZP-UUvSNmf4nHUZzCMOumb6hKnheZSe0dmnuc4Q2")},
|
||||
|
||||
{"directory", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
{"directory", pkg.CAssumeChecksum, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
id := pkg.MustDecode(
|
||||
"HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY",
|
||||
)
|
||||
@@ -804,9 +816,7 @@ func TestCache(t *testing.T) {
|
||||
})
|
||||
}, pkg.MustDecode("WVpvsVqVKg9Nsh744x57h51AuWUoUR2nnh8Md-EYBQpk6ziyTuUn6PLtF2e0Eu_d")},
|
||||
|
||||
{"pending", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
c.SetStrict(true)
|
||||
|
||||
{"pending", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
wantErr := stub.UniqueError(0xcafe)
|
||||
n, ready := make(chan struct{}), make(chan struct{})
|
||||
go func() {
|
||||
@@ -876,7 +886,54 @@ func TestCache(t *testing.T) {
|
||||
<-wCureDone
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")},
|
||||
|
||||
{"scrub", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
{"no assume checksum", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
makeGarbage := func(work *check.Absolute, wantErr error) error {
|
||||
if err := os.Mkdir(work.String(), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(work.Append(
|
||||
"check",
|
||||
).String(), nil, 0400); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return wantErr
|
||||
}
|
||||
|
||||
wantChecksum := pkg.MustDecode("Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
{"create", overrideChecksum{wantChecksum, overrideIdent{pkg.ID{0xff, 0}, &stubArtifact{
|
||||
kind: pkg.KindTar,
|
||||
cure: func(t *pkg.TContext) error {
|
||||
return makeGarbage(t.GetWorkDir(), nil)
|
||||
},
|
||||
}}}, base.Append(
|
||||
"identifier",
|
||||
pkg.Encode(pkg.ID{0xff, 0}),
|
||||
), wantChecksum, nil},
|
||||
|
||||
{"reject", overrideChecksum{wantChecksum, overrideIdent{pkg.ID{0xfe, 1}, &stubArtifact{
|
||||
kind: pkg.KindTar,
|
||||
cure: func(t *pkg.TContext) error {
|
||||
return makeGarbage(t.GetWorkDir(), stub.UniqueError(0xbad))
|
||||
},
|
||||
}}}, nil, pkg.Checksum{}, stub.UniqueError(0xbad)},
|
||||
|
||||
{"match", overrideChecksum{wantChecksum, overrideIdent{pkg.ID{0xff, 1}, &stubArtifact{
|
||||
kind: pkg.KindTar,
|
||||
cure: func(t *pkg.TContext) error {
|
||||
return makeGarbage(t.GetWorkDir(), nil)
|
||||
},
|
||||
}}}, base.Append(
|
||||
"identifier",
|
||||
pkg.Encode(pkg.ID{0xff, 1}),
|
||||
), wantChecksum, nil},
|
||||
})
|
||||
}, pkg.MustDecode("OC290t23aimNo2Rp2pPwan5GI2KRLRdOwYxXQMD9jw0QROgHnNXWodoWdV0hwu2w")},
|
||||
|
||||
{"scrub", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
cureMany(t, c, []cureStep{
|
||||
{"bad measured file", newStubFile(
|
||||
pkg.KindHTTPGet,
|
||||
@@ -1182,7 +1239,7 @@ func (a earlyFailureF) Cure(*pkg.FContext) error {
|
||||
|
||||
func TestDependencyCureErrorEarly(t *testing.T) {
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"early", nil, func(t *testing.T, _ *check.Absolute, c *pkg.Cache) {
|
||||
{"early", 0, nil, func(t *testing.T, _ *check.Absolute, c *pkg.Cache) {
|
||||
_, _, err := c.Cure(earlyFailureF(8))
|
||||
if !errors.Is(err, stub.UniqueError(0xcafe)) {
|
||||
t.Fatalf("Cure: error = %v", err)
|
||||
@@ -1205,7 +1262,7 @@ func TestNew(t *testing.T) {
|
||||
if _, err := pkg.Open(
|
||||
t.Context(),
|
||||
message.New(nil),
|
||||
0, check.MustAbs(container.Nonexistent),
|
||||
0, 0, check.MustAbs(container.Nonexistent),
|
||||
); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
@@ -1233,7 +1290,7 @@ func TestNew(t *testing.T) {
|
||||
if _, err := pkg.Open(
|
||||
t.Context(),
|
||||
message.New(nil),
|
||||
0, tempDir.Append("cache"),
|
||||
0, 0, tempDir.Append("cache"),
|
||||
); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -169,7 +169,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
||||
}
|
||||
|
||||
if typeflag >= '0' && typeflag <= '9' && typeflag != tar.TypeDir {
|
||||
if err = root.MkdirAll(path.Dir(header.Name), 0700); err != nil {
|
||||
if err = root.MkdirAll(filepath.Dir(header.Name), 0700); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func TestTar(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"http", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
{"http", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
checkTarHTTP(t, base, c, fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestTar(t *testing.T) {
|
||||
))
|
||||
}, pkg.MustDecode("NQTlc466JmSVLIyWklm_u8_g95jEEb98PxJU-kjwxLpfdjwMWJq0G8ze9R4Vo1Vu")},
|
||||
|
||||
{"http expand", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
{"http expand", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
checkTarHTTP(t, base, c, fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
|
||||
18
internal/pkg/testdata/main.go
vendored
18
internal/pkg/testdata/main.go
vendored
@@ -7,7 +7,7 @@ import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
@@ -68,7 +68,7 @@ func main() {
|
||||
if got, err := os.Executable(); err != nil {
|
||||
log.Fatalf("Executable: error = %v", err)
|
||||
} else {
|
||||
iftPath = path.Join(path.Dir(path.Dir(got)), "ift")
|
||||
iftPath = filepath.Join(filepath.Dir(filepath.Dir(got)), "ift")
|
||||
|
||||
if got != wantExec {
|
||||
switch got {
|
||||
@@ -161,7 +161,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
if !layers {
|
||||
if path.Base(lowerdir) != checksumEmptyDir {
|
||||
if filepath.Base(lowerdir) != checksumEmptyDir {
|
||||
log.Fatal("unexpected artifact checksum")
|
||||
}
|
||||
} else {
|
||||
@@ -187,8 +187,8 @@ func main() {
|
||||
}
|
||||
|
||||
if len(lowerdirs) != 2 ||
|
||||
path.Base(lowerdirs[0]) != "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU" ||
|
||||
path.Base(lowerdirs[1]) != "nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK" {
|
||||
filepath.Base(lowerdirs[0]) != "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU" ||
|
||||
filepath.Base(lowerdirs[1]) != "nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK" {
|
||||
log.Fatalf("unexpected lowerdirs %s", strings.Join(lowerdirs, ", "))
|
||||
}
|
||||
}
|
||||
@@ -202,12 +202,12 @@ func main() {
|
||||
}
|
||||
|
||||
next()
|
||||
if path.Base(m.Root) != "OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb" {
|
||||
if filepath.Base(m.Root) != "OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb" {
|
||||
log.Fatal("unexpected file artifact checksum")
|
||||
}
|
||||
|
||||
next()
|
||||
if path.Base(m.Root) != checksumEmptyDir {
|
||||
if filepath.Base(m.Root) != checksumEmptyDir {
|
||||
log.Fatal("unexpected artifact checksum")
|
||||
}
|
||||
}
|
||||
@@ -226,13 +226,13 @@ func main() {
|
||||
log.Fatal("unexpected work mount entry")
|
||||
}
|
||||
} else {
|
||||
if path.Base(m.Root) != ident || m.Target != "/work" {
|
||||
if filepath.Base(m.Root) != ident || m.Target != "/work" {
|
||||
log.Fatal("unexpected work mount entry")
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
if path.Base(m.Root) != ident || m.Target != "/tmp" {
|
||||
if filepath.Base(m.Root) != ident || m.Target != "/tmp" {
|
||||
log.Fatal("unexpected temp mount entry")
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,10 @@ const (
|
||||
Bison
|
||||
Bzip2
|
||||
CMake
|
||||
Connman
|
||||
Coreutils
|
||||
Curl
|
||||
DBus
|
||||
DTC
|
||||
Diffutils
|
||||
Elfutils
|
||||
@@ -62,23 +64,32 @@ const (
|
||||
GenInitCPIO
|
||||
Gettext
|
||||
Git
|
||||
GnuTLS
|
||||
Go
|
||||
Gperf
|
||||
Grep
|
||||
Gzip
|
||||
Hakurei
|
||||
HakureiDist
|
||||
IPTables
|
||||
Kmod
|
||||
LibXau
|
||||
Libbsd
|
||||
Libcap
|
||||
Libev
|
||||
Libexpat
|
||||
Libiconv
|
||||
Libpsl
|
||||
Libffi
|
||||
Libgd
|
||||
Libtool
|
||||
Libiconv
|
||||
Libmd
|
||||
Libmnl
|
||||
Libnftnl
|
||||
Libpsl
|
||||
Libseccomp
|
||||
Libtasn1
|
||||
Libtool
|
||||
Libucontext
|
||||
Libunistring
|
||||
Libxml2
|
||||
Libxslt
|
||||
M4
|
||||
@@ -95,6 +106,7 @@ const (
|
||||
Nettle
|
||||
Ninja
|
||||
OpenSSL
|
||||
P11Kit
|
||||
PCRE2
|
||||
Parallel
|
||||
Patch
|
||||
@@ -119,6 +131,7 @@ const (
|
||||
PythonPygments
|
||||
QEMU
|
||||
Rdfind
|
||||
Readline
|
||||
Rsync
|
||||
Sed
|
||||
Setuptools
|
||||
@@ -153,6 +166,9 @@ const (
|
||||
// stages only. This preset and its direct output must never be exposed.
|
||||
gcc
|
||||
|
||||
// nettle3 is an older version of [Nettle].
|
||||
nettle3
|
||||
|
||||
// Stage0 is a tarball containing all compile-time dependencies of artifacts
|
||||
// part of the [Std] toolchain.
|
||||
Stage0
|
||||
@@ -291,6 +307,17 @@ var (
|
||||
artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once
|
||||
)
|
||||
|
||||
// zero zeros the value pointed to by p.
|
||||
func zero[T any](p *T) { var v T; *p = v }
|
||||
|
||||
// DropCaches arranges for all cached [pkg.Artifact] to be freed some time after
|
||||
// it returns. Must not be used concurrently with any other function from this
|
||||
// package.
|
||||
func DropCaches() {
|
||||
zero(&artifacts)
|
||||
zero(&artifactsOnce)
|
||||
}
|
||||
|
||||
// GetMetadata returns [Metadata] of a [PArtifact].
|
||||
func GetMetadata(p PArtifact) *Metadata { return &artifactsM[p] }
|
||||
|
||||
|
||||
@@ -19,6 +19,18 @@ func TestLoad(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAll(b *testing.B) {
|
||||
for b.Loop() {
|
||||
for i := range rosa.PresetEnd {
|
||||
rosa.Std.Load(rosa.PArtifact(i))
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
rosa.DropCaches()
|
||||
b.StartTimer()
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
func (t Toolchain) newCMake() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "4.3.0"
|
||||
checksum = "amBtnY2eGsEdlrB-cTRuOESBTsIqtyaxWlEKNlnp2EWLwAKWINjssilo4KXE6El9"
|
||||
version = "4.3.1"
|
||||
checksum = "RHpzZiM1kJ5bwLjo9CpXSeHJJg3hTtV9QxBYpQoYwKFtRh5YhGWpShrqZCSOzQN6"
|
||||
)
|
||||
return t.NewPackage("cmake", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/Kitware/CMake/releases/download/"+
|
||||
@@ -144,11 +144,11 @@ func (attr *CMakeHelper) name(name, version string) string {
|
||||
}
|
||||
|
||||
// extra returns a hardcoded slice of [CMake] and [Ninja].
|
||||
func (attr *CMakeHelper) extra(int) []PArtifact {
|
||||
func (attr *CMakeHelper) extra(int) P {
|
||||
if attr != nil && attr.Make {
|
||||
return []PArtifact{CMake, Make}
|
||||
return P{CMake, Make}
|
||||
}
|
||||
return []PArtifact{CMake, Ninja}
|
||||
return P{CMake, Ninja}
|
||||
}
|
||||
|
||||
// wantsChmod returns false.
|
||||
@@ -200,7 +200,7 @@ cmake -G ` + generate + ` \
|
||||
}
|
||||
}), " \\\n\t") + ` \
|
||||
-DCMAKE_INSTALL_PREFIX=/system \
|
||||
'/usr/src/` + name + `/` + path.Join(attr.Append...) + `'
|
||||
'/usr/src/` + name + `/` + filepath.Join(attr.Append...) + `'
|
||||
cmake --build .` + jobs + `
|
||||
cmake --install . --prefix=/work/system
|
||||
` + attr.Script
|
||||
|
||||
109
internal/rosa/connman.go
Normal file
109
internal/rosa/connman.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newConnman() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.0"
|
||||
checksum = "MhVTdJOhndnZn2SWd8URKo_Pj7Zvc14tntEbrVOf9L3yVWJvpb3v3Q6104tWJgtW"
|
||||
)
|
||||
return t.NewPackage("connman", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://git.kernel.org/pub/scm/network/connman/connman.git/"+
|
||||
"snapshot/connman-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"alpine-musl-res", `musl does not implement res_ninit
|
||||
|
||||
--- a/gweb/gresolv.c
|
||||
+++ b/gweb/gresolv.c
|
||||
@@ -877,8 +877,6 @@
|
||||
resolv->index = index;
|
||||
resolv->nameserver_list = NULL;
|
||||
|
||||
- res_ninit(&resolv->res);
|
||||
-
|
||||
return resolv;
|
||||
}
|
||||
|
||||
@@ -918,8 +916,6 @@
|
||||
|
||||
flush_nameservers(resolv);
|
||||
|
||||
- res_nclose(&resolv->res);
|
||||
-
|
||||
g_free(resolv);
|
||||
}
|
||||
|
||||
@@ -1022,24 +1018,19 @@
|
||||
debug(resolv, "hostname %s", hostname);
|
||||
|
||||
if (!resolv->nameserver_list) {
|
||||
- int i;
|
||||
-
|
||||
- for (i = 0; i < resolv->res.nscount; i++) {
|
||||
- char buf[100];
|
||||
- int family = resolv->res.nsaddr_list[i].sin_family;
|
||||
- void *sa_addr = &resolv->res.nsaddr_list[i].sin_addr;
|
||||
-
|
||||
- if (family != AF_INET &&
|
||||
- resolv->res._u._ext.nsaddrs[i]) {
|
||||
- family = AF_INET6;
|
||||
- sa_addr = &resolv->res._u._ext.nsaddrs[i]->sin6_addr;
|
||||
+ FILE *f = fopen("/etc/resolv.conf", "r");
|
||||
+ if (f) {
|
||||
+ char line[256], *s;
|
||||
+ int i;
|
||||
+ while (fgets(line, sizeof(line), f)) {
|
||||
+ if (strncmp(line, "nameserver", 10) || !isspace(line[10]))
|
||||
+ continue;
|
||||
+ for (s = &line[11]; isspace(s[0]); s++);
|
||||
+ for (i = 0; s[i] && !isspace(s[i]); i++);
|
||||
+ s[i] = 0;
|
||||
+ g_resolv_add_nameserver(resolv, s, 53, 0);
|
||||
}
|
||||
-
|
||||
- if (family != AF_INET && family != AF_INET6)
|
||||
- continue;
|
||||
-
|
||||
- if (inet_ntop(family, sa_addr, buf, sizeof(buf)))
|
||||
- g_resolv_add_nameserver(resolv, buf, 53, 0);
|
||||
+ fclose(f);
|
||||
}
|
||||
|
||||
if (!resolv->nameserver_list)
|
||||
`},
|
||||
},
|
||||
}, &MakeHelper{
|
||||
Generate: "./bootstrap",
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
PkgConfig,
|
||||
|
||||
DBus,
|
||||
IPTables,
|
||||
GnuTLS,
|
||||
Readline,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Connman] = Metadata{
|
||||
f: Toolchain.newConnman,
|
||||
|
||||
Name: "connman",
|
||||
Description: "a daemon for managing Internet connections",
|
||||
Website: "https://git.kernel.org/pub/scm/network/connman/connman.git/",
|
||||
|
||||
Dependencies: P{
|
||||
DBus,
|
||||
IPTables,
|
||||
GnuTLS,
|
||||
Readline,
|
||||
},
|
||||
|
||||
ID: 337,
|
||||
}
|
||||
}
|
||||
46
internal/rosa/dbus.go
Normal file
46
internal/rosa/dbus.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newDBus() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.16.2"
|
||||
checksum = "INwOuNdrDG7XW5ilW_vn8JSxEa444rRNc5ho97i84I1CNF09OmcFcV-gzbF4uCyg"
|
||||
)
|
||||
return t.NewPackage("dbus", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://gitlab.freedesktop.org/dbus/dbus/-/archive/"+
|
||||
"dbus-"+version+"/dbus-dbus-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
// OSError: [Errno 30] Read-only file system: '/usr/src/dbus/subprojects/packagecache'
|
||||
Writable: true,
|
||||
// PermissionError: [Errno 13] Permission denied: '/usr/src/dbus/subprojects/packagecache'
|
||||
Chmod: true,
|
||||
}, &MesonHelper{
|
||||
Setup: []KV{
|
||||
{"Depoll", "enabled"},
|
||||
{"Dinotify", "enabled"},
|
||||
{"Dx11_autolaunch", "disabled"},
|
||||
},
|
||||
},
|
||||
GLib,
|
||||
Libexpat,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[DBus] = Metadata{
|
||||
f: Toolchain.newDBus,
|
||||
|
||||
Name: "dbus",
|
||||
Description: "a message bus system",
|
||||
Website: "https://www.freedesktop.org/wiki/Software/dbus/",
|
||||
|
||||
Dependencies: P{
|
||||
GLib,
|
||||
Libexpat,
|
||||
},
|
||||
|
||||
ID: 5356,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func (t Toolchain) newGit() (pkg.Artifact, string) {
|
||||
const (
|
||||
@@ -87,17 +92,23 @@ func init() {
|
||||
|
||||
// NewViaGit returns a [pkg.Artifact] for cloning a git repository.
|
||||
func (t Toolchain) NewViaGit(
|
||||
name, url, rev string,
|
||||
url, rev string,
|
||||
checksum pkg.Checksum,
|
||||
) pkg.Artifact {
|
||||
return t.New(name+"-"+rev, 0, t.AppendPresets(nil,
|
||||
return t.New(strings.TrimSuffix(
|
||||
path.Base(url),
|
||||
".git",
|
||||
)+"-src-"+path.Base(rev), 0, t.AppendPresets(nil,
|
||||
NSSCACert,
|
||||
Git,
|
||||
), &checksum, nil, `
|
||||
git \
|
||||
-c advice.detachedHead=false \
|
||||
clone \
|
||||
--depth=1 \
|
||||
--revision=`+rev+` \
|
||||
--shallow-submodules \
|
||||
--recurse-submodules \
|
||||
`+url+` \
|
||||
/work
|
||||
rm -rf /work/.git
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func (t Toolchain) newM4() (pkg.Artifact, string) {
|
||||
const (
|
||||
@@ -754,8 +758,8 @@ func init() {
|
||||
|
||||
func (t Toolchain) newParallel() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "20260222"
|
||||
checksum = "4wxjMi3G2zMxr9hvLcIn6D7_12A3e5UNObeTPhzn7mDAYwsZApmmkxfGPyllQQ7E"
|
||||
version = "20260322"
|
||||
checksum = "gHoPmFkOO62ev4xW59HqyMlodhjp8LvTsBOwsVKHUUdfrt7KwB8koXmSVqQ4VOrB"
|
||||
)
|
||||
return t.NewPackage("parallel", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/parallel/parallel-"+version+".tar.bz2",
|
||||
@@ -781,6 +785,278 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLibunistring() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.4.2"
|
||||
checksum = "iW9BbfLoVlXjWoLTZ4AekQSu4cFBnLcZ4W8OHWbv0AhJNgD3j65_zqaLMzFKylg2"
|
||||
)
|
||||
return t.NewPackage("libunistring", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftp.gnu.org/gnu/libunistring/libunistring-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
ScriptEarly: `
|
||||
test_disable() { chmod +w "$2" && echo "$1" > "$2"; }
|
||||
|
||||
test_disable '#!/bin/sh' tests/test-c32ispunct.sh
|
||||
test_disable 'int main(){return 0;}' tests/test-c32ispunct.c
|
||||
`,
|
||||
}, (*MakeHelper)(nil),
|
||||
Diffutils,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libunistring] = Metadata{
|
||||
f: Toolchain.newLibunistring,
|
||||
|
||||
Name: "libunistring",
|
||||
Description: "provides functions for manipulating Unicode strings",
|
||||
Website: "https://www.gnu.org/software/libunistring/",
|
||||
|
||||
ID: 1747,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLibtasn1() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "4.21.0"
|
||||
checksum = "9DYI3UYbfYLy8JsKUcY6f0irskbfL0fHZA91Q-JEOA3kiUwpodyjemRsYRjUpjuq"
|
||||
)
|
||||
return t.NewPackage("libtasn1", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/libtasn1/libtasn1-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil)), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libtasn1] = Metadata{
|
||||
f: Toolchain.newLibtasn1,
|
||||
|
||||
Name: "libtasn1",
|
||||
Description: "the ASN.1 library used by GnuTLS, p11-kit and some other packages",
|
||||
Website: "https://www.gnu.org/software/libtasn1/",
|
||||
|
||||
ID: 1734,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newReadline() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "8.3"
|
||||
checksum = "r-lcGRJq_MvvBpOq47Z2Y1OI2iqrmtcqhTLVXR0xWo37ZpC2uT_md7gKq5o_qTMV"
|
||||
)
|
||||
return t.NewPackage("readline", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftp.gnu.org/gnu/readline/readline-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, &MakeHelper{
|
||||
Configure: []KV{
|
||||
{"with-curses"},
|
||||
{"with-shared-termcap-library"},
|
||||
},
|
||||
},
|
||||
Ncurses,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Readline] = Metadata{
|
||||
f: Toolchain.newReadline,
|
||||
|
||||
Name: "readline",
|
||||
Description: "provides a set of functions for use by applications that allow users to edit command lines as they are typed in",
|
||||
Website: "https://tiswww.cwru.edu/php/chet/readline/rltop.html",
|
||||
|
||||
Dependencies: P{
|
||||
Ncurses,
|
||||
},
|
||||
|
||||
ID: 4173,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newGnuTLS() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "3.8.12"
|
||||
checksum = "VPdP-nRydQQRJcnma-YA7CJYA_kzTJ2rb3QFeP6D27emSyInJ8sQ-Wzn518I38dl"
|
||||
)
|
||||
|
||||
var configureExtra []KV
|
||||
switch runtime.GOARCH {
|
||||
case "arm64":
|
||||
configureExtra = []KV{
|
||||
{"disable-hardware-acceleration"},
|
||||
}
|
||||
}
|
||||
|
||||
return t.NewPackage("gnutls", version, t.NewViaGit(
|
||||
"https://gitlab.com/gnutls/gnutls.git",
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"bootstrap-remove-gtk-doc", `diff --git a/bootstrap.conf b/bootstrap.conf
|
||||
index 1c3cc61e6..32bae9387 100644
|
||||
--- a/bootstrap.conf
|
||||
+++ b/bootstrap.conf
|
||||
@@ -50,7 +50,6 @@ bison 2.4
|
||||
gettext 0.17
|
||||
git 1.4.4
|
||||
gperf -
|
||||
-gtkdocize -
|
||||
perl 5.5
|
||||
wget -
|
||||
"
|
||||
diff --git a/configure.ac b/configure.ac
|
||||
index 5057536e5..731558a15 100644
|
||||
--- a/configure.ac
|
||||
+++ b/configure.ac
|
||||
@@ -403,11 +403,6 @@ if test "$enable_fuzzer_target" != "no";then
|
||||
AC_DEFINE([FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION], 1, [Enable fuzzer target -not for production])
|
||||
fi
|
||||
|
||||
-dnl
|
||||
-dnl check for gtk-doc
|
||||
-dnl
|
||||
-GTK_DOC_CHECK([1.14],[--flavour no-tmpl])
|
||||
-
|
||||
AM_GNU_GETTEXT([external])
|
||||
AM_GNU_GETTEXT_VERSION([0.19])
|
||||
m4_ifdef([AM_GNU_GET][TEXT_REQUIRE_VERSION],[
|
||||
diff --git a/doc/Makefile.am b/doc/Makefile.am
|
||||
index fb1390d70..52f0ad9af 100644
|
||||
--- a/doc/Makefile.am
|
||||
+++ b/doc/Makefile.am
|
||||
@@ -33,9 +33,6 @@ IMAGES = \
|
||||
pkcs11-vision.png
|
||||
|
||||
SUBDIRS = examples scripts credentials latex
|
||||
-if ENABLE_GTK_DOC
|
||||
-SUBDIRS += reference
|
||||
-endif
|
||||
|
||||
-include $(top_srcdir)/doc/doc.mk
|
||||
|
||||
diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am
|
||||
index f10c8ed3c..b711b58ec 100644
|
||||
--- a/doc/reference/Makefile.am
|
||||
+++ b/doc/reference/Makefile.am
|
||||
@@ -82,13 +82,4 @@ include $(top_srcdir)/gtk-doc.make
|
||||
# e.g. EXTRA_DIST += version.xml.in
|
||||
EXTRA_DIST += version.xml.in
|
||||
|
||||
-# Comment this out if you want 'make check' to test you doc status
|
||||
-# and run some sanity checks
|
||||
-if ENABLE_GTK_DOC
|
||||
-TESTS_ENVIRONMENT = \
|
||||
- DOC_MODULE=$(DOC_MODULE) DOC_MAIN_SGML_FILE=$(DOC_MAIN_SGML_FILE) \
|
||||
- SRCDIR=$(abs_srcdir) BUILDDIR=$(abs_builddir)
|
||||
-#TESTS = $(GTKDOC_CHECK)
|
||||
-endif
|
||||
-
|
||||
-include $(top_srcdir)/git.mk
|
||||
`},
|
||||
|
||||
{"alpine-tests-certtool", `I think this tests is simply wrong.
|
||||
When a PIN is given, the program should run in batch mode.
|
||||
So the question for "Enter password" should _not_ be present.
|
||||
|
||||
DO NOT REMOVE UNLESS VERIFIED IT'S NOT ACTUALLY NECESSARY ANYMORE.
|
||||
|
||||
--- a/tests/cert-tests/certtool.sh 2019-02-07 07:33:45.960887338 +0000
|
||||
+++ b/tests/cert-tests/certtool.sh 2019-02-07 07:36:14.550955051 +0000
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
#check whether password is being honoured
|
||||
#some CI runners need GNUTLS_PIN (GNUTLS_PIN=${PASS})
|
||||
- ${SETSID} "${CERTTOOL}" --generate-self-signed --load-privkey ${TMPFILE1} --template ${srcdir}/templates/template-test.tmpl --ask-pass >${TMPFILE2} 2>&1 <<EOF
|
||||
+ GNUTLS_PIN=${PASS} ${SETSID} "${CERTTOOL}" --generate-self-signed --load-privkey ${TMPFILE1} --template ${srcdir}/templates/template-test.tmpl --ask-pass >${TMPFILE2} 2>&1 <<EOF
|
||||
$PASS
|
||||
EOF
|
||||
if test $? != 0;then
|
||||
@@ -59,7 +59,7 @@
|
||||
fi
|
||||
|
||||
grep "Enter password" ${TMPFILE2} >/dev/null 2>&1
|
||||
- if test $? != 0;then
|
||||
+ if test $? != 1; then
|
||||
cat ${TMPFILE2}
|
||||
echo "No password was asked"
|
||||
exit 1
|
||||
`},
|
||||
|
||||
{"test-kernel-version-ksh", `diff --git a/tests/scripts/common.sh b/tests/scripts/common.sh
|
||||
index 1b78b8cf1..350156a86 100644
|
||||
--- a/tests/scripts/common.sh
|
||||
+++ b/tests/scripts/common.sh
|
||||
@@ -279,10 +279,6 @@ kernel_version_check() {
|
||||
kernel_major=$(echo $kernel_version | cut -d. -f1 2>/dev/null)
|
||||
kernel_minor=$(echo $kernel_version | cut -d. -f2 2>/dev/null)
|
||||
|
||||
- if ! [[ "$kernel_major" =~ ^[0-9]+$ ]] || ! [[ "$kernel_minor" =~ ^[0-9]+$ ]]; then
|
||||
- return 1
|
||||
- fi
|
||||
-
|
||||
if [ "$kernel_major" -lt "$required_major" ]; then
|
||||
return 1
|
||||
fi
|
||||
`},
|
||||
},
|
||||
}, &MakeHelper{
|
||||
Generate: "./bootstrap --skip-po --no-git --gnulib-srcdir=gnulib",
|
||||
|
||||
Configure: append([]KV{
|
||||
{"disable-doc"},
|
||||
{"disable-openssl-compatibility"},
|
||||
|
||||
{"with-default-trust-store-file", "/system/etc/ssl/certs/ca-bundle.crt"},
|
||||
{"with-default-trust-store-pkcs11", "pkcs11:"},
|
||||
|
||||
{"with-zlib", "link"},
|
||||
{"with-zstd", "link"},
|
||||
}, configureExtra...),
|
||||
},
|
||||
Gzip,
|
||||
Automake,
|
||||
Libtool,
|
||||
Bison,
|
||||
Gettext,
|
||||
Gperf,
|
||||
PkgConfig,
|
||||
|
||||
Python,
|
||||
Texinfo,
|
||||
Diffutils,
|
||||
NSSCACert,
|
||||
|
||||
Libev,
|
||||
Zlib,
|
||||
Zstd,
|
||||
P11Kit,
|
||||
nettle3,
|
||||
Libunistring,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[GnuTLS] = Metadata{
|
||||
f: Toolchain.newGnuTLS,
|
||||
|
||||
Name: "gnutls",
|
||||
Description: "a secure communications library implementing the SSL, TLS and DTLS protocols",
|
||||
Website: "https://gnutls.org",
|
||||
|
||||
Dependencies: P{
|
||||
Zlib,
|
||||
Zstd,
|
||||
P11Kit,
|
||||
nettle3,
|
||||
Libunistring,
|
||||
},
|
||||
|
||||
ID: 1221,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newBinutils() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.46.0"
|
||||
@@ -864,15 +1140,24 @@ func init() {
|
||||
|
||||
func (t Toolchain) newMPC() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.3.1"
|
||||
checksum = "o8r8K9R4x7PuRx0-JE3-bC5jZQrtxGV2nkB773aqJ3uaxOiBDCID1gKjPaaDxX4V"
|
||||
version = "1.4.0"
|
||||
checksum = "TbrxLiE3ipQrHz_F3Xzz4zqBAnkMWyjhNwIK6wh9360RZ39xMt8rxfW3LxA9SnvU"
|
||||
)
|
||||
return t.NewPackage("mpc", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://gcc.gnu.org/pub/gcc/infrastructure/"+
|
||||
"mpc-"+version+".tar.gz",
|
||||
return t.NewPackage("mpc", version, t.NewViaGit(
|
||||
"https://gitlab.inria.fr/mpc/mpc.git",
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
), &PackageAttr{
|
||||
// does not find mpc-impl.h otherwise
|
||||
EnterSource: true,
|
||||
}, &MakeHelper{
|
||||
InPlace: true,
|
||||
Generate: "autoreconf -vfi",
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
Texinfo,
|
||||
|
||||
MPFR,
|
||||
), version
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func (t Toolchain) newGoLatest() (pkg.Artifact, string) {
|
||||
case "amd64":
|
||||
bootstrapExtra = append(bootstrapExtra, t.newGoBootstrap())
|
||||
|
||||
case "arm64":
|
||||
case "arm64", "riscv64":
|
||||
bootstrapEnv = append(bootstrapEnv, "GOROOT_BOOTSTRAP=/system")
|
||||
bootstrapExtra = t.AppendPresets(bootstrapExtra, gcc)
|
||||
finalEnv = append(finalEnv, "CGO_ENABLED=0")
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
@@ -10,16 +8,13 @@ import (
|
||||
func (t Toolchain) newGLib() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.88.0"
|
||||
checksum = "bCLkAmp1o_Po4cXDbC06AyjLyxkBxyNJnflwBpSdf4W8K6dc9xKj6Pm3JYbHPdDf"
|
||||
checksum = "T79Cg4z6j-sDZ2yIwvbY4ccRv2-fbwbqgcw59F5NQ6qJT6z4v261vbYp3dHO6Ma3"
|
||||
)
|
||||
return t.NewPackage("glib", version, pkg.NewHTTPGet(
|
||||
nil, "https://download.gnome.org/sources/glib/"+
|
||||
strings.Join(strings.SplitN(version, ".", 3)[:2], ".")+
|
||||
"/glib-"+version+".tar.xz",
|
||||
return t.NewPackage("glib", version, t.NewViaGit(
|
||||
"https://gitlab.gnome.org/GNOME/glib.git",
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
SourceKind: SourceKindTarXZ,
|
||||
|
||||
Paths: []pkg.ExecPath{
|
||||
pkg.Path(fhs.AbsEtc.Append(
|
||||
"machine-id",
|
||||
@@ -39,7 +34,6 @@ func (t Toolchain) newGLib() (pkg.Artifact, string) {
|
||||
{"Ddefault_library", "both"},
|
||||
},
|
||||
},
|
||||
XZ,
|
||||
PythonPackaging,
|
||||
Bash,
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@ package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
const kernelVersion = "6.12.78"
|
||||
const kernelVersion = "6.12.80"
|
||||
|
||||
var kernelSource = pkg.NewHTTPGetTar(
|
||||
nil, "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/"+
|
||||
"snapshot/linux-"+kernelVersion+".tar.gz",
|
||||
mustDecode("iUlZA-nv04TUOL0TmgDGBjaOe0sIaXTqLvuR4owYgHMZM8vecusnMMqbeuuZP4_G"),
|
||||
mustDecode("_iJEAYoQISJxefuWZYfv0RPWUmHHIjHQw33Fapix-irXrEIREP5ruK37UJW4uMZO"),
|
||||
pkg.TarGzip,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Automatically generated file; DO NOT EDIT.
|
||||
# Linux/x86 6.12.78 Kernel Configuration
|
||||
# Linux/x86 6.12.80 Kernel Configuration
|
||||
#
|
||||
CONFIG_CC_VERSION_TEXT="clang version 22.1.2"
|
||||
CONFIG_GCC_VERSION=0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Automatically generated file; DO NOT EDIT.
|
||||
# Linux/arm64 6.12.78 Kernel Configuration
|
||||
# Linux/arm64 6.12.80 Kernel Configuration
|
||||
#
|
||||
CONFIG_CC_VERSION_TEXT="clang version 21.1.8"
|
||||
CONFIG_GCC_VERSION=0
|
||||
|
||||
11450
internal/rosa/kernel_riscv64.config
Normal file
11450
internal/rosa/kernel_riscv64.config
Normal file
File diff suppressed because it is too large
Load Diff
8
internal/rosa/kernel_riscv64.go
Normal file
8
internal/rosa/kernel_riscv64.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package rosa
|
||||
|
||||
import _ "embed"
|
||||
|
||||
//go:embed kernel_riscv64.config
|
||||
var kernelConfig []byte
|
||||
|
||||
const kernelName = "bzImage"
|
||||
64
internal/rosa/libbsd.go
Normal file
64
internal/rosa/libbsd.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibmd() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.1.0"
|
||||
checksum = "9apYqPPZm0j5HQT8sCsVIhnVIqRD7XgN7kPIaTwTqnTuUq5waUAMq4M7ev8CODJ1"
|
||||
)
|
||||
return t.NewPackage("libmd", version, t.NewViaGit(
|
||||
"https://git.hadrons.org/git/libmd.git",
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
), nil, &MakeHelper{
|
||||
Generate: "echo '" + version + "' > .dist-version && ./autogen",
|
||||
ScriptMakeEarly: `
|
||||
install -D /usr/src/libmd/src/helper.c src/helper.c
|
||||
`,
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libmd] = Metadata{
|
||||
f: Toolchain.newLibmd,
|
||||
|
||||
Name: "libmd",
|
||||
Description: "Message Digest functions from BSD systems",
|
||||
Website: "https://www.hadrons.org/software/libmd/",
|
||||
|
||||
ID: 15525,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLibbsd() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.12.2"
|
||||
checksum = "NVS0xFLTwSP8JiElEftsZ-e1_C-IgJhHrHE77RwKt5178M7r087waO-zYx2_dfGX"
|
||||
)
|
||||
return t.NewPackage("libbsd", version, t.NewViaGit(
|
||||
"https://gitlab.freedesktop.org/libbsd/libbsd.git",
|
||||
"refs/tags/"+version,
|
||||
mustDecode(checksum),
|
||||
), nil, &MakeHelper{
|
||||
Generate: "echo '" + version + "' > .dist-version && ./autogen",
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
|
||||
Libmd,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libbsd] = Metadata{
|
||||
f: Toolchain.newLibbsd,
|
||||
|
||||
Name: "libbsd",
|
||||
Description: "provides useful functions commonly found on BSD systems",
|
||||
Website: "https://libbsd.freedesktop.org/",
|
||||
|
||||
ID: 1567,
|
||||
}
|
||||
}
|
||||
26
internal/rosa/libev.go
Normal file
26
internal/rosa/libev.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibev() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "4.33"
|
||||
checksum = "774eSXV_4k8PySRprUDChbEwsw-kzjIFnJ3MpNOl5zDpamBRvC3BqPyRxvkwcL6_"
|
||||
)
|
||||
return t.NewPackage("libev", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://dist.schmorp.de/libev/Attic/libev-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil)), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libev] = Metadata{
|
||||
f: Toolchain.newLibev,
|
||||
|
||||
Name: "libev",
|
||||
Description: "a full-featured and high-performance event loop",
|
||||
Website: "http://libev.schmorp.de/",
|
||||
|
||||
ID: 1605,
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,22 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibxml2() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.15.2"
|
||||
checksum = "xba8VCofMsbWmQypA2__M9_RXNq9HDEuccjib6-tOni6OPngplRoAsYdY3NdYf8o"
|
||||
checksum = "zwQvCIBnjzUFY-inX5ckfNT3mIezsCRV55C_Iztde5OnRTB3u33lfO5h03g7DK_8"
|
||||
)
|
||||
return t.NewPackage("libxml2", version, pkg.NewHTTPGet(
|
||||
nil, "https://download.gnome.org/sources/libxml2/"+
|
||||
strings.Join(strings.Split(version, ".")[:2], ".")+
|
||||
"/libxml2-"+version+".tar.xz",
|
||||
return t.NewPackage("libxml2", version, t.NewViaGit(
|
||||
"https://gitlab.gnome.org/GNOME/libxml2.git",
|
||||
"refs/tags/v"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
SourceKind: SourceKindTarXZ,
|
||||
}, (*MakeHelper)(nil),
|
||||
// can't create shell.out: Read-only file system
|
||||
Writable: true,
|
||||
}, (*MesonHelper)(nil),
|
||||
Git,
|
||||
Diffutils,
|
||||
XZ,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibxslt() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.1.45"
|
||||
checksum = "vw72UbREQnA3YDYuZ9-93hDr9BYCaKV6oh_U4Kt4n1Js_na4E-nFj-ksZnZ0kvEK"
|
||||
checksum = "MZc_dyUWpHChkWDKa5iycrECxBsRd4ZMbYfL4VojTbung593mlH2tHGmxYB6NFYT"
|
||||
)
|
||||
return t.NewPackage("libxslt", version, pkg.NewHTTPGet(
|
||||
nil, "https://download.gnome.org/sources/libxslt/"+
|
||||
strings.Join(strings.Split(version, ".")[:2], ".")+
|
||||
"/libxslt-"+version+".tar.xz",
|
||||
return t.NewPackage("libxslt", version, t.NewViaGit(
|
||||
"https://gitlab.gnome.org/GNOME/libxslt.git",
|
||||
"refs/tags/v"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
SourceKind: SourceKindTarXZ,
|
||||
}, &MakeHelper{
|
||||
), nil, &MakeHelper{
|
||||
Generate: "NOCONFIGURE=1 ./autogen.sh",
|
||||
|
||||
// python libxml2 cyclic dependency
|
||||
SkipCheck: true,
|
||||
},
|
||||
XZ,
|
||||
Automake,
|
||||
Libtool,
|
||||
Python,
|
||||
PkgConfig,
|
||||
|
||||
|
||||
@@ -207,6 +207,8 @@ func (t Toolchain) newLLVM() (musl, compilerRT, runtimes, clang pkg.Artifact) {
|
||||
target = "X86"
|
||||
case "arm64":
|
||||
target = "AArch64"
|
||||
case "riscv64":
|
||||
target = "RISCV"
|
||||
|
||||
default:
|
||||
panic("unsupported target " + runtime.GOARCH)
|
||||
|
||||
4
internal/rosa/llvm_riscv64.go
Normal file
4
internal/rosa/llvm_riscv64.go
Normal file
@@ -0,0 +1,4 @@
|
||||
package rosa
|
||||
|
||||
// clangPatches are patches applied to the LLVM source tree for building clang.
|
||||
var clangPatches []KV
|
||||
@@ -84,8 +84,8 @@ func (*MakeHelper) name(name, version string) string {
|
||||
}
|
||||
|
||||
// extra returns make and other optional dependencies.
|
||||
func (attr *MakeHelper) extra(flag int) []PArtifact {
|
||||
extra := []PArtifact{Make}
|
||||
func (attr *MakeHelper) extra(flag int) P {
|
||||
extra := P{Make}
|
||||
if (attr == nil || !attr.OmitDefaults) && flag&TEarly == 0 {
|
||||
extra = append(extra,
|
||||
Gawk,
|
||||
|
||||
@@ -72,9 +72,7 @@ func (*MesonHelper) name(name, version string) string {
|
||||
}
|
||||
|
||||
// extra returns hardcoded meson runtime dependencies.
|
||||
func (*MesonHelper) extra(int) []PArtifact {
|
||||
return []PArtifact{Meson}
|
||||
}
|
||||
func (*MesonHelper) extra(int) P { return P{Meson} }
|
||||
|
||||
// wantsChmod returns false.
|
||||
func (*MesonHelper) wantsChmod() bool { return false }
|
||||
@@ -114,6 +112,7 @@ cd "$(mktemp -d)"
|
||||
meson setup \
|
||||
` + strings.Join(slices.Collect(func(yield func(string) bool) {
|
||||
for _, v := range append([]KV{
|
||||
{"wrap-mode", "nodownload"},
|
||||
{"prefix", "/system"},
|
||||
{"buildtype", "release"},
|
||||
}, attr.Setup...) {
|
||||
|
||||
@@ -18,6 +18,8 @@ func (t Toolchain) newNcurses() (pkg.Artifact, string) {
|
||||
Configure: []KV{
|
||||
{"with-pkg-config"},
|
||||
{"enable-pc-files"},
|
||||
{"with-shared"},
|
||||
{"with-cxx-shared"},
|
||||
},
|
||||
},
|
||||
PkgConfig,
|
||||
|
||||
149
internal/rosa/netfilter.go
Normal file
149
internal/rosa/netfilter.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newLibmnl() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.0.5"
|
||||
checksum = "DN-vbbvQDpxXJm0TJ6xlluILvfrB86avrCTX50XyE9SEFSAZ_o8nuKc5Gu0Am7-u"
|
||||
)
|
||||
return t.NewPackage("libmnl", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://www.netfilter.org/projects/libmnl/files/"+
|
||||
"libmnl-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"libbsd-sys-queue", `diff --git a/examples/netfilter/nfct-daemon.c b/examples/netfilter/nfct-daemon.c
|
||||
index d223ac2..a7878d0 100644
|
||||
--- a/examples/netfilter/nfct-daemon.c
|
||||
+++ b/examples/netfilter/nfct-daemon.c
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <linux/netfilter/nfnetlink.h>
|
||||
#include <linux/netfilter/nfnetlink_conntrack.h>
|
||||
|
||||
-#include <sys/queue.h>
|
||||
+#include <bsd/sys/queue.h>
|
||||
|
||||
struct nstats {
|
||||
LIST_ENTRY(nstats) list;
|
||||
`},
|
||||
},
|
||||
}, &MakeHelper{
|
||||
Configure: []KV{
|
||||
{"enable-static"},
|
||||
},
|
||||
},
|
||||
Libbsd,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libmnl] = Metadata{
|
||||
f: Toolchain.newLibmnl,
|
||||
|
||||
Name: "libmnl",
|
||||
Description: "a minimalistic user-space library oriented to Netlink developers",
|
||||
Website: "https://www.netfilter.org/projects/libmnl/",
|
||||
|
||||
ID: 1663,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLibnftnl() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.3.1"
|
||||
checksum = "91ou66K-I17iX6DB6hiQkhhC_v4DFW5iDGzwjVRNbJNEmKqowLZBlh3FY-ZDO0r9"
|
||||
)
|
||||
return t.NewPackage("libnftnl", version, t.NewViaGit(
|
||||
"https://git.netfilter.org/libnftnl",
|
||||
"refs/tags/libnftnl-"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
Env: []string{
|
||||
"CFLAGS=-D_GNU_SOURCE",
|
||||
},
|
||||
}, &MakeHelper{
|
||||
Generate: "./autogen.sh",
|
||||
Configure: []KV{
|
||||
{"enable-static"},
|
||||
},
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
PkgConfig,
|
||||
|
||||
Libmnl,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libnftnl] = Metadata{
|
||||
f: Toolchain.newLibnftnl,
|
||||
|
||||
Name: "libnftnl",
|
||||
Description: "a userspace library providing a low-level netlink API to the in-kernel nf_tables subsystem",
|
||||
Website: "https://www.netfilter.org/projects/libnftnl/",
|
||||
|
||||
Dependencies: P{
|
||||
Libmnl,
|
||||
},
|
||||
|
||||
ID: 1681,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newIPTables() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.8.13"
|
||||
checksum = "TUA-cFIAsiMvtRR-XzQvXzoIhJUOc9J2gQDJCbBRjmgmVfGfPTCf58wL7e-cUKVQ"
|
||||
)
|
||||
return t.NewPackage("iptables", version, t.NewViaGit(
|
||||
"https://git.netfilter.org/iptables",
|
||||
"refs/tags/v"+version,
|
||||
mustDecode(checksum),
|
||||
), &PackageAttr{
|
||||
ScriptEarly: `
|
||||
rm \
|
||||
extensions/libxt_connlabel.txlate \
|
||||
extensions/libxt_conntrack.txlate
|
||||
sed -i \
|
||||
's/de:ad:0:be:ee:ff/DE:AD:00:BE:EE:FF/g' \
|
||||
extensions/libebt_dnat.txlate \
|
||||
extensions/libebt_snat.txlate
|
||||
`,
|
||||
}, &MakeHelper{
|
||||
Generate: "./autogen.sh",
|
||||
Configure: []KV{
|
||||
{"enable-static"},
|
||||
},
|
||||
ScriptCheckEarly: `
|
||||
ln -s ../system/bin/bash /bin/
|
||||
chmod +w /etc/ && ln -s ../usr/src/iptables/etc/ethertypes /etc/
|
||||
`,
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
PkgConfig,
|
||||
Bash,
|
||||
Python,
|
||||
|
||||
Libnftnl,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[IPTables] = Metadata{
|
||||
f: Toolchain.newIPTables,
|
||||
|
||||
Name: "iptables",
|
||||
Description: "the userspace command line program used to configure the Linux 2.4.x and later packet filtering ruleset",
|
||||
Website: "https://www.netfilter.org/projects/iptables/",
|
||||
|
||||
Dependencies: P{
|
||||
Libnftnl,
|
||||
},
|
||||
|
||||
ID: 1394,
|
||||
}
|
||||
}
|
||||
33
internal/rosa/nettle3.go
Normal file
33
internal/rosa/nettle3.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newNettle3() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "3.10.2"
|
||||
checksum = "07aXlj10X5llf67jIqRQAA1pgLSgb0w_JYggZVPuKNoc-B-_usb5Kr8FrfBe7g1S"
|
||||
)
|
||||
return t.NewPackage("nettle", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://ftpmirror.gnu.org/gnu/nettle/nettle-"+version+".tar.gz",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
M4,
|
||||
Diffutils,
|
||||
|
||||
GMP,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[nettle3] = Metadata{
|
||||
f: Toolchain.newNettle3,
|
||||
|
||||
Name: "nettle3",
|
||||
Description: "a low-level cryptographic library",
|
||||
Website: "https://www.lysator.liu.se/~nisse/nettle/",
|
||||
|
||||
Dependencies: P{
|
||||
GMP,
|
||||
},
|
||||
}
|
||||
}
|
||||
40
internal/rosa/p11.go
Normal file
40
internal/rosa/p11.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newP11Kit() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.26.2"
|
||||
checksum = "3ei-6DUVtYzrRVe-SubtNgRlweXd6H2qHmUu-_5qVyIn6gSTvZbGS2u79Y8IFb2N"
|
||||
)
|
||||
return t.NewPackage("p11-kit", version, t.NewViaGit(
|
||||
"https://github.com/p11-glue/p11-kit.git",
|
||||
"refs/tags/"+version, mustDecode(checksum),
|
||||
), nil, &MesonHelper{
|
||||
Setup: []KV{
|
||||
{"Dsystemd", "disabled"},
|
||||
{"Dlibffi", "enabled"},
|
||||
},
|
||||
},
|
||||
Coreutils,
|
||||
Diffutils,
|
||||
|
||||
Libtasn1,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[P11Kit] = Metadata{
|
||||
f: Toolchain.newP11Kit,
|
||||
|
||||
Name: "p11-kit",
|
||||
Description: "provides a way to load and enumerate PKCS#11 modules",
|
||||
Website: "https://p11-glue.freedesktop.org/p11-kit.html",
|
||||
|
||||
Dependencies: P{
|
||||
Libffi,
|
||||
Libtasn1,
|
||||
},
|
||||
|
||||
ID: 2582,
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
func (t Toolchain) newPerl() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "5.42.1"
|
||||
checksum = "FsJVq5CZFA7nZklfUl1eC6z2ECEu02XaB1pqfHSKtRLZWpnaBjlB55QOhjKpjkQ2"
|
||||
version = "5.42.2"
|
||||
checksum = "Me_xFfgkRnVyG0sE6a74TktK2OUq9Z1LVJNEu_9RdZG3S2fbjfzNiuk2SJqHAgbm"
|
||||
)
|
||||
return t.NewPackage("perl", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://www.cpan.org/src/5.0/perl-"+version+".tar.gz",
|
||||
@@ -135,7 +135,7 @@ func (t Toolchain) newViaPerlMakeMaker(
|
||||
{"PREFIX", "/system"},
|
||||
},
|
||||
Check: []string{"test"},
|
||||
}, slices.Concat(extra, []PArtifact{
|
||||
}, slices.Concat(extra, P{
|
||||
Perl,
|
||||
})...)
|
||||
}
|
||||
|
||||
@@ -5,19 +5,23 @@ import "hakurei.app/internal/pkg"
|
||||
func (t Toolchain) newPkgConfig() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.29.2"
|
||||
checksum = "gi7yAvkwo20Inys1tHbeYZ3Wjdm5VPkrnO0Q6_QZPCAwa1zrA8F4a63cdZDd-717"
|
||||
checksum = "6UsGqEMA8EER_5b9N0b32UCqiRy39B6_RnPfvuslWhtFV1qYD4DfS10crGZN_TP2"
|
||||
)
|
||||
return t.NewPackage("pkg-config", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://pkgconfig.freedesktop.org/releases/"+
|
||||
"pkg-config-"+version+".tar.gz",
|
||||
nil, "https://gitlab.freedesktop.org/pkg-config/pkg-config/-/archive"+
|
||||
"/pkg-config-"+version+"/pkg-config-pkg-config-"+version+".tar.bz2",
|
||||
mustDecode(checksum),
|
||||
pkg.TarGzip,
|
||||
pkg.TarBzip2,
|
||||
), nil, &MakeHelper{
|
||||
Generate: "./autogen.sh --no-configure",
|
||||
Configure: []KV{
|
||||
{"CFLAGS", "'-Wno-int-conversion'"},
|
||||
{"with-internal-glib"},
|
||||
},
|
||||
}), version
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[PkgConfig] = Metadata{
|
||||
|
||||
@@ -3,7 +3,7 @@ package rosa_test
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unique"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
func TestReportZeroLength(t *testing.T) {
|
||||
report := path.Join(t.TempDir(), "report")
|
||||
report := filepath.Join(t.TempDir(), "report")
|
||||
if err := os.WriteFile(report, nil, 0400); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func TestReportZeroLength(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReportSIGSEGV(t *testing.T) {
|
||||
report := path.Join(t.TempDir(), "report")
|
||||
report := filepath.Join(t.TempDir(), "report")
|
||||
if err := os.WriteFile(report, make([]byte, 64), 0400); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ func linuxArch() string {
|
||||
return "x86_64"
|
||||
case "arm64":
|
||||
return "aarch64"
|
||||
case "riscv64":
|
||||
return "riscv64"
|
||||
|
||||
default:
|
||||
panic("unsupported target " + runtime.GOARCH)
|
||||
@@ -409,7 +411,7 @@ type Helper interface {
|
||||
// name returns the value passed to the name argument of [Toolchain.New].
|
||||
name(name, version string) string
|
||||
// extra returns helper-specific dependencies.
|
||||
extra(flag int) []PArtifact
|
||||
extra(flag int) P
|
||||
|
||||
// wantsChmod returns whether the source directory should be made writable.
|
||||
wantsChmod() bool
|
||||
|
||||
@@ -60,7 +60,7 @@ func getCache(t *testing.T) *pkg.Cache {
|
||||
msg := message.New(log.New(os.Stderr, "rosa: ", 0))
|
||||
msg.SwapVerbose(true)
|
||||
|
||||
if buildTestCache, err = pkg.Open(ctx, msg, 0, a); err != nil {
|
||||
if buildTestCache, err = pkg.Open(ctx, msg, 0, 0, a); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -91,3 +91,13 @@ func TestCureAll(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStage3(b *testing.B) {
|
||||
for b.Loop() {
|
||||
rosa.Std.Load(rosa.LLVMClang)
|
||||
|
||||
b.StopTimer()
|
||||
rosa.DropCaches()
|
||||
b.StartTimer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,8 @@ func NewStage0() pkg.Artifact {
|
||||
seed = "tqM1Li15BJ-uFG8zU-XjgFxoN_kuzh1VxrSDVUVa0vGmo-NeWapSftH739sY8EAg"
|
||||
case "arm64":
|
||||
seed = "CJj3ZSnRyLmFHlWIQtTPQD9oikOZY4cD_mI3v_-LIYc2hhg-cq_CZFBLzQBAkFIn"
|
||||
case "riscv64":
|
||||
seed = "FcszJjcVWdKAnn-bt8qmUn5GUUTjv_xQjXOWkUpOplRkG3Ckob3StUoAi5KQ5-QF"
|
||||
|
||||
default:
|
||||
panic("unsupported target " + runtime.GOARCH)
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
func (t Toolchain) newUtilLinux() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.41.3"
|
||||
checksum = "gPTd5JJ2ho_Rd0qainuogcLiiWwKSXEZPXN3yCCRl0m0KBgMaqwFuMjYgu9z8zCH"
|
||||
version = "2.42"
|
||||
checksum = "Uy8Nxg9DsW5YwDoeaZeZTyQJ2YmnaaL_fSsQXsLUiFFUd7wnZeD_3SEaVO7ClJlk"
|
||||
)
|
||||
return t.NewPackage("util-linux", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://www.kernel.org/pub/linux/utils/util-linux/"+
|
||||
|
||||
@@ -54,8 +54,8 @@ func init() {
|
||||
|
||||
func (t Toolchain) newWaylandProtocols() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.47"
|
||||
checksum = "B_NodZ7AQfCstcx7kgbaVjpkYOzbAQq0a4NOk-SA8bQixAE20FY3p1-6gsbPgHn9"
|
||||
version = "1.48"
|
||||
checksum = "xvfHCBIzXGwOqOu9b8dfhGw_U29Pd-g4JBwpYIaxee8SwEbxi6NaVU-Y1Q7wY4jK"
|
||||
)
|
||||
return t.NewPackage("wayland-protocols", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://gitlab.freedesktop.org/wayland/wayland-protocols/"+
|
||||
|
||||
@@ -4,8 +4,8 @@ import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newXZ() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "5.8.2"
|
||||
checksum = "rXT-XCp9R2q6cXqJ5qenp0cmGPfiENQiU3BWtUVeVgArfRmSsISeUJgvCR3zI0a0"
|
||||
version = "5.8.3"
|
||||
checksum = "nCdayphPGdIdVoAZ2hR4vYlhDG9LeVKho_i7ealTud4Vxy5o5dWe0VwFlN7utuUL"
|
||||
)
|
||||
return t.NewPackage("xz", version, pkg.NewHTTPGetTar(
|
||||
nil, "https://github.com/tukaani-project/xz/releases/download/"+
|
||||
|
||||
@@ -3,7 +3,7 @@ package system
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -18,7 +18,7 @@ func TestPipeWireOp(t *testing.T) {
|
||||
|
||||
checkOpBehaviour(t, checkNoParallel, []opBehaviourTestCase{
|
||||
{"success", 0xbeef, 0xff, &pipewireOp{nil,
|
||||
m(path.Join(t.TempDir(), "pipewire")),
|
||||
m(filepath.Join(t.TempDir(), "pipewire")),
|
||||
"org.chromium.Chromium",
|
||||
"ebf083d1b175911782d413369b64ce7c",
|
||||
}, []stub.Call{
|
||||
|
||||
78
internal/uevent/coldboot.go
Normal file
78
internal/uevent/coldboot.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package uevent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// synthAdd is prepared bytes written to uevent to cause a synthetic add event
|
||||
// to be emitted during coldboot.
|
||||
var synthAdd = []byte(KOBJ_ADD.String())
|
||||
|
||||
// Coldboot writes "add" to every uevent file that it finds in /sys/devices.
|
||||
// This causes the kernel to regenerate the uevents for these paths.
|
||||
//
|
||||
// The specified pathname must present the sysfs root.
|
||||
//
|
||||
// Note that while [AOSP documentation] claims to also scan /sys/class and
|
||||
// /sys/block, this is no longer the case, and the documentation was not updated
|
||||
// when this changed.
|
||||
//
|
||||
// [AOSP documentation]: https://android.googlesource.com/platform/system/core/+/master/init/README.ueventd.md
|
||||
func Coldboot(
|
||||
ctx context.Context,
|
||||
pathname string,
|
||||
uuid *UUID,
|
||||
visited chan<- string,
|
||||
handleWalkErr func(error) error,
|
||||
) error {
|
||||
if handleWalkErr == nil {
|
||||
handleWalkErr = func(err error) error {
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
log.Println("coldboot", err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
add := synthAdd
|
||||
if uuid != nil {
|
||||
add = slices.Concat(add, []byte{' '}, []byte(uuid.String()))
|
||||
}
|
||||
|
||||
return filepath.WalkDir(filepath.Join(pathname, "devices"), func(
|
||||
path string,
|
||||
d fs.DirEntry,
|
||||
err error,
|
||||
) error {
|
||||
if err != nil {
|
||||
return handleWalkErr(err)
|
||||
}
|
||||
if err = ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() || d.Name() != "uevent" {
|
||||
return nil
|
||||
}
|
||||
if err = os.WriteFile(path, add, 0); err != nil {
|
||||
return handleWalkErr(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case visited <- path:
|
||||
break
|
||||
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
227
internal/uevent/coldboot_test.go
Normal file
227
internal/uevent/coldboot_test.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package uevent_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/uevent"
|
||||
)
|
||||
|
||||
func TestColdboot(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
d := t.TempDir()
|
||||
if err := os.Chmod(d, 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, s := range []string{
|
||||
"devices",
|
||||
"devices/sub",
|
||||
"devices/empty",
|
||||
"block",
|
||||
} {
|
||||
if err := os.MkdirAll(filepath.Join(d, s), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range [][2]string{
|
||||
{"devices/uevent", ""},
|
||||
{"devices/sub/uevent", ""},
|
||||
{"block/uevent", ""},
|
||||
} {
|
||||
if err := os.WriteFile(
|
||||
filepath.Join(d, f[0]),
|
||||
[]byte(f[1]),
|
||||
0600,
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
visited := make(chan string)
|
||||
var got []string
|
||||
wg.Go(func() {
|
||||
for path := range visited {
|
||||
got = append(got, path)
|
||||
}
|
||||
})
|
||||
|
||||
err := uevent.Coldboot(t.Context(), d, nil, visited, func(err error) error {
|
||||
t.Errorf("handleWalkErr: %v", err)
|
||||
return err
|
||||
})
|
||||
close(visited)
|
||||
if err != nil {
|
||||
t.Fatalf("Coldboot: error = %v", err)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
want := []string{
|
||||
"devices/sub/uevent",
|
||||
"devices/uevent",
|
||||
}
|
||||
for i, rel := range want {
|
||||
want[i] = filepath.Join(d, rel)
|
||||
}
|
||||
if !slices.Equal(got, want) {
|
||||
t.Errorf("Coldboot: %#v, want %#v", got, want)
|
||||
}
|
||||
|
||||
var checksum pkg.Checksum
|
||||
if err = pkg.HashDir(&checksum, check.MustAbs(d)); err != nil {
|
||||
t.Fatalf("HashDir: error = %v", err)
|
||||
}
|
||||
|
||||
wantChecksum := pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8")
|
||||
if checksum != wantChecksum {
|
||||
t.Errorf(
|
||||
"Coldboot: checksum = %s, want %s",
|
||||
pkg.Encode(checksum),
|
||||
pkg.Encode(wantChecksum),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestColdbootError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
dF func(t *testing.T, d string) (wantErr error)
|
||||
vF func(<-chan string, context.Context, context.CancelFunc)
|
||||
hF func(d string, err error) error
|
||||
}{
|
||||
{"walk", func(t *testing.T, d string) (wantErr error) {
|
||||
wantErr = &os.PathError{
|
||||
Op: "open",
|
||||
Path: filepath.Join(d, "devices"),
|
||||
Err: syscall.EACCES,
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(d, "devices"), 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}, nil, nil},
|
||||
|
||||
{"write", func(t *testing.T, d string) (wantErr error) {
|
||||
wantErr = &os.PathError{
|
||||
Op: "open",
|
||||
Path: filepath.Join(d, "devices/uevent"),
|
||||
Err: syscall.EACCES,
|
||||
}
|
||||
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = os.WriteFile(filepath.Join(d, "devices/uevent"), nil, 0); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}, nil, nil},
|
||||
|
||||
{"deref", func(t *testing.T, d string) (wantErr error) {
|
||||
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = os.Symlink("/proc/nonexistent", filepath.Join(d, "devices/uevent")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}, nil, nil},
|
||||
|
||||
{"deref handle", func(t *testing.T, d string) (wantErr error) {
|
||||
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = os.Symlink("/proc/nonexistent", filepath.Join(d, "devices/uevent")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}, nil, func(d string, err error) error {
|
||||
if reflect.DeepEqual(err, &os.PathError{
|
||||
Op: "open",
|
||||
Path: filepath.Join(d, "devices/uevent"),
|
||||
Err: syscall.ENOENT,
|
||||
}) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}},
|
||||
|
||||
{"cancel early", func(t *testing.T, d string) (wantErr error) {
|
||||
wantErr = context.Canceled
|
||||
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}, func(visited <-chan string, ctx context.Context, cancel context.CancelFunc) {
|
||||
if visited == nil {
|
||||
cancel()
|
||||
}
|
||||
return
|
||||
}, nil},
|
||||
|
||||
{"cancel", func(t *testing.T, d string) (wantErr error) {
|
||||
wantErr = context.Canceled
|
||||
if err := os.Mkdir(filepath.Join(d, "devices"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = os.WriteFile(filepath.Join(d, "devices/uevent"), nil, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = os.Mkdir(filepath.Join(d, "devices/sub"), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = os.WriteFile(filepath.Join(d, "devices/sub/uevent"), nil, 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return
|
||||
}, func(visited <-chan string, ctx context.Context, cancel context.CancelFunc) {
|
||||
if visited == nil {
|
||||
return
|
||||
}
|
||||
<-visited
|
||||
cancel()
|
||||
return
|
||||
}, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
d := t.TempDir()
|
||||
wantErr := tc.dF(t, d)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
var visited chan string
|
||||
if tc.vF != nil {
|
||||
tc.vF(nil, ctx, cancel)
|
||||
visited = make(chan string)
|
||||
defer close(visited)
|
||||
wg.Go(func() { tc.vF(visited, ctx, cancel) })
|
||||
}
|
||||
|
||||
var handleWalkErr func(error) error
|
||||
if tc.hF != nil {
|
||||
handleWalkErr = func(err error) error {
|
||||
return tc.hF(d, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := uevent.Coldboot(ctx, d, new(uevent.UUID), visited, handleWalkErr); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("Coldboot: error = %v, want %v", err, wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,14 @@ package uevent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/internal/netlink"
|
||||
)
|
||||
@@ -18,6 +22,19 @@ type (
|
||||
Recoverable interface{ recoverable() }
|
||||
// Nontrivial is satisfied by errors preferring a JSON encoding.
|
||||
Nontrivial interface{ nontrivial() }
|
||||
|
||||
// NeedsColdboot is satisfied by errors indicating divergence of local state
|
||||
// from the kernel, usually from lost uevent data.
|
||||
NeedsColdboot interface {
|
||||
Recoverable
|
||||
coldboot()
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
exclConsume = iota
|
||||
|
||||
_exclLen
|
||||
)
|
||||
|
||||
// Conn represents a NETLINK_KOBJECT_UEVENT socket.
|
||||
@@ -25,27 +42,27 @@ type Conn struct {
|
||||
conn *netlink.Conn
|
||||
|
||||
// Whether currently between a call to enterExcl and exitExcl.
|
||||
excl atomic.Bool
|
||||
excl [_exclLen]atomic.Bool
|
||||
}
|
||||
|
||||
// enterExcl must be called entering a critical section that interacts with conn.
|
||||
func (c *Conn) enterExcl() error {
|
||||
if !c.excl.CompareAndSwap(false, true) {
|
||||
func (c *Conn) enterExcl(k int) error {
|
||||
if !c.excl[k].CompareAndSwap(false, true) {
|
||||
return syscall.EAGAIN
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// exitExcl must be called exiting a critical section that interacts with conn.
|
||||
func (c *Conn) exitExcl() { c.excl.Store(false) }
|
||||
func (c *Conn) exitExcl(k int) { c.excl[k].Store(false) }
|
||||
|
||||
// Close closes the underlying socket.
|
||||
func (c *Conn) Close() error { return c.conn.Close() }
|
||||
|
||||
// Dial returns the address of a newly connected [Conn].
|
||||
func Dial() (*Conn, error) {
|
||||
func Dial(rcvbuf int64) (*Conn, error) {
|
||||
// kernel group is hard coded in lib/kobject_uevent.c, undocumented
|
||||
c, err := netlink.Dial(syscall.NETLINK_KOBJECT_UEVENT, 1)
|
||||
c, err := netlink.Dial(syscall.NETLINK_KOBJECT_UEVENT, 1, rcvbuf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -58,6 +75,18 @@ var (
|
||||
ErrBadSocket = errors.New("unexpected socket address")
|
||||
)
|
||||
|
||||
// ReceiveBufferError indicates one or more [Message] being lost due to the
|
||||
// socket receive buffer filling up. This is usually caused by epoll waking the
|
||||
// receiving program up too late.
|
||||
type ReceiveBufferError struct{ _ [0]*ReceiveBufferError }
|
||||
|
||||
var _ NeedsColdboot = ReceiveBufferError{}
|
||||
|
||||
func (ReceiveBufferError) recoverable() {}
|
||||
func (ReceiveBufferError) coldboot() {}
|
||||
func (ReceiveBufferError) Unwrap() error { return syscall.ENOBUFS }
|
||||
func (e ReceiveBufferError) Error() string { return syscall.ENOBUFS.Error() }
|
||||
|
||||
// BadPortError is returned by [Conn.Consume] upon receiving a message that did
|
||||
// not come from the kernel.
|
||||
type BadPortError syscall.SockaddrNetlink
|
||||
@@ -70,36 +99,240 @@ func (e *BadPortError) Error() string {
|
||||
" on NETLINK_KOBJECT_UEVENT"
|
||||
}
|
||||
|
||||
// Consume continuously receives and parses events from the kernel. It returns
|
||||
// the first error it encounters.
|
||||
// receiveEvent receives a single event and returns the address of its [Message].
|
||||
func (c *Conn) receiveEvent(ctx context.Context) (*Message, error) {
|
||||
data, _, from, err := c.conn.Recvmsg(ctx, 0)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.ENOBUFS) {
|
||||
return nil, ReceiveBufferError{}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// lib/kobject_uevent.c:
|
||||
// set portid 0 to inform userspace message comes from kernel
|
||||
if v, ok := from.(*syscall.SockaddrNetlink); !ok {
|
||||
return nil, ErrBadSocket
|
||||
} else if v.Pid != 0 {
|
||||
return nil, (*BadPortError)(v)
|
||||
|
||||
}
|
||||
|
||||
var msg Message
|
||||
if err = msg.UnmarshalBinary(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &msg, err
|
||||
}
|
||||
|
||||
// UUID represents the value of SYNTH_UUID.
|
||||
//
|
||||
// This is not a generic UUID implementation. Do not attempt to use it for
|
||||
// anything other than passing and interpreting the SYNTH_UUID environment
|
||||
// variable of a uevent.
|
||||
type UUID [16]byte
|
||||
|
||||
const (
|
||||
// SizeUUID is the fixed size of string representation of [UUID] according
|
||||
// to Documentation/ABI/testing/sysfs-uevent.
|
||||
SizeUUID = 4 + len(UUID{})*2
|
||||
|
||||
// UUIDSep is the separator byte of [UUID].
|
||||
UUIDSep = '-'
|
||||
)
|
||||
|
||||
var (
|
||||
_ encoding.TextAppender = new(UUID)
|
||||
_ encoding.TextMarshaler = new(UUID)
|
||||
_ encoding.TextUnmarshaler = new(UUID)
|
||||
)
|
||||
|
||||
// String formats uuid according to Documentation/ABI/testing/sysfs-uevent.
|
||||
func (uuid *UUID) String() string {
|
||||
s := make([]byte, 0, SizeUUID)
|
||||
s = hex.AppendEncode(s, uuid[:4])
|
||||
s = append(s, UUIDSep)
|
||||
s = hex.AppendEncode(s, uuid[4:6])
|
||||
s = append(s, UUIDSep)
|
||||
s = hex.AppendEncode(s, uuid[6:8])
|
||||
s = append(s, UUIDSep)
|
||||
s = hex.AppendEncode(s, uuid[8:10])
|
||||
s = append(s, UUIDSep)
|
||||
s = hex.AppendEncode(s, uuid[10:16])
|
||||
return unsafe.String(unsafe.SliceData(s), len(s))
|
||||
}
|
||||
|
||||
func (uuid *UUID) AppendText(data []byte) ([]byte, error) {
|
||||
return append(data, uuid.String()...), nil
|
||||
}
|
||||
func (uuid *UUID) MarshalText() ([]byte, error) {
|
||||
return uuid.AppendText(nil)
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrAutoUUID is returned parsing a SYNTH_UUID generated by the kernel for
|
||||
// a synthetic event without a UUID passed in.
|
||||
ErrAutoUUID = errors.New("UUID is not passed in")
|
||||
)
|
||||
|
||||
// UUIDSizeError describes an incorrectly sized string representation of [UUID].
|
||||
type UUIDSizeError int
|
||||
|
||||
func (e UUIDSizeError) Error() string {
|
||||
return "got " + strconv.Itoa(int(e)) + " bytes " +
|
||||
"instead of " + strconv.Itoa(SizeUUID)
|
||||
}
|
||||
|
||||
// UUIDSeparatorError is an invalid separator in a malformed string
|
||||
// representation of [UUID].
|
||||
type UUIDSeparatorError byte
|
||||
|
||||
func (e UUIDSeparatorError) Error() string {
|
||||
return fmt.Sprintf("invalid UUID separator: %#U", rune(e))
|
||||
}
|
||||
|
||||
// UnmarshalText parses data according to Documentation/ABI/testing/sysfs-uevent.
|
||||
func (uuid *UUID) UnmarshalText(data []byte) (err error) {
|
||||
if len(data) == 1 && data[0] == '0' {
|
||||
return ErrAutoUUID
|
||||
}
|
||||
if len(data) != SizeUUID {
|
||||
return UUIDSizeError(len(data))
|
||||
}
|
||||
|
||||
if _, err = hex.Decode(uuid[:], data[:8]); err != nil {
|
||||
return
|
||||
}
|
||||
if data[8] != UUIDSep {
|
||||
return UUIDSeparatorError(data[8])
|
||||
}
|
||||
data = data[9:]
|
||||
|
||||
if _, err = hex.Decode(uuid[4:], data[:4]); err != nil {
|
||||
return
|
||||
}
|
||||
if data[4] != UUIDSep {
|
||||
return UUIDSeparatorError(data[4])
|
||||
}
|
||||
data = data[5:]
|
||||
|
||||
if _, err = hex.Decode(uuid[6:], data[:4]); err != nil {
|
||||
return
|
||||
}
|
||||
if data[4] != UUIDSep {
|
||||
return UUIDSeparatorError(data[4])
|
||||
}
|
||||
data = data[5:]
|
||||
|
||||
if _, err = hex.Decode(uuid[8:], data[:4]); err != nil {
|
||||
return
|
||||
}
|
||||
if data[4] != UUIDSep {
|
||||
return UUIDSeparatorError(data[4])
|
||||
}
|
||||
data = data[5:]
|
||||
|
||||
_, err = hex.Decode(uuid[10:], data)
|
||||
return
|
||||
}
|
||||
|
||||
// Consume continuously receives and parses events from the kernel and handles
|
||||
// [Recoverable] and [NeedsColdboot] errors via caller-supplied functions,
|
||||
// entering coldboot when required.
|
||||
//
|
||||
// For each uevent file visited by [Coldboot], handleColdbootVisited is called
|
||||
// with its pathname. This function must never block.
|
||||
//
|
||||
// When consuming events, a non-nil error not satisfying [Recoverable] is
|
||||
// returned immediately. Otherwise, handleConsumeErr is called with the error
|
||||
// value. If the error satisfies [NeedsColdboot], a [Coldboot] is arranged
|
||||
// before event processing resumes. If handleConsumeErr returns false, the error
|
||||
// value is immediately returned as is.
|
||||
//
|
||||
// Callers are expected to reject excessively frequent [NeedsColdboot] errors
|
||||
// in handleConsumeErr to avoid being stuck in a [Coldboot] loop. Event
|
||||
// processing is allowed to restart without initial coldboot after recovering
|
||||
// from such a condition, provided the caller adequately reports the degraded,
|
||||
// diverging state to the user.
|
||||
//
|
||||
// Callers must not restart event processing after a non-nil error that does not
|
||||
// satisfy [Recoverable] is returned.
|
||||
func (c *Conn) Consume(ctx context.Context, events chan<- *Message) error {
|
||||
if err := c.enterExcl(); err != nil {
|
||||
func (c *Conn) Consume(
|
||||
ctx context.Context,
|
||||
sysfs string,
|
||||
uuid *UUID,
|
||||
events chan<- *Message,
|
||||
coldboot bool,
|
||||
|
||||
handleColdbootVisited func(string),
|
||||
handleConsumeErr func(error) bool,
|
||||
handleWalkErr func(error) error,
|
||||
) error {
|
||||
if err := c.enterExcl(exclConsume); err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.exitExcl()
|
||||
defer c.exitExcl(exclConsume)
|
||||
|
||||
for {
|
||||
data, from, err := c.conn.Recvfrom(ctx, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
filterErr := func(err error) (error, bool) {
|
||||
if _, ok := err.(Recoverable); !ok {
|
||||
return err, true
|
||||
}
|
||||
|
||||
// lib/kobject_uevent.c:
|
||||
// set portid 0 to inform userspace message comes from kernel
|
||||
if v, ok := from.(*syscall.SockaddrNetlink); !ok {
|
||||
return ErrBadSocket
|
||||
} else if v.Pid != 0 {
|
||||
return (*BadPortError)(v)
|
||||
|
||||
// avoids dropping pending coldboot
|
||||
if _, ok := err.(NeedsColdboot); ok {
|
||||
coldboot = true
|
||||
}
|
||||
|
||||
var msg Message
|
||||
if err = msg.UnmarshalBinary(data); err != nil {
|
||||
return err
|
||||
}
|
||||
events <- &msg
|
||||
return err, !handleConsumeErr(err)
|
||||
}
|
||||
|
||||
retry:
|
||||
if coldboot {
|
||||
goto coldboot
|
||||
}
|
||||
for {
|
||||
msg, err := c.receiveEvent(ctx)
|
||||
if err == nil {
|
||||
events <- msg
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := filterErr(err); ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
coldboot:
|
||||
coldboot = false
|
||||
|
||||
visited := make(chan string)
|
||||
ctxColdboot, cancelColdboot := context.WithCancel(ctx)
|
||||
var coldbootErr error
|
||||
go func() {
|
||||
coldbootErr = Coldboot(ctxColdboot, sysfs, uuid, visited, handleWalkErr)
|
||||
close(visited)
|
||||
}()
|
||||
for pathname := range visited {
|
||||
handleColdbootVisited(pathname)
|
||||
|
||||
for {
|
||||
msg, err := c.receiveEvent(nil)
|
||||
if err == nil {
|
||||
events <- msg
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, syscall.EWOULDBLOCK) {
|
||||
break
|
||||
}
|
||||
if filteredErr, ok := filterErr(err); ok {
|
||||
cancelColdboot()
|
||||
return filteredErr
|
||||
}
|
||||
}
|
||||
}
|
||||
cancelColdboot()
|
||||
if coldbootErr != nil {
|
||||
return coldbootErr
|
||||
}
|
||||
goto retry
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@ package uevent_test
|
||||
import (
|
||||
"context"
|
||||
"encoding"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/uevent"
|
||||
)
|
||||
|
||||
@@ -22,6 +25,12 @@ func adeT[V any, S interface {
|
||||
*V
|
||||
}](t *testing.T, name string, v V, want string, wantErr, wantErrE error) {
|
||||
t.Helper()
|
||||
|
||||
noEncode := strings.HasSuffix(name, "\x00")
|
||||
if noEncode {
|
||||
name = name[:len(name)-1]
|
||||
}
|
||||
|
||||
f := func(t *testing.T) {
|
||||
if name != "" {
|
||||
t.Parallel()
|
||||
@@ -45,6 +54,10 @@ func adeT[V any, S interface {
|
||||
}
|
||||
})
|
||||
|
||||
if noEncode {
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("encode", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Helper()
|
||||
@@ -113,10 +126,49 @@ func adeB[V any, S interface {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUUID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
adeT(t, "sample", uevent.UUID{
|
||||
0xfe, 0x4d, 0x7c, 0x9d,
|
||||
0xb8, 0xc6,
|
||||
0x4a, 0x70,
|
||||
0x9e, 0xf1,
|
||||
0x3d, 0x8a, 0x58, 0xd1, 0x8e, 0xed,
|
||||
}, "fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed", nil, nil)
|
||||
|
||||
adeT(t, "auto\x00", uevent.UUID{}, "0", uevent.ErrAutoUUID, nil)
|
||||
adeT(t, "short\x00", uevent.UUID{}, "1", uevent.UUIDSizeError(1), nil)
|
||||
|
||||
adeT(t, "bad0\x00", uevent.UUID{}, "fe4d7c9\x00-b8c6-4a70-9ef1-3d8a58d18eed",
|
||||
hex.InvalidByteError(0), nil)
|
||||
adeT(t, "sep0\x00", uevent.UUID{}, "fe4d7c9d\x00b8c6-4a70-9ef1-3d8a58d18eed",
|
||||
uevent.UUIDSeparatorError(0), nil)
|
||||
|
||||
adeT(t, "bad1\x00", uevent.UUID{}, "fe4d7c9d-b8c\x00-4a70-9ef1-3d8a58d18eed",
|
||||
hex.InvalidByteError(0), nil)
|
||||
adeT(t, "sep1\x00", uevent.UUID{}, "fe4d7c9d-b8c6\x004a70-9ef1-3d8a58d18eed",
|
||||
uevent.UUIDSeparatorError(0), nil)
|
||||
|
||||
adeT(t, "bad2\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a7\x00-9ef1-3d8a58d18eed",
|
||||
hex.InvalidByteError(0), nil)
|
||||
adeT(t, "sep2\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a70\x009ef1-3d8a58d18eed",
|
||||
uevent.UUIDSeparatorError(0), nil)
|
||||
|
||||
adeT(t, "bad3\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a70-9ef\x00-3d8a58d18eed",
|
||||
hex.InvalidByteError(0), nil)
|
||||
adeT(t, "sep3\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a70-9ef1\x003d8a58d18eed",
|
||||
uevent.UUIDSeparatorError(0), nil)
|
||||
|
||||
adeT(t, "bad4\x00", uevent.UUID{}, "fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18ee\x00",
|
||||
hex.InvalidByteError(0), nil)
|
||||
|
||||
}
|
||||
|
||||
func TestDialConsume(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c, err := uevent.Dial()
|
||||
c, err := uevent.Dial(0)
|
||||
if err != nil {
|
||||
t.Fatalf("Dial: error = %v", err)
|
||||
}
|
||||
@@ -127,7 +179,7 @@ func TestDialConsume(t *testing.T) {
|
||||
})
|
||||
|
||||
// check kernel-assigned port id
|
||||
c0, err0 := uevent.Dial()
|
||||
c0, err0 := uevent.Dial(0)
|
||||
if err0 != nil {
|
||||
t.Fatalf("Dial: error = %v", err)
|
||||
}
|
||||
@@ -155,13 +207,23 @@ func TestDialConsume(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(t.Context())
|
||||
defer cancel()
|
||||
|
||||
consume := func(c *uevent.Conn, ctx context.Context) error {
|
||||
return c.Consume(ctx, fhs.Sys, nil, events, false, func(path string) {
|
||||
t.Log("coldboot visited", path)
|
||||
}, func(err error) bool {
|
||||
t.Log(err)
|
||||
_, ok := err.(uevent.NeedsColdboot)
|
||||
return !ok
|
||||
}, nil)
|
||||
}
|
||||
|
||||
wg.Go(func() {
|
||||
if err = c.Consume(ctx, events); err != context.Canceled {
|
||||
if err = consume(c, ctx); err != context.Canceled {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
wg.Go(func() {
|
||||
if err0 = c0.Consume(ctx, events); err0 != context.Canceled {
|
||||
if err0 = consume(c0, ctx); err0 != context.Canceled {
|
||||
panic(err0)
|
||||
}
|
||||
})
|
||||
@@ -185,11 +247,11 @@ func TestDialConsume(t *testing.T) {
|
||||
exclExit := make(chan struct{})
|
||||
wg.Go(func() {
|
||||
defer func() { exclExit <- struct{}{} }()
|
||||
errs[0] = c.Consume(ctx, events)
|
||||
errs[0] = consume(c, ctx)
|
||||
})
|
||||
wg.Go(func() {
|
||||
defer func() { exclExit <- struct{}{} }()
|
||||
errs[1] = c.Consume(ctx, events)
|
||||
errs[1] = consume(c, ctx)
|
||||
})
|
||||
<-exclExit
|
||||
cancel()
|
||||
@@ -233,6 +295,11 @@ func TestErrors(t *testing.T) {
|
||||
{"BadPortError", &uevent.BadPortError{
|
||||
Pid: 1,
|
||||
}, "unexpected message from port id 1 on NETLINK_KOBJECT_UEVENT"},
|
||||
|
||||
{"UUIDSizeError", uevent.UUIDSizeError(0xbad),
|
||||
"got 2989 bytes instead of 36"},
|
||||
{"UUIDSeparatorError", uevent.UUIDSeparatorError(0xfd),
|
||||
"invalid UUID separator: U+00FD 'ý'"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package wayland
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"testing"
|
||||
@@ -19,7 +19,7 @@ func TestSecurityContextClose(t *testing.T) {
|
||||
}
|
||||
|
||||
var ctx SecurityContext
|
||||
if f, err := os.Create(path.Join(t.TempDir(), "remove")); err != nil {
|
||||
if f, err := os.Create(filepath.Join(t.TempDir(), "remove")); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
ctx.bindPath = check.MustAbs(f.Name())
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/container"
|
||||
@@ -27,9 +26,6 @@ const (
|
||||
|
||||
// lddName is the file name of ldd(1) passed to exec.LookPath.
|
||||
lddName = "ldd"
|
||||
// lddTimeout is the maximum duration ldd(1) is allowed to ran for before it
|
||||
// is terminated.
|
||||
lddTimeout = 15 * time.Second
|
||||
)
|
||||
|
||||
// Resolve runs ldd(1) in a strict sandbox and connects its stdout to a [Decoder].
|
||||
@@ -52,7 +48,7 @@ func Resolve(
|
||||
}
|
||||
}
|
||||
|
||||
c, cancel := context.WithTimeout(ctx, lddTimeout)
|
||||
c, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
var toolPath *check.Absolute
|
||||
|
||||
@@ -82,9 +82,6 @@ buildGo126Module rec {
|
||||
env = {
|
||||
# use clang instead of gcc
|
||||
CC = "clang -O3 -Werror";
|
||||
|
||||
# nix build environment does not allow acls
|
||||
HAKUREI_TEST_SKIP_ACL = 1;
|
||||
};
|
||||
|
||||
buildInputs = [
|
||||
@@ -116,7 +113,7 @@ buildGo126Module rec {
|
||||
];
|
||||
in
|
||||
''
|
||||
install -D --target-directory=$out/share/zsh/site-functions dist/comp/*
|
||||
install -D --target-directory=$out/share/zsh/site-functions cmd/dist/comp/*
|
||||
|
||||
mkdir "$out/libexec"
|
||||
mv "$out"/bin/* "$out/libexec/"
|
||||
|
||||
@@ -44,7 +44,7 @@ testers.nixosTest {
|
||||
|
||||
cd ${self.packages.${system}.hakurei.src}
|
||||
${fhs}/bin/hakurei-fhs -c \
|
||||
'CC="clang -O3 -Werror" go test ${if withRace then "-race" else "-count 16"} ./...' \
|
||||
'CC="clang -O3 -Werror" go test --tags=noskip ${if withRace then "-race" else "-count 16"} ./...' \
|
||||
&> /tmp/hakurei-test.log && \
|
||||
touch /tmp/hakurei-test-ok
|
||||
touch /tmp/hakurei-test-done
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user