Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b4348a3db3 | |||
| 486180c11f | |||
| d3d68ad472 | |||
| d2dc6a716d |
@@ -13,6 +13,3 @@
|
||||
|
||||
# cmd/dist default destination
|
||||
/dist
|
||||
|
||||
# local packages
|
||||
/internal/rosa/package/local
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ func (e AbsoluteError) Error() string {
|
||||
}
|
||||
|
||||
func (e AbsoluteError) Is(target error) bool {
|
||||
ce, ok := errors.AsType[AbsoluteError](target)
|
||||
if !ok {
|
||||
var ce AbsoluteError
|
||||
if !errors.As(target, &ce) {
|
||||
return errors.Is(target, syscall.EINVAL)
|
||||
}
|
||||
return e == ce
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
v0.4.3
|
||||
Vendored
+1
-6
@@ -18,13 +18,8 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:generate sh -c "git describe --tags > VERSION"
|
||||
//go:embed VERSION
|
||||
var version string
|
||||
|
||||
// getenv looks up an environment variable, and returns fallback if it is unset.
|
||||
func getenv(key, fallback string) string {
|
||||
if v, ok := os.LookupEnv(key); ok {
|
||||
@@ -52,7 +47,7 @@ func main() {
|
||||
|
||||
verbose := os.Getenv("VERBOSE") != ""
|
||||
runTests := os.Getenv("HAKUREI_DIST_MAKE") == ""
|
||||
version = getenv("HAKUREI_VERSION", strings.TrimSpace(version))
|
||||
version := getenv("HAKUREI_VERSION", "untagged")
|
||||
prefix := getenv("PREFIX", "/usr")
|
||||
destdir := getenv("DESTDIR", "dist")
|
||||
|
||||
|
||||
+20
-153
@@ -5,91 +5,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"slices"
|
||||
"strings"
|
||||
. "syscall"
|
||||
|
||||
"hakurei.app/internal/kobject"
|
||||
"hakurei.app/internal/report"
|
||||
"hakurei.app/internal/uevent"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
var r report.Reporter
|
||||
|
||||
func init() {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("earlyinit: ")
|
||||
r.SetOutput(log.Default())
|
||||
|
||||
// this handles SIGQUIT to provide useful debugging information without
|
||||
// terminating, and prevents the runtime from throwing on the must family
|
||||
// of early error reporting functions, DO NOT REMOVE
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, SIGQUIT)
|
||||
go func() {
|
||||
for {
|
||||
<-c
|
||||
if p := pprof.Lookup("goroutine"); p == nil {
|
||||
log.Println("initial built-in goroutine profile does not exist")
|
||||
} else if err := p.WriteTo(os.Stderr, 2); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// fatal calls [log.Println] with v and blocks forever. Must be called from
|
||||
// main. Must not be used after error reporting is set up.
|
||||
func fatal(v ...any) {
|
||||
log.Println(v...)
|
||||
log.Println("unable to continue, please reboot and resolve the problem manually")
|
||||
select {}
|
||||
}
|
||||
|
||||
// must calls fatal with err if it is non-nil.
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
select {}
|
||||
}
|
||||
}
|
||||
|
||||
// mustSyscall is like must, but with an additional action name.
|
||||
func mustSyscall(action string, err error) {
|
||||
if err != nil {
|
||||
fatal("cannot "+action+":", err)
|
||||
select {}
|
||||
}
|
||||
}
|
||||
|
||||
// must1 is like must, but with an additional passed through value.
|
||||
func must1[T any](v T, err error) T {
|
||||
must(err)
|
||||
return v
|
||||
}
|
||||
|
||||
const (
|
||||
// optionSystem specifies devpath of the system device.
|
||||
optionSystem = "system"
|
||||
|
||||
// flagVerbose increases output verbosity.
|
||||
flagVerbose = "verbose"
|
||||
// flagStrict sets [report.DStrict] on r.
|
||||
flagStrict = "strict"
|
||||
// flagNoRecover sets [report.DNoRecover] on r.
|
||||
flagNoRecover = "no_recover"
|
||||
)
|
||||
|
||||
func main() {
|
||||
runtime.LockOSThread()
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix("earlyinit: ")
|
||||
|
||||
var (
|
||||
option map[string]string
|
||||
@@ -107,44 +33,15 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var flag uint64
|
||||
if slices.Contains(flags, flagStrict) {
|
||||
flag |= report.DStrict
|
||||
}
|
||||
if slices.Contains(flags, flagNoRecover) {
|
||||
flag |= report.DNoRecover
|
||||
}
|
||||
log.Printf("reporting flags %x", flag)
|
||||
r.SetFlags(flag)
|
||||
}
|
||||
|
||||
msg := message.New(log.Default())
|
||||
msg.SwapVerbose(slices.Contains(flags, flagVerbose))
|
||||
|
||||
mustSyscall("mount devtmpfs", Mount(
|
||||
if err := Mount(
|
||||
"devtmpfs",
|
||||
"/dev/",
|
||||
"devtmpfs",
|
||||
MS_NOSUID|MS_NOEXEC,
|
||||
"",
|
||||
))
|
||||
must(os.Mkdir("/dev/pts/", 0))
|
||||
mustSyscall("mount devpts", Mount(
|
||||
"devpts",
|
||||
"/dev/pts/",
|
||||
"devpts",
|
||||
MS_NOSUID|MS_NOEXEC,
|
||||
"mode=620,ptmxmode=666",
|
||||
))
|
||||
must(os.Mkdir("/dev/shm/", 0))
|
||||
mustSyscall("mount shm", Mount(
|
||||
"shm",
|
||||
"/dev/shm/",
|
||||
"tmpfs",
|
||||
MS_NOSUID|MS_NODEV,
|
||||
"",
|
||||
))
|
||||
); err != nil {
|
||||
log.Fatalf("cannot mount devtmpfs: %v", err)
|
||||
}
|
||||
|
||||
// The kernel might be unable to set up the console. When that happens,
|
||||
// printk is called with "Warning: unable to open an initial console."
|
||||
@@ -201,49 +98,6 @@ func main() {
|
||||
"",
|
||||
))
|
||||
|
||||
conn := must1(uevent.Dial(-128 * 1024 * 1024))
|
||||
events := make(chan *uevent.Message, 1<<10)
|
||||
var uuid uevent.UUID
|
||||
must1(rand.Read(uuid[:]))
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
go consume(ctx, msg, &r, conn, uuid, events)
|
||||
s := kobject.New(uuid, func(o *kobject.Object, env map[string]string) {
|
||||
p := make([]string, 0, len(env))
|
||||
for k, v := range env {
|
||||
p = append(p, k+"="+v)
|
||||
}
|
||||
slices.Sort(p)
|
||||
log.Printf("change %s: %s", o.DevPath, strings.Join(p, ", "))
|
||||
}, func(err error) {
|
||||
severity := report.Inconsistent
|
||||
if e, ok := err.(kobject.EventError); ok && e.Kind == kobject.EBadTarget {
|
||||
severity = report.Trivial
|
||||
}
|
||||
r.Dispatch(
|
||||
severity,
|
||||
"processed inconsistent uevent",
|
||||
err,
|
||||
)
|
||||
})
|
||||
go func() {
|
||||
s.Consume(ctx, events)
|
||||
|
||||
log.Println("closing NETLINK_KOBJECT_UEVENT socket")
|
||||
cancel()
|
||||
if err := conn.Close(); err != nil {
|
||||
log.Fatal(err) // not reached
|
||||
}
|
||||
}()
|
||||
|
||||
must(os.Mkdir("/system", 0))
|
||||
if devpath := option[optionSystem]; devpath == "" {
|
||||
fatal("system must be nonempty")
|
||||
} else {
|
||||
log.Printf("waiting for devpath pattern %q", devpath)
|
||||
mustMountSystem(ctx, s, devpath)
|
||||
}
|
||||
|
||||
// after top level has been set up
|
||||
mustSyscall("remount root", Mount(
|
||||
"",
|
||||
@@ -259,6 +113,19 @@ func main() {
|
||||
[]byte("/system/lib/firmware"),
|
||||
0,
|
||||
))
|
||||
go dispatchModprobe(ctx, s)
|
||||
|
||||
}
|
||||
|
||||
// mustSyscall calls [log.Fatalln] if err is non-nil.
|
||||
func mustSyscall(action string, err error) {
|
||||
if err != nil {
|
||||
log.Fatalln("cannot "+action+":", err)
|
||||
}
|
||||
}
|
||||
|
||||
// must calls [log.Fatal] with err if it is non-nil.
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/kobject"
|
||||
"hakurei.app/internal/report"
|
||||
"hakurei.app/internal/uevent"
|
||||
)
|
||||
|
||||
// ModprobeError describes an unsuccessful modprobe invocation.
|
||||
type ModprobeError struct {
|
||||
ModAlias string `json:"modalias"`
|
||||
Stdout string `json:"stdout"`
|
||||
Stderr string `json:"stderr"`
|
||||
ExitCode int `json:"exit_code"`
|
||||
}
|
||||
|
||||
var _ report.RepresentableError = ModprobeError{}
|
||||
|
||||
func (ModprobeError) Representable() {}
|
||||
func (e ModprobeError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"modprobe exit status %d: %s",
|
||||
e.ExitCode, strings.TrimSpace(e.Stderr),
|
||||
)
|
||||
}
|
||||
|
||||
// dispatchModprobe invokes modprobe for [uevent.KOBJ_ADD] events raising new
|
||||
// MODALIAS strings.
|
||||
func dispatchModprobe(
|
||||
ctx context.Context,
|
||||
s *kobject.State,
|
||||
) {
|
||||
aliases := make(chan string, 1<<8)
|
||||
go func() {
|
||||
defer close(aliases)
|
||||
s.Range(ctx, func(o *kobject.Object, act uevent.KobjectAction) bool {
|
||||
if act == uevent.KOBJ_ADD && o.Driver == "" && o.ModAlias != "" {
|
||||
aliases <- o.ModAlias
|
||||
}
|
||||
return true
|
||||
})
|
||||
}()
|
||||
|
||||
for alias := range aliases {
|
||||
stdout, err := exec.Command("/system/sbin/modprobe", alias).Output()
|
||||
if err == nil {
|
||||
if len(stdout) > 0 {
|
||||
log.Println(string(stdout))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
exitError, ok := errors.AsType[*exec.ExitError](err)
|
||||
if !ok || exitError == nil {
|
||||
r.Dispatch(report.Degraded, "invoke modprobe", err)
|
||||
continue
|
||||
}
|
||||
|
||||
r.Dispatch(report.Trivial, "load device driver", ModprobeError{
|
||||
ModAlias: alias,
|
||||
Stdout: string(stdout),
|
||||
Stderr: string(exitError.Stderr),
|
||||
ExitCode: exitError.ExitCode(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/kobject"
|
||||
"hakurei.app/internal/uevent"
|
||||
)
|
||||
|
||||
// mustMountSystem waits for and mounts a system device matching pattern.
|
||||
func mustMountSystem(
|
||||
ctx context.Context,
|
||||
s *kobject.State,
|
||||
pattern string,
|
||||
) {
|
||||
c, stop := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer stop()
|
||||
|
||||
for {
|
||||
var matchErr error
|
||||
var systemPath *check.Absolute
|
||||
s.Range(c, func(o *kobject.Object, act uevent.KobjectAction) bool {
|
||||
if (act != uevent.KOBJ_ADD && act != uevent.KOBJ_CHANGE) ||
|
||||
o.Subsystem != "block" ||
|
||||
o.Env["DEVTYPE"] != "disk" {
|
||||
return true
|
||||
}
|
||||
|
||||
if ok, err := filepath.Match(pattern, o.DevPath); err != nil {
|
||||
matchErr = err
|
||||
return false
|
||||
} else if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
name, ok := o.Env["DEVNAME"]
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
systemPath = fhs.AbsDev.Append(name)
|
||||
return false
|
||||
})
|
||||
if c.Err() != nil {
|
||||
fatal("devpath", strconv.Quote(pattern), "never appeared")
|
||||
}
|
||||
if matchErr != nil {
|
||||
fatal("cannot match system devpath:", matchErr)
|
||||
}
|
||||
err := syscall.Mount(
|
||||
systemPath.String(),
|
||||
"/system/",
|
||||
"squashfs",
|
||||
0,
|
||||
"threads=multi",
|
||||
)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
fatal("cannot mount system:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/report"
|
||||
"hakurei.app/internal/uevent"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// newRejectColdboot returns a function to be called on every subsequent pending
|
||||
// coldboot, and returns whether coldboot should proceed. Rejection is sticky.
|
||||
func newRejectColdboot() func() bool {
|
||||
// one coldboot per five minutes, two consecutive coldboot
|
||||
const (
|
||||
coldbootInterval = 5 * time.Minute
|
||||
coldbootBurst = 2
|
||||
)
|
||||
|
||||
done := make(chan struct{})
|
||||
s := make(chan struct{}, coldbootBurst)
|
||||
s <- struct{}{} // for early fault before reporting is ready
|
||||
go func() {
|
||||
t := time.NewTicker(coldbootInterval)
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
|
||||
case <-t.C:
|
||||
select {
|
||||
case s <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return func() bool {
|
||||
select {
|
||||
case <-s:
|
||||
return true
|
||||
|
||||
case <-done:
|
||||
return false
|
||||
|
||||
default:
|
||||
close(done)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// consume continuously consumes events from conn with retries.
|
||||
func consume(
|
||||
ctx context.Context,
|
||||
msg message.Msg,
|
||||
r *report.Reporter,
|
||||
conn *uevent.Conn,
|
||||
uuid uevent.UUID,
|
||||
events chan<- *uevent.Message,
|
||||
) {
|
||||
defer close(events)
|
||||
|
||||
nextColdboot := newRejectColdboot()
|
||||
coldboot := true
|
||||
retry:
|
||||
if dispatchErr := conn.Consume(ctx, fhs.Sys, &uuid, events, coldboot, func(path string) {
|
||||
msg.Verbose("coldboot visited", path)
|
||||
}, func(err error) bool {
|
||||
if _, ok := err.(uevent.NeedsColdboot); ok && !nextColdboot() {
|
||||
r.Dispatch(
|
||||
report.Degraded,
|
||||
"rejecting coldboot loop",
|
||||
err,
|
||||
)
|
||||
return false
|
||||
}
|
||||
r.Dispatch(
|
||||
report.Inconsistent,
|
||||
"consumed invalid message",
|
||||
err,
|
||||
)
|
||||
return true
|
||||
}, nil); dispatchErr != nil {
|
||||
if _, ok := dispatchErr.(uevent.Recoverable); !ok {
|
||||
r.Dispatch(
|
||||
report.Fatal,
|
||||
"discontinuing uevent processing due to nonrecoverable error",
|
||||
dispatchErr,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := dispatchErr.(uevent.NeedsColdboot); ok {
|
||||
// coldboot loop rejected by handler
|
||||
coldboot = false
|
||||
}
|
||||
|
||||
goto retry
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRejectColdboot(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
nextColdboot := newRejectColdboot()
|
||||
want := func(want bool) {
|
||||
if got := nextColdboot(); got != want {
|
||||
t.Fatalf("nextColdboot: %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
synctest.Wait()
|
||||
want(true)
|
||||
time.Sleep(time.Hour)
|
||||
synctest.Wait()
|
||||
want(true)
|
||||
want(true)
|
||||
time.Sleep(5 * time.Minute)
|
||||
synctest.Wait()
|
||||
want(true)
|
||||
want(false)
|
||||
time.Sleep(time.Hour)
|
||||
synctest.Wait()
|
||||
want(false)
|
||||
want(false)
|
||||
})
|
||||
}
|
||||
+5
-6
@@ -7,8 +7,7 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// decodeJSON decodes json from r and stores it in v. A non-nil error results in
|
||||
// a call to fatal.
|
||||
// decodeJSON decodes json from r and stores it in v. A non-nil error results in a call to fatal.
|
||||
func decodeJSON(fatal func(v ...any), op string, r io.Reader, v any) {
|
||||
err := json.NewDecoder(r).Decode(v)
|
||||
if err == nil {
|
||||
@@ -48,14 +47,14 @@ func encodeJSON(fatal func(v ...any), output io.Writer, short bool, v any) {
|
||||
}
|
||||
|
||||
if err := encoder.Encode(v); err != nil {
|
||||
if e, ok := errors.AsType[*json.MarshalerError](err); ok && e != nil {
|
||||
var marshalerError *json.MarshalerError
|
||||
if errors.As(err, &marshalerError) && marshalerError != nil {
|
||||
// this likely indicates an implementation error in hst
|
||||
fatal("cannot encode json for " + e.Type.String() + ": " + e.Err.Error())
|
||||
fatal("cannot encode json for " + marshalerError.Type.String() + ": " + marshalerError.Err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does
|
||||
// not need to be handled
|
||||
// UnsupportedTypeError, UnsupportedValueError: incorrect usage, does not need to be handled
|
||||
fatal("cannot write json: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
+23
-1
@@ -21,6 +21,15 @@
|
||||
// following paragraphs are considered an internal detail and not covered by the
|
||||
// compatibility promise.
|
||||
//
|
||||
// After checking credentials, hsu checks via /proc/ the absolute pathname of
|
||||
// its parent process, and fails if it does not match the hakurei pathname set
|
||||
// at link time. This is not a security feature: the priv-side is considered
|
||||
// trusted, and this feature makes no attempt to address the racy nature of
|
||||
// querying /proc/, or debuggers attached to the parent process. Instead, this
|
||||
// aims to discourage misuse and reduce confusion if the user accidentally
|
||||
// stumbles upon this program. It also prevents accidental use of the incorrect
|
||||
// installation of hsu in some environments.
|
||||
//
|
||||
// Since target container environment variables are set up in shim via the
|
||||
// [container] infrastructure, the environment is used for parameters from the
|
||||
// parent process.
|
||||
@@ -53,6 +62,7 @@ import (
|
||||
"runtime"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
@@ -97,6 +107,18 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
var toolPath string
|
||||
pexe := filepath.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
|
||||
if p, err := os.Readlink(pexe); err != nil {
|
||||
log.Fatalf("cannot read parent executable path: %v", err)
|
||||
} else if strings.HasSuffix(p, " (deleted)") {
|
||||
log.Fatal("hakurei executable has been deleted")
|
||||
} else if p != hakureiPath {
|
||||
log.Fatal("this program must be started by hakurei")
|
||||
} else {
|
||||
toolPath = p
|
||||
}
|
||||
|
||||
// refuse to run if hsurc is not protected correctly
|
||||
if s, err := os.Stat(hsuConfPath); err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -183,7 +205,7 @@ func main() {
|
||||
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
|
||||
}
|
||||
|
||||
if err := syscall.Exec(hakureiPath, []string{
|
||||
if err := syscall.Exec(toolPath, []string{
|
||||
"hakurei",
|
||||
"shim",
|
||||
}, []string{
|
||||
|
||||
+20
-21
@@ -2,14 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/rosa"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
@@ -31,7 +30,7 @@ type cache struct {
|
||||
// Loaded artifact of [rosa.QEMU].
|
||||
qemu pkg.Artifact
|
||||
|
||||
base, mirror string
|
||||
base string
|
||||
}
|
||||
|
||||
// open opens the underlying [pkg.Cache].
|
||||
@@ -87,21 +86,6 @@ func (cache *cache) open() (err error) {
|
||||
}
|
||||
done <- struct{}{}
|
||||
|
||||
if cache.mirror != "" {
|
||||
var pub []byte
|
||||
pub, err = os.ReadFile(base.Append("ed25519.pub").String())
|
||||
if err != nil {
|
||||
cache.c.Close()
|
||||
return
|
||||
}
|
||||
var r rosa.Remote
|
||||
if r, err = rosa.NewRemote(cache.mirror, pub, http.DefaultClient); err != nil {
|
||||
cache.c.Close()
|
||||
return err
|
||||
}
|
||||
cache.c.SetExternal(r)
|
||||
}
|
||||
|
||||
if cache.qemu != nil {
|
||||
var pathname *check.Absolute
|
||||
pathname, _, err = cache.c.Cure(cache.qemu)
|
||||
@@ -110,9 +94,24 @@ func (cache *cache) open() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
for arch, entry := range rosa.Arches(pathname) {
|
||||
pkg.RegisterArch(arch, entry)
|
||||
}
|
||||
pkg.RegisterArch("riscv64", container.BinfmtEntry{
|
||||
Offset: 0,
|
||||
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00",
|
||||
Mask: "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
||||
Interpreter: pathname.Append(
|
||||
"system/bin",
|
||||
"qemu-riscv64",
|
||||
),
|
||||
})
|
||||
pkg.RegisterArch("arm64", container.BinfmtEntry{
|
||||
Offset: 0,
|
||||
Magic: "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00",
|
||||
Mask: "\xff\xff\xff\xff\xff\xff\xff\xfc\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff",
|
||||
Interpreter: pathname.Append(
|
||||
"system/bin",
|
||||
"qemu-aarch64",
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
+9
-13
@@ -6,7 +6,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"unique"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/internal/rosa"
|
||||
@@ -36,19 +35,17 @@ func commandInfo(
|
||||
}
|
||||
}
|
||||
|
||||
t := rosa.Native().Std()
|
||||
for i, name := range args {
|
||||
handle := rosa.ArtifactH(unique.Make(name))
|
||||
if meta, a := t.Load(handle); meta == nil {
|
||||
if p, ok := rosa.ResolveName(name); !ok {
|
||||
return fmt.Errorf("unknown artifact %q", name)
|
||||
} else {
|
||||
var suffix string
|
||||
|
||||
if meta.Version != rosa.Unversioned {
|
||||
suffix += "-" + meta.Version
|
||||
if version := rosa.Std.Version(p); version != rosa.Unversioned {
|
||||
suffix += "-" + version
|
||||
}
|
||||
mustPrintln("name : " + name + suffix)
|
||||
|
||||
meta := rosa.GetMetadata(p)
|
||||
mustPrintln("description : " + meta.Description)
|
||||
if meta.Website != "" {
|
||||
mustPrintln("website : " +
|
||||
@@ -57,10 +54,9 @@ func commandInfo(
|
||||
if len(meta.Dependencies) > 0 {
|
||||
mustPrint("depends on :")
|
||||
for _, d := range meta.Dependencies {
|
||||
_meta, _ := rosa.Native().Std().MustLoad(d)
|
||||
s := _meta.Name
|
||||
if _meta.Version != rosa.Unversioned {
|
||||
s += "-" + _meta.Version
|
||||
s := rosa.GetMetadata(d).Name
|
||||
if version := rosa.Std.Version(d); version != rosa.Unversioned {
|
||||
s += "-" + version
|
||||
}
|
||||
mustPrint(" " + s)
|
||||
}
|
||||
@@ -72,7 +68,7 @@ func commandInfo(
|
||||
if r == nil {
|
||||
var f io.ReadSeekCloser
|
||||
err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
f, err = cache.OpenStatus(a)
|
||||
f, err = cache.OpenStatus(rosa.Std.Load(p))
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
@@ -91,7 +87,7 @@ func commandInfo(
|
||||
}
|
||||
}
|
||||
} else if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
status, n := r.ArtifactOf(cache.Ident(a))
|
||||
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
|
||||
if status == nil {
|
||||
mustPrintln(
|
||||
statusPrefix + "not in report",
|
||||
|
||||
+13
-22
@@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"unique"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
@@ -21,14 +20,6 @@ import (
|
||||
func TestInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_t := rosa.Native().Std()
|
||||
qemuMeta, _ := _t.Load(rosa.H("qemu"))
|
||||
glibMeta, _ := _t.Load(rosa.H("glib"))
|
||||
zlibMeta, zlib := _t.Load(rosa.H("zlib"))
|
||||
zstdMeta, _ := _t.Load(rosa.H("zstd"))
|
||||
hakureiMeta, _ := _t.Load(rosa.H("hakurei"))
|
||||
hakureiDistMeta, _ := _t.Load(rosa.H("hakurei-dist"))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
@@ -38,24 +29,24 @@ func TestInfo(t *testing.T) {
|
||||
wantErr any
|
||||
}{
|
||||
{"qemu", []string{"qemu"}, nil, "", `
|
||||
name : qemu-` + qemuMeta.Version + `
|
||||
name : qemu-` + rosa.Std.Version(rosa.QEMU) + `
|
||||
description : a generic and open source machine emulator and virtualizer
|
||||
website : https://www.qemu.org
|
||||
depends on : glib-` + glibMeta.Version + ` zstd-` + zstdMeta.Version + `
|
||||
depends on : glib-` + rosa.Std.Version(rosa.GLib) + ` zstd-` + rosa.Std.Version(rosa.Zstd) + `
|
||||
`, nil},
|
||||
|
||||
{"multi", []string{"hakurei", "hakurei-dist"}, nil, "", `
|
||||
name : hakurei-` + hakureiMeta.Version + `
|
||||
name : hakurei-` + rosa.Std.Version(rosa.Hakurei) + `
|
||||
description : low-level userspace tooling for Rosa OS
|
||||
website : https://hakurei.app
|
||||
|
||||
name : hakurei-dist-` + hakureiDistMeta.Version + `
|
||||
name : hakurei-dist-` + rosa.Std.Version(rosa.HakureiDist) + `
|
||||
description : low-level userspace tooling for Rosa OS (distribution tarball)
|
||||
website : https://hakurei.app
|
||||
`, nil},
|
||||
|
||||
{"nonexistent", []string{"zlib", "\x00"}, nil, "", `
|
||||
name : zlib-` + zlibMeta.Version + `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
|
||||
@@ -65,12 +56,12 @@ website : https://zlib.net
|
||||
"zstd": "internal/pkg (amd64) on satori\n",
|
||||
"hakurei": "internal/pkg (amd64) on satori\n\n",
|
||||
}, "", `
|
||||
name : zlib-` + zlibMeta.Version + `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
status : not yet cured
|
||||
|
||||
name : zstd-` + zstdMeta.Version + `
|
||||
name : zstd-` + rosa.Std.Version(rosa.Zstd) + `
|
||||
description : a fast compression algorithm
|
||||
website : https://facebook.github.io/zstd
|
||||
status : internal/pkg (amd64) on satori
|
||||
@@ -79,19 +70,19 @@ status : internal/pkg (amd64) on satori
|
||||
{"status cache perm", []string{"zlib"}, map[string]string{
|
||||
"zlib": "\x00",
|
||||
}, "", `
|
||||
name : zlib-` + zlibMeta.Version + `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
`, func(cm *cache) error {
|
||||
return &os.PathError{
|
||||
Op: "open",
|
||||
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(zlib).Value())),
|
||||
Path: filepath.Join(cm.base, "status", pkg.Encode(cm.c.Ident(rosa.Std.Load(rosa.Zlib)).Value())),
|
||||
Err: syscall.EACCES,
|
||||
}
|
||||
}},
|
||||
|
||||
{"status report", []string{"zlib"}, nil, strings.Repeat("\x00", len(pkg.Checksum{})+8), `
|
||||
name : zlib-` + zlibMeta.Version + `
|
||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
||||
description : lossless data-compression library
|
||||
website : https://zlib.net
|
||||
status : not in report
|
||||
@@ -140,8 +131,8 @@ status : not in report
|
||||
|
||||
if tc.status != nil {
|
||||
for name, status := range tc.status {
|
||||
_, a := _t.Load(rosa.ArtifactH(unique.Make(name)))
|
||||
if a == nil {
|
||||
p, ok := rosa.ResolveName(name)
|
||||
if !ok {
|
||||
t.Fatalf("invalid name %q", name)
|
||||
}
|
||||
perm := os.FileMode(0400)
|
||||
@@ -152,7 +143,7 @@ status : not in report
|
||||
return os.WriteFile(filepath.Join(
|
||||
cm.base,
|
||||
"status",
|
||||
pkg.Encode(cache.Ident(a).Value()),
|
||||
pkg.Encode(cache.Ident(rosa.Std.Load(p)).Value()),
|
||||
), unsafe.Slice(unsafe.StringData(status), len(status)), perm)
|
||||
}); err != nil {
|
||||
t.Fatalf("Do: error = %v", err)
|
||||
|
||||
@@ -30,7 +30,7 @@ var (
|
||||
// handleInfo writes constant system information.
|
||||
func handleInfo(w http.ResponseWriter, _ *http.Request) {
|
||||
infoPayloadOnce.Do(func() {
|
||||
infoPayload.Count = len(rosa.Native().Collect())
|
||||
infoPayload.Count = int(rosa.PresetUnexportedStart)
|
||||
infoPayload.HakureiVersion = info.Version()
|
||||
})
|
||||
// TODO(mae): cache entire response if no additional fields are planned
|
||||
@@ -91,7 +91,7 @@ func (index *packageIndex) handleGet(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||
http.Error(
|
||||
w, "index must be an integer between 0 and "+
|
||||
strconv.Itoa(len(index.sorts[0])-1),
|
||||
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
@@ -125,7 +125,7 @@ func (index *packageIndex) handleSearch(w http.ResponseWriter, r *http.Request)
|
||||
if err != nil || i >= len(index.sorts[0]) || i < 0 {
|
||||
http.Error(
|
||||
w, "index must be an integer between 0 and "+
|
||||
strconv.Itoa(len(index.sorts[0])-1),
|
||||
strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||
http.StatusBadRequest,
|
||||
)
|
||||
return
|
||||
|
||||
@@ -3,6 +3,7 @@ package pkgserver
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -31,7 +32,7 @@ func TestAPIInfo(t *testing.T) {
|
||||
checkPayload(t, resp, struct {
|
||||
Count int `json:"count"`
|
||||
HakureiVersion string `json:"hakurei_version"`
|
||||
}{len(rosa.Native().Collect()), info.Version()})
|
||||
}{int(rosa.PresetUnexportedStart), info.Version()})
|
||||
}
|
||||
|
||||
func TestAPIGet(t *testing.T) {
|
||||
@@ -92,12 +93,11 @@ func TestAPIGet(t *testing.T) {
|
||||
)
|
||||
})
|
||||
|
||||
count := len(rosa.Native().Collect())
|
||||
t.Run("index", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
checkValidate(
|
||||
t, "limit=1&sort=0&index", 0, count-1,
|
||||
"index must be an integer between 0 and "+strconv.Itoa(count-1),
|
||||
t, "limit=1&sort=0&index", 0, int(rosa.PresetUnexportedStart-1),
|
||||
"index must be an integer between 0 and "+strconv.Itoa(int(rosa.PresetUnexportedStart-1)),
|
||||
)
|
||||
})
|
||||
|
||||
@@ -108,4 +108,74 @@ func TestAPIGet(t *testing.T) {
|
||||
"sort must be an integer between 0 and "+strconv.Itoa(int(sortOrderEnd)),
|
||||
)
|
||||
})
|
||||
|
||||
checkWithSuffix := func(name, suffix string, want []*metadata) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := newRequest(suffix)
|
||||
resp := w.Result()
|
||||
checkStatus(t, resp, http.StatusOK)
|
||||
checkAPIHeader(t, w.Header())
|
||||
checkPayloadFunc(t, resp, func(got *struct {
|
||||
Values []*metadata `json:"values"`
|
||||
}) bool {
|
||||
return slices.EqualFunc(got.Values, want, func(a, b *metadata) bool {
|
||||
return (a.Version == b.Version ||
|
||||
a.Version == rosa.Unversioned ||
|
||||
b.Version == rosa.Unversioned) &&
|
||||
a.HasReport == b.HasReport &&
|
||||
a.Name == b.Name &&
|
||||
a.Description == b.Description &&
|
||||
a.Website == b.Website
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
checkWithSuffix("declarationAscending", "?limit=2&index=1&sort=0", []*metadata{
|
||||
{
|
||||
Metadata: rosa.GetMetadata(1),
|
||||
Version: rosa.Std.Version(1),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(2),
|
||||
Version: rosa.Std.Version(2),
|
||||
},
|
||||
})
|
||||
checkWithSuffix("declarationAscending offset", "?limit=3&index=5&sort=0", []*metadata{
|
||||
{
|
||||
Metadata: rosa.GetMetadata(5),
|
||||
Version: rosa.Std.Version(5),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(6),
|
||||
Version: rosa.Std.Version(6),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(7),
|
||||
Version: rosa.Std.Version(7),
|
||||
},
|
||||
})
|
||||
checkWithSuffix("declarationDescending", "?limit=3&index=0&sort=1", []*metadata{
|
||||
{
|
||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 1),
|
||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 1),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 2),
|
||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 2),
|
||||
},
|
||||
{
|
||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 3),
|
||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 3),
|
||||
},
|
||||
})
|
||||
checkWithSuffix("declarationDescending offset", "?limit=1&index=37&sort=1", []*metadata{
|
||||
{
|
||||
Metadata: rosa.GetMetadata(rosa.PresetUnexportedStart - 38),
|
||||
Version: rosa.Std.Version(rosa.PresetUnexportedStart - 38),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ const (
|
||||
|
||||
// packageIndex refers to metadata by name and various sort orders.
|
||||
type packageIndex struct {
|
||||
sorts [sortOrderEnd + 1][]*metadata
|
||||
sorts [sortOrderEnd + 1][rosa.PresetUnexportedStart]*metadata
|
||||
names map[string]*metadata
|
||||
search searchCache
|
||||
// Taken from [rosa.Report] if available.
|
||||
@@ -32,11 +32,11 @@ type packageIndex struct {
|
||||
|
||||
// metadata holds [rosa.Metadata] extended with additional information.
|
||||
type metadata struct {
|
||||
handle rosa.ArtifactH
|
||||
p rosa.PArtifact
|
||||
*rosa.Metadata
|
||||
|
||||
// Copied from [rosa.Metadata], [rosa.Unversioned] is equivalent to the zero
|
||||
// value. Otherwise, the zero value is invalid.
|
||||
// Populated via [rosa.Toolchain.Version], [rosa.Unversioned] is equivalent
|
||||
// to the zero value. Otherwise, the zero value is invalid.
|
||||
Version string `json:"version,omitempty"`
|
||||
// Output data size, available if present in report.
|
||||
Size int64 `json:"size,omitempty"`
|
||||
@@ -56,17 +56,15 @@ func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
||||
index.handleAccess = report.HandleAccess
|
||||
}
|
||||
|
||||
handles := rosa.Native().Collect()
|
||||
work := make([]*metadata, len(handles))
|
||||
var work [rosa.PresetUnexportedStart]*metadata
|
||||
index.names = make(map[string]*metadata)
|
||||
ir := pkg.NewIR()
|
||||
for i, handle := range handles {
|
||||
meta, a := rosa.Native().Std().MustLoad(handle)
|
||||
for p := range rosa.PresetUnexportedStart {
|
||||
m := metadata{
|
||||
handle: handle,
|
||||
p: p,
|
||||
|
||||
Metadata: meta,
|
||||
Version: meta.Version,
|
||||
Metadata: rosa.GetMetadata(p),
|
||||
Version: rosa.Std.Version(p),
|
||||
}
|
||||
if m.Version == "" {
|
||||
return errors.New("invalid version from " + m.Name)
|
||||
@@ -76,32 +74,32 @@ func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
||||
}
|
||||
|
||||
if report != nil {
|
||||
id := ir.Ident(a)
|
||||
id := ir.Ident(rosa.Std.Load(p))
|
||||
m.ids = pkg.Encode(id.Value())
|
||||
m.status, m.Size = report.ArtifactOf(id)
|
||||
m.HasReport = m.Size >= 0
|
||||
}
|
||||
|
||||
work[i] = &m
|
||||
work[p] = &m
|
||||
index.names[m.Name] = &m
|
||||
}
|
||||
|
||||
index.sorts[declarationAscending] = work
|
||||
index.sorts[declarationDescending] = slices.Clone(work)
|
||||
index.sorts[declarationDescending] = work
|
||||
slices.Reverse(index.sorts[declarationDescending][:])
|
||||
|
||||
index.sorts[nameAscending] = slices.Clone(work)
|
||||
index.sorts[nameAscending] = work
|
||||
slices.SortFunc(index.sorts[nameAscending][:], func(a, b *metadata) int {
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
index.sorts[nameDescending] = slices.Clone(index.sorts[nameAscending])
|
||||
index.sorts[nameDescending] = index.sorts[nameAscending]
|
||||
slices.Reverse(index.sorts[nameDescending][:])
|
||||
|
||||
index.sorts[sizeAscending] = slices.Clone(work)
|
||||
index.sorts[sizeAscending] = work
|
||||
slices.SortFunc(index.sorts[sizeAscending][:], func(a, b *metadata) int {
|
||||
return cmp.Compare(a.Size, b.Size)
|
||||
})
|
||||
index.sorts[sizeDescending] = slices.Clone(index.sorts[sizeAscending])
|
||||
index.sorts[sizeDescending] = index.sorts[sizeAscending]
|
||||
slices.Reverse(index.sorts[sizeDescending][:])
|
||||
|
||||
return
|
||||
|
||||
@@ -74,7 +74,7 @@ func (s *searchCache) clean() {
|
||||
}
|
||||
func indexsum(in [][]int) int {
|
||||
sum := 0
|
||||
for i := range in {
|
||||
for i := 0; i < len(in); i++ {
|
||||
sum += in[i][1] - in[i][0]
|
||||
}
|
||||
return sum
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="icon" href="https://hakurei.app/favicon.ico"/>
|
||||
<title>Rosa OS Packages</title>
|
||||
<title>Hakurei PkgServer</title>
|
||||
<script src="index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Rosa OS Packages</h1>
|
||||
<h1>Hakurei PkgServer</h1>
|
||||
<div class="top-controls" id="top-controls-regular">
|
||||
<p>Showing entries <span id="entry-counter"></span>.</p>
|
||||
<span id="search-bar">
|
||||
@@ -55,4 +54,4 @@
|
||||
</footer>
|
||||
<script>main();</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
+175
-307
@@ -14,7 +14,6 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ed25519"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -32,11 +31,12 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
"unique"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/command"
|
||||
"hakurei.app/container"
|
||||
"hakurei.app/container/seccomp"
|
||||
"hakurei.app/container/std"
|
||||
"hakurei.app/ext"
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/pkg"
|
||||
@@ -47,19 +47,6 @@ import (
|
||||
"hakurei.app/cmd/mbf/internal/pkgserver/ui"
|
||||
)
|
||||
|
||||
// writeFileExcl is like [os.WriteFile], but sets [os.O_EXCL] instead.
|
||||
func writeFileExcl(name string, data []byte, perm os.FileMode) error {
|
||||
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Write(data)
|
||||
if err1 := f.Close(); err1 != nil && err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
container.TryArgv0(nil)
|
||||
|
||||
@@ -71,20 +58,6 @@ func main() {
|
||||
log.Fatal("this program must not run as root")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch r.(type) {
|
||||
case rosa.LoadError, pkg.IRStringError:
|
||||
log.Fatal(r)
|
||||
default:
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(),
|
||||
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
defer stop()
|
||||
@@ -98,32 +71,18 @@ func main() {
|
||||
flagArch string
|
||||
flagCheck bool
|
||||
flagLTO bool
|
||||
flagPT bool
|
||||
flagDry bool
|
||||
flagPath string
|
||||
|
||||
flagSourcePath string
|
||||
flagCrossOverride int
|
||||
|
||||
addr net.UnixAddr
|
||||
)
|
||||
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) error {
|
||||
if !rosa.Native().HasStageEarly() {
|
||||
return pkg.UnsupportedArchError(runtime.GOARCH)
|
||||
}
|
||||
|
||||
if flagPT {
|
||||
log.Println("parsed in", rosa.ParseTime())
|
||||
}
|
||||
|
||||
msg.SwapVerbose(!flagQuiet)
|
||||
cm.ctx, cm.msg = ctx, msg
|
||||
cm.base = os.ExpandEnv(cm.base)
|
||||
if cm.base == "" {
|
||||
cm.base = "cache"
|
||||
}
|
||||
cm.mirror = os.ExpandEnv(cm.mirror)
|
||||
azaleaPath := os.ExpandEnv(flagPath)
|
||||
|
||||
addr.Net = "unix"
|
||||
addr.Name = os.ExpandEnv(addr.Name)
|
||||
@@ -138,10 +97,10 @@ func main() {
|
||||
if !flagLTO {
|
||||
flags |= rosa.OptLLVMNoLTO
|
||||
}
|
||||
rosa.Native().DropCaches("", flags)
|
||||
rosa.DropCaches("", flags)
|
||||
cross := flagArch != "" && flagArch != runtime.GOARCH
|
||||
if flagQEMU || cross {
|
||||
_, cm.qemu = rosa.Native().Std().MustLoad(rosa.H("qemu"))
|
||||
cm.qemu = rosa.Std.Load(rosa.QEMU)
|
||||
}
|
||||
|
||||
if cross {
|
||||
@@ -149,29 +108,12 @@ func main() {
|
||||
flags = flagCrossOverride
|
||||
}
|
||||
|
||||
rosa.Native().DropCaches(flagArch, flags)
|
||||
if !rosa.Native().HasStageEarly() {
|
||||
rosa.DropCaches(flagArch, flags)
|
||||
if !rosa.HasStage0() {
|
||||
return pkg.UnsupportedArchError(flagArch)
|
||||
}
|
||||
}
|
||||
|
||||
if flagSourcePath != "" {
|
||||
if err := rosa.Native().SetSource(os.DirFS(flagSourcePath)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if azaleaPath != "" {
|
||||
var root *os.Root
|
||||
if a, err := check.NewAbs(azaleaPath); err != nil {
|
||||
return err
|
||||
} else if root, err = os.OpenRoot(a.String()); err != nil {
|
||||
return err
|
||||
} else if err = rosa.Native().RegisterFS(root.FS()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}).Flag(
|
||||
&flagQuiet,
|
||||
@@ -213,10 +155,6 @@ func main() {
|
||||
&cm.base,
|
||||
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
||||
"Directory to store cured artifacts",
|
||||
).Flag(
|
||||
&cm.mirror,
|
||||
"r", command.StringFlag("$MBF_REMOTE"),
|
||||
"URL of mirror service",
|
||||
).Flag(
|
||||
&cm.idle,
|
||||
"sched-idle", command.BoolFlag(false),
|
||||
@@ -232,22 +170,6 @@ func main() {
|
||||
&addr.Name,
|
||||
"socket", command.StringFlag("$MBF_DAEMON_SOCKET"),
|
||||
"Pathname of socket to bind to",
|
||||
).Flag(
|
||||
&flagPT,
|
||||
"parse-time", command.BoolFlag(false),
|
||||
"Print duration of the initial azalea parse",
|
||||
).Flag(
|
||||
&flagDry,
|
||||
"dry", command.BoolFlag(false),
|
||||
"Do not destroy cache entries",
|
||||
).Flag(
|
||||
&flagSourcePath,
|
||||
"source", command.StringFlag(""),
|
||||
"Override hakurei source tree",
|
||||
).Flag(
|
||||
&flagPath,
|
||||
"p", command.StringFlag("$AZALEA_PATH"),
|
||||
"Load additional azalea files",
|
||||
)
|
||||
|
||||
c.NewCommand(
|
||||
@@ -400,10 +322,7 @@ func main() {
|
||||
)
|
||||
|
||||
{
|
||||
var (
|
||||
flagJobs int
|
||||
flagNoBlock bool
|
||||
)
|
||||
var flagJobs int
|
||||
c.NewCommand("updates", command.UsageInternal, func([]string) error {
|
||||
var (
|
||||
errsMu sync.Mutex
|
||||
@@ -412,21 +331,16 @@ func main() {
|
||||
n atomic.Uint64
|
||||
)
|
||||
|
||||
w := make(chan rosa.ArtifactH)
|
||||
w := make(chan rosa.PArtifact)
|
||||
var wg sync.WaitGroup
|
||||
for range max(flagJobs, 1) {
|
||||
wg.Go(func() {
|
||||
for p := range w {
|
||||
meta, _ := rosa.Native().Std().MustLoad(p)
|
||||
meta := rosa.GetMetadata(p)
|
||||
if meta.ID == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if !flagNoBlock && meta.Blocked != "" {
|
||||
msg.Verbosef("%s is blocked: %s", meta.Name, meta.Blocked)
|
||||
continue
|
||||
}
|
||||
|
||||
v, err := meta.GetVersions(ctx)
|
||||
if err != nil {
|
||||
errsMu.Lock()
|
||||
@@ -435,9 +349,12 @@ func main() {
|
||||
continue
|
||||
}
|
||||
|
||||
if latest := meta.GetLatest(v); meta.Version != latest {
|
||||
if current, latest :=
|
||||
rosa.Std.Version(p),
|
||||
meta.GetLatest(v); current != latest {
|
||||
|
||||
n.Add(1)
|
||||
log.Printf("%s %s < %s", meta.Name, meta.Version, latest)
|
||||
log.Printf("%s %s < %s", meta.Name, current, latest)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -447,9 +364,9 @@ func main() {
|
||||
}
|
||||
|
||||
done:
|
||||
for _, p := range rosa.Native().CollectAll() {
|
||||
for i := range rosa.PresetEnd {
|
||||
select {
|
||||
case w <- p:
|
||||
case w <- rosa.PArtifact(i):
|
||||
break
|
||||
case <-ctx.Done():
|
||||
break done
|
||||
@@ -467,23 +384,9 @@ func main() {
|
||||
&flagJobs,
|
||||
"j", command.IntFlag(32),
|
||||
"Maximum number of simultaneous connections",
|
||||
).Flag(
|
||||
&flagNoBlock,
|
||||
"ignore-block", command.BoolFlag(false),
|
||||
"Inhibit update blocking",
|
||||
)
|
||||
}
|
||||
|
||||
c.NewCommand("blocked", command.UsageInternal, func([]string) error {
|
||||
for _, p := range rosa.Native().CollectAll() {
|
||||
meta, _ := rosa.Native().Std().Load(p)
|
||||
if meta.Blocked != "" {
|
||||
fmt.Printf("%s: %s\n", meta.Name, meta.Blocked)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
c.NewCommand(
|
||||
"daemon",
|
||||
"Service artifact IR with Rosa OS extensions",
|
||||
@@ -497,82 +400,20 @@ func main() {
|
||||
},
|
||||
)
|
||||
|
||||
c.NewCommand(
|
||||
"keygen",
|
||||
"Create keypair for local cache",
|
||||
func([]string) error {
|
||||
pub, priv, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.Join(writeFileExcl(filepath.Join(
|
||||
cm.base,
|
||||
"ed25519.pub",
|
||||
), pub, 0444), writeFileExcl(filepath.Join(
|
||||
cm.base,
|
||||
"ed25519",
|
||||
), priv, 0400))
|
||||
},
|
||||
)
|
||||
|
||||
c.NewCommand(
|
||||
"serve",
|
||||
"Export local cache as mirror",
|
||||
func(args []string) error {
|
||||
const shutdownTimeout = 15 * time.Second
|
||||
|
||||
if len(args) != 1 {
|
||||
return errors.New("serve requires 1 argument")
|
||||
}
|
||||
|
||||
var key ed25519.PrivateKey
|
||||
if p, err := os.ReadFile(filepath.Join(cm.base, "ed25519")); err != nil {
|
||||
return err
|
||||
} else if len(p) != ed25519.PrivateKeySize {
|
||||
return errors.New("invalid private key")
|
||||
} else {
|
||||
key = p
|
||||
}
|
||||
|
||||
var h http.Handler
|
||||
if base, err := os.OpenRoot(cm.base); err != nil {
|
||||
return err
|
||||
} else {
|
||||
h = rosa.NewMirror(msg, base.FS(), key)
|
||||
}
|
||||
|
||||
server := http.Server{Addr: args[0], Handler: h}
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
cc, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||
defer cancel()
|
||||
if err := server.Shutdown(cc); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
msg.Verbosef("listening on %q", args[0])
|
||||
err := server.ListenAndServe()
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
},
|
||||
)
|
||||
|
||||
{
|
||||
var (
|
||||
flagGentoo string
|
||||
flagChecksum string
|
||||
|
||||
flagStage0 bool
|
||||
)
|
||||
c.NewCommand(
|
||||
"stage3",
|
||||
"Check for toolchain 3-stage non-determinism",
|
||||
func(args []string) (err error) {
|
||||
s := rosa.Std
|
||||
t := rosa.Std
|
||||
if flagGentoo != "" {
|
||||
s -= 3 // magic number to discourage misuse
|
||||
t -= 3 // magic number to discourage misuse
|
||||
|
||||
var checksum pkg.Checksum
|
||||
if len(flagChecksum) != 0 {
|
||||
@@ -580,7 +421,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
}
|
||||
rosa.Native().SetGentooStage3(flagGentoo, checksum)
|
||||
rosa.SetGentooStage3(flagGentoo, checksum)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -588,10 +429,10 @@ func main() {
|
||||
checksum [2]unique.Handle[pkg.Checksum]
|
||||
)
|
||||
|
||||
_llvm := rosa.H("llvm")
|
||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
_, llvm := rosa.Native().New(s - 2).Load(_llvm)
|
||||
pathname, _, err = cache.Cure(llvm)
|
||||
pathname, _, err = cache.Cure(
|
||||
(t - 2).Load(rosa.LLVM),
|
||||
)
|
||||
return
|
||||
}); err != nil {
|
||||
return
|
||||
@@ -599,16 +440,18 @@ func main() {
|
||||
log.Println("stage1:", pathname)
|
||||
|
||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
_, llvm := rosa.Native().New(s - 1).Load(_llvm)
|
||||
pathname, checksum[0], err = cache.Cure(llvm)
|
||||
pathname, checksum[0], err = cache.Cure(
|
||||
(t - 1).Load(rosa.LLVM),
|
||||
)
|
||||
return
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
log.Println("stage2:", pathname)
|
||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
_, llvm := rosa.Native().New(s).Load(_llvm)
|
||||
pathname, checksum[1], err = cache.Cure(llvm)
|
||||
pathname, checksum[1], err = cache.Cure(
|
||||
t.Load(rosa.LLVM),
|
||||
)
|
||||
return
|
||||
}); err != nil {
|
||||
return
|
||||
@@ -626,6 +469,19 @@ func main() {
|
||||
"("+pkg.Encode(checksum[0].Value())+")",
|
||||
)
|
||||
}
|
||||
|
||||
if flagStage0 {
|
||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
pathname, _, err = cache.Cure(
|
||||
t.Load(rosa.Stage0),
|
||||
)
|
||||
return
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
log.Println(pathname)
|
||||
}
|
||||
|
||||
return
|
||||
},
|
||||
).Flag(
|
||||
@@ -636,6 +492,10 @@ func main() {
|
||||
&flagChecksum,
|
||||
"checksum", command.StringFlag(""),
|
||||
"Checksum of Gentoo stage3 tarball",
|
||||
).Flag(
|
||||
&flagStage0,
|
||||
"stage0", command.BoolFlag(false),
|
||||
"Create bootstrap stage0 tarball",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -659,6 +519,10 @@ func main() {
|
||||
if len(args) != 1 {
|
||||
return errors.New("cure requires 1 argument")
|
||||
}
|
||||
p, ok := rosa.ResolveName(args[0])
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown artifact %q", args[0])
|
||||
}
|
||||
|
||||
t := rosa.Std
|
||||
if flagBoot {
|
||||
@@ -667,16 +531,11 @@ func main() {
|
||||
t -= 1
|
||||
}
|
||||
|
||||
_, a := rosa.Native().New(t).Load(rosa.ArtifactH(unique.Make(args[0])))
|
||||
if a == nil {
|
||||
return fmt.Errorf("unknown artifact %q", args[0])
|
||||
}
|
||||
|
||||
switch {
|
||||
default:
|
||||
var pathname *check.Absolute
|
||||
err := cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
pathname, _, err = cache.Cure(a)
|
||||
pathname, _, err = cache.Cure(t.Load(p))
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
@@ -694,7 +553,7 @@ func main() {
|
||||
0400,
|
||||
); err != nil {
|
||||
return err
|
||||
} else if err = pkg.Write(
|
||||
} else if _, err = pkg.Flatten(
|
||||
os.DirFS(pathname.String()),
|
||||
".",
|
||||
f,
|
||||
@@ -718,7 +577,7 @@ func main() {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = pkg.NewIR().EncodeAll(f, a); err != nil {
|
||||
if err = pkg.NewIR().EncodeAll(f, rosa.Std.Load(p)); err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
@@ -729,8 +588,8 @@ func main() {
|
||||
return cm.Do(func(cache *pkg.Cache) error {
|
||||
return cache.EnterExec(
|
||||
ctx,
|
||||
a,
|
||||
"", true, os.Stdin, os.Stdout, os.Stderr,
|
||||
t.Load(p),
|
||||
true, os.Stdin, os.Stdout, os.Stderr,
|
||||
rosa.AbsSystem.Append("bin", "mksh"),
|
||||
"sh",
|
||||
)
|
||||
@@ -741,6 +600,7 @@ func main() {
|
||||
if flagNoReply {
|
||||
flags |= remoteNoReply
|
||||
}
|
||||
a := t.Load(p)
|
||||
pathname, err := cureRemote(ctx, &addr, a, flags)
|
||||
if !flagNoReply && err == nil {
|
||||
log.Println(pathname)
|
||||
@@ -760,7 +620,7 @@ func main() {
|
||||
case flagFaults:
|
||||
var faults []pkg.Fault
|
||||
if err := cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
faults, err = cache.ReadFaults(a)
|
||||
faults, err = cache.ReadFaults(t.Load(p))
|
||||
return
|
||||
}); err != nil {
|
||||
return err
|
||||
@@ -774,7 +634,7 @@ func main() {
|
||||
case flagPop:
|
||||
var faults []pkg.Fault
|
||||
if err := cm.Do(func(cache *pkg.Cache) (err error) {
|
||||
faults, err = cache.ReadFaults(a)
|
||||
faults, err = cache.ReadFaults(t.Load(p))
|
||||
return
|
||||
}); err != nil {
|
||||
return err
|
||||
@@ -840,9 +700,8 @@ func main() {
|
||||
)
|
||||
}
|
||||
|
||||
cleanC := c.New("clean", "Remove unused entries from the cache")
|
||||
cleanC.NewCommand(
|
||||
"fault",
|
||||
c.NewCommand(
|
||||
"clear",
|
||||
"Remove all fault entries from the cache",
|
||||
func([]string) error {
|
||||
return cm.Do(func(*pkg.Cache) error {
|
||||
@@ -863,58 +722,6 @@ func main() {
|
||||
})
|
||||
},
|
||||
)
|
||||
cleanC.NewCommand(
|
||||
"checksum",
|
||||
"Remove unreachable checksum entries",
|
||||
func([]string) error {
|
||||
return cm.Do(func(cache *pkg.Cache) error {
|
||||
_, checksums, err := cache.Clean(flagDry, false)
|
||||
log.Printf("destroyed %d entries", len(checksums))
|
||||
return err
|
||||
})
|
||||
},
|
||||
)
|
||||
{
|
||||
var flagDeep bool
|
||||
cleanC.NewCommand(
|
||||
"all",
|
||||
"Remove identifiers not reachable by loaded packages",
|
||||
func([]string) error {
|
||||
return cm.Do(func(cache *pkg.Cache) error {
|
||||
t := rosa.Native().Clone().Std()
|
||||
handles := t.CollectAll()
|
||||
|
||||
flags := t.Flags()
|
||||
a := t.Append(nil, handles...)
|
||||
for arch := range rosa.Arches(nil) {
|
||||
if arch == runtime.GOARCH {
|
||||
continue
|
||||
}
|
||||
|
||||
t.DropCaches(arch, rosa.OptLLVMNoLTO|rosa.OptSkipCheck)
|
||||
a = t.Append(a, handles...)
|
||||
t.DropCaches(arch, flags)
|
||||
a = t.Append(a, handles...)
|
||||
}
|
||||
|
||||
ids, checksums, err := cache.Clean(
|
||||
flagDry,
|
||||
!flagDeep,
|
||||
a...,
|
||||
)
|
||||
log.Printf(
|
||||
"destroyed %d identifier and %d checksum entries",
|
||||
len(ids), len(checksums),
|
||||
)
|
||||
return err
|
||||
})
|
||||
},
|
||||
).Flag(
|
||||
&flagDeep,
|
||||
"deep", command.BoolFlag(false),
|
||||
"Include transitive inputs",
|
||||
)
|
||||
}
|
||||
|
||||
c.NewCommand(
|
||||
"abort",
|
||||
@@ -933,72 +740,132 @@ func main() {
|
||||
"shell",
|
||||
"Interactive shell in the specified Rosa OS environment",
|
||||
func(args []string) error {
|
||||
resolvconf := "nameserver 1.1.1.1\nnameserver 1.0.0.1\n"
|
||||
if p, err := os.ReadFile(fhs.AbsEtc.Append(
|
||||
"resolv.conf",
|
||||
).String()); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
resolvconf = unsafe.String(unsafe.SliceData(p), len(p))
|
||||
}
|
||||
|
||||
handles := make([]rosa.ArtifactH, len(args), len(args)+3)
|
||||
presets := make([]rosa.PArtifact, len(args)+3)
|
||||
for i, arg := range args {
|
||||
handles[i] = rosa.ArtifactH(unique.Make(arg))
|
||||
if meta, _ := rosa.Native().Std().Load(handles[i]); meta == nil {
|
||||
p, ok := rosa.ResolveName(arg)
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown artifact %q", arg)
|
||||
}
|
||||
presets[i] = p
|
||||
}
|
||||
|
||||
base := rosa.H("llvm")
|
||||
base := rosa.LLVM
|
||||
if !flagWithToolchain {
|
||||
base = rosa.H("musl")
|
||||
base = rosa.Musl
|
||||
}
|
||||
handles = append(handles,
|
||||
presets = append(presets,
|
||||
base,
|
||||
rosa.H("mksh"),
|
||||
rosa.H("toybox"),
|
||||
rosa.Mksh,
|
||||
rosa.Toybox,
|
||||
)
|
||||
|
||||
root := make(pkg.Collect, 0, 6+len(args))
|
||||
root = append(root, rosa.NewEtc(false))
|
||||
root = rosa.Native().Std().Append(root, handles...)
|
||||
root = rosa.Std.AppendPresets(root, presets...)
|
||||
|
||||
return cm.Do(func(cache *pkg.Cache) error {
|
||||
return cache.EnterExec(
|
||||
ctx,
|
||||
pkg.NewExec(
|
||||
"",
|
||||
rosa.Native().Arch(),
|
||||
new(pkg.Checksum),
|
||||
1,
|
||||
flagNet,
|
||||
false,
|
||||
fhs.AbsRoot,
|
||||
[]string{
|
||||
"SHELL=/system/bin/mksh",
|
||||
"PATH=/system/bin",
|
||||
"HOME=/",
|
||||
},
|
||||
fhs.AbsProc.Append("nonexistent"),
|
||||
nil,
|
||||
pkg.Path(fhs.AbsRoot, true, root...),
|
||||
pkg.Path(
|
||||
fhs.AbsEtc.Append("resolv.conf"), false,
|
||||
pkg.NewFile(
|
||||
"resolv.conf",
|
||||
unsafe.Slice(unsafe.StringData(resolvconf), len(resolvconf)),
|
||||
),
|
||||
),
|
||||
),
|
||||
"localhost",
|
||||
flagSession, os.Stdin, os.Stdout, os.Stderr,
|
||||
rosa.AbsSystem.Append("bin", "mksh"),
|
||||
"sh",
|
||||
)
|
||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||
_, _, err := cache.Cure(&root)
|
||||
return err
|
||||
}); err == nil {
|
||||
return errors.New("unreachable")
|
||||
} else if !pkg.IsCollected(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
type cureRes struct {
|
||||
pathname *check.Absolute
|
||||
checksum unique.Handle[pkg.Checksum]
|
||||
}
|
||||
cured := make(map[pkg.Artifact]cureRes)
|
||||
for _, a := range root {
|
||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
||||
pathname, checksum, err := cache.Cure(a)
|
||||
if err == nil {
|
||||
cured[a] = cureRes{pathname, checksum}
|
||||
}
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// explicitly open for direct error-free use from this point
|
||||
if cm.c == nil {
|
||||
if err := cm.open(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
|
||||
*check.Absolute,
|
||||
unique.Handle[pkg.Checksum],
|
||||
) {
|
||||
res := cured[a]
|
||||
return res.pathname, res.checksum
|
||||
}, func(i int, d pkg.Artifact) {
|
||||
r := pkg.Encode(cm.c.Ident(d).Value())
|
||||
if s, ok := d.(fmt.Stringer); ok {
|
||||
if name := s.String(); name != "" {
|
||||
r += "-" + name
|
||||
}
|
||||
}
|
||||
msg.Verbosef("promoted layer %d as %s", i, r)
|
||||
})
|
||||
|
||||
z := container.New(ctx, msg)
|
||||
z.WaitDelay = 3 * time.Second
|
||||
z.SeccompPresets = pkg.SeccompPresets
|
||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||
z.ParentPerm = 0700
|
||||
z.HostNet = flagNet
|
||||
z.RetainSession = flagSession
|
||||
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
|
||||
z.Quiet = !cm.verboseInit
|
||||
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 {
|
||||
return err
|
||||
} else if tempdir, err = check.NewAbs(s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
z.Dir = fhs.AbsRoot
|
||||
z.Env = []string{
|
||||
"SHELL=/system/bin/mksh",
|
||||
"PATH=/system/bin",
|
||||
"HOME=/",
|
||||
}
|
||||
z.Path = rosa.AbsSystem.Append("bin", "mksh")
|
||||
z.Args = []string{"mksh"}
|
||||
z.
|
||||
OverlayEphemeral(fhs.AbsRoot, layers...).
|
||||
Place(
|
||||
fhs.AbsEtc.Append("hosts"),
|
||||
[]byte("127.0.0.1 localhost\n"),
|
||||
).
|
||||
Place(
|
||||
fhs.AbsEtc.Append("passwd"),
|
||||
[]byte("media_rw:x:1023:1023::/:/system/bin/sh\n"+
|
||||
"nobody:x:65534:65534::/proc/nonexistent:/system/bin/false\n"),
|
||||
).
|
||||
Place(
|
||||
fhs.AbsEtc.Append("group"),
|
||||
[]byte("media_rw:x:1023:\nnobody:x:65534:\n"),
|
||||
).
|
||||
Bind(tempdir, fhs.AbsTmp, std.BindWritable).
|
||||
Proc(fhs.AbsProc).Dev(fhs.AbsDev, true)
|
||||
|
||||
if err := z.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := z.Serve(); err != nil {
|
||||
return err
|
||||
}
|
||||
return z.Wait()
|
||||
},
|
||||
).Flag(
|
||||
&flagNet,
|
||||
@@ -1013,6 +880,7 @@ func main() {
|
||||
"with-toolchain", command.BoolFlag(false),
|
||||
"Include the stage2 LLVM toolchain",
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
c.Command(
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
rosa.Native().DropCaches("", rosa.OptLLVMNoLTO)
|
||||
rosa.DropCaches("", rosa.OptLLVMNoLTO)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
@@ -35,10 +35,10 @@ func TestCureAll(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
for _, handle := range rosa.Native().Collect() {
|
||||
_, a := rosa.Native().Std().MustLoad(handle)
|
||||
t.Run(handle.String(), func(t *testing.T) {
|
||||
_, err := cureRemote(t.Context(), &addr, a, 0)
|
||||
for i := range rosa.PresetEnd {
|
||||
p := rosa.PArtifact(i)
|
||||
t.Run(rosa.GetMetadata(p).Name, func(t *testing.T) {
|
||||
_, err := cureRemote(t.Context(), &addr, rosa.Std.Load(p), 0)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
+2
-2
@@ -508,8 +508,8 @@ func _main(s ...string) (exitCode int) {
|
||||
|
||||
if !z.AllowOrphan {
|
||||
if err := z.Wait(); err != nil {
|
||||
exitError, ok := errors.AsType[*exec.ExitError](err)
|
||||
if !ok || exitError == nil {
|
||||
var exitError *exec.ExitError
|
||||
if !errors.As(err, &exitError) || exitError == nil {
|
||||
log.Println(err)
|
||||
return 5
|
||||
}
|
||||
|
||||
+2
-2
@@ -91,8 +91,8 @@ func (n *node) MustParse(arguments []string, handleError func(error)) {
|
||||
case ErrEmptyTree:
|
||||
os.Exit(1)
|
||||
default:
|
||||
flagError, ok := errors.AsType[FlagError](err)
|
||||
if !ok { // returned by HandlerFunc
|
||||
var flagError FlagError
|
||||
if !errors.As(err, &flagError) { // returned by HandlerFunc
|
||||
handleError(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -154,8 +154,11 @@ func (e *StartError) Error() string {
|
||||
return e.Step
|
||||
}
|
||||
|
||||
if se, ok := errors.AsType[*os.SyscallError](e.Err); ok && se != nil {
|
||||
return e.Step + " " + se.Error()
|
||||
{
|
||||
var syscallError *os.SyscallError
|
||||
if errors.As(e.Err, &syscallError) && syscallError != nil {
|
||||
return e.Step + " " + syscallError.Error()
|
||||
}
|
||||
}
|
||||
|
||||
return e.Step + ": " + e.Err.Error()
|
||||
|
||||
@@ -235,6 +235,8 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||
})
|
||||
}
|
||||
|
||||
func sliceAddr[S any](s []S) *[]S { return &s }
|
||||
|
||||
func newCheckedFile(t *testing.T, name, wantData string, closeErr error) osFile {
|
||||
f := &checkedOsFile{t: t, name: name, want: wantData, closeErr: closeErr}
|
||||
// check happens in Close, and cleanup is not guaranteed to run, so relying
|
||||
|
||||
+8
-6
@@ -46,8 +46,9 @@ func messageFromError(err error) (m string, ok bool) {
|
||||
// While this is usable for pointer errors, such use should be avoided as nil
|
||||
// check is omitted.
|
||||
func messagePrefix[T error](prefix string, err error) (string, bool) {
|
||||
if e, ok := errors.AsType[T](err); ok {
|
||||
return prefix + e.Error(), true
|
||||
var targetError T
|
||||
if errors.As(err, &targetError) {
|
||||
return prefix + targetError.Error(), true
|
||||
}
|
||||
return zeroString, false
|
||||
}
|
||||
@@ -57,8 +58,9 @@ func messagePrefixP[V any, T interface {
|
||||
*V
|
||||
error
|
||||
}](prefix string, err error) (string, bool) {
|
||||
if e, ok := errors.AsType[T](err); ok && e != nil {
|
||||
return prefix + e.Error(), true
|
||||
var targetError T
|
||||
if errors.As(err, &targetError) && targetError != nil {
|
||||
return prefix + targetError.Error(), true
|
||||
}
|
||||
return zeroString, false
|
||||
}
|
||||
@@ -107,8 +109,8 @@ func optionalErrorUnwrap(err error) error {
|
||||
|
||||
// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
|
||||
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
||||
errno, ok := errors.AsType[syscall.Errno](err)
|
||||
if !ok {
|
||||
var errno syscall.Errno
|
||||
if !errors.As(err, &errno) {
|
||||
return 0, &os.PathError{Op: op, Path: path, Err: err}
|
||||
}
|
||||
return errno, nil
|
||||
|
||||
@@ -95,7 +95,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -123,7 +123,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -152,7 +152,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -182,7 +182,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -213,7 +213,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -245,7 +245,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -279,7 +279,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
@@ -315,7 +315,7 @@ func TestInitEntrypoint(t *testing.T) {
|
||||
Uid: 1 << 16,
|
||||
Gid: 1 << 15,
|
||||
Hostname: "hakurei-check",
|
||||
Ops: new(make(Ops, 1)),
|
||||
Ops: (*Ops)(sliceAddr(make(Ops, 1))),
|
||||
SeccompRules: make([]std.NativeRule, 0),
|
||||
SeccompPresets: std.PresetStrict,
|
||||
RetainSession: true,
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ func TestSyscall(t *testing.T) {
|
||||
t.Errorf("Unmarshal: %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
if _, ok := errors.AsType[ext.SyscallNameError](tc.err); ok {
|
||||
if errors.As(tc.err, new(ext.SyscallNameError)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Generated
+8
-8
@@ -7,32 +7,32 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1780361225,
|
||||
"narHash": "sha256-wnV9ttf4fPWNonBIQmvlrSlNpQYgx5HgWWd007mwIFA=",
|
||||
"lastModified": 1772985280,
|
||||
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "e28654b71096e08c019d4861ca26acb646f583d8",
|
||||
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "release-26.05",
|
||||
"ref": "release-25.11",
|
||||
"repo": "home-manager",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1780453794,
|
||||
"narHash": "sha256-bXMRa9VTsHSPXL4Cw8R6JJLQeY3Y/IP4+YJCYVmQ7FY=",
|
||||
"lastModified": 1772822230,
|
||||
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6b316287bae2ee04c9b93c8c858d930fd07d7338",
|
||||
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-26.05",
|
||||
"ref": "nixos-25.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
description = "hakurei container tool and nixos module";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
|
||||
|
||||
home-manager = {
|
||||
url = "github:nix-community/home-manager/release-26.05";
|
||||
url = "github:nix-community/home-manager/release-25.11";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
@@ -37,7 +37,7 @@
|
||||
inherit (pkgs)
|
||||
runCommandLocal
|
||||
callPackage
|
||||
nixfmt
|
||||
nixfmt-rfc-style
|
||||
deadnix
|
||||
statix
|
||||
;
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
sharefs = callPackage ./cmd/sharefs/test { inherit system self; };
|
||||
|
||||
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt ]; } ''
|
||||
formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt-rfc-style ]; } ''
|
||||
cd ${./.}
|
||||
|
||||
echo "running nixfmt..."
|
||||
@@ -139,6 +139,7 @@
|
||||
GOCACHE="$(mktemp -d)" \
|
||||
PATH="${pkgs.pkgsStatic.musl.bin}/bin:$PATH" \
|
||||
DESTDIR="$out" \
|
||||
HAKUREI_VERSION="v${hakurei.version}" \
|
||||
./all.sh
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
@@ -79,17 +78,17 @@ type FSImplError struct{ Value FilesystemConfig }
|
||||
|
||||
func (f FSImplError) Error() string {
|
||||
implType := reflect.TypeOf(f.Value)
|
||||
var buf strings.Builder
|
||||
for implType != nil && implType.Kind() == reflect.Pointer {
|
||||
buf.WriteByte('*')
|
||||
var name string
|
||||
for implType != nil && implType.Kind() == reflect.Ptr {
|
||||
name += "*"
|
||||
implType = implType.Elem()
|
||||
}
|
||||
if implType != nil {
|
||||
buf.WriteString(implType.Name())
|
||||
name += implType.Name()
|
||||
} else {
|
||||
buf.WriteString("nil")
|
||||
name += "nil"
|
||||
}
|
||||
return "implementation " + buf.String() + " not supported"
|
||||
return fmt.Sprintf("implementation %s not supported", name)
|
||||
}
|
||||
|
||||
// FilesystemConfigJSON is the [json] adapter for [FilesystemConfig].
|
||||
|
||||
+2
-2
@@ -103,7 +103,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
t.Run("marshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
wantErr := tc.wantErr
|
||||
if _, ok := errors.AsType[hst.FSTypeError](wantErr); ok {
|
||||
if errors.As(wantErr, new(hst.FSTypeError)) {
|
||||
// for unsupported implementation tc
|
||||
wantErr = hst.FSImplError{Value: stubFS{"cat"}}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func TestFilesystemConfigJSON(t *testing.T) {
|
||||
t.Run("unmarshal", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if tc.data == "\x00" && tc.sData == "\x00" {
|
||||
if _, ok := errors.AsType[hst.FSImplError](tc.wantErr); ok {
|
||||
if errors.As(tc.wantErr, new(hst.FSImplError)) {
|
||||
// this error is only returned on marshal
|
||||
return
|
||||
}
|
||||
|
||||
+6
-1
@@ -43,13 +43,18 @@ func (e *FSEphemeral) Apply(z *ApplyState) {
|
||||
return
|
||||
}
|
||||
|
||||
size := e.Size
|
||||
if size < 0 {
|
||||
size = 0
|
||||
}
|
||||
|
||||
perm := e.Perm
|
||||
if perm == 0 {
|
||||
perm = fsEphemeralDefaultPerm
|
||||
}
|
||||
|
||||
if e.Write {
|
||||
z.Tmpfs(e.Target, max(e.Size, 0), perm)
|
||||
z.Tmpfs(e.Target, size, perm)
|
||||
} else {
|
||||
z.Readonly(e.Target, perm)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
||||
continue
|
||||
}
|
||||
|
||||
if found := bytes.Contains([]byte("-_/.\\*"), []byte{b}); found { // - // _/.\*
|
||||
if ib := bytes.IndexByte([]byte("-_/.\\*"), b); ib != -1 { // - // _/.\*
|
||||
goto opt
|
||||
} else if b >= '0' && b <= '9' { // 0-9
|
||||
goto opt
|
||||
@@ -101,7 +101,7 @@ func unescapeValue(v []byte) (val []byte, errno ParseError) {
|
||||
break
|
||||
}
|
||||
if c, err := hex.Decode(val[i:i+1], v[iu+1:iu+3]); err != nil {
|
||||
if _, ok := errors.AsType[hex.InvalidByteError](err); ok {
|
||||
if errors.As(err, new(hex.InvalidByteError)) {
|
||||
errno = ErrBadValHexByte
|
||||
break
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package kobject
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"maps"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
@@ -29,22 +28,6 @@ type Event struct {
|
||||
Subsystem string `json:"subsystem"`
|
||||
}
|
||||
|
||||
// Clone returns a copy of e.
|
||||
func (e *Event) Clone() Event {
|
||||
v := *e
|
||||
v.Env = maps.Clone(e.Env)
|
||||
return v
|
||||
}
|
||||
|
||||
// makeColdboot allocates a new [Object] from e in [StateColdboot].
|
||||
func (e *Event) makeColdboot() *Object {
|
||||
return &Object{
|
||||
State: StateColdboot,
|
||||
DevPath: e.DevPath,
|
||||
Subsystem: e.Subsystem,
|
||||
}
|
||||
}
|
||||
|
||||
// Populate populates e with the contents of a [uevent.Message].
|
||||
//
|
||||
// The ACTION and DEVPATH environment variables are ignored and assumed to be
|
||||
|
||||
@@ -1,491 +0,0 @@
|
||||
// Package kobject interprets uevent messages from a NETLINK_KOBJECT_UEVENT socket.
|
||||
package kobject
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"hakurei.app/internal/report"
|
||||
"hakurei.app/internal/uevent"
|
||||
)
|
||||
|
||||
const (
|
||||
// StateColdboot denotes an [Object] populated by a coldboot event. It is
|
||||
// eligible for all event actions.
|
||||
StateColdboot = iota
|
||||
// StateNew denotes an [Object] previously populated by a [uevent.KOBJ_ADD]
|
||||
// event, but has not yet been targeted by a [uevent.KOBJ_BIND] event, or
|
||||
// has been targeted by a [uevent.KOBJ_UNBIND] event.
|
||||
StateNew
|
||||
// StateBound denotes an [Object] that has been targeted by a
|
||||
// [uevent.KOBJ_BIND] and has not been targeted by a [uevent.KOBJ_UNBIND]
|
||||
// after that.
|
||||
StateBound
|
||||
)
|
||||
|
||||
// Object represents a kernel object.
|
||||
type Object struct {
|
||||
// Origin of the object.
|
||||
State int `json:"state,omitempty"`
|
||||
// Set by [uevent.KOBJ_OFFLINE] and [uevent.KOBJ_ONLINE].
|
||||
Offline bool `json:"offline,omitempty"`
|
||||
|
||||
// alloc_uevent_skb: devpath
|
||||
DevPath string `json:"devpath"`
|
||||
// registered per-driver (optional)
|
||||
ModAlias string `json:"modalias,omitempty"`
|
||||
// dev_driver_uevent: drv->name (optional)
|
||||
Driver string `json:"driver,omitempty"`
|
||||
|
||||
// SUBSYSTEM value set by the kernel.
|
||||
Subsystem string `json:"subsystem"`
|
||||
|
||||
// Uninterpreted environment variable pairs. An entry missing a separator
|
||||
// gains the value "\x00".
|
||||
Env map[string]string `json:"env"`
|
||||
}
|
||||
|
||||
// Clone returns the address of a copy of o.
|
||||
func (o *Object) Clone() *Object {
|
||||
v := *o
|
||||
v.Env = maps.Clone(o.Env)
|
||||
return &v
|
||||
}
|
||||
|
||||
// GoString returns compound literal for the underlying value.
|
||||
func (o *Object) GoString() string {
|
||||
return fmt.Sprintf("&%#v", *o)
|
||||
}
|
||||
|
||||
// merge merges uninterpreted environment variable pairs from an [Event].
|
||||
func (o *Object) merge(env map[string]string) {
|
||||
for k, v := range env {
|
||||
if v == "\x00" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch k {
|
||||
case "MODALIAS":
|
||||
o.ModAlias = v
|
||||
continue
|
||||
|
||||
case "DRIVER":
|
||||
o.Driver = v
|
||||
continue
|
||||
|
||||
default:
|
||||
if o.Env == nil {
|
||||
o.Env = make(map[string]string)
|
||||
}
|
||||
o.Env[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update updates o with pairs from env, optionally stripping visited pairs.
|
||||
func (o *Object) update(env map[string]string, strip bool) {
|
||||
for k := range o.Env {
|
||||
if v, ok := env[k]; ok {
|
||||
if strip {
|
||||
delete(env, k)
|
||||
}
|
||||
o.Env[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A pendingIterator is a callback currently iterating through objects targeted
|
||||
// by ongoing events.
|
||||
type pendingIterator struct {
|
||||
f func(o *Object, act uevent.KobjectAction) bool
|
||||
done chan<- struct{}
|
||||
}
|
||||
|
||||
// State processes a stream of [Event] populated from [uevent.Message] received
|
||||
// from a NETLINK_KOBJECT_UEVENT socket and presents an efficient representation
|
||||
// of kernel state.
|
||||
type State struct {
|
||||
// Next expected SEQNUM.
|
||||
seq uint64
|
||||
// DevPath to environment variables.
|
||||
uevent map[string]*Object
|
||||
// Synchronises access to uevent and its objects.
|
||||
ueventMu sync.RWMutex
|
||||
// Alive iterators.
|
||||
iter []*pendingIterator
|
||||
// Synchronises access to iter.
|
||||
iterMu sync.Mutex
|
||||
// UUID for synthetic [uevent.Coldboot] events.
|
||||
coldboot uevent.UUID
|
||||
// Called on [uevent.KOBJ_CHANGE] with stripped environment variables.
|
||||
handleChange func(o *Object, env map[string]string)
|
||||
// Reports errors populating [Event] from [uevent.Message]. A user-supplied
|
||||
// nil value is replaced with a noop.
|
||||
reportErr func(error)
|
||||
}
|
||||
|
||||
// New returns the address of a new [State].
|
||||
func New(
|
||||
coldboot uevent.UUID,
|
||||
handleChange func(o *Object, env map[string]string),
|
||||
reportErr func(error),
|
||||
) *State {
|
||||
return &State{
|
||||
uevent: make(map[string]*Object),
|
||||
coldboot: coldboot,
|
||||
handleChange: handleChange,
|
||||
reportErr: reportErr,
|
||||
}
|
||||
}
|
||||
|
||||
// deleteIter removes an iterator from s. Must be called after acquiring iterMu.
|
||||
func (s *State) deleteIter(p *pendingIterator) {
|
||||
s.iter = slices.DeleteFunc(s.iter, func(v *pendingIterator) bool {
|
||||
return p == v
|
||||
})
|
||||
}
|
||||
|
||||
// dispatchIter broadcasts an [Object] to all alive iterators.
|
||||
func (s *State) dispatchIter(o *Object, act uevent.KobjectAction) {
|
||||
s.iterMu.Lock()
|
||||
defer s.iterMu.Unlock()
|
||||
|
||||
for _, p := range s.iter {
|
||||
if !p.f(o, act) {
|
||||
s.deleteIter(p)
|
||||
close(p.done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Range calls f on all current and upcoming [Object] values tracked by s until
|
||||
// f returns false or the context is cancelled. f must not retain o or modify
|
||||
// the value it points to.
|
||||
func (s *State) Range(
|
||||
ctx context.Context,
|
||||
f func(o *Object, act uevent.KobjectAction) bool,
|
||||
) {
|
||||
done := make(chan struct{})
|
||||
p := pendingIterator{f, done}
|
||||
|
||||
s.iterMu.Lock()
|
||||
s.ueventMu.RLock()
|
||||
for _, o := range s.uevent {
|
||||
if !f(o, uevent.KOBJ_ADD) {
|
||||
s.ueventMu.RUnlock()
|
||||
s.iterMu.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
s.ueventMu.RUnlock()
|
||||
s.iter = append(s.iter, &p)
|
||||
s.iterMu.Unlock()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.iterMu.Lock()
|
||||
s.deleteIter(&p)
|
||||
s.iterMu.Unlock()
|
||||
return
|
||||
|
||||
case <-done:
|
||||
// deregistered by dispatchIter
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// An EventError describes a malformed or inconsistent [Event].
|
||||
type EventError struct {
|
||||
Kind int `json:"fault"`
|
||||
E Event `json:"event"`
|
||||
O *Object `json:"object,omitempty"`
|
||||
}
|
||||
|
||||
var _ report.RepresentableError = EventError{}
|
||||
|
||||
func (EventError) Representable() {}
|
||||
|
||||
const (
|
||||
// EUnexpectedColdboot is reported for a coldboot event with action other
|
||||
// than the expected [uevent.KOBJ_ADD].
|
||||
EUnexpectedColdboot = iota
|
||||
// EDuplicateAdd is reported for a [uevent.KOBJ_ADD] event on a
|
||||
// still-existing entry that was not the result of a coldboot.
|
||||
EDuplicateAdd
|
||||
// EBadTarget is reported for an event on a nonexistent [Object]. This is
|
||||
// generally only possible before coldboot completes.
|
||||
EBadTarget
|
||||
// ERemoveState is reported for a [uevent.KOBJ_REMOVE] event targeting an
|
||||
// entry in a state other than [StateColdboot] and [StateNew].
|
||||
ERemoveState
|
||||
// EUnexpectedOffline is reported for a [uevent.KOBJ_OFFLINE] or
|
||||
// [uevent.KOBJ_ONLINE] event targeting an already offline or online object.
|
||||
EUnexpectedOffline
|
||||
// EBindState is reported for a [uevent.KOBJ_BIND] event targeting an entry
|
||||
// in a state other than [StateColdboot] and [StateNew].
|
||||
EBindState
|
||||
// EUnbindState is reported for a [uevent.KOBJ_UNBIND] event targeting an
|
||||
// entry in a state other than [StateBound].
|
||||
EUnbindState
|
||||
// EMalformedMove is reported for a [uevent.KOBJ_MOVE] event missing the
|
||||
// DEVPATH_OLD environment variable.
|
||||
EMalformedMove
|
||||
)
|
||||
|
||||
func (e EventError) Error() string {
|
||||
switch e.Kind {
|
||||
case EUnexpectedColdboot:
|
||||
return "unexpected " + e.E.Action.String() + " coldboot event"
|
||||
case EDuplicateAdd:
|
||||
return "duplicate add event on devpath " + strconv.Quote(e.E.DevPath)
|
||||
case EBadTarget:
|
||||
return "unexpected " + e.E.Action.String() + " event on devpath " +
|
||||
strconv.Quote(e.E.DevPath)
|
||||
case ERemoveState:
|
||||
if e.O == nil {
|
||||
return "invalid remove event error"
|
||||
}
|
||||
return "remove event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
||||
" in state " + strconv.Itoa(e.O.State)
|
||||
case EUnexpectedOffline:
|
||||
if e.O == nil {
|
||||
return "invalid unexpected offline error"
|
||||
}
|
||||
if e.O.Offline {
|
||||
return "offline event targeting devpath " + strconv.Quote(e.E.DevPath)
|
||||
}
|
||||
return "online event targeting devpath " + strconv.Quote(e.E.DevPath)
|
||||
case EBindState:
|
||||
if e.O == nil {
|
||||
return "invalid bind state error"
|
||||
}
|
||||
return "bind event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
||||
" in state " + strconv.Itoa(e.O.State)
|
||||
case EUnbindState:
|
||||
if e.O == nil {
|
||||
return "invalid unbind state error"
|
||||
}
|
||||
return "unbind event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
||||
" in state " + strconv.Itoa(e.O.State)
|
||||
case EMalformedMove:
|
||||
return "move event targeting devpath " + strconv.Quote(e.E.DevPath) +
|
||||
" missing DEVPATH_OLD"
|
||||
|
||||
default:
|
||||
return "invalid event error kind " + strconv.Itoa(e.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
// NewError returns a new [EventError] for e and o.
|
||||
func (e *Event) NewError(kind int, o *Object) error {
|
||||
if o != nil {
|
||||
o = o.Clone()
|
||||
}
|
||||
return EventError{kind, e.Clone(), o}
|
||||
}
|
||||
|
||||
// processEvent merges an event into s.
|
||||
func (s *State) processEvent(e *Event) {
|
||||
s.ueventMu.Lock()
|
||||
defer s.ueventMu.Unlock()
|
||||
|
||||
coldboot := e.Synth != nil
|
||||
if e.Action != uevent.KOBJ_ADD && coldboot {
|
||||
s.reportErr(e.NewError(EUnexpectedColdboot, nil))
|
||||
return
|
||||
}
|
||||
|
||||
switch act := e.Action; act {
|
||||
case uevent.KOBJ_ADD:
|
||||
if e.Synth == nil {
|
||||
if o, ok := s.uevent[e.DevPath]; ok {
|
||||
s.reportErr(e.NewError(EDuplicateAdd, o))
|
||||
o.merge(e.Env)
|
||||
s.dispatchIter(o, act)
|
||||
return
|
||||
}
|
||||
}
|
||||
o := e.makeColdboot()
|
||||
if !coldboot {
|
||||
o.State = StateNew
|
||||
}
|
||||
o.merge(e.Env)
|
||||
s.uevent[e.DevPath] = o
|
||||
s.dispatchIter(o, act)
|
||||
return
|
||||
|
||||
case uevent.KOBJ_REMOVE:
|
||||
if o, ok := s.uevent[e.DevPath]; !ok {
|
||||
s.reportErr(e.NewError(EBadTarget, nil))
|
||||
return
|
||||
} else if o.State != StateColdboot && o.State != StateNew {
|
||||
s.reportErr(e.NewError(ERemoveState, o))
|
||||
}
|
||||
delete(s.uevent, e.DevPath)
|
||||
return
|
||||
|
||||
case uevent.KOBJ_CHANGE:
|
||||
o, ok := s.uevent[e.DevPath]
|
||||
if !ok {
|
||||
s.reportErr(e.NewError(EBadTarget, nil))
|
||||
// this suffers from the coldboot race window similar to KOBJ_MOVE,
|
||||
// however this action combines driver-specific and change-specific
|
||||
// environment variables and combines them with environment
|
||||
// variables meant to convey state of the kobject, and it is not
|
||||
// possible to reliably separate them, so this fallback avoids the
|
||||
// race at the cost of including some garbage in tracked state
|
||||
o = e.makeColdboot()
|
||||
o.merge(e.Env)
|
||||
s.uevent[e.DevPath] = o
|
||||
s.dispatchIter(o, act)
|
||||
return
|
||||
}
|
||||
o.update(e.Env, true)
|
||||
if s.handleChange != nil {
|
||||
s.handleChange(o, e.Env)
|
||||
}
|
||||
s.dispatchIter(o, act)
|
||||
return
|
||||
|
||||
case uevent.KOBJ_MOVE:
|
||||
var o *Object
|
||||
if old, ok := e.Env["DEVPATH_OLD"]; !ok {
|
||||
s.reportErr(e.NewError(EMalformedMove, nil))
|
||||
// not reached
|
||||
o = e.makeColdboot()
|
||||
} else if o, ok = s.uevent[old]; !ok {
|
||||
s.reportErr(e.NewError(EBadTarget, nil))
|
||||
// this generally happens during coldboot, dropping the event here
|
||||
// may cause inconsistent state if the coldboot event for this
|
||||
// object was generated before the bind event
|
||||
delete(e.Env, "DEVPATH_OLD")
|
||||
o = e.makeColdboot()
|
||||
} else {
|
||||
delete(s.uevent, old)
|
||||
delete(e.Env, "DEVPATH_OLD")
|
||||
}
|
||||
o.merge(e.Env)
|
||||
s.uevent[e.DevPath] = o
|
||||
o.DevPath = e.DevPath
|
||||
s.dispatchIter(o, act)
|
||||
return
|
||||
|
||||
case uevent.KOBJ_ONLINE:
|
||||
o, ok := s.uevent[e.DevPath]
|
||||
if !ok {
|
||||
s.reportErr(e.NewError(EBadTarget, nil))
|
||||
// coldboot race window similar to an unexpected KOBJ_MOVE
|
||||
o = e.makeColdboot()
|
||||
s.uevent[e.DevPath] = o
|
||||
o.merge(e.Env)
|
||||
}
|
||||
if !o.Offline {
|
||||
s.reportErr(e.NewError(EUnexpectedOffline, o))
|
||||
}
|
||||
o.Offline = false
|
||||
s.dispatchIter(o, act)
|
||||
return
|
||||
|
||||
case uevent.KOBJ_OFFLINE:
|
||||
o, ok := s.uevent[e.DevPath]
|
||||
if !ok {
|
||||
s.reportErr(e.NewError(EBadTarget, nil))
|
||||
// coldboot race window similar to an unexpected KOBJ_MOVE
|
||||
o = e.makeColdboot()
|
||||
s.uevent[e.DevPath] = o
|
||||
o.merge(e.Env)
|
||||
}
|
||||
if o.Offline {
|
||||
s.reportErr(e.NewError(EUnexpectedOffline, o))
|
||||
}
|
||||
o.Offline = true
|
||||
s.dispatchIter(o, act)
|
||||
return
|
||||
|
||||
case uevent.KOBJ_BIND:
|
||||
o, ok := s.uevent[e.DevPath]
|
||||
if !ok {
|
||||
s.reportErr(e.NewError(EBadTarget, nil))
|
||||
// coldboot race window similar to an unexpected KOBJ_MOVE
|
||||
o = e.makeColdboot()
|
||||
s.uevent[e.DevPath] = o
|
||||
}
|
||||
if o.State != StateColdboot && o.State != StateNew {
|
||||
s.reportErr(e.NewError(EBindState, o))
|
||||
}
|
||||
o.State = StateBound
|
||||
o.merge(e.Env)
|
||||
s.dispatchIter(o, act)
|
||||
return
|
||||
|
||||
case uevent.KOBJ_UNBIND:
|
||||
o, ok := s.uevent[e.DevPath]
|
||||
if !ok {
|
||||
s.reportErr(e.NewError(EBadTarget, nil))
|
||||
// coldboot race window similar to an unexpected KOBJ_MOVE, but does
|
||||
// not result in inconsistent state if dropped
|
||||
return
|
||||
}
|
||||
if o.State != StateBound {
|
||||
s.reportErr(e.NewError(EUnbindState, o))
|
||||
}
|
||||
o.State = StateNew
|
||||
o.Driver = ""
|
||||
s.dispatchIter(o, act)
|
||||
return
|
||||
|
||||
default: // not reached
|
||||
s.reportErr(fmt.Errorf("invalid action %d", e.Action))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// BadSequenceError is reported for an unexpected SEQNUM.
|
||||
type BadSequenceError struct{ Got, Want uint64 }
|
||||
|
||||
func (e BadSequenceError) Error() string {
|
||||
return "SEQNUM=" + strconv.FormatUint(e.Got, 10) +
|
||||
", want " + strconv.FormatUint(e.Want, 10)
|
||||
}
|
||||
|
||||
// Consume receives uevent messages and updates s to reflect state of kernel.
|
||||
func (s *State) Consume(ctx context.Context, events <-chan *uevent.Message) {
|
||||
if s.uevent == nil {
|
||||
s.uevent = make(map[string]*Object)
|
||||
}
|
||||
if s.reportErr == nil {
|
||||
s.reportErr = func(error) {}
|
||||
}
|
||||
|
||||
var e Event
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
||||
case m, ok := <-events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
e.Populate(s.reportErr, m)
|
||||
|
||||
// skip external synthetic event
|
||||
if e.Synth != nil && *e.Synth != s.coldboot {
|
||||
continue
|
||||
}
|
||||
|
||||
if s.seq == 0 {
|
||||
s.seq = e.Sequence
|
||||
}
|
||||
if s.seq != e.Sequence {
|
||||
s.reportErr(BadSequenceError{e.Sequence, s.seq})
|
||||
}
|
||||
s.seq++
|
||||
s.processEvent(&e)
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
-266
@@ -1,266 +0,0 @@
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXPWRBN:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXPWRBN:","SEQNUM=777"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXPWRBN:00/wakeup/wakeup7","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXPWRBN:00/wakeup/wakeup7","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=778"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00/LNXCPU:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00/LNXCPU:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXCPU:","SEQNUM=779"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0010:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:ACPI0010:PNP0A05:","SEQNUM=780"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0103:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0103:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0103:","SEQNUM=781"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=782"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=783"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/PNP0A06:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A06:","SEQNUM=784"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/QEMU0002:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/QEMU0002:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:QEMU0002:","SEQNUM=785"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=786"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00/wakeup/wakeup0","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:00/wakeup/wakeup0","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=787"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0303:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0303:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0303:","SEQNUM=788"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0400:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0400:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0400:","SEQNUM=789"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0501:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0501:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0501:","SEQNUM=790"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00/device:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00/device:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=791"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0700:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0700:","SEQNUM=792"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0B00:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0B00:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0B00:","SEQNUM=793"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0F13:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/PNP0F13:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0F13:","SEQNUM=794"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=795"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/wakeup/wakeup1","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:01/wakeup/wakeup1","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=796"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=797"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03/wakeup/wakeup2","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:03/wakeup/wakeup2","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=798"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=799"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04/wakeup/wakeup3","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:04/wakeup/wakeup3","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=800"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=801"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05/wakeup/wakeup4","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:05/wakeup/wakeup4","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=802"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=803"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06/wakeup/wakeup5","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:06/wakeup/wakeup5","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=804"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=805"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=806"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:09","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:09","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=807"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0a","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0a","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=808"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0b","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0b","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=809"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0c","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=810"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0d","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0d","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=811"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0e","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0e","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=812"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0f","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:0f","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=813"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:10","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:10","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=814"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:11","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:11","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=815"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:12","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:12","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=816"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:13","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:13","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=817"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:14","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:14","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=818"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:15","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:15","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=819"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:16","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:16","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=820"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:17","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:17","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=821"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:18","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:18","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=822"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:19","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:19","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=823"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1a","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1a","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=824"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1b","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1b","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=825"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1c","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1c","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=826"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1d","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1d","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=827"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1e","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1e","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=828"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1f","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:1f","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=829"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:20","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:20","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=830"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:21","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:21","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=831"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:22","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:22","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=832"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0A03:","SEQNUM=833"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/wakeup/wakeup6","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/wakeup/wakeup6","SUBSYSTEM=wakeup","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=834"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=835"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=836"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:02","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:02","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=837"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:03","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:03","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=838"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:04","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0F:04","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0C0F:","SEQNUM=839"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYBUS:","SEQNUM=840"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:01","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:01","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYBUS:","SEQNUM=841"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00","SUBSYSTEM=acpi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:LNXSYSTM:","SEQNUM=842"]}
|
||||
{"action":"add","devpath":"/devices/breakpoint","env":["ACTION=add","DEVPATH=/devices/breakpoint","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=843"]}
|
||||
{"action":"add","devpath":"/devices/cpu","env":["ACTION=add","DEVPATH=/devices/cpu","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=844"]}
|
||||
{"action":"add","devpath":"/devices/kprobe","env":["ACTION=add","DEVPATH=/devices/kprobe","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=845"]}
|
||||
{"action":"add","devpath":"/devices/msr","env":["ACTION=add","DEVPATH=/devices/msr","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=846"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:00.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:00.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=60000","PCI_ID=8086:1237","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:00.0","MODALIAS=pci:v00008086d00001237sv00001AF4sd00001100bc06sc00i00","SEQNUM=847"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=60100","PCI_ID=8086:7000","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.0","MODALIAS=pci:v00008086d00007000sv00001AF4sd00001100bc06sc01i00","SEQNUM=848"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/ata_port/ata1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/ata_port/ata1","SUBSYSTEM=ata_port","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=849"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/host0/scsi_host/host0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/host0/scsi_host/host0","SUBSYSTEM=scsi_host","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=850"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/host0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/host0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_host","SEQNUM=851"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/ata_link/link1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/ata_link/link1","SUBSYSTEM=ata_link","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=852"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.0/ata_device/dev1.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.0/ata_device/dev1.0","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=853"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.1/ata_device/dev1.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata1/link1/dev1.1/ata_device/dev1.1","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=854"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/ata_port/ata2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/ata_port/ata2","SUBSYSTEM=ata_port","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=855"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/scsi_host/host1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/scsi_host/host1","SUBSYSTEM=scsi_host","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=856"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/bsg/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/bsg/1:0:0:0","SUBSYSTEM=bsg","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=251","MINOR=0","DEVNAME=bsg/1:0:0:0","SEQNUM=857"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/scsi_device/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0/scsi_device/1:0:0:0","SUBSYSTEM=scsi_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=858"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0/1:0:0:0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_device","MODALIAS=scsi:t-0x05","SEQNUM=859"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:0","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_target","SEQNUM=860"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/host1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/host1","SUBSYSTEM=scsi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=scsi_host","SEQNUM=861"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/ata_link/link2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/ata_link/link2","SUBSYSTEM=ata_link","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=862"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.0/ata_device/dev2.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.0/ata_device/dev2.0","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=863"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.1/ata_device/dev2.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1/ata2/link2/dev2.1/ata_device/dev2.1","SUBSYSTEM=ata_device","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=864"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.1","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=ata_piix","PCI_CLASS=10180","PCI_ID=8086:7010","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.1","MODALIAS=pci:v00008086d00007010sv00001AF4sd00001100bc01sc01i80","SEQNUM=865"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:01.3","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:01.3","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=68000","PCI_ID=8086:7113","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:01.3","MODALIAS=pci:v00008086d00007113sv00001AF4sd00001100bc06sc80i00","SEQNUM=866"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:02.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:02.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","PCI_CLASS=20000","PCI_ID=8086:100E","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:02.0","MODALIAS=pci:v00008086d0000100Esv00001AF4sd00001100bc02sc00i00","SEQNUM=867"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0","SUBSYSTEM=pci","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=virtio-pci","PCI_CLASS=10000","PCI_ID=1AF4:1001","PCI_SUBSYS_ID=1AF4:0002","PCI_SLOT_NAME=0000:00:03.0","MODALIAS=pci:v00001AF4d00001001sv00001AF4sd00000002bc01sc00i00","SEQNUM=868"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0/virtio0/block/vda","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0/virtio0/block/vda","SUBSYSTEM=block","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=254","MINOR=0","DEVNAME=vda","DEVTYPE=disk","DISKSEQ=1","SEQNUM=869"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:03.0/virtio0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:03.0/virtio0","SUBSYSTEM=virtio","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=virtio_blk","MODALIAS=virtio:d00000002v00001AF4","SEQNUM=870"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/QEMU0002:00","env":["ACTION=add","DEVPATH=/devices/pci0000:00/QEMU0002:00","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:QEMU0002:","SEQNUM=871"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/pci_bus/0000:00","env":["ACTION=add","DEVPATH=/devices/pci0000:00/pci_bus/0000:00","SUBSYSTEM=pci_bus","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=872"]}
|
||||
{"action":"add","devpath":"/devices/platform/PNP0103:00","env":["ACTION=add","DEVPATH=/devices/platform/PNP0103:00","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=acpi:PNP0103:","SEQNUM=873"]}
|
||||
{"action":"add","devpath":"/devices/platform/pcspkr","env":["ACTION=add","DEVPATH=/devices/platform/pcspkr","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=platform:pcspkr","SEQNUM=874"]}
|
||||
{"action":"add","devpath":"/devices/platform/reg-dummy/regulator/regulator.0","env":["ACTION=add","DEVPATH=/devices/platform/reg-dummy/regulator/regulator.0","SUBSYSTEM=regulator","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=875"]}
|
||||
{"action":"add","devpath":"/devices/platform/reg-dummy","env":["ACTION=add","DEVPATH=/devices/platform/reg-dummy","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=reg-dummy","MODALIAS=platform:reg-dummy","SEQNUM=876"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.1/tty/ttyS1","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.1/tty/ttyS1","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=65","DEVNAME=ttyS1","SEQNUM=877"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.1","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.1","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=878"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.2/tty/ttyS2","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.2/tty/ttyS2","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=66","DEVNAME=ttyS2","SEQNUM=879"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.2","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.2","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=880"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.3/tty/ttyS3","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.3/tty/ttyS3","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=67","DEVNAME=ttyS3","SEQNUM=881"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0/serial8250:0.3","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0/serial8250:0.3","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=882"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250/serial8250:0","env":["ACTION=add","DEVPATH=/devices/platform/serial8250/serial8250:0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=ctrl","DRIVER=ctrl","SEQNUM=883"]}
|
||||
{"action":"add","devpath":"/devices/platform/serial8250","env":["ACTION=add","DEVPATH=/devices/platform/serial8250","SUBSYSTEM=platform","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=serial8250","MODALIAS=platform:serial8250","SEQNUM=884"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:00","env":["ACTION=add","DEVPATH=/devices/pnp0/00:00","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=885"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:01","env":["ACTION=add","DEVPATH=/devices/pnp0/00:01","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=886"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:02","env":["ACTION=add","DEVPATH=/devices/pnp0/00:02","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=887"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:03","env":["ACTION=add","DEVPATH=/devices/pnp0/00:03","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=888"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0/00:04:0.0/tty/ttyS0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0/00:04:0.0/tty/ttyS0","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=64","DEVNAME=ttyS0","SEQNUM=889"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0/00:04:0.0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0/00:04:0.0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=port","DRIVER=port","SEQNUM=890"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:04/00:04:0","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04/00:04:0","SUBSYSTEM=serial-base","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DEVTYPE=ctrl","DRIVER=ctrl","SEQNUM=891"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:04","env":["ACTION=add","DEVPATH=/devices/pnp0/00:04","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=serial","SEQNUM=892"]}
|
||||
{"action":"add","devpath":"/devices/pnp0/00:05","env":["ACTION=add","DEVPATH=/devices/pnp0/00:05","SUBSYSTEM=pnp","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=893"]}
|
||||
{"action":"add","devpath":"/devices/software","env":["ACTION=add","DEVPATH=/devices/software","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=894"]}
|
||||
{"action":"add","devpath":"/devices/system/clockevents/broadcast","env":["ACTION=add","DEVPATH=/devices/system/clockevents/broadcast","SUBSYSTEM=clockevents","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=895"]}
|
||||
{"action":"add","devpath":"/devices/system/clockevents/clockevent0","env":["ACTION=add","DEVPATH=/devices/system/clockevents/clockevent0","SUBSYSTEM=clockevents","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=896"]}
|
||||
{"action":"add","devpath":"/devices/system/clocksource/clocksource0","env":["ACTION=add","DEVPATH=/devices/system/clocksource/clocksource0","SUBSYSTEM=clocksource","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=897"]}
|
||||
{"action":"add","devpath":"/devices/system/container/PNP0A06:00","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:00","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=898"]}
|
||||
{"action":"add","devpath":"/devices/system/container/PNP0A06:01","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:01","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=899"]}
|
||||
{"action":"add","devpath":"/devices/system/container/PNP0A06:02","env":["ACTION=add","DEVPATH=/devices/system/container/PNP0A06:02","SUBSYSTEM=container","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=900"]}
|
||||
{"action":"add","devpath":"/devices/system/cpu/cpu0","env":["ACTION=add","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=901"]}
|
||||
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=902"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory0","env":["ACTION=add","DEVPATH=/devices/system/memory/memory0","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=903"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory1","env":["ACTION=add","DEVPATH=/devices/system/memory/memory1","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=904"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory2","env":["ACTION=add","DEVPATH=/devices/system/memory/memory2","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=905"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory3","env":["ACTION=add","DEVPATH=/devices/system/memory/memory3","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=906"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory4","env":["ACTION=add","DEVPATH=/devices/system/memory/memory4","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=907"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory5","env":["ACTION=add","DEVPATH=/devices/system/memory/memory5","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=908"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory6","env":["ACTION=add","DEVPATH=/devices/system/memory/memory6","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=909"]}
|
||||
{"action":"add","devpath":"/devices/system/memory/memory7","env":["ACTION=add","DEVPATH=/devices/system/memory/memory7","SUBSYSTEM=memory","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=910"]}
|
||||
{"action":"add","devpath":"/devices/system/node/node0","env":["ACTION=add","DEVPATH=/devices/system/node/node0","SUBSYSTEM=node","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=911"]}
|
||||
{"action":"add","devpath":"/devices/tracepoint","env":["ACTION=add","DEVPATH=/devices/tracepoint","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=912"]}
|
||||
{"action":"add","devpath":"/devices/uprobe","env":["ACTION=add","DEVPATH=/devices/uprobe","SUBSYSTEM=event_source","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=913"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/254:0","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/254:0","SUBSYSTEM=bdi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=914"]}
|
||||
{"action":"add","devpath":"/devices/virtual/devlink/:ata2--scsi:1:0:0:0","env":["ACTION=add","DEVPATH=/devices/virtual/devlink/:ata2--scsi:1:0:0:0","SUBSYSTEM=devlink","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=915"]}
|
||||
{"action":"add","devpath":"/devices/virtual/dmi/id","env":["ACTION=add","DEVPATH=/devices/virtual/dmi/id","SUBSYSTEM=dmi","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MODALIAS=dmi:bvnSeaBIOS:bvrrel-1.17.0-0-gb52ca86e094d-prebuilt.qemu.org:bd04/01/2014:br0.0:svnQEMU:pnStandardPC(i440FX+PIIX,1996):pvrpc-i440fx-10.1:cvnQEMU:ct1:cvrpc-i440fx-10.1:sku:","SEQNUM=916"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/full","env":["ACTION=add","DEVPATH=/devices/virtual/mem/full","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=7","DEVNAME=full","DEVMODE=0666","SEQNUM=917"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/kmsg","env":["ACTION=add","DEVPATH=/devices/virtual/mem/kmsg","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=11","DEVNAME=kmsg","DEVMODE=0644","SEQNUM=918"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/mem","env":["ACTION=add","DEVPATH=/devices/virtual/mem/mem","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=1","DEVNAME=mem","SEQNUM=919"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/null","env":["ACTION=add","DEVPATH=/devices/virtual/mem/null","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=3","DEVNAME=null","DEVMODE=0666","SEQNUM=920"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/port","env":["ACTION=add","DEVPATH=/devices/virtual/mem/port","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=4","DEVNAME=port","SEQNUM=921"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/random","env":["ACTION=add","DEVPATH=/devices/virtual/mem/random","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=8","DEVNAME=random","DEVMODE=0666","SEQNUM=922"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/urandom","env":["ACTION=add","DEVPATH=/devices/virtual/mem/urandom","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=9","DEVNAME=urandom","DEVMODE=0666","SEQNUM=923"]}
|
||||
{"action":"add","devpath":"/devices/virtual/mem/zero","env":["ACTION=add","DEVPATH=/devices/virtual/mem/zero","SUBSYSTEM=mem","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=1","MINOR=5","DEVNAME=zero","DEVMODE=0666","SEQNUM=924"]}
|
||||
{"action":"add","devpath":"/devices/virtual/memory_tiering/memory_tier4","env":["ACTION=add","DEVPATH=/devices/virtual/memory_tiering/memory_tier4","SUBSYSTEM=memory_tiering","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=925"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/cpu_dma_latency","env":["ACTION=add","DEVPATH=/devices/virtual/misc/cpu_dma_latency","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=259","DEVNAME=cpu_dma_latency","SEQNUM=926"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/hpet","env":["ACTION=add","DEVPATH=/devices/virtual/misc/hpet","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=228","DEVNAME=hpet","SEQNUM=927"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/snapshot","env":["ACTION=add","DEVPATH=/devices/virtual/misc/snapshot","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=231","DEVNAME=snapshot","SEQNUM=928"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/udmabuf","env":["ACTION=add","DEVPATH=/devices/virtual/misc/udmabuf","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=258","DEVNAME=udmabuf","SEQNUM=929"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/userfaultfd","env":["ACTION=add","DEVPATH=/devices/virtual/misc/userfaultfd","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=257","DEVNAME=userfaultfd","SEQNUM=930"]}
|
||||
{"action":"add","devpath":"/devices/virtual/misc/vga_arbiter","env":["ACTION=add","DEVPATH=/devices/virtual/misc/vga_arbiter","SUBSYSTEM=misc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=10","MINOR=256","DEVNAME=vga_arbiter","SEQNUM=931"]}
|
||||
{"action":"add","devpath":"/devices/virtual/net/lo","env":["ACTION=add","DEVPATH=/devices/virtual/net/lo","SUBSYSTEM=net","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","INTERFACE=lo","IFINDEX=1","SEQNUM=932"]}
|
||||
{"action":"add","devpath":"/devices/virtual/thermal/cooling_device0","env":["ACTION=add","DEVPATH=/devices/virtual/thermal/cooling_device0","SUBSYSTEM=thermal","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=933"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/console","env":["ACTION=add","DEVPATH=/devices/virtual/tty/console","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=1","DEVNAME=console","SEQNUM=934"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/ptmx","env":["ACTION=add","DEVPATH=/devices/virtual/tty/ptmx","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=2","DEVNAME=ptmx","DEVMODE=0666","SEQNUM=935"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=5","MINOR=0","DEVNAME=tty","DEVMODE=0666","SEQNUM=936"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty0","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty0","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=0","DEVNAME=tty0","SEQNUM=937"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty1","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty1","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=1","DEVNAME=tty1","SEQNUM=938"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty10","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty10","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=10","DEVNAME=tty10","SEQNUM=939"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty11","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty11","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=11","DEVNAME=tty11","SEQNUM=940"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty12","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty12","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=12","DEVNAME=tty12","SEQNUM=941"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty13","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty13","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=13","DEVNAME=tty13","SEQNUM=942"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty14","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty14","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=14","DEVNAME=tty14","SEQNUM=943"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty15","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty15","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=15","DEVNAME=tty15","SEQNUM=944"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty16","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty16","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=16","DEVNAME=tty16","SEQNUM=945"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty17","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty17","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=17","DEVNAME=tty17","SEQNUM=946"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty18","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty18","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=18","DEVNAME=tty18","SEQNUM=947"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty19","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty19","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=19","DEVNAME=tty19","SEQNUM=948"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty2","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty2","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=2","DEVNAME=tty2","SEQNUM=949"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty20","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty20","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=20","DEVNAME=tty20","SEQNUM=950"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty21","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty21","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=21","DEVNAME=tty21","SEQNUM=951"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty22","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty22","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=22","DEVNAME=tty22","SEQNUM=952"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty23","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty23","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=23","DEVNAME=tty23","SEQNUM=953"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty24","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty24","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=24","DEVNAME=tty24","SEQNUM=954"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty25","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty25","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=25","DEVNAME=tty25","SEQNUM=955"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty26","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty26","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=26","DEVNAME=tty26","SEQNUM=956"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty27","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty27","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=27","DEVNAME=tty27","SEQNUM=957"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty28","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty28","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=28","DEVNAME=tty28","SEQNUM=958"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty29","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty29","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=29","DEVNAME=tty29","SEQNUM=959"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty3","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty3","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=3","DEVNAME=tty3","SEQNUM=960"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty30","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty30","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=30","DEVNAME=tty30","SEQNUM=961"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty31","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty31","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=31","DEVNAME=tty31","SEQNUM=962"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty32","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty32","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=32","DEVNAME=tty32","SEQNUM=963"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty33","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty33","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=33","DEVNAME=tty33","SEQNUM=964"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty34","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty34","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=34","DEVNAME=tty34","SEQNUM=965"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty35","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty35","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=35","DEVNAME=tty35","SEQNUM=966"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty36","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty36","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=36","DEVNAME=tty36","SEQNUM=967"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty37","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty37","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=37","DEVNAME=tty37","SEQNUM=968"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty38","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty38","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=38","DEVNAME=tty38","SEQNUM=969"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty39","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty39","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=39","DEVNAME=tty39","SEQNUM=970"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty4","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty4","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=4","DEVNAME=tty4","SEQNUM=971"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty40","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty40","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=40","DEVNAME=tty40","SEQNUM=972"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty41","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty41","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=41","DEVNAME=tty41","SEQNUM=973"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty42","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty42","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=42","DEVNAME=tty42","SEQNUM=974"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty43","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty43","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=43","DEVNAME=tty43","SEQNUM=975"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty44","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty44","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=44","DEVNAME=tty44","SEQNUM=976"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty45","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty45","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=45","DEVNAME=tty45","SEQNUM=977"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty46","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty46","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=46","DEVNAME=tty46","SEQNUM=978"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty47","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty47","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=47","DEVNAME=tty47","SEQNUM=979"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty48","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty48","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=48","DEVNAME=tty48","SEQNUM=980"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty49","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty49","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=49","DEVNAME=tty49","SEQNUM=981"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty5","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty5","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=5","DEVNAME=tty5","SEQNUM=982"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty50","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty50","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=50","DEVNAME=tty50","SEQNUM=983"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty51","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty51","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=51","DEVNAME=tty51","SEQNUM=984"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty52","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty52","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=52","DEVNAME=tty52","SEQNUM=985"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty53","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty53","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=53","DEVNAME=tty53","SEQNUM=986"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty54","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty54","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=54","DEVNAME=tty54","SEQNUM=987"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty55","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty55","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=55","DEVNAME=tty55","SEQNUM=988"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty56","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty56","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=56","DEVNAME=tty56","SEQNUM=989"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty57","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty57","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=57","DEVNAME=tty57","SEQNUM=990"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty58","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty58","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=58","DEVNAME=tty58","SEQNUM=991"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty59","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty59","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=59","DEVNAME=tty59","SEQNUM=992"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty6","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty6","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=6","DEVNAME=tty6","SEQNUM=993"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty60","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty60","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=60","DEVNAME=tty60","SEQNUM=994"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty61","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty61","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=61","DEVNAME=tty61","SEQNUM=995"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty62","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty62","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=62","DEVNAME=tty62","SEQNUM=996"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty63","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty63","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=63","DEVNAME=tty63","SEQNUM=997"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty7","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty7","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=7","DEVNAME=tty7","SEQNUM=998"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty8","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty8","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=8","DEVNAME=tty8","SEQNUM=999"]}
|
||||
{"action":"add","devpath":"/devices/virtual/tty/tty9","env":["ACTION=add","DEVPATH=/devices/virtual/tty/tty9","SUBSYSTEM=tty","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=4","MINOR=9","DEVNAME=tty9","SEQNUM=1000"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcs","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcs","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=0","DEVNAME=vcs","SEQNUM=1001"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcs1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcs1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=1","DEVNAME=vcs1","SEQNUM=1002"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcsa","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsa","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=128","DEVNAME=vcsa","SEQNUM=1003"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcsa1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsa1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=129","DEVNAME=vcsa1","SEQNUM=1004"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcsu","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsu","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=64","DEVNAME=vcsu","SEQNUM=1005"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vc/vcsu1","env":["ACTION=add","DEVPATH=/devices/virtual/vc/vcsu1","SUBSYSTEM=vc","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","MAJOR=7","MINOR=65","DEVNAME=vcsu1","SEQNUM=1006"]}
|
||||
{"action":"add","devpath":"/devices/virtual/vtconsole/vtcon0","env":["ACTION=add","DEVPATH=/devices/virtual/vtconsole/vtcon0","SUBSYSTEM=vtconsole","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1007"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-auth-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-auth-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1008"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-delete-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-delete-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1009"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-reset-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-reset-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1010"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/nvme-wq","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/nvme-wq","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1011"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/scsi_tmf_0","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/scsi_tmf_0","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1012"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/scsi_tmf_1","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/scsi_tmf_1","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1013"]}
|
||||
{"action":"add","devpath":"/devices/virtual/workqueue/writeback","env":["ACTION=add","DEVPATH=/devices/virtual/workqueue/writeback","SUBSYSTEM=workqueue","SYNTH_UUID=fe4d7c9d-b8c6-4a70-9ef1-3d8a58d18eed","SEQNUM=1014"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1015"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1016"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1017"]}
|
||||
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1018"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1019"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1020"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1021"]}
|
||||
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1022"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1023"]}
|
||||
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","SEQNUM=1024"]}
|
||||
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1025"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1026"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1027"]}
|
||||
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","SEQNUM=1028"]}
|
||||
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1029"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1030"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:07/wakeup/wakeup8","SUBSYSTEM=wakeup","SEQNUM=1031"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1032"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:04.0/virtio1","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:04.0/virtio1","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1033"]}
|
||||
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:04.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:04.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:04.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1034"]}
|
||||
{"action":"add","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=add","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1035"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1036"]}
|
||||
{"action":"add","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=add","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1037"]}
|
||||
{"action":"bind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=bind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","DRIVER=virtio-pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1038"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0/virtio2","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0/virtio2","SUBSYSTEM=virtio","MODALIAS=virtio:d00000019v00001AF4","SEQNUM=1039"]}
|
||||
{"action":"unbind","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=unbind","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","SEQNUM=1040"]}
|
||||
{"action":"remove","devpath":"/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","env":["ACTION=remove","DEVPATH=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A03:00/device:08/wakeup/wakeup9","SUBSYSTEM=wakeup","SEQNUM=1041"]}
|
||||
{"action":"remove","devpath":"/devices/pci0000:00/0000:00:05.0","env":["ACTION=remove","DEVPATH=/devices/pci0000:00/0000:00:05.0","SUBSYSTEM=pci","PCI_CLASS=40100","PCI_ID=1AF4:1059","PCI_SUBSYS_ID=1AF4:1100","PCI_SLOT_NAME=0000:00:05.0","MODALIAS=pci:v00001AF4d00001059sv00001AF4sd00001100bc04sc01i00","SEQNUM=1042"]}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"action":"move","devpath":"/devices/virtual/net/_lo","env":["ACTION=move","DEVPATH=/devices/virtual/net/_lo","SUBSYSTEM=net","DEVPATH_OLD=/devices/virtual/net/lo","INTERFACE=_lo","IFINDEX=1","SEQNUM=1043"]}
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
{"action":"remove","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=remove","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1044"]}
|
||||
{"action":"offline","devpath":"/devices/system/cpu/cpu0","env":["ACTION=offline","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1045"]}
|
||||
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1046"]}
|
||||
{"action":"online","devpath":"/devices/system/cpu/cpu0","env":["ACTION=online","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1047"]}
|
||||
{"action":"remove","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=remove","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1048"]}
|
||||
{"action":"offline","devpath":"/devices/system/cpu/cpu0","env":["ACTION=offline","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1049"]}
|
||||
{"action":"add","devpath":"/devices/system/machinecheck/machinecheck0","env":["ACTION=add","DEVPATH=/devices/system/machinecheck/machinecheck0","SUBSYSTEM=machinecheck","SEQNUM=1050"]}
|
||||
{"action":"online","devpath":"/devices/system/cpu/cpu0","env":["ACTION=online","DEVPATH=/devices/system/cpu/cpu0","SUBSYSTEM=cpu","DRIVER=processor","MODALIAS=cpu:type:x86,ven0002fam000Fmod006B:feature:,0000,0002,0003,0004,0005,0006,0007,0008,0009,000B,000C,000D,000E,000F,0010,0011,0013,0017,0018,0019,001A,001C,0020,0022,0023,0024,0025,0026,0027,0028,0029,002B,002C,002D,002E,002F,0030,0031,0034,0037,0038,003D,0064,006E,0070,0074,0075,0076,0079,007A,007F,0080,008D,0095,009F,00C0,00C1,00C8,00ED,00F3,010F,0115,0165,016C,0282\n","SEQNUM=1051"]}
|
||||
Vendored
-19
@@ -1,19 +0,0 @@
|
||||
{"action":"add","devpath":"/devices/virtual/misc/loop-control","env":["ACTION=add","DEVPATH=/devices/virtual/misc/loop-control","SUBSYSTEM=misc","MAJOR=10","MINOR=237","DEVNAME=loop-control","SEQNUM=1052"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:0","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:0","SUBSYSTEM=bdi","SEQNUM=1053"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop0","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=2","SEQNUM=1054"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:1","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:1","SUBSYSTEM=bdi","SEQNUM=1055"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop1","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop1","SUBSYSTEM=block","MAJOR=7","MINOR=1","DEVNAME=loop1","DEVTYPE=disk","DISKSEQ=3","SEQNUM=1056"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:2","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:2","SUBSYSTEM=bdi","SEQNUM=1057"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop2","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop2","SUBSYSTEM=block","MAJOR=7","MINOR=2","DEVNAME=loop2","DEVTYPE=disk","DISKSEQ=4","SEQNUM=1058"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:3","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:3","SUBSYSTEM=bdi","SEQNUM=1059"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop3","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop3","SUBSYSTEM=block","MAJOR=7","MINOR=3","DEVNAME=loop3","DEVTYPE=disk","DISKSEQ=5","SEQNUM=1060"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:4","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:4","SUBSYSTEM=bdi","SEQNUM=1061"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop4","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop4","SUBSYSTEM=block","MAJOR=7","MINOR=4","DEVNAME=loop4","DEVTYPE=disk","DISKSEQ=6","SEQNUM=1062"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:5","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:5","SUBSYSTEM=bdi","SEQNUM=1063"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop5","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop5","SUBSYSTEM=block","MAJOR=7","MINOR=5","DEVNAME=loop5","DEVTYPE=disk","DISKSEQ=7","SEQNUM=1064"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:6","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:6","SUBSYSTEM=bdi","SEQNUM=1065"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop6","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop6","SUBSYSTEM=block","MAJOR=7","MINOR=6","DEVNAME=loop6","DEVTYPE=disk","DISKSEQ=8","SEQNUM=1066"]}
|
||||
{"action":"add","devpath":"/devices/virtual/bdi/7:7","env":["ACTION=add","DEVPATH=/devices/virtual/bdi/7:7","SUBSYSTEM=bdi","SEQNUM=1067"]}
|
||||
{"action":"add","devpath":"/devices/virtual/block/loop7","env":["ACTION=add","DEVPATH=/devices/virtual/block/loop7","SUBSYSTEM=block","MAJOR=7","MINOR=7","DEVNAME=loop7","DEVTYPE=disk","DISKSEQ=9","SEQNUM=1068"]}
|
||||
{"action":"add","devpath":"/module/loop","env":["ACTION=add","DEVPATH=/module/loop","SUBSYSTEM=module","SEQNUM=1069"]}
|
||||
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1070"]}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1071"]}
|
||||
{"action":"change","devpath":"/devices/virtual/block/loop0","env":["ACTION=change","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","DISK_MEDIA_CHANGE=1","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=10","SEQNUM=1072"]}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{"action":"remove","devpath":"/devices/virtual/misc/loop-control","env":["ACTION=remove","DEVPATH=/devices/virtual/misc/loop-control","SUBSYSTEM=misc","MAJOR=10","MINOR=237","DEVNAME=loop-control","SEQNUM=1073"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:0","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:0","SUBSYSTEM=bdi","SEQNUM=1074"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop0","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop0","SUBSYSTEM=block","MAJOR=7","MINOR=0","DEVNAME=loop0","DEVTYPE=disk","DISKSEQ=11","SEQNUM=1075"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:1","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:1","SUBSYSTEM=bdi","SEQNUM=1076"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop1","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop1","SUBSYSTEM=block","MAJOR=7","MINOR=1","DEVNAME=loop1","DEVTYPE=disk","DISKSEQ=3","SEQNUM=1077"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:2","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:2","SUBSYSTEM=bdi","SEQNUM=1078"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop2","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop2","SUBSYSTEM=block","MAJOR=7","MINOR=2","DEVNAME=loop2","DEVTYPE=disk","DISKSEQ=4","SEQNUM=1079"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:3","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:3","SUBSYSTEM=bdi","SEQNUM=1080"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop3","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop3","SUBSYSTEM=block","MAJOR=7","MINOR=3","DEVNAME=loop3","DEVTYPE=disk","DISKSEQ=5","SEQNUM=1081"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:4","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:4","SUBSYSTEM=bdi","SEQNUM=1082"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop4","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop4","SUBSYSTEM=block","MAJOR=7","MINOR=4","DEVNAME=loop4","DEVTYPE=disk","DISKSEQ=6","SEQNUM=1083"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:5","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:5","SUBSYSTEM=bdi","SEQNUM=1084"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop5","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop5","SUBSYSTEM=block","MAJOR=7","MINOR=5","DEVNAME=loop5","DEVTYPE=disk","DISKSEQ=7","SEQNUM=1085"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:6","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:6","SUBSYSTEM=bdi","SEQNUM=1086"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop6","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop6","SUBSYSTEM=block","MAJOR=7","MINOR=6","DEVNAME=loop6","DEVTYPE=disk","DISKSEQ=8","SEQNUM=1087"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/bdi/7:7","env":["ACTION=remove","DEVPATH=/devices/virtual/bdi/7:7","SUBSYSTEM=bdi","SEQNUM=1088"]}
|
||||
{"action":"remove","devpath":"/devices/virtual/block/loop7","env":["ACTION=remove","DEVPATH=/devices/virtual/block/loop7","SUBSYSTEM=block","MAJOR=7","MINOR=7","DEVNAME=loop7","DEVTYPE=disk","DISKSEQ=9","SEQNUM=1089"]}
|
||||
{"action":"remove","devpath":"/module/loop","env":["ACTION=remove","DEVPATH=/module/loop","SUBSYSTEM=module","SEQNUM=1090"]}
|
||||
@@ -40,7 +40,7 @@ func TestTransform(t *testing.T) {
|
||||
|
||||
const maxChunkWords = 8 << 10
|
||||
buf := make([]byte, 2*maxChunkWords*8)
|
||||
for i := range uint64(2 * maxChunkWords) {
|
||||
for i := uint64(0); i < 2*maxChunkWords; i++ {
|
||||
binary.LittleEndian.PutUint64(buf[i*8:], i)
|
||||
}
|
||||
if err := lockedfile.Write(path, bytes.NewReader(buf[:8]), 0666); err != nil {
|
||||
|
||||
@@ -58,7 +58,8 @@ func (k *outcome) finalise(
|
||||
supp := make([]string, len(config.Groups))
|
||||
for i, name := range config.Groups {
|
||||
if gid, err := k.lookupGroupId(name); err != nil {
|
||||
if unknownGroupError, ok := errors.AsType[user.UnknownGroupError](err); ok {
|
||||
var unknownGroupError user.UnknownGroupError
|
||||
if errors.As(err, &unknownGroupError) {
|
||||
return newWithMessageError(fmt.Sprintf("unknown group %q", name), unknownGroupError)
|
||||
} else {
|
||||
return &hst.AppError{Step: "look up group by name", Err: err, Msg: err.Error()}
|
||||
|
||||
@@ -51,16 +51,18 @@ func (h *Hsu) ID() (int, error) {
|
||||
cmd.Stderr = os.Stderr // pass through fatal messages
|
||||
cmd.Env = make([]string, 0)
|
||||
cmd.Dir = fhs.Root
|
||||
var p []byte
|
||||
var (
|
||||
p []byte
|
||||
exitError *exec.ExitError
|
||||
)
|
||||
|
||||
const step = "obtain uid from hsu"
|
||||
if p, h.idErr = h.k.cmdOutput(cmd); h.idErr == nil {
|
||||
h.id, h.idErr = strconv.Atoi(string(p))
|
||||
if h.idErr != nil {
|
||||
h.idErr = &hst.AppError{Step: step, Err: h.idErr, Msg: "invalid uid string from hsu"}
|
||||
}
|
||||
} else if exitError, ok := errors.AsType[*exec.ExitError](h.idErr); ok &&
|
||||
exitError != nil &&
|
||||
exitError.ExitCode() == 1 {
|
||||
} else if errors.As(h.idErr, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
|
||||
// hsu prints an error message in this case
|
||||
h.idErr = &hst.AppError{Step: step, Err: ErrHsuAccess}
|
||||
} else if errors.Is(h.idErr, os.ErrNotExist) {
|
||||
|
||||
@@ -328,11 +328,11 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
||||
}
|
||||
|
||||
if err := k.sys.Revert((*system.Criteria)(&ec)); err != nil {
|
||||
joinError, ok := errors.AsType[interface {
|
||||
var joinError interface {
|
||||
Unwrap() []error
|
||||
error
|
||||
}](err)
|
||||
if !ok || joinError == nil {
|
||||
}
|
||||
if !errors.As(err, &joinError) || joinError == nil {
|
||||
perror(err, "revert system setup")
|
||||
} else {
|
||||
for _, v := range joinError.Unwrap() {
|
||||
|
||||
@@ -390,8 +390,8 @@ func shimEntrypoint(k syscallDispatcher) {
|
||||
if err := k.containerWait(z); err != nil {
|
||||
sp.destroy()
|
||||
|
||||
exitError, ok := errors.AsType[*exec.ExitError](err)
|
||||
if !ok {
|
||||
var exitError *exec.ExitError
|
||||
if !errors.As(err, &exitError) {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
k.exit(hst.ExitCancel)
|
||||
}
|
||||
|
||||
@@ -176,8 +176,8 @@ func marshalValueAppendRaw(data []byte, v reflect.Value) ([]byte, error) {
|
||||
case reflect.Struct:
|
||||
data = SPA_TYPE_Struct.append(data)
|
||||
var err error
|
||||
for _, field := range v.Fields() {
|
||||
data, err = marshalValueAppend(data, field)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
data, err = marshalValueAppend(data, v.Field(i))
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
@@ -370,8 +370,8 @@ func unmarshalValue(data []byte, v reflect.Value, wireSizeP *Word) error {
|
||||
}
|
||||
|
||||
var fieldWireSize Word
|
||||
for _, field := range v.Fields() {
|
||||
if err := unmarshalValue(data, field, &fieldWireSize); err != nil {
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if err := unmarshalValue(data, v.Field(i), &fieldWireSize); err != nil {
|
||||
return err
|
||||
}
|
||||
// bounds check completed in successful call to unmarshalValue
|
||||
|
||||
@@ -1,405 +0,0 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
|
||||
/*
|
||||
| mode uint32 | path_sz uint32 |
|
||||
| data_sz uint64 |
|
||||
| path string |
|
||||
| data []byte |
|
||||
*/
|
||||
|
||||
// An ArchiveHeader represents a single header in an archive.
|
||||
type ArchiveHeader struct {
|
||||
Mode fs.FileMode // file mode bits
|
||||
Path string // pathname of the file
|
||||
Size uint64 // size of data segment
|
||||
}
|
||||
|
||||
// Writer implements sequential writing of an archive. [Writer.WriteHeader]
|
||||
// begins a new file with the provided [ArchiveHeader], and then Writer can be
|
||||
// treated as an [io.Writer] to supply that file's data.
|
||||
//
|
||||
// It is the caller's responsibility to write entries in lexical order.
|
||||
type Writer struct {
|
||||
// Underlying writer.
|
||||
w io.Writer
|
||||
// Current header.
|
||||
h ArchiveHeader
|
||||
// Fixed-size header segment.
|
||||
buf [wordSize * 2]byte
|
||||
// Current position in data segment.
|
||||
n uint64
|
||||
}
|
||||
|
||||
// NewWriter returns the address of a new [Writer] writing to w.
|
||||
func NewWriter(w io.Writer) *Writer { return &Writer{w: w} }
|
||||
|
||||
var zero [wordSize]byte
|
||||
|
||||
// padSize returns the padding size for aligning sz.
|
||||
func padSize[T int | uint64](sz T) T {
|
||||
return (wordSize - (sz)%wordSize) % wordSize
|
||||
}
|
||||
|
||||
// flush concludes writing to the current file and writes padding.
|
||||
func (aw *Writer) flush() error {
|
||||
if aw.h.Size > aw.n {
|
||||
return fmt.Errorf("missed writing %d bytes", aw.h.Size-aw.n)
|
||||
} else if aw.h.Size < aw.n {
|
||||
return fmt.Errorf("wrote %d bytes beyond end of file", aw.n-aw.h.Size)
|
||||
}
|
||||
|
||||
if psz := padSize(aw.h.Size); psz != 0 {
|
||||
if _, err := aw.w.Write(zero[:psz]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
aw.n = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteHeader writes h and begins accepting its corresponding file.
|
||||
func (aw *Writer) WriteHeader(h *ArchiveHeader) error {
|
||||
if err := aw.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aw.h = *h
|
||||
binary.LittleEndian.PutUint32(aw.buf[:], uint32(aw.h.Mode))
|
||||
binary.LittleEndian.PutUint32(aw.buf[wordSize/2:], uint32(len(aw.h.Path)))
|
||||
binary.LittleEndian.PutUint64(aw.buf[wordSize:], aw.h.Size)
|
||||
if _, err := aw.w.Write(aw.buf[:]); err != nil {
|
||||
return err
|
||||
} else if _, err = aw.w.Write(
|
||||
unsafe.Slice(unsafe.StringData(aw.h.Path), len(aw.h.Path)),
|
||||
); err != nil {
|
||||
return err
|
||||
} else if psz := padSize(len(aw.h.Path)); psz != 0 {
|
||||
if _, err = aw.w.Write(zero[:psz]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes p to the underlying writer and records the new position. Invalid
|
||||
// positions are reported by WriteHeader and Close.
|
||||
func (aw *Writer) Write(p []byte) (n int, err error) {
|
||||
n, err = aw.w.Write(p)
|
||||
aw.n += uint64(n)
|
||||
return
|
||||
}
|
||||
|
||||
// Close concludes writing to the archive stream.
|
||||
func (aw *Writer) Close() (err error) {
|
||||
err = aw.flush()
|
||||
aw.w = nil
|
||||
return
|
||||
}
|
||||
|
||||
// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested
|
||||
// and a nonlocal path is encountered in the stream.
|
||||
var ErrInsecurePath = errors.New("insecure file path")
|
||||
|
||||
// Reader implements sequential reading of an archive. [Reader.Next] advances to
|
||||
// the next file in the archive (including the first), and then Reader can be
|
||||
// treated as an [io.Reader] to access the file's data.
|
||||
type Reader struct {
|
||||
// Underlying reader.
|
||||
r io.Reader
|
||||
// Fixed-size header segment.
|
||||
buf [wordSize * 2]byte
|
||||
// Remaining bytes in current data segment.
|
||||
n, pad uint64
|
||||
}
|
||||
|
||||
// NewReader returns the address of a new [Reader] reading from r.
|
||||
func NewReader(r io.Reader) *Reader { return &Reader{r: r} }
|
||||
|
||||
// Next advances ar to the next entry. Remaining bytes of the current data
|
||||
// segment are discarded. Advancing beyond the final entry returns [io.EOF].
|
||||
func (ar *Reader) Next() (*ArchiveHeader, error) {
|
||||
if dsz := int64(ar.n + ar.pad); dsz > 0 {
|
||||
if n, err := io.CopyN(io.Discard, ar.r, dsz); err != nil {
|
||||
if errors.Is(err, io.EOF) && n != dsz {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(ar.r, ar.buf[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := ArchiveHeader{
|
||||
Mode: fs.FileMode(binary.LittleEndian.Uint32(ar.buf[:])),
|
||||
Size: binary.LittleEndian.Uint64(ar.buf[wordSize:]),
|
||||
}
|
||||
pathSize := int(binary.LittleEndian.Uint32(ar.buf[wordSize/2:]))
|
||||
pPathSize := alignSize(pathSize)
|
||||
|
||||
buf := make([]byte, pPathSize)
|
||||
if _, err := io.ReadFull(ar.r, buf); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.Path = unsafe.String(unsafe.SliceData(buf), pathSize)
|
||||
if !filepath.IsLocal(h.Path) {
|
||||
return &h, ErrInsecurePath
|
||||
}
|
||||
|
||||
ar.n = h.Size
|
||||
ar.pad = padSize(h.Size)
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// Read implements [io.Reader] for the data segment of the current entry.
|
||||
func (ar *Reader) Read(p []byte) (n int, err error) {
|
||||
if uint64(len(p)) > ar.n {
|
||||
p = p[:ar.n]
|
||||
}
|
||||
|
||||
if len(p) > 0 {
|
||||
n, err = ar.r.Read(p)
|
||||
ar.n -= uint64(n)
|
||||
}
|
||||
|
||||
switch err {
|
||||
case io.EOF:
|
||||
if ar.n > 0 {
|
||||
return n, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
case nil:
|
||||
if ar.n == 0 {
|
||||
return n, io.EOF
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Write writes a deterministic representation of the contents of fsys to w.
|
||||
// The resulting data can be hashed to produce a deterministic checksum for the
|
||||
// directory.
|
||||
func Write(fsys fs.FS, root string, w io.Writer) error {
|
||||
aw := NewWriter(w)
|
||||
if err := fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fi fs.FileInfo
|
||||
fi, err = d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h := ArchiveHeader{
|
||||
Path: path,
|
||||
Mode: fi.Mode(),
|
||||
}
|
||||
if h.Mode.IsRegular() {
|
||||
h.Size = uint64(fi.Size())
|
||||
if err = aw.WriteHeader(&h); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var r fs.File
|
||||
r, err = fsys.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(aw, r)
|
||||
if _err := r.Close(); err == nil {
|
||||
err = _err
|
||||
}
|
||||
return err
|
||||
} else if h.Mode&fs.ModeSymlink != 0 {
|
||||
var newpath string
|
||||
if newpath, err = fs.ReadLink(fsys, path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.Size = uint64(len(newpath))
|
||||
if err = aw.WriteHeader(&h); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = aw.Write(unsafe.Slice(unsafe.StringData(newpath), len(newpath)))
|
||||
return err
|
||||
} else if !h.Mode.IsDir() {
|
||||
return InvalidFileModeError(h.Mode)
|
||||
}
|
||||
return aw.WriteHeader(&h)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return aw.Close()
|
||||
}
|
||||
|
||||
// SumFS saves checksum of the archive of fsys to the value pointed to by buf.
|
||||
func SumFS(buf *Checksum, fsys fs.FS, root string) error {
|
||||
h := sha512.New384()
|
||||
if err := Write(fsys, root, h); err != nil {
|
||||
return err
|
||||
}
|
||||
h.Sum(buf[:0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// SumDir saves checksum of the archive of directory at pathname to the value
|
||||
// pointed to by buf.
|
||||
func SumDir(buf *Checksum, pathname *check.Absolute) error {
|
||||
return SumFS(buf, os.DirFS(pathname.String()), ".")
|
||||
}
|
||||
|
||||
// archiveArtifact is an [Artifact] unpacking an archive supported by [Reader]
|
||||
// backed by a [FileArtifact].
|
||||
type archiveArtifact struct {
|
||||
// Caller-supplied backing archive.
|
||||
f Artifact
|
||||
}
|
||||
|
||||
// NewArchive returns a new [Artifact] backed by the supplied [Artifact]. The
|
||||
// source [Artifact] must be a [FileArtifact] and produce a stream compatible
|
||||
// with [Reader].
|
||||
func NewArchive(a Artifact) Artifact {
|
||||
return archiveArtifact{a}
|
||||
}
|
||||
|
||||
// Kind returns the hardcoded [Kind] constant.
|
||||
func (archiveArtifact) Kind() Kind { return KindArchive }
|
||||
|
||||
// Params is a noop.
|
||||
func (archiveArtifact) Params(*IContext) {}
|
||||
|
||||
func init() {
|
||||
register(KindArchive, func(r *IRReader) Artifact {
|
||||
a := NewArchive(r.Next())
|
||||
if _, ok := r.Finalise(); ok {
|
||||
panic(ErrUnexpectedChecksum)
|
||||
}
|
||||
return a
|
||||
})
|
||||
}
|
||||
|
||||
// Dependencies returns a slice containing the backing file.
|
||||
func (a archiveArtifact) Dependencies() []Artifact {
|
||||
return []Artifact{a.f}
|
||||
}
|
||||
|
||||
// IsExclusive returns false: [Reader] is fully sequential.
|
||||
func (archiveArtifact) IsExclusive() bool { return false }
|
||||
|
||||
// Cure cures the [Artifact], producing a directory located at work.
|
||||
func (a archiveArtifact) Cure(t *TContext) (err error) {
|
||||
var r io.ReadCloser
|
||||
if r, err = t.Open(a.f); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closeErr := r.Close()
|
||||
if err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
type dirTargetPerm struct {
|
||||
path string
|
||||
mode fs.FileMode
|
||||
}
|
||||
var madeDirectories []dirTargetPerm
|
||||
|
||||
if err = os.MkdirAll(t.GetWorkDir().String(), 0700); err != nil {
|
||||
return
|
||||
}
|
||||
var root *os.Root
|
||||
if root, err = os.OpenRoot(t.GetWorkDir().String()); err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
closeErr := root.Close()
|
||||
if err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
var header *ArchiveHeader
|
||||
ar := NewReader(r)
|
||||
for header, err = ar.Next(); err == nil; header, err = ar.Next() {
|
||||
if header.Mode.IsRegular() {
|
||||
var f *os.File
|
||||
if f, err = root.OpenFile(
|
||||
header.Path,
|
||||
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
|
||||
header.Mode.Perm(),
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.Copy(f, ar); err != nil {
|
||||
_ = f.Close()
|
||||
return
|
||||
} else if err = f.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
} else if header.Mode&fs.ModeSymlink != 0 {
|
||||
var p []byte
|
||||
if p, err = io.ReadAll(ar); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = root.Symlink(
|
||||
unsafe.String(unsafe.SliceData(p), len(p)),
|
||||
header.Path,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
} else if header.Mode.IsDir() {
|
||||
if header.Path == "." {
|
||||
continue
|
||||
}
|
||||
|
||||
madeDirectories = append(madeDirectories, dirTargetPerm{
|
||||
path: header.Path,
|
||||
mode: header.Mode,
|
||||
})
|
||||
if err = root.Mkdir(header.Path, 0700); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return InvalidFileModeError(header.Mode)
|
||||
}
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
err = nil
|
||||
}
|
||||
if err == nil {
|
||||
for _, e := range madeDirectories {
|
||||
if err = root.Chmod(e.path, e.mode.Perm()); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
package pkg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/fs"
|
||||
"maps"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func TestArchive(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type entry struct {
|
||||
path string
|
||||
mode fs.FileMode
|
||||
data string
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
fsys fs.FS
|
||||
entries []entry
|
||||
sum pkg.Checksum
|
||||
err error
|
||||
}{
|
||||
{"bad type", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
"invalid": {Mode: fs.ModeCharDevice | 0400},
|
||||
}, nil, pkg.Checksum{}, pkg.InvalidFileModeError(
|
||||
fs.ModeCharDevice | 0400,
|
||||
)},
|
||||
|
||||
{"coldboot", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices": {Mode: fs.ModeDir | 0700},
|
||||
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
|
||||
"block": {Mode: fs.ModeDir | 0700},
|
||||
"block/uevent": {Mode: 0600},
|
||||
}, []entry{
|
||||
{".", fs.ModeDir | 0700, ""},
|
||||
|
||||
{"block", fs.ModeDir | 0700, ""},
|
||||
{"block/uevent", 0600, ""},
|
||||
|
||||
{"devices", fs.ModeDir | 0700, ""},
|
||||
{"devices/empty", fs.ModeDir | 0700, ""},
|
||||
{"devices/sub", fs.ModeDir | 0700, ""},
|
||||
{"devices/sub/uevent", 0600, "add"},
|
||||
{"devices/uevent", 0600, "add"},
|
||||
}, pkg.MustDecode("mEy_Lf5KotThm7OwMx7yTKZh5HCCyaB41pVAvI9uDMgVQFM91iosBLYsRm8bDsX8"), nil},
|
||||
|
||||
{"empty", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []entry{
|
||||
{".", fs.ModeDir | 0700, ""},
|
||||
{"checksum", fs.ModeDir | 0700, ""},
|
||||
{"identifier", fs.ModeDir | 0700, ""},
|
||||
{"work", fs.ModeDir | 0700, ""},
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
|
||||
|
||||
{"sample directory step garbage", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
|
||||
"lib": {Mode: fs.ModeDir | 0500},
|
||||
"lib/check": {Mode: 0400},
|
||||
|
||||
"lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
||||
}, []entry{
|
||||
{".", fs.ModeDir | 0500, ""},
|
||||
|
||||
{"lib", fs.ModeDir | 0500, ""},
|
||||
{"lib/check", 0400, ""},
|
||||
|
||||
{"lib/pkgconfig", fs.ModeDir | 0500, ""},
|
||||
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("roundtrip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := pkg.Write(
|
||||
tc.fsys,
|
||||
".",
|
||||
&buf,
|
||||
); !reflect.DeepEqual(err, tc.err) {
|
||||
t.Fatalf("Flatten: error = %v, want %v", err, tc.err)
|
||||
} else if tc.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
r := pkg.NewReader(bytes.NewReader(buf.Bytes()))
|
||||
var got []entry
|
||||
for {
|
||||
h, err := r.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
t.Fatalf("Next: error = %v", err)
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if data, err = io.ReadAll(r); err != nil {
|
||||
t.Fatalf("Read: error = %v", err)
|
||||
}
|
||||
|
||||
got = append(got, entry{
|
||||
path: h.Path,
|
||||
mode: h.Mode,
|
||||
data: unsafe.String(unsafe.SliceData(data), len(data)),
|
||||
})
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tc.entries) {
|
||||
t.Fatalf("Reader: %#v, want %#v", got, tc.entries)
|
||||
}
|
||||
})
|
||||
|
||||
if tc.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var got pkg.Checksum
|
||||
if err := pkg.SumFS(&got, tc.fsys, "."); err != nil {
|
||||
t.Fatalf("SumFS: error = %v", err)
|
||||
} else if got != tc.sum {
|
||||
t.Fatalf("SumFS: %v", &pkg.ChecksumMismatchError{
|
||||
Got: got,
|
||||
Want: tc.sum,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var archiveTestdata = fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices": {Mode: fs.ModeDir | 0700},
|
||||
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
|
||||
"block": {Mode: fs.ModeDir | 0700},
|
||||
"block/uevent": {Mode: 0600},
|
||||
}
|
||||
|
||||
func TestArchiveArtifact(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := maps.Clone(archiveTestdata)
|
||||
want["."].Mode = fs.ModeDir | 0500
|
||||
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"unpack", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
var buf bytes.Buffer
|
||||
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
{"sample", pkg.NewArchive(
|
||||
pkg.NewFile("", buf.Bytes()),
|
||||
), ignorePathname, expectsFS(want), nil},
|
||||
})
|
||||
}, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block/uevent": {Mode: 0600},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/empty": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
"checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/3oYyAbRJ_we7AgWo1BRcRcnxXFk3mAQ0Qui2nGQMi8GIJNJQtvUC6P2IeoA5mbjD": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F")},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkArchiveRead(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
testdata := buf.Bytes()
|
||||
|
||||
for b.Loop() {
|
||||
r := pkg.NewReader(bytes.NewReader(testdata))
|
||||
for {
|
||||
_, err := r.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkArchiveWrite(b *testing.B) {
|
||||
for b.Loop() {
|
||||
if err := pkg.Write(archiveTestdata, ".", io.Discard); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"unique"
|
||||
)
|
||||
|
||||
// Clean destroys checksum backing entries without any identifier or substitute
|
||||
// entry referring to it. If at least one keep [Artifact] is specified,
|
||||
// identifier and substitute entries not kept alive by them are destroyed first.
|
||||
func (c *Cache) Clean(dry, inputs bool, keep ...Artifact) (
|
||||
[]unique.Handle[ID],
|
||||
[]unique.Handle[Checksum],
|
||||
error,
|
||||
) {
|
||||
c.identMu.Lock()
|
||||
defer c.identMu.Unlock()
|
||||
|
||||
c.checksumMu.Lock()
|
||||
defer c.checksumMu.Unlock()
|
||||
|
||||
dents, err := os.ReadDir(c.base.Append(dirChecksum).String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
checksums := make(map[unique.Handle[Checksum]]string, len(dents))
|
||||
var buf Checksum
|
||||
for _, dent := range dents {
|
||||
name := dent.Name()
|
||||
if err = Decode(&buf, name); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
checksums[unique.Make(buf)] = name
|
||||
}
|
||||
|
||||
type identPair struct {
|
||||
id unique.Handle[ID]
|
||||
name string
|
||||
}
|
||||
dents, err = os.ReadDir(c.base.Append(dirIdentifier).String())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
keepIdents := make(map[unique.Handle[ID]]struct{})
|
||||
if inputs {
|
||||
for _, id := range Inputs((*Collect)(&keep)) {
|
||||
keepIdents[id] = struct{}{}
|
||||
}
|
||||
} else {
|
||||
for _, a := range keep {
|
||||
keepIdents[c.Ident(a)] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
idents := make([]identPair, 0, len(dents))
|
||||
for _, dent := range dents {
|
||||
name := dent.Name()
|
||||
if err = Decode(&buf, name); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
id := unique.Make(ID(buf))
|
||||
|
||||
if _, ok := keepIdents[id]; len(keep) == 0 || ok {
|
||||
if err = readlinkChecksum(c.base.Append(
|
||||
dirIdentifier,
|
||||
name,
|
||||
), &buf); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
delete(checksums, unique.Make(buf))
|
||||
continue
|
||||
}
|
||||
|
||||
c.msg.Verbosef("arranging for destruction of %s...", name)
|
||||
idents = append(idents, identPair{id, name})
|
||||
}
|
||||
|
||||
destroyedIdents := make([]unique.Handle[ID], 0, len(idents))
|
||||
for _, pair := range idents {
|
||||
if !dry {
|
||||
if err = os.Remove(c.base.Append(
|
||||
dirStatus,
|
||||
pair.name,
|
||||
).String()); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return destroyedIdents, nil, err
|
||||
}
|
||||
|
||||
if err = os.Remove(c.base.Append(
|
||||
dirIdentifier,
|
||||
pair.name,
|
||||
).String()); err != nil {
|
||||
return destroyedIdents, nil, err
|
||||
}
|
||||
}
|
||||
destroyedIdents = append(destroyedIdents, pair.id)
|
||||
}
|
||||
|
||||
destroyedChecksums := make([]unique.Handle[Checksum], 0, len(checksums))
|
||||
for checksum, name := range checksums {
|
||||
if err = c.parent.Err(); err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
c.msg.Verbosef("destroying checksum %s...", name)
|
||||
if !dry {
|
||||
if err = errors.Join(removeAll(c.base.Append(
|
||||
dirChecksum,
|
||||
name,
|
||||
))); err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
}
|
||||
destroyedChecksums = append(destroyedChecksums, checksum)
|
||||
}
|
||||
|
||||
dents, err = os.ReadDir(c.base.Append(dirSubstitute).String())
|
||||
if err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
for _, dent := range dents {
|
||||
name := dent.Name()
|
||||
if err = readlinkChecksum(c.base.Append(
|
||||
dirSubstitute,
|
||||
name,
|
||||
), &buf); err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
if _, ok := checksums[unique.Make(buf)]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
c.msg.Verbosef("destroying substitute %s...", name)
|
||||
if !dry {
|
||||
if err = os.Remove(c.base.Append(
|
||||
dirStatus,
|
||||
name,
|
||||
).String()); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return destroyedIdents, nil, err
|
||||
}
|
||||
|
||||
if err = os.Remove(c.base.Append(
|
||||
dirSubstitute,
|
||||
name,
|
||||
).String()); err != nil {
|
||||
return destroyedIdents, destroyedChecksums, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return destroyedIdents, destroyedChecksums, nil
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
package pkg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"unique"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
"hakurei.app/message"
|
||||
)
|
||||
|
||||
// formatHandles returns a user-facing string representing h.
|
||||
func formatHandles[T pkg.ID | pkg.Checksum](handles ...unique.Handle[T]) string {
|
||||
var buf strings.Builder
|
||||
for _, h := range handles {
|
||||
buf.WriteString(pkg.Encode(pkg.Checksum(h.Value())))
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
return strings.TrimSuffix(buf.String(), ", ")
|
||||
}
|
||||
|
||||
func TestClean(t *testing.T) {
|
||||
t.Parallel()
|
||||
ic := pkg.NewIR()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
a []pkg.Artifact
|
||||
keep []pkg.Artifact
|
||||
inputs bool
|
||||
want expectsFS
|
||||
|
||||
wantIdents []unique.Handle[pkg.ID]
|
||||
wantChecksums []unique.Handle[pkg.Checksum]
|
||||
}{
|
||||
{"simple", []pkg.Artifact{
|
||||
pkg.NewFile("file", nil),
|
||||
}, nil, false, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/pPRjw2XYgjB5k8dYedwxTBMgHh4_v2JM_G2Vd-skQbAGOOgPsl3CGSUbEF7om_MO": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||
|
||||
"lock": {Mode: 0644},
|
||||
"variant": {Mode: 0400},
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"fault": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, nil, nil},
|
||||
|
||||
{"keep", []pkg.Artifact{
|
||||
pkg.NewFile("removed-file", []byte("removed file")),
|
||||
}, []pkg.Artifact{
|
||||
pkg.NewFile("file", []byte("\xfd")),
|
||||
}, false, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk": {Mode: 0400, Data: []byte("\xfd")},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/FMwSBYw22KqM8jZryfY2ChHXpLuVDdWYyNOYdHvIVYk8ujY6UnGRm5brr2sTTfpD": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/KgZ-FjbGuU-XP2QEHInpgv-2Zn0cTH5NqFMgTU0XrSdKmSwyC-3baVs1BMCP5spk")},
|
||||
|
||||
"lock": {Mode: 0644},
|
||||
"variant": {Mode: 0400},
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"fault": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []unique.Handle[pkg.ID]{
|
||||
ic.Ident(pkg.NewFile("removed-file", []byte("removed file"))),
|
||||
}, []unique.Handle[pkg.Checksum]{
|
||||
unique.Make(sha512.Sum384([]byte("removed file"))),
|
||||
}},
|
||||
|
||||
{"inputs anchored substitute", []pkg.Artifact{
|
||||
&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("destroyed"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||
},
|
||||
cure: func(f *pkg.FContext) error {
|
||||
p := f.GetWorkDir()
|
||||
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||
},
|
||||
},
|
||||
}, []pkg.Artifact{
|
||||
&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("kept"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("kept-input", []byte("kept")),
|
||||
},
|
||||
cure: func(f *pkg.FContext) error {
|
||||
p := f.GetWorkDir()
|
||||
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||
},
|
||||
},
|
||||
}, true, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")},
|
||||
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE/result": {Mode: 0444},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/Ef8KX6s_rS_nLgze0rj90zKyrGAvOnzyU0DL7nrYQWEG_f4a9pmUKI6HBEMD8AE8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")},
|
||||
"identifier/xoIGLemzLF227e-w_AJcf_1Sgqh2gs3KFgqvOIWUQE-9P_y2vHBMBytL4GRGQqTb": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||
|
||||
"lock": {Mode: 0644},
|
||||
"variant": {Mode: 0400},
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"substitute/4bjS-QjGcSV4nth-W6Vg3-wolKmKgiq4Ld2oRIWcOfy6Wi41XXLAWPoo8FcDx6BH": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||
"substitute/dzO8FEY9lu4hwRT6BfRZOX-uYGsC_5XH4jEJ7sJyThcmG9J_w1ArOAaUCGfL8wAM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||
|
||||
"fault": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []unique.Handle[pkg.ID]{
|
||||
ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))),
|
||||
ic.Ident(&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("destroyed"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||
},
|
||||
}),
|
||||
}, []unique.Handle[pkg.Checksum]{
|
||||
unique.Make(sha512.Sum384([]byte("destroyed"))),
|
||||
}},
|
||||
|
||||
{"inputs", []pkg.Artifact{
|
||||
&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("destroyed"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||
},
|
||||
cure: func(f *pkg.FContext) error {
|
||||
if w, err := f.GetStatusWriter(); err != nil {
|
||||
return err
|
||||
} else if _, err = w.Write([]byte("destroyed")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := f.GetWorkDir()
|
||||
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||
},
|
||||
},
|
||||
}, []pkg.Artifact{
|
||||
&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("kept"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("kept-input", []byte("kept")),
|
||||
},
|
||||
cure: func(f *pkg.FContext) error {
|
||||
if w, err := f.GetStatusWriter(); err != nil {
|
||||
return err
|
||||
} else if _, err = w.Write([]byte("kept")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := f.GetWorkDir()
|
||||
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p.Append("result").String(), []byte{0}, 0444)
|
||||
},
|
||||
},
|
||||
}, true, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W/result": {Mode: 0444, Data: []byte("\x00")},
|
||||
"checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv": {Mode: 0400, Data: []byte("kept")},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/Ef8KX6s_rS_nLgze0rj90zKyrGAvOnzyU0DL7nrYQWEG_f4a9pmUKI6HBEMD8AE8": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/H-eSiCo227-xdqyNl2R-5G3eqXPtbb8XegAB70I5OQb2majeZXJoCxTq9wJy5qqv")},
|
||||
"identifier/xoIGLemzLF227e-w_AJcf_1Sgqh2gs3KFgqvOIWUQE-9P_y2vHBMBytL4GRGQqTb": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")},
|
||||
|
||||
"lock": {Mode: 0644},
|
||||
"variant": {Mode: 0400},
|
||||
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
"status/4bjS-QjGcSV4nth-W6Vg3-wolKmKgiq4Ld2oRIWcOfy6Wi41XXLAWPoo8FcDx6BH": {Mode: 0400, Data: []byte(statusHeader + "kept")},
|
||||
"status/xoIGLemzLF227e-w_AJcf_1Sgqh2gs3KFgqvOIWUQE-9P_y2vHBMBytL4GRGQqTb": {Mode: 0400, Data: []byte(statusHeader + "kept")},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"substitute/4bjS-QjGcSV4nth-W6Vg3-wolKmKgiq4Ld2oRIWcOfy6Wi41XXLAWPoo8FcDx6BH": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CyDnDvF-LaeGPcSW70tPosNCoclByWkTjznUUF1DcgzlIwkN9yzz1ZFME1TlPj6W")},
|
||||
|
||||
"fault": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []unique.Handle[pkg.ID]{
|
||||
ic.Ident(pkg.NewFile("destroyed-input", []byte("destroyed"))),
|
||||
ic.Ident(&stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("destroyed"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||
},
|
||||
}),
|
||||
}, []unique.Handle[pkg.Checksum]{
|
||||
unique.Make(expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
"result": {Mode: 0444},
|
||||
}.hash()),
|
||||
unique.Make(sha512.Sum384([]byte("destroyed"))),
|
||||
}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
base := makeBase(t)
|
||||
msg := message.New(log.New(os.Stderr, "clean: ", 0))
|
||||
msg.SwapVerbose(testing.Verbose())
|
||||
c, err := pkg.Open(t.Context(), msg, 0, 0, 0, base)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(c.Close)
|
||||
|
||||
all := pkg.Collect(slices.Concat(tc.a, tc.keep))
|
||||
if _, _, err = c.Cure(&all); !pkg.IsCollected(err) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var (
|
||||
idents []unique.Handle[pkg.ID]
|
||||
checksums []unique.Handle[pkg.Checksum]
|
||||
)
|
||||
idents, checksums, err = c.Clean(false, tc.inputs, tc.keep...)
|
||||
if err != nil {
|
||||
t.Fatalf("Clean: error = %v", err)
|
||||
}
|
||||
var buf [2]pkg.Checksum
|
||||
|
||||
slices.SortFunc(idents, func(a, b unique.Handle[pkg.ID]) int {
|
||||
buf[0], buf[1] = a.Value(), b.Value()
|
||||
return bytes.Compare(buf[0][:], buf[1][:])
|
||||
})
|
||||
slices.SortFunc(checksums, func(a, b unique.Handle[pkg.Checksum]) int {
|
||||
buf[0], buf[1] = a.Value(), b.Value()
|
||||
return bytes.Compare(buf[0][:], buf[1][:])
|
||||
})
|
||||
|
||||
if !slices.Equal(idents, tc.wantIdents) {
|
||||
t.Errorf(
|
||||
"Clean: idents = %s, want %s",
|
||||
formatHandles(idents...), formatHandles(tc.wantIdents...),
|
||||
)
|
||||
}
|
||||
if !slices.Equal(checksums, tc.wantChecksums) {
|
||||
t.Errorf(
|
||||
"Clean: checksums = %s, want %s",
|
||||
formatHandles(checksums...), formatHandles(tc.wantChecksums...),
|
||||
)
|
||||
}
|
||||
|
||||
want := tc.want.hash()
|
||||
var checksum pkg.Checksum
|
||||
if err = pkg.SumDir(&checksum, base); err != nil {
|
||||
t.Fatalf("SumDir: error = %v", err)
|
||||
} else if checksum != want {
|
||||
t.Error(expectsFrom(base.String()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
// Gzip denotes a stream compressed via [gzip].
|
||||
Gzip = iota
|
||||
// Bzip2 denotes a stream compressed via [bzip2].
|
||||
Bzip2
|
||||
)
|
||||
|
||||
// A decompressArtifact is a [FileArtifact] decompressing a backing
|
||||
// [FileArtifact] stream.
|
||||
type decompressArtifact struct {
|
||||
// Caller-supplied backing stream.
|
||||
f Artifact
|
||||
// Compression on top of the stream.
|
||||
compress uint32
|
||||
}
|
||||
|
||||
var _ FileArtifact = new(decompressArtifact)
|
||||
|
||||
// decompressArtifactNamed embeds decompressArtifact for a [fmt.Stringer] stream.
|
||||
type decompressArtifactNamed struct {
|
||||
decompressArtifact
|
||||
// Copied from decompressArtifact.f.
|
||||
name string
|
||||
}
|
||||
|
||||
var _ fmt.Stringer = new(decompressArtifactNamed)
|
||||
|
||||
// NewDecompress returns a [FileArtifact] decompressing the supplied [Artifact].
|
||||
func NewDecompress(a Artifact, compress uint32) FileArtifact {
|
||||
da := decompressArtifact{a, compress}
|
||||
if s, ok := a.(fmt.Stringer); ok {
|
||||
if name := s.String(); name != "" {
|
||||
return &decompressArtifactNamed{da, name}
|
||||
}
|
||||
}
|
||||
return &da
|
||||
}
|
||||
|
||||
// String returns the name of the underlying [Artifact] prefixed with decompress.
|
||||
func (a *decompressArtifactNamed) String() string { return "decompress-" + a.name }
|
||||
|
||||
// Kind returns the hardcoded [Kind] constant.
|
||||
func (a *decompressArtifact) Kind() Kind { return KindDecompress }
|
||||
|
||||
// Params writes value of compression enum.
|
||||
func (a *decompressArtifact) Params(ctx *IContext) { ctx.WriteUint32(a.compress) }
|
||||
|
||||
func init() {
|
||||
register(KindDecompress, func(r *IRReader) Artifact {
|
||||
a := NewDecompress(r.Next(), r.ReadUint32())
|
||||
if _, ok := r.Finalise(); ok {
|
||||
panic(ErrUnexpectedChecksum)
|
||||
}
|
||||
return a
|
||||
})
|
||||
}
|
||||
|
||||
// Dependencies returns a slice containing the backing file.
|
||||
func (a *decompressArtifact) Dependencies() []Artifact {
|
||||
return []Artifact{a.f}
|
||||
}
|
||||
|
||||
// IsExclusive returns false: decompressor is fully sequential.
|
||||
func (a *decompressArtifact) IsExclusive() bool { return false }
|
||||
|
||||
// compoundCloser is an [io.ReadCloser] with an additional [io.Closer] attached.
|
||||
type compoundCloser struct {
|
||||
io.ReadCloser
|
||||
c io.Closer
|
||||
}
|
||||
|
||||
// Close closes [io.ReadCloser] and the additional [io.Closer]. It returns the
|
||||
// non-nil error returned by the underlying [io.ReadCloser], otherwise it
|
||||
// returns the error returned by the additional [io.Closer].
|
||||
func (c compoundCloser) Close() error {
|
||||
err := c.ReadCloser.Close()
|
||||
if _err := c.c.Close(); err == nil {
|
||||
err = _err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Cure returns a decompressor [io.ReadCloser].
|
||||
func (a *decompressArtifact) Cure(r *RContext) (io.ReadCloser, error) {
|
||||
sr, err := r.Open(a.f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
br := r.cache.getReaderRC(sr)
|
||||
|
||||
var dr io.ReadCloser
|
||||
switch a.compress {
|
||||
case Gzip:
|
||||
if dr, err = gzip.NewReader(br); err != nil {
|
||||
_ = br.Close()
|
||||
return nil, err
|
||||
}
|
||||
return compoundCloser{dr, br}, nil
|
||||
|
||||
case Bzip2:
|
||||
return struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}{bzip2.NewReader(br), br}, nil
|
||||
|
||||
default:
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package pkg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha512"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func TestDecompress(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
gw := gzip.NewWriter(&buf)
|
||||
if _, err := gw.Write([]byte{0}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if err = gw.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testdata := buf.String()
|
||||
|
||||
var transport http.Transport
|
||||
client := http.Client{Transport: &transport}
|
||||
transport.RegisterProtocol("file", http.NewFileTransportFS(fstest.MapFS{
|
||||
"testdata": {Data: []byte(testdata), Mode: 0400},
|
||||
}))
|
||||
testdataChecksum := func() pkg.Checksum {
|
||||
h := sha512.New384()
|
||||
h.Write([]byte(testdata))
|
||||
return (pkg.Checksum)(h.Sum(nil))
|
||||
}()
|
||||
|
||||
checkWithCache(t, []cacheTestCase{
|
||||
{"decompress", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
cureMany(t, c, []cureStep{
|
||||
{"close", pkg.NewDecompress(pkg.NewHTTPGet(
|
||||
&client,
|
||||
"file:///testdata",
|
||||
pkg.Checksum{0xfd},
|
||||
), pkg.Gzip), nil, nil, &pkg.ChecksumMismatchError{
|
||||
Got: testdataChecksum,
|
||||
Want: pkg.Checksum{0xfd},
|
||||
}},
|
||||
|
||||
{"gzip", pkg.NewDecompress(pkg.NewHTTPGet(
|
||||
&client,
|
||||
"file:///testdata",
|
||||
testdataChecksum,
|
||||
), pkg.Gzip), ignorePathname, expectsChecksum(sha512.Sum384([]byte{0})), nil},
|
||||
})
|
||||
}, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/" + pkg.Encode(sha512.Sum384([]byte{0})): {Mode: 0400, Data: []byte{0}},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/QpjkahDrz7pz-tv0eAGNXR6x9NAtTjWCK5Hr7G1cIZj9rT7bLYJWUQeLD4wamAlF": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package pkg
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
|
||||
// FlatEntry is a directory entry to be encoded for [Flatten].
|
||||
type FlatEntry struct {
|
||||
Mode fs.FileMode // file mode bits
|
||||
Path string // pathname of the file
|
||||
Data []byte // file content or symlink destination
|
||||
}
|
||||
|
||||
/*
|
||||
| mode uint32 | path_sz uint32 |
|
||||
| data_sz uint64 |
|
||||
| path string |
|
||||
| data []byte |
|
||||
*/
|
||||
|
||||
// Encode encodes the entry for transmission or hashing.
|
||||
func (ent *FlatEntry) Encode(w io.Writer) (n int, err error) {
|
||||
pPathSize := alignSize(len(ent.Path))
|
||||
if pPathSize > math.MaxUint32 {
|
||||
return 0, syscall.E2BIG
|
||||
}
|
||||
pDataSize := alignSize(len(ent.Data))
|
||||
|
||||
payload := make([]byte, wordSize*2+pPathSize+pDataSize)
|
||||
binary.LittleEndian.PutUint32(payload, uint32(ent.Mode))
|
||||
binary.LittleEndian.PutUint32(payload[wordSize/2:], uint32(len(ent.Path)))
|
||||
binary.LittleEndian.PutUint64(payload[wordSize:], uint64(len(ent.Data)))
|
||||
copy(payload[wordSize*2:], ent.Path)
|
||||
copy(payload[wordSize*2+pPathSize:], ent.Data)
|
||||
return w.Write(payload)
|
||||
}
|
||||
|
||||
// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested
|
||||
// and a nonlocal path is encountered in the stream.
|
||||
var ErrInsecurePath = errors.New("insecure file path")
|
||||
|
||||
// Decode decodes the entry from its representation produced by Encode.
|
||||
func (ent *FlatEntry) Decode(r io.Reader, validate bool) (n int, err error) {
|
||||
var nr int
|
||||
|
||||
header := make([]byte, wordSize*2)
|
||||
nr, err = r.Read(header)
|
||||
n += nr
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) && n != 0 {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ent.Mode = fs.FileMode(binary.LittleEndian.Uint32(header))
|
||||
pathSize := int(binary.LittleEndian.Uint32(header[wordSize/2:]))
|
||||
pPathSize := alignSize(pathSize)
|
||||
dataSize := int(binary.LittleEndian.Uint64(header[wordSize:]))
|
||||
pDataSize := alignSize(dataSize)
|
||||
|
||||
buf := make([]byte, pPathSize+pDataSize)
|
||||
nr, err = r.Read(buf)
|
||||
n += nr
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
if nr != len(buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ent.Path = string(buf[:pathSize])
|
||||
if ent.Mode.IsDir() {
|
||||
ent.Data = nil
|
||||
} else {
|
||||
ent.Data = buf[pPathSize : pPathSize+dataSize]
|
||||
}
|
||||
|
||||
if validate && !filepath.IsLocal(ent.Path) {
|
||||
err = ErrInsecurePath
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DirScanner provides an efficient interface for reading a stream of encoded
|
||||
// [FlatEntry]. Successive calls to the Scan method will step through the
|
||||
// entries in the stream.
|
||||
type DirScanner struct {
|
||||
// Underlying reader to scan [FlatEntry] representations from.
|
||||
r io.Reader
|
||||
|
||||
// First non-EOF I/O error, returned by the Err method.
|
||||
err error
|
||||
|
||||
// Entry to store results in. Its address is returned by the Entry method
|
||||
// and is updated on every call to Scan.
|
||||
ent FlatEntry
|
||||
|
||||
// Validate pathnames during decoding.
|
||||
validate bool
|
||||
}
|
||||
|
||||
// NewDirScanner returns the address of a new instance of [DirScanner] reading
|
||||
// from r. The caller must no longer read from r after this function returns.
|
||||
func NewDirScanner(r io.Reader, validate bool) *DirScanner {
|
||||
return &DirScanner{r: r, validate: validate}
|
||||
}
|
||||
|
||||
// Err returns the first non-EOF I/O error.
|
||||
func (s *DirScanner) Err() error {
|
||||
if errors.Is(s.err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return s.err
|
||||
}
|
||||
|
||||
// Entry returns the address to the [FlatEntry] value storing the last result.
|
||||
func (s *DirScanner) Entry() *FlatEntry { return &s.ent }
|
||||
|
||||
// Scan advances to the next [FlatEntry].
|
||||
func (s *DirScanner) Scan() bool {
|
||||
if s.err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var n int
|
||||
n, s.err = s.ent.Decode(s.r, s.validate)
|
||||
if errors.Is(s.err, io.EOF) {
|
||||
return n != 0
|
||||
}
|
||||
return s.err == nil
|
||||
}
|
||||
|
||||
// Flatten writes a deterministic representation of the contents of fsys to w.
|
||||
// The resulting data can be hashed to produce a deterministic checksum for the
|
||||
// directory.
|
||||
func Flatten(fsys fs.FS, root string, w io.Writer) (n int, err error) {
|
||||
var nr int
|
||||
err = fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fi fs.FileInfo
|
||||
fi, err = d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ent := FlatEntry{
|
||||
Path: path,
|
||||
Mode: fi.Mode(),
|
||||
}
|
||||
if ent.Mode.IsRegular() {
|
||||
if ent.Data, err = fs.ReadFile(fsys, path); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if ent.Mode&fs.ModeSymlink != 0 {
|
||||
var newpath string
|
||||
if newpath, err = fs.ReadLink(fsys, path); err != nil {
|
||||
return err
|
||||
}
|
||||
ent.Data = []byte(newpath)
|
||||
} else if !ent.Mode.IsDir() {
|
||||
return InvalidFileModeError(ent.Mode)
|
||||
}
|
||||
|
||||
nr, err = ent.Encode(w)
|
||||
n += nr
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// HashFS returns a checksum produced by hashing the result of [Flatten].
|
||||
func HashFS(buf *Checksum, fsys fs.FS, root string) error {
|
||||
h := sha512.New384()
|
||||
if _, err := Flatten(fsys, root, h); err != nil {
|
||||
return err
|
||||
}
|
||||
h.Sum(buf[:0])
|
||||
return nil
|
||||
}
|
||||
|
||||
// HashDir returns a checksum produced by hashing the result of [Flatten].
|
||||
func HashDir(buf *Checksum, pathname *check.Absolute) error {
|
||||
return HashFS(buf, os.DirFS(pathname.String()), ".")
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package pkg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/fs"
|
||||
"reflect"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func TestFlatten(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
fsys fs.FS
|
||||
entries []pkg.FlatEntry
|
||||
sum pkg.Checksum
|
||||
err error
|
||||
}{
|
||||
{"bad type", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
"invalid": {Mode: fs.ModeCharDevice | 0400},
|
||||
}, nil, pkg.Checksum{}, pkg.InvalidFileModeError(
|
||||
fs.ModeCharDevice | 0400,
|
||||
)},
|
||||
|
||||
{"coldboot", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices": {Mode: fs.ModeDir | 0700},
|
||||
"devices/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
"devices/empty": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"devices/sub": {Mode: fs.ModeDir | 0700},
|
||||
"devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
|
||||
|
||||
"block": {Mode: fs.ModeDir | 0700},
|
||||
"block/uevent": {Mode: 0600, 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},
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}, []pkg.FlatEntry{
|
||||
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
||||
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
|
||||
|
||||
{"sample directory step garbage", fstest.MapFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
|
||||
"lib": {Mode: fs.ModeDir | 0500},
|
||||
"lib/check": {Mode: 0400, Data: []byte{}},
|
||||
|
||||
"lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
||||
}, []pkg.FlatEntry{
|
||||
{Mode: fs.ModeDir | 0500, Path: "."},
|
||||
|
||||
{Mode: fs.ModeDir | 0500, Path: "lib"},
|
||||
{Mode: 0400, Path: "lib/check", Data: []byte{}},
|
||||
|
||||
{Mode: fs.ModeDir | 0500, Path: "lib/pkgconfig"},
|
||||
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("roundtrip", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var buf bytes.Buffer
|
||||
if _, err := pkg.Flatten(
|
||||
tc.fsys,
|
||||
".",
|
||||
&buf,
|
||||
); !reflect.DeepEqual(err, tc.err) {
|
||||
t.Fatalf("Flatten: error = %v, want %v", err, tc.err)
|
||||
} else if tc.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s := pkg.NewDirScanner(bytes.NewReader(buf.Bytes()), true)
|
||||
var got []pkg.FlatEntry
|
||||
for s.Scan() {
|
||||
got = append(got, *s.Entry())
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
t.Fatalf("Err: error = %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tc.entries) {
|
||||
t.Fatalf("Scan: %#v, want %#v", got, tc.entries)
|
||||
}
|
||||
})
|
||||
|
||||
if tc.err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.Run("hash", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var got pkg.Checksum
|
||||
if err := pkg.HashFS(&got, tc.fsys, "."); err != nil {
|
||||
t.Fatalf("HashFS: error = %v", err)
|
||||
} else if got != tc.sum {
|
||||
t.Fatalf("HashFS: %v", &pkg.ChecksumMismatchError{
|
||||
Got: got,
|
||||
Want: tc.sum,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -123,15 +122,6 @@ func RegisterArch(arch string, e container.BinfmtEntry) {
|
||||
binfmt[arch] = e
|
||||
}
|
||||
|
||||
// Arch returns a snapshot of currently registered [KindExec] and [KindExecNet]
|
||||
// binfmt entries.
|
||||
func Arch() map[string]container.BinfmtEntry {
|
||||
binfmtMu.RLock()
|
||||
r := maps.Clone(binfmt)
|
||||
binfmtMu.RUnlock()
|
||||
return r
|
||||
}
|
||||
|
||||
const (
|
||||
// ExecTimeoutDefault replaces out of range [NewExec] timeout values.
|
||||
ExecTimeoutDefault = 15 * time.Minute
|
||||
@@ -591,7 +581,6 @@ var (
|
||||
func (c *Cache) EnterExec(
|
||||
ctx context.Context,
|
||||
a Artifact,
|
||||
hostname string,
|
||||
retainSession bool,
|
||||
stdin io.Reader,
|
||||
stdout, stderr io.Writer,
|
||||
@@ -672,9 +661,6 @@ func (c *Cache) EnterExec(
|
||||
z.Env = append(z.Env, "TERM="+s)
|
||||
}
|
||||
}
|
||||
if hostname != "" {
|
||||
z.Hostname = hostname
|
||||
}
|
||||
|
||||
if err = z.Start(); err != nil {
|
||||
return
|
||||
|
||||
@@ -183,10 +183,10 @@ func TestExec(t *testing.T) {
|
||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/IY91PCtOpCYy21AaIK0c9f8-Z6fb2_2ewoHWkt4dxoLf0GOrWqS8yAGFLV84b1Dw": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||
"identifier/QwS7SmiatdqryQYgESdGw7Yw2PcpNf0vNfpvUA0t92BTlKiUjfCrXyMW17G2X77X": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||
"identifier/" + expected.Offline: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||
"identifier/" + expected.OfflineS: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package expected
|
||||
|
||||
const (
|
||||
Offline = "q5ktDTq0miP-VvB2blxqXQeaRXCUWgP_KbC18KNtUDtyoaI_h5mHmGuPMArVEBDs"
|
||||
OfflineS = "IY91PCtOpCYy21AaIK0c9f8-Z6fb2_2ewoHWkt4dxoLf0GOrWqS8yAGFLV84b1Dw"
|
||||
OvlRoot = "NacZGXwuRkTvcHaG08a22ujJ8qCWN0RSoFlRSR5FSt0ZcBbJ28FRvkYsHEtX7G8i"
|
||||
Layers = "WBJDrATtX6rIE5yAu8ePX3WmDF0Tt9kFiue0m3cRnyRoVx1my8a67fh3CAW486oP"
|
||||
Net = "CmYtj2sNB3LHtqiDuck_Lz3MjLLIiwyP8N4NDitQ1Icvv__LVP9p8tm-sHeQaKKp"
|
||||
Promote = "TX3eCloaQFkV-SZIH6Jg6E5WKH--rcXY1P0jnZKmLFKWrNqnOzd4G9eIBh6i5ywN"
|
||||
Work = "OuNiLSC68pZhAOr1YQ4WbV1tzASA0nxLEBcK7lO7MqxDY_j8dmP_C612RTuF23Lu"
|
||||
Offline = "q5ktDTq0miP-VvB2blxqXQeaRXCUWgP_KbC18KNtUDtyoaI_h5mHmGuPMArVEBDs"
|
||||
OvlRoot = "NacZGXwuRkTvcHaG08a22ujJ8qCWN0RSoFlRSR5FSt0ZcBbJ28FRvkYsHEtX7G8i"
|
||||
Layers = "WBJDrATtX6rIE5yAu8ePX3WmDF0Tt9kFiue0m3cRnyRoVx1my8a67fh3CAW486oP"
|
||||
Net = "CmYtj2sNB3LHtqiDuck_Lz3MjLLIiwyP8N4NDitQ1Icvv__LVP9p8tm-sHeQaKKp"
|
||||
Promote = "TX3eCloaQFkV-SZIH6Jg6E5WKH--rcXY1P0jnZKmLFKWrNqnOzd4G9eIBh6i5ywN"
|
||||
Work = "OuNiLSC68pZhAOr1YQ4WbV1tzASA0nxLEBcK7lO7MqxDY_j8dmP_C612RTuF23Lu"
|
||||
)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package expected
|
||||
|
||||
const (
|
||||
Offline = "WapqyoPxbWSnq07dWHt71mHaJXq99pAjJfFlELlJljSiZMhTFqqlzU1_mN86shSj"
|
||||
OfflineS = "ibQZHcdXgNQ1OiMX1FrburBbGPVvKEHvPilbQCkm_0oV0BQCHomyyTbYNrFMGIwl"
|
||||
OvlRoot = "V9anFOiRvjGfAeBhLl14AL8TKdWZyD0WTPYe4fS9mOBw8iW5Lmarvt6TG6MV8uWm"
|
||||
Layers = "tKx7JNRoSBdK_7MdzI-nwTNV2wmiPzwYdcd17oLmXKL_iLmUzUiA79qTqdrTasrv"
|
||||
Net = "aXyDLzBCJ9XltXZIfetEVsEkrqHfcXuD5XE_FcUnYbN3emwL55N6P8LlHzNfGnM5"
|
||||
Promote = "3k4V16n96Lq04gjFSKmm4sFjyQ883FFBNXgTy9s_DjeTwxT3pg_iacEh8yMb_S4m"
|
||||
Work = "6Q49MhFWRE3Ne6MycwAotgl1GtoU5WCHqJNWG2byYZCY-zX-IxPrWiKk7bKkNzhE"
|
||||
Offline = "WapqyoPxbWSnq07dWHt71mHaJXq99pAjJfFlELlJljSiZMhTFqqlzU1_mN86shSj"
|
||||
OvlRoot = "V9anFOiRvjGfAeBhLl14AL8TKdWZyD0WTPYe4fS9mOBw8iW5Lmarvt6TG6MV8uWm"
|
||||
Layers = "tKx7JNRoSBdK_7MdzI-nwTNV2wmiPzwYdcd17oLmXKL_iLmUzUiA79qTqdrTasrv"
|
||||
Net = "aXyDLzBCJ9XltXZIfetEVsEkrqHfcXuD5XE_FcUnYbN3emwL55N6P8LlHzNfGnM5"
|
||||
Promote = "3k4V16n96Lq04gjFSKmm4sFjyQ883FFBNXgTy9s_DjeTwxT3pg_iacEh8yMb_S4m"
|
||||
Work = "6Q49MhFWRE3Ne6MycwAotgl1GtoU5WCHqJNWG2byYZCY-zX-IxPrWiKk7bKkNzhE"
|
||||
)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package expected
|
||||
|
||||
const (
|
||||
Offline = "Z6yXE5gOJScL3srmnVMWgCXccDiUNZ5snSrf6RkXuU1_U0rX_kGVwsfHUgNG_awd"
|
||||
OfflineS = "zN16xv6LKRJRipUJwupyxg2rZcvf-qpsMn_qCxUmgxlTSuNwYI70ZEb7dHW5k0gO"
|
||||
OvlRoot = "zYXJHFRLuxvUhuisZEXgGgVvdQd6piMfp5jmtT6jdVjvC2gICXquOq-UTwlrSD5I"
|
||||
Layers = "_F8EDazHbcLeT0sVSQXRN_kn9IjduqJcDYgzXpsT-hpKU4EBcZ0PISN2zchpqMbm"
|
||||
Net = "CA_FAaSIYJgapBEHV40doxpH23PdUEy_6s1TZc7wfSPN0XYqwGpMceXXDSabGveO"
|
||||
Promote = "_3LPrLp--4h9k4GsNNApu9hHtAafq-GUhfU6d4hJKBDKT3bz_szOsvkXxc5sK53d"
|
||||
Work = "FEgHeiCD_WT4wsfB-9kDH5n6cRWCEYtJmXdKZgmUUukAOoXumH_hLlosXREC-tqq"
|
||||
Offline = "Z6yXE5gOJScL3srmnVMWgCXccDiUNZ5snSrf6RkXuU1_U0rX_kGVwsfHUgNG_awd"
|
||||
OvlRoot = "zYXJHFRLuxvUhuisZEXgGgVvdQd6piMfp5jmtT6jdVjvC2gICXquOq-UTwlrSD5I"
|
||||
Layers = "_F8EDazHbcLeT0sVSQXRN_kn9IjduqJcDYgzXpsT-hpKU4EBcZ0PISN2zchpqMbm"
|
||||
Net = "CA_FAaSIYJgapBEHV40doxpH23PdUEy_6s1TZc7wfSPN0XYqwGpMceXXDSabGveO"
|
||||
Promote = "_3LPrLp--4h9k4GsNNApu9hHtAafq-GUhfU6d4hJKBDKT3bz_szOsvkXxc5sK53d"
|
||||
Work = "FEgHeiCD_WT4wsfB-9kDH5n6cRWCEYtJmXdKZgmUUukAOoXumH_hLlosXREC-tqq"
|
||||
)
|
||||
|
||||
+1
-14
@@ -8,7 +8,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
"slices"
|
||||
"strconv"
|
||||
"sync"
|
||||
@@ -21,7 +20,7 @@ import (
|
||||
const wordSize = 8
|
||||
|
||||
// alignSize returns the padded size for aligning sz.
|
||||
func alignSize[T int | uint64](sz T) T {
|
||||
func alignSize(sz int) int {
|
||||
return sz + (wordSize-(sz)%wordSize)%wordSize
|
||||
}
|
||||
|
||||
@@ -66,18 +65,6 @@ func NewIR() *IRCache {
|
||||
return &IRCache{zeroIRCache()}
|
||||
}
|
||||
|
||||
// Inputs returns an iterator over direct and transitive inputs of an [Artifact]
|
||||
// in randomised order.
|
||||
func Inputs(a Artifact) iter.Seq2[Artifact, unique.Handle[ID]] {
|
||||
ic := NewIR()
|
||||
ic.Ident(a)
|
||||
return func(yield func(Artifact, unique.Handle[ID]) bool) {
|
||||
ic.artifact.Range(func(key, value any) bool {
|
||||
return yield(key.(Artifact), value.(unique.Handle[ID]))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// IContext is passed to [Artifact.Params] and provides methods for writing
|
||||
// values to the IR writer. It does not expose the underlying [io.Writer].
|
||||
//
|
||||
|
||||
+21
-17
@@ -27,14 +27,16 @@ func TestIRRoundtrip(t *testing.T) {
|
||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||
)},
|
||||
|
||||
{"http get tar", pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet(
|
||||
{"http get tar", pkg.NewHTTPGetTar(
|
||||
nil, "file:///testdata",
|
||||
pkg.Checksum(bytes.Repeat([]byte{0xff}, len(pkg.Checksum{}))),
|
||||
), pkg.Bzip2))},
|
||||
{"http get tar unaligned", pkg.NewTar(pkg.NewHTTPGet(
|
||||
pkg.TarBzip2,
|
||||
)},
|
||||
{"http get tar unaligned", pkg.NewHTTPGetTar(
|
||||
nil, "https://hakurei.app",
|
||||
pkg.Checksum(bytes.Repeat([]byte{0xfe}, len(pkg.Checksum{}))),
|
||||
))},
|
||||
pkg.TarUncompressed,
|
||||
)},
|
||||
|
||||
{"exec offline", pkg.NewExec(
|
||||
"exec-offline", "", nil, 0, false, false,
|
||||
@@ -45,13 +47,15 @@ func TestIRRoundtrip(t *testing.T) {
|
||||
|
||||
pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
|
||||
"stub file",
|
||||
))), pkg.MustPath("/.hakurei", false, pkg.NewTar(pkg.NewHTTPGet(
|
||||
))), pkg.MustPath("/.hakurei", false, pkg.NewHTTPGetTar(
|
||||
nil, "file:///hakurei.tar",
|
||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||
))), pkg.MustPath("/opt", false, pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet(
|
||||
pkg.TarUncompressed,
|
||||
)), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar(
|
||||
nil, "file:///testtool.tar.gz",
|
||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||
), pkg.Gzip))),
|
||||
pkg.TarGzip,
|
||||
)),
|
||||
)},
|
||||
|
||||
{"exec net", pkg.NewExec(
|
||||
@@ -65,13 +69,15 @@ func TestIRRoundtrip(t *testing.T) {
|
||||
|
||||
pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
|
||||
"stub file",
|
||||
))), pkg.MustPath("/.hakurei", false, pkg.NewTar(pkg.NewHTTPGet(
|
||||
))), pkg.MustPath("/.hakurei", false, pkg.NewHTTPGetTar(
|
||||
nil, "file:///hakurei.tar",
|
||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||
))), pkg.MustPath("/opt", false, pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet(
|
||||
pkg.TarUncompressed,
|
||||
)), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar(
|
||||
nil, "file:///testtool.tar.gz",
|
||||
pkg.Checksum(bytes.Repeat([]byte{0xfc}, len(pkg.Checksum{}))),
|
||||
), pkg.Gzip))),
|
||||
pkg.TarGzip,
|
||||
)),
|
||||
)},
|
||||
|
||||
{"exec measured", pkg.NewExec(
|
||||
@@ -85,21 +91,19 @@ func TestIRRoundtrip(t *testing.T) {
|
||||
|
||||
pkg.MustPath("/file", false, pkg.NewFile("file", []byte(
|
||||
"stub file",
|
||||
))), pkg.MustPath("/.hakurei", false, pkg.NewTar(pkg.NewHTTPGet(
|
||||
))), pkg.MustPath("/.hakurei", false, pkg.NewHTTPGetTar(
|
||||
nil, "file:///hakurei.tar",
|
||||
pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))),
|
||||
))), pkg.MustPath("/opt", false, pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet(
|
||||
pkg.TarUncompressed,
|
||||
)), pkg.MustPath("/opt", false, pkg.NewHTTPGetTar(
|
||||
nil, "file:///testtool.tar.gz",
|
||||
pkg.Checksum(bytes.Repeat([]byte{0xfd}, len(pkg.Checksum{}))),
|
||||
), pkg.Gzip))),
|
||||
pkg.TarGzip,
|
||||
)),
|
||||
)},
|
||||
|
||||
{"file anonymous", pkg.NewFile("", []byte{0})},
|
||||
{"file", pkg.NewFile("stub", []byte("stub"))},
|
||||
|
||||
{"decompress", pkg.NewDecompress(pkg.NewFile("", []byte{0}), pkg.Bzip2)},
|
||||
|
||||
{"archive", pkg.NewArchive(pkg.NewFile("", []byte{0}))},
|
||||
}
|
||||
testCasesCache := make([]cacheTestCase, len(testCases))
|
||||
for i, tc := range testCases {
|
||||
|
||||
+39
-285
@@ -155,7 +155,7 @@ type TContext struct {
|
||||
// Target [Artifact] encoded identifier.
|
||||
ids string
|
||||
// Pathname status was created at.
|
||||
statusPath, statusSPath *check.Absolute
|
||||
statusPath *check.Absolute
|
||||
// File statusHeader and logs are written to.
|
||||
status *os.File
|
||||
// Error value during prepareStatus.
|
||||
@@ -187,7 +187,7 @@ func makeStatusHeader(extension string) string {
|
||||
var statusHeader = makeStatusHeader("")
|
||||
|
||||
// prepareStatus initialises the status file once.
|
||||
func (t *TContext) prepareStatus(writeHeader bool) error {
|
||||
func (t *TContext) prepareStatus() error {
|
||||
if t.statusPath != nil || t.status != nil {
|
||||
return t.statusErr
|
||||
}
|
||||
@@ -204,16 +204,14 @@ func (t *TContext) prepareStatus(writeHeader bool) error {
|
||||
return t.statusErr
|
||||
}
|
||||
|
||||
if writeHeader {
|
||||
_, t.statusErr = t.status.WriteString(statusHeader)
|
||||
}
|
||||
_, t.statusErr = t.status.WriteString(statusHeader)
|
||||
return t.statusErr
|
||||
}
|
||||
|
||||
// GetStatusWriter returns a [io.Writer] for build logs. The caller must not
|
||||
// seek this writer before the position it was first returned in.
|
||||
func (t *TContext) GetStatusWriter() (io.Writer, error) {
|
||||
err := t.prepareStatus(true)
|
||||
err := t.prepareStatus()
|
||||
return t.status, err
|
||||
}
|
||||
|
||||
@@ -238,8 +236,8 @@ func (t *TContext) destroy(errP *error) {
|
||||
if chmodErr != nil || removeErr != nil {
|
||||
*errP = errors.Join(*errP, chmodErr, removeErr)
|
||||
} else if errors.Is(*errP, os.ErrExist) {
|
||||
if linkError, ok := errors.AsType[*os.LinkError](*errP); ok &&
|
||||
linkError != nil &&
|
||||
var linkError *os.LinkError
|
||||
if errors.As(*errP, &linkError) && linkError != nil &&
|
||||
linkError.Op == "rename" {
|
||||
// two artifacts may be backed by the same file
|
||||
*errP = nil
|
||||
@@ -260,11 +258,6 @@ func (t *TContext) destroy(errP *error) {
|
||||
), 10),
|
||||
).String(),
|
||||
))
|
||||
if t.statusSPath != nil {
|
||||
t.cache.checksumMu.Lock()
|
||||
*errP = errors.Join(*errP, os.Remove(t.statusSPath.String()))
|
||||
t.cache.checksumMu.Unlock()
|
||||
}
|
||||
}
|
||||
t.status = nil
|
||||
}
|
||||
@@ -334,28 +327,6 @@ type FContext struct {
|
||||
deps map[Artifact]cureRes
|
||||
}
|
||||
|
||||
// linkSubstitute links status for substitute if populated.
|
||||
func (f *FContext) linkSubstitute(ids, substitutes string) (err error) {
|
||||
if f.status == nil || ids == substitutes {
|
||||
return
|
||||
}
|
||||
|
||||
statusS := f.cache.base.Append(
|
||||
dirStatus,
|
||||
substitutes,
|
||||
)
|
||||
f.cache.checksumMu.Lock()
|
||||
err = os.Link(f.cache.base.Append(
|
||||
dirStatus,
|
||||
ids,
|
||||
).String(), statusS.String())
|
||||
f.cache.checksumMu.Unlock()
|
||||
if err == nil {
|
||||
f.statusSPath = statusS
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// InvalidLookupError is the identifier of non-dependency [Artifact] looked up
|
||||
// via [FContext.GetArtifact] by a misbehaving [Artifact] implementation.
|
||||
type InvalidLookupError ID
|
||||
@@ -532,10 +503,6 @@ const (
|
||||
KindExecNet
|
||||
// KindFile is the kind of [Artifact] returned by [NewFile].
|
||||
KindFile
|
||||
// KindDecompress is the kind of [Artifact] returned by [NewDecompress].
|
||||
KindDecompress
|
||||
// KindArchive is the kind of [Artifact] returned by [NewArchive].
|
||||
KindArchive
|
||||
|
||||
// _kindEnd is the total number of kinds and does not denote a kind.
|
||||
_kindEnd
|
||||
@@ -694,20 +661,6 @@ type pendingCure struct {
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// An External cache provides prepared [Artifact] cure outcomes.
|
||||
type External interface {
|
||||
// Artifact returns the address of the [Checksum] of the cure outcome of
|
||||
// an [Artifact] corresponding to id, or nil if this [Artifact] is not
|
||||
// available in the external cache.
|
||||
Artifact(ctx context.Context, id unique.Handle[ID]) (*Checksum, error)
|
||||
// Checksum returns an [Artifact] producing the specified checksum.
|
||||
Checksum(checksum unique.Handle[Checksum]) Artifact
|
||||
// Status returns [io.ReadCloser] of the status file of an [Artifact]
|
||||
// corresponding to id, or nil if this [Artifact] is not available or a
|
||||
// status file is not present.
|
||||
Status(r *RContext, id unique.Handle[ID]) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
// Cache is a support layer that implementations of [Artifact] can use to store
|
||||
// cured [Artifact] data in a content addressed fashion.
|
||||
type Cache struct {
|
||||
@@ -758,11 +711,6 @@ type Cache struct {
|
||||
// Buffered I/O free list, must not be accessed directly.
|
||||
brPool, bwPool sync.Pool
|
||||
|
||||
// Optional external cache implementation.
|
||||
extern External
|
||||
// Synchronises access to extern.
|
||||
externMu sync.RWMutex
|
||||
|
||||
// Unlocks the on-filesystem cache. Must only be called from Close.
|
||||
unlock func()
|
||||
// Whether [Cache] is considered closed.
|
||||
@@ -842,38 +790,6 @@ func (c *Cache) getReader(r io.Reader) *bufio.Reader {
|
||||
// putReader adds br to brPool.
|
||||
func (c *Cache) putReader(br *bufio.Reader) { c.brPool.Put(br) }
|
||||
|
||||
// bufioReadCloser is the concrete type of value returned by Cache.getReaderRC.
|
||||
type bufioReadCloser struct {
|
||||
// Saved close error.
|
||||
closeErr error
|
||||
// Synchronises calls to Close.
|
||||
closeOnce sync.Once
|
||||
|
||||
// For backing freelist.
|
||||
c *Cache
|
||||
// Underlying reader.
|
||||
r io.ReadCloser
|
||||
// Allocated from c.
|
||||
*bufio.Reader
|
||||
}
|
||||
|
||||
// Close closes the underlying reader, saves its return value, and returns the
|
||||
// [bufio.Reader] instance to the backing [Cache].
|
||||
func (brc *bufioReadCloser) Close() error {
|
||||
brc.closeOnce.Do(func() {
|
||||
br := brc.Reader
|
||||
brc.Reader = nil
|
||||
brc.c.putReader(br)
|
||||
brc.closeErr = brc.r.Close()
|
||||
})
|
||||
return brc.closeErr
|
||||
}
|
||||
|
||||
// getReaderRC is like getReader, but returns an [io.ReadCloser].
|
||||
func (c *Cache) getReaderRC(r io.ReadCloser) io.ReadCloser {
|
||||
return &bufioReadCloser{c: c, r: r, Reader: c.getReader(r)}
|
||||
}
|
||||
|
||||
// getWriter is like [bufio.NewWriter] but for bwPool.
|
||||
func (c *Cache) getWriter(w io.Writer) *bufio.Writer {
|
||||
bw := c.bwPool.Get().(*bufio.Writer)
|
||||
@@ -895,35 +811,6 @@ func (e *ChecksumMismatchError) Error() string {
|
||||
" instead of " + Encode(e.Want)
|
||||
}
|
||||
|
||||
// LinknamePrefixError describes a malformed linkname to a [Checksum].
|
||||
type LinknamePrefixError string
|
||||
|
||||
func (e LinknamePrefixError) Error() string {
|
||||
return "linkname " + strconv.Quote(string(e)) + " missing prefix"
|
||||
}
|
||||
|
||||
// readlinkChecksum reads a symbolic link to a dirChecksum entry and saves the
|
||||
// decoded [Checksum] to the value pointed to by buf. The checksumLinknamePrefix
|
||||
// is required.
|
||||
func readlinkChecksum(a *check.Absolute, buf *Checksum) error {
|
||||
linkname, err := os.Readlink(a.String())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(linkname, checksumLinknamePrefix) {
|
||||
return LinknamePrefixError(linkname)
|
||||
}
|
||||
return Decode(buf, linkname[len(checksumLinknamePrefix):])
|
||||
}
|
||||
|
||||
// SetExternal sets e as the [External] implementation of c.
|
||||
func (c *Cache) SetExternal(e External) {
|
||||
c.externMu.Lock()
|
||||
c.extern = e
|
||||
c.externMu.Unlock()
|
||||
}
|
||||
|
||||
// ScrubError describes the outcome of a [Cache.Scrub] call where errors were
|
||||
// found and removed from the underlying storage of [Cache].
|
||||
type ScrubError struct {
|
||||
@@ -974,42 +861,36 @@ func (e *ScrubError) Unwrap() []error {
|
||||
// Error returns a multi-line representation of [ScrubError].
|
||||
func (e *ScrubError) Error() string {
|
||||
var segments []string
|
||||
var buf strings.Builder
|
||||
|
||||
if len(e.ChecksumMismatches) > 0 {
|
||||
buf.Reset()
|
||||
buf.WriteString("checksum mismatches:\n")
|
||||
s := "checksum mismatches:\n"
|
||||
for _, m := range e.ChecksumMismatches {
|
||||
buf.WriteString(m.Error() + "\n")
|
||||
s += m.Error() + "\n"
|
||||
}
|
||||
segments = append(segments, buf.String())
|
||||
segments = append(segments, s)
|
||||
}
|
||||
if len(e.DanglingIdentifiers) > 0 {
|
||||
buf.Reset()
|
||||
buf.WriteString("dangling identifiers:\n")
|
||||
s := "dangling identifiers:\n"
|
||||
for _, id := range e.DanglingIdentifiers {
|
||||
buf.WriteString(Encode(id) + "\n")
|
||||
s += Encode(id) + "\n"
|
||||
}
|
||||
segments = append(segments, buf.String())
|
||||
segments = append(segments, s)
|
||||
}
|
||||
if len(e.DanglingStatus) > 0 {
|
||||
buf.Reset()
|
||||
buf.WriteString("dangling status:\n")
|
||||
s := "dangling status:\n"
|
||||
for _, id := range e.DanglingStatus {
|
||||
buf.WriteString(Encode(id) + "\n")
|
||||
s += Encode(id) + "\n"
|
||||
}
|
||||
segments = append(segments, buf.String())
|
||||
segments = append(segments, s)
|
||||
}
|
||||
if len(e.Errs) > 0 {
|
||||
buf.Reset()
|
||||
buf.WriteString("errors during scrub:\n")
|
||||
s := "errors during scrub:\n"
|
||||
for pathname, errs := range e.errs {
|
||||
buf.WriteString(" " + pathname.Value() + ":\n")
|
||||
s += " " + pathname.Value() + ":\n"
|
||||
for _, err := range errs {
|
||||
buf.WriteString(" " + err.Error() + "\n")
|
||||
s += " " + err.Error() + "\n"
|
||||
}
|
||||
}
|
||||
segments = append(segments, buf.String())
|
||||
segments = append(segments, s)
|
||||
}
|
||||
return strings.Join(segments, "\n")
|
||||
}
|
||||
@@ -1099,7 +980,7 @@ func (c *Cache) Scrub(checks int) error {
|
||||
|
||||
pathname := dir.Append(ent.Name())
|
||||
if ent.IsDir() {
|
||||
if err := SumDir(got, pathname); err != nil {
|
||||
if err := HashDir(got, pathname); err != nil {
|
||||
addErr(pathname, err)
|
||||
return true
|
||||
}
|
||||
@@ -1199,28 +1080,19 @@ func (c *Cache) Scrub(checks int) error {
|
||||
got := p.Get().(*Checksum)
|
||||
defer p.Put(got)
|
||||
|
||||
var ok bool
|
||||
for _, name := range [...]string{
|
||||
if _, err := os.Stat(c.base.Append(
|
||||
dirIdentifier,
|
||||
dirSubstitute,
|
||||
} {
|
||||
if _, err := os.Stat(c.base.Append(
|
||||
name,
|
||||
ent.Name(),
|
||||
).String()); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
addErr(dir.Append(ent.Name()), err)
|
||||
}
|
||||
continue
|
||||
ent.Name(),
|
||||
).String()); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
addErr(dir.Append(ent.Name()), err)
|
||||
}
|
||||
ok = true
|
||||
}
|
||||
if !ok {
|
||||
seMu.Lock()
|
||||
se.DanglingStatus = append(se.DanglingStatus, *want)
|
||||
seMu.Unlock()
|
||||
return false
|
||||
}
|
||||
return ok
|
||||
return true
|
||||
}}
|
||||
}
|
||||
wg.Wait()
|
||||
@@ -1838,40 +1710,6 @@ func (r *RContext) NewMeasuredReader(
|
||||
return r.cache.newMeasuredReader(rc, checksum)
|
||||
}
|
||||
|
||||
// tryExtern attempts to obtain an [Artifact] outcome from extern.
|
||||
func (c *Cache) tryExtern(ctx context.Context, id unique.Handle[ID]) (
|
||||
unique.Handle[Checksum],
|
||||
io.ReadCloser,
|
||||
error,
|
||||
) {
|
||||
c.externMu.RLock()
|
||||
defer c.externMu.RUnlock()
|
||||
|
||||
if c.extern == nil {
|
||||
return zeroChecksum, nil, nil
|
||||
}
|
||||
|
||||
v, err := c.extern.Artifact(ctx, id)
|
||||
if err != nil {
|
||||
return zeroChecksum, nil, err
|
||||
}
|
||||
if v == nil {
|
||||
return zeroChecksum, nil, nil
|
||||
}
|
||||
checksum := unique.Make(*v)
|
||||
|
||||
var got unique.Handle[Checksum]
|
||||
if _, got, err = c.Cure(c.extern.Checksum(checksum)); err != nil {
|
||||
return checksum, nil, err
|
||||
} else if got != checksum {
|
||||
return zeroChecksum, nil, &ChecksumMismatchError{got.Value(), checksum.Value()}
|
||||
}
|
||||
|
||||
var status io.ReadCloser
|
||||
status, err = c.extern.Status(&RContext{common{ctx, c}}, id)
|
||||
return checksum, status, err
|
||||
}
|
||||
|
||||
// cure implements Cure without acquiring a read lock on abortMu. cure must not
|
||||
// be entered during Abort.
|
||||
func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
@@ -1938,7 +1776,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
err = zeroTimes(pathname.String())
|
||||
}
|
||||
|
||||
if err == nil && alternative != nil && substitute != id {
|
||||
if err == nil && alternative != nil {
|
||||
c.substituteMu.Lock()
|
||||
err = os.Symlink(
|
||||
linkname,
|
||||
@@ -2085,9 +1923,6 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
}
|
||||
c.exitCure(a, curesExempt)
|
||||
if err != nil {
|
||||
if c.msg.IsVerbose() {
|
||||
c.msg.Verbosef("cure file %s: %v", reportName(f, id), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2119,7 +1954,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
t := TContext{
|
||||
c.base.Append(dirWork, ids),
|
||||
c.base.Append(dirTemp, ids),
|
||||
ids, nil, nil, nil, nil,
|
||||
ids, nil, nil, nil,
|
||||
common{ctx, c},
|
||||
}
|
||||
switch ca := a.(type) {
|
||||
@@ -2131,9 +1966,6 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
err = ca.Cure(&t)
|
||||
c.exitCure(a, curesExempt)
|
||||
if err != nil {
|
||||
if c.msg.IsVerbose() {
|
||||
c.msg.Verbosef("cure trivial %s: %v", reportName(ca, id), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
break
|
||||
@@ -2206,65 +2038,21 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
}
|
||||
|
||||
defer f.destroy(&err)
|
||||
|
||||
var (
|
||||
externChecksum unique.Handle[Checksum]
|
||||
externStatus io.ReadCloser
|
||||
)
|
||||
if externChecksum, externStatus, err = c.tryExtern(ctx, id); err != nil {
|
||||
if c.msg.IsVerbose() {
|
||||
c.msg.Verbosef("extern %s: %v", reportName(ca, id), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if externChecksum != zeroChecksum {
|
||||
if checksum != zeroChecksum && externChecksum != checksum {
|
||||
if externStatus != nil {
|
||||
_ = externStatus.Close()
|
||||
}
|
||||
err = &ChecksumMismatchError{externChecksum.Value(), checksum.Value()}
|
||||
if c.msg.IsVerbose() {
|
||||
c.msg.Verbosef("extern %s: %v", reportName(ca, id), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
checksum = externChecksum
|
||||
checksums = Encode(checksum.Value())
|
||||
checksumPathname = c.base.Append(
|
||||
dirChecksum,
|
||||
checksums,
|
||||
)
|
||||
|
||||
if externStatus != nil {
|
||||
if err = f.prepareStatus(false); err != nil {
|
||||
_ = externStatus.Close()
|
||||
return
|
||||
} else if _, err = io.Copy(f.status, externStatus); err != nil {
|
||||
_ = externStatus.Close()
|
||||
return
|
||||
} else if err = externStatus.Close(); err != nil {
|
||||
return
|
||||
} else if err = f.linkSubstitute(ids, substitutes); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err = c.enterCure(a, curesExempt); err != nil {
|
||||
return
|
||||
}
|
||||
err = ca.Cure(&f)
|
||||
c.exitCure(a, curesExempt)
|
||||
|
||||
if err == nil {
|
||||
err = f.linkSubstitute(ids, substitutes)
|
||||
if err == nil && f.status != nil {
|
||||
err = os.Link(c.base.Append(
|
||||
dirStatus,
|
||||
ids,
|
||||
).String(), c.base.Append(
|
||||
dirStatus,
|
||||
substitutes,
|
||||
).String())
|
||||
}
|
||||
c.exitCure(a, curesExempt)
|
||||
if err != nil {
|
||||
if c.msg.IsVerbose() {
|
||||
c.msg.Verbosef("cure %s: %v", reportName(ca, id), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
break
|
||||
@@ -2293,7 +2081,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
}
|
||||
|
||||
var gotChecksum Checksum
|
||||
if err = SumFS(
|
||||
if err = HashFS(
|
||||
&gotChecksum,
|
||||
dotOverrideFS{os.DirFS(t.work.String()).(dirFS)},
|
||||
".",
|
||||
@@ -2313,9 +2101,6 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||
Got: gotChecksum,
|
||||
Want: checksum.Value(),
|
||||
}
|
||||
if c.msg.IsVerbose() {
|
||||
c.msg.Verbosef("validate %s: %v", reportName(a, id), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2568,37 +2353,6 @@ func open(
|
||||
c.unlock = func() {}
|
||||
}
|
||||
|
||||
for _, name := range []string{
|
||||
dirWork,
|
||||
dirTemp,
|
||||
} {
|
||||
dents, err := os.ReadDir(base.Append(name).String())
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
c.unlock()
|
||||
return nil, err
|
||||
}
|
||||
if len(dents) != 0 {
|
||||
c.unlock()
|
||||
return nil, fmt.Errorf(
|
||||
"%s is not empty, scrub likely required",
|
||||
name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.ReadDir(base.Append(
|
||||
dirExecScratch,
|
||||
).String()); !errors.Is(err, os.ErrNotExist) {
|
||||
c.unlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.New(dirExecScratch + " is present, scrub likely required")
|
||||
}
|
||||
|
||||
variantPath := base.Append(fileVariant).String()
|
||||
if p, err := os.ReadFile(variantPath); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
|
||||
+35
-300
@@ -61,11 +61,6 @@ var (
|
||||
//
|
||||
//go:linkname irArtifact hakurei.app/internal/pkg.irArtifact
|
||||
irArtifact map[pkg.Kind]pkg.IRReadFunc
|
||||
|
||||
// statusHeader is the header written to all status files in dirStatus.
|
||||
//
|
||||
//go:linkname statusHeader hakurei.app/internal/pkg.statusHeader
|
||||
statusHeader string
|
||||
)
|
||||
|
||||
// newRContext returns the address of a new [pkg.RContext] unsafely created for
|
||||
@@ -197,35 +192,6 @@ func newStubFile(
|
||||
}
|
||||
}
|
||||
|
||||
// stubExtern implements [External] with hardcoded prepared outcomes.
|
||||
type stubExtern struct {
|
||||
artifact map[unique.Handle[pkg.ID]]pkg.Checksum
|
||||
checksum map[unique.Handle[pkg.Checksum]]fstest.MapFS
|
||||
status map[unique.Handle[pkg.ID]]string
|
||||
}
|
||||
|
||||
func (e stubExtern) Artifact(_ context.Context, id unique.Handle[pkg.ID]) (*pkg.Checksum, error) {
|
||||
if checksum, ok := e.artifact[id]; ok {
|
||||
return &checksum, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e stubExtern) Checksum(checksum unique.Handle[pkg.Checksum]) pkg.Artifact {
|
||||
var buf bytes.Buffer
|
||||
if err := pkg.Write(e.checksum[checksum], ".", &buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pkg.NewArchive(pkg.NewFile("", buf.Bytes()))
|
||||
}
|
||||
|
||||
func (e stubExtern) Status(_ *pkg.RContext, id unique.Handle[pkg.ID]) (io.ReadCloser, error) {
|
||||
if status, ok := e.status[id]; ok {
|
||||
return io.NopCloser(strings.NewReader(status)), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// destroyArtifact removes all traces of an [Artifact] from the on-disk cache.
|
||||
// Do not use this in a test case without a very good reason to do so.
|
||||
func destroyArtifact(
|
||||
@@ -322,15 +288,15 @@ func TestIdent(t *testing.T) {
|
||||
a pkg.Artifact
|
||||
want unique.Handle[pkg.ID]
|
||||
}{
|
||||
{"decompress", &stubArtifact{
|
||||
pkg.KindDecompress,
|
||||
[]byte{pkg.Gzip, 0, 0, 0, 0, 0, 0, 0},
|
||||
{"tar", &stubArtifact{
|
||||
pkg.KindTar,
|
||||
[]byte{pkg.TarGzip, 0, 0, 0, 0, 0, 0, 0},
|
||||
[]pkg.Artifact{
|
||||
overrideIdent{pkg.ID{}, new(stubArtifact)},
|
||||
},
|
||||
nil,
|
||||
}, unique.Make[pkg.ID](pkg.MustDecode(
|
||||
"97Y85QewssfPbNIN9cyNhzD4e6dLHcDTU8rb2c34k-aCrZfBNXFUc0duPiLFFcw_",
|
||||
"WKErnjTOVbuH2P9a0gM4OcAAO4p-CoX2HQu7CbZrg8ZOzApvWoO3-ISzPw6av_rN",
|
||||
))},
|
||||
}
|
||||
|
||||
@@ -386,7 +352,7 @@ type expectsFS fstest.MapFS
|
||||
|
||||
// hash computes the checksum of e.
|
||||
func (e expectsFS) hash() (checksum pkg.Checksum) {
|
||||
if err := pkg.SumFS(&checksum, fstest.MapFS(e), "."); err != nil {
|
||||
if err := pkg.HashFS(&checksum, fstest.MapFS(e), "."); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
@@ -468,31 +434,6 @@ const (
|
||||
checkDestroySubstitutes = 1 << (iota + 32)
|
||||
)
|
||||
|
||||
// makeBase returns a [pkg.Cache] base directory created for tb.
|
||||
func makeBase(tb testing.TB) (base *check.Absolute) {
|
||||
tb.Helper()
|
||||
|
||||
base = check.MustAbs(tb.TempDir())
|
||||
if err := os.Chmod(base.String(), 0700); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
tb.Cleanup(func() {
|
||||
if err := filepath.WalkDir(base.String(), func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
tb.Error(err)
|
||||
return nil
|
||||
}
|
||||
if !d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return os.Chmod(path, 0700)
|
||||
}); err != nil {
|
||||
tb.Fatal(err)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// checkWithCache runs a slice of cacheTestCase.
|
||||
func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
t.Helper()
|
||||
@@ -502,7 +443,25 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
t.Helper()
|
||||
t.Parallel()
|
||||
|
||||
base := makeBase(t)
|
||||
base := check.MustAbs(t.TempDir())
|
||||
if err := os.Chmod(base.String(), 0700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := filepath.WalkDir(base.String(), func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return nil
|
||||
}
|
||||
if !d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return os.Chmod(path, 0700)
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
msg := message.New(log.New(os.Stderr, "cache: ", 0))
|
||||
msg.SwapVerbose(testing.Verbose())
|
||||
|
||||
@@ -527,17 +486,7 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
tc.early(t, base)
|
||||
}
|
||||
tc.f(t, base, c)
|
||||
scrubFunc = func() error {
|
||||
err = c.Scrub(1 << 7)
|
||||
idents, checksums, cleanErr := c.Clean(false, false)
|
||||
if len(idents) > 0 {
|
||||
t.Errorf("destroyed %d idents", len(idents))
|
||||
}
|
||||
if len(checksums) > 0 {
|
||||
t.Errorf("destroyed %d checksums", len(checksums))
|
||||
}
|
||||
return errors.Join(err, cleanErr)
|
||||
}
|
||||
scrubFunc = func() error { return c.Scrub(1 << 7) }
|
||||
}
|
||||
|
||||
var restoreTemp bool
|
||||
@@ -577,9 +526,8 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
|
||||
// destroy empty status directory
|
||||
if err := syscall.Rmdir(base.Append("status").String()); err != nil {
|
||||
if !errors.Is(err, syscall.ENOTEMPTY) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Error(expectsFrom(base.Append("status").String()))
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// destroy empty fault directory
|
||||
@@ -590,8 +538,8 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
want := tc.want.hash()
|
||||
|
||||
var checksum pkg.Checksum
|
||||
if err := pkg.SumDir(&checksum, base); err != nil {
|
||||
t.Fatalf("SumDir: error = %v", err)
|
||||
if err := pkg.HashDir(&checksum, base); err != nil {
|
||||
t.Fatalf("HashDir: error = %v", err)
|
||||
} else if checksum != want {
|
||||
t.Fatal(expectsFrom(base.String()))
|
||||
}
|
||||
@@ -610,10 +558,13 @@ func checkWithCache(t *testing.T, testCases []cacheTestCase) {
|
||||
}
|
||||
|
||||
// validate again to make sure scrub did not condemn anything
|
||||
if err := pkg.SumDir(&checksum, base); err != nil {
|
||||
t.Fatalf("SumDir: error = %v", err)
|
||||
if err := pkg.HashDir(&checksum, base); err != nil {
|
||||
t.Fatalf("HashDir: error = %v", err)
|
||||
} else if checksum != want {
|
||||
t.Fatalf("(scrubbed) %s", expectsFrom(base.String()))
|
||||
t.Fatalf("(scrubbed) HashDir: %v", &pkg.ChecksumMismatchError{
|
||||
Got: checksum,
|
||||
Want: want,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1448,179 +1399,6 @@ func TestCache(t *testing.T) {
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}},
|
||||
|
||||
{"status substitute clean", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
destroyed := &stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("destroyed"),
|
||||
deps: []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input", []byte("destroyed")),
|
||||
},
|
||||
cure: func(f *pkg.FContext) error {
|
||||
if w, err := f.GetStatusWriter(); err != nil {
|
||||
return err
|
||||
} else if _, err = w.Write([]byte("destroyed")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := f.GetWorkDir()
|
||||
if err := os.MkdirAll(p.String(), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(p.Append("result").String(), nil, 0444)
|
||||
},
|
||||
}
|
||||
substituted := new(*destroyed)
|
||||
substituted.deps = []pkg.Artifact{
|
||||
pkg.NewFile("destroyed-input-0", []byte("destroyed")),
|
||||
}
|
||||
substituted.cure = func(*pkg.FContext) error {
|
||||
panic("substitutable cure reached")
|
||||
}
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
{"destroyed", destroyed, base.Append(
|
||||
"identifier",
|
||||
pkg.Encode(c.Ident(destroyed).Value()),
|
||||
), expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
"result": {Mode: 0444},
|
||||
}, nil},
|
||||
|
||||
{"substituted", substituted, base.Append(
|
||||
"identifier",
|
||||
pkg.Encode(c.Ident(substituted).Value()),
|
||||
), expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
"result": {Mode: 0444},
|
||||
}, nil},
|
||||
})
|
||||
}, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE/result": {Mode: 0444},
|
||||
"checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV": {Mode: 0400, Data: []byte("destroyed")},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/0Jmc-vGnNDlXTD3jxy-4DGxHW-2-2LtLZ9SXaJtDIqu4uyHfDwDbghNBQ2aYRpab": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||
"identifier/L-A8SK4ZX631eyealbJVH08u5pAVEf2NMk8RmLlxl7BVADkU4hNjWD6pi5H7uL1F": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV")},
|
||||
"identifier/VbVV2dFVosCriHnG9t5vwfW4lbHvzOkV2eYwpGpPJZOgNBc0g1wZAhbdKEweLqmW": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||
"identifier/aSMwvPAwdsIF9e1spuLyRNEc8aTFA4HRVasoNqGjxdm1laSMs2h2teGsoSzLp_pR": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/wILUy2izpj2sgKJVhGUGIAde1XVuqvp5BpFMIQHanT5Q8R6jK4QPVSrJsjZh-njV")},
|
||||
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
"status/0Jmc-vGnNDlXTD3jxy-4DGxHW-2-2LtLZ9SXaJtDIqu4uyHfDwDbghNBQ2aYRpab": {Mode: fs.ModeSymlink | 0777, Data: []byte("dzO8FEY9lu4hwRT6BfRZOX-uYGsC_5XH4jEJ7sJyThcmG9J_w1ArOAaUCGfL8wAM")},
|
||||
"status/VbVV2dFVosCriHnG9t5vwfW4lbHvzOkV2eYwpGpPJZOgNBc0g1wZAhbdKEweLqmW": {Mode: 0400, Data: []byte(statusHeader + "destroyed")},
|
||||
"status/dzO8FEY9lu4hwRT6BfRZOX-uYGsC_5XH4jEJ7sJyThcmG9J_w1ArOAaUCGfL8wAM": {Mode: 0400, Data: []byte(statusHeader + "destroyed")},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"substitute/dzO8FEY9lu4hwRT6BfRZOX-uYGsC_5XH4jEJ7sJyThcmG9J_w1ArOAaUCGfL8wAM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/UjZSrgz7_B7XMd9fHU7jM33UZhWlFgX0rz7JZbCBYR28bCS7jr_CAJdcDhi52ruE")},
|
||||
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}},
|
||||
|
||||
{"extern", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||
a := &stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("extern"),
|
||||
}
|
||||
wantIdent := c.Ident(a)
|
||||
wantOutput := expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
"result": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent")},
|
||||
}
|
||||
var wantChecksum pkg.Checksum
|
||||
if err := pkg.SumFS(
|
||||
&wantChecksum,
|
||||
fstest.MapFS(wantOutput),
|
||||
".",
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantChecksumH := unique.Make(wantChecksum)
|
||||
|
||||
_a := &stubArtifactF{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("extern substitute"),
|
||||
deps: []pkg.Artifact{pkg.NewFile("", nil)},
|
||||
}
|
||||
_wantIdent := c.Ident(_a)
|
||||
_wantOutput := expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0500},
|
||||
}
|
||||
var _wantChecksum pkg.Checksum
|
||||
if err := pkg.SumFS(
|
||||
&_wantChecksum,
|
||||
fstest.MapFS(_wantOutput),
|
||||
".",
|
||||
); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_wantChecksumH := unique.Make(_wantChecksum)
|
||||
|
||||
kca := pkg.NewExec(
|
||||
"", "",
|
||||
new(pkg.Checksum), 0, false, false,
|
||||
fhs.AbsRoot, nil, fhs.AbsRoot, nil,
|
||||
)
|
||||
kcIdent := c.Ident(kca)
|
||||
|
||||
c.SetExternal(stubExtern{
|
||||
artifact: map[unique.Handle[pkg.ID]]pkg.Checksum{
|
||||
wantIdent: wantChecksum,
|
||||
_wantIdent: _wantChecksum,
|
||||
kcIdent: wantChecksum,
|
||||
},
|
||||
checksum: map[unique.Handle[pkg.Checksum]]fstest.MapFS{
|
||||
wantChecksumH: fstest.MapFS(wantOutput),
|
||||
_wantChecksumH: fstest.MapFS(_wantOutput),
|
||||
},
|
||||
status: map[unique.Handle[pkg.ID]]string{
|
||||
wantIdent: "\x00",
|
||||
kcIdent: "unreachable",
|
||||
},
|
||||
})
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
{"extern", a, base.Append(
|
||||
"identifier",
|
||||
pkg.Encode(wantIdent.Value()),
|
||||
), wantOutput, nil},
|
||||
|
||||
{"substitute", _a, base.Append(
|
||||
"identifier",
|
||||
pkg.Encode(_wantIdent.Value()),
|
||||
), _wantOutput, nil},
|
||||
|
||||
{"mismatch", kca, nil, nil, &pkg.ChecksumMismatchError{
|
||||
Got: wantChecksum,
|
||||
}},
|
||||
})
|
||||
}, expectsFS{
|
||||
".": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
"checksum": {Mode: fs.ModeDir | 0700},
|
||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400},
|
||||
"checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl": {Mode: fs.ModeDir | 0500},
|
||||
"checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl/result": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent")},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/4HqRo4uTwRQjfy3d2cujMoDC_pC4iv20h4a7NYlx0UdbVuky18o5iK78TFEfPX2U": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||
"identifier/7AZcJm58ghFyTVf_v2baSntgpsxkP5el7ti9dC77C29n8YTEqQW9jRW92KGNdYnz": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
|
||||
"identifier/c4aCI00C-ZVyo_FQDQLl1OYK4U_kjzxwrLdFDiXMHnbMcZXCkXo_nxUWauScZ_4V": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||
"identifier/cNoG77frXGRCJa7fUi1INKUEQg7L4qrX5acsSv-wqZdGZT7dQwM93rD3at6kSFFF": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||
"identifier/gvCqzexZVqXjF8B5lKMcP5onmq3jJ6AKqzOW_WN0Fl2yTr9NKhPt9l_ClD2EOSlS": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fHkl_RuHOoc4rso__nV-qreikovd6Yhrq5mpBlkf5hmPGaxDlik2bYOQ4dhUQjtl")},
|
||||
|
||||
"status": {Mode: fs.ModeDir | 0700},
|
||||
"status/gvCqzexZVqXjF8B5lKMcP5onmq3jJ6AKqzOW_WN0Fl2yTr9NKhPt9l_ClD2EOSlS": {Mode: 0400, Data: []byte("\x00")},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
"substitute/qOYrxy9ztKeOA96Os811_0Ox5sd8FBOxis6psJAnRJL5MLazFMaqmd4g7t7k1OHk": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||
|
||||
"work": {Mode: fs.ModeDir | 0700},
|
||||
}},
|
||||
}
|
||||
checkWithCache(t, testCases)
|
||||
}
|
||||
@@ -1983,49 +1761,6 @@ func TestOpen(t *testing.T) {
|
||||
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dirty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tempDir := check.MustAbs(t.TempDir())
|
||||
if err := os.MkdirAll(tempDir.Append(
|
||||
"cache",
|
||||
"work",
|
||||
"dirty",
|
||||
).String(), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wantErr := errors.New("work is not empty, scrub likely required")
|
||||
if _, err := pkg.Open(
|
||||
t.Context(),
|
||||
message.New(nil),
|
||||
0, 0, 0, tempDir.Append("cache"),
|
||||
); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("scratch", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tempDir := check.MustAbs(t.TempDir())
|
||||
if err := os.MkdirAll(tempDir.Append(
|
||||
"cache",
|
||||
"scratch",
|
||||
).String(), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
wantErr := errors.New("scratch is present, scrub likely required")
|
||||
if _, err := pkg.Open(
|
||||
t.Context(),
|
||||
message.New(nil),
|
||||
0, 0, 0, tempDir.Append("cache"),
|
||||
); !reflect.DeepEqual(err, wantErr) {
|
||||
t.Errorf("Open: error = %#v, want %#v", err, wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtensionRegister(t *testing.T) {
|
||||
|
||||
+66
-17
@@ -2,18 +2,32 @@ package pkg
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
// TarUncompressed denotes an uncompressed tarball.
|
||||
TarUncompressed = iota
|
||||
// TarGzip denotes a tarball compressed via [gzip].
|
||||
TarGzip
|
||||
// TarBzip2 denotes a tarball compressed via [bzip2].
|
||||
TarBzip2
|
||||
)
|
||||
|
||||
// A tarArtifact is an [Artifact] unpacking a tarball backed by a [FileArtifact].
|
||||
type tarArtifact struct {
|
||||
// Caller-supplied backing tarball.
|
||||
f Artifact
|
||||
// Compression on top of the tarball.
|
||||
compression uint32
|
||||
}
|
||||
|
||||
// tarArtifactNamed embeds tarArtifact for a [fmt.Stringer] tarball.
|
||||
@@ -25,13 +39,13 @@ type tarArtifactNamed struct {
|
||||
|
||||
var _ fmt.Stringer = new(tarArtifactNamed)
|
||||
|
||||
// String returns the name of the underlying [Artifact] prefixed with unpack.
|
||||
func (a *tarArtifactNamed) String() string { return "unpack-" + a.name }
|
||||
// String returns the name of the underlying [Artifact] suffixed with unpack.
|
||||
func (a *tarArtifactNamed) String() string { return a.name + "-unpack" }
|
||||
|
||||
// NewTar returns a new [Artifact] unpacking the tar stream produced by the
|
||||
// backing [Artifact]. The source [Artifact] must be a [FileArtifact].
|
||||
func NewTar(a Artifact) Artifact {
|
||||
ta := tarArtifact{a}
|
||||
// NewTar returns a new [Artifact] backed by the supplied [Artifact] and
|
||||
// compression method. The source [Artifact] must be a [FileArtifact].
|
||||
func NewTar(a Artifact, compression uint32) Artifact {
|
||||
ta := tarArtifact{a, compression}
|
||||
if s, ok := a.(fmt.Stringer); ok {
|
||||
if name := s.String(); name != "" {
|
||||
return &tarArtifactNamed{ta, name}
|
||||
@@ -40,15 +54,25 @@ func NewTar(a Artifact) Artifact {
|
||||
return &ta
|
||||
}
|
||||
|
||||
// NewHTTPGetTar is abbreviation for NewHTTPGet passed to NewTar.
|
||||
func NewHTTPGetTar(
|
||||
hc *http.Client,
|
||||
url string,
|
||||
checksum Checksum,
|
||||
compression uint32,
|
||||
) Artifact {
|
||||
return NewTar(NewHTTPGet(hc, url, checksum), compression)
|
||||
}
|
||||
|
||||
// Kind returns the hardcoded [Kind] constant.
|
||||
func (a *tarArtifact) Kind() Kind { return KindTar }
|
||||
|
||||
// Params is a noop.
|
||||
func (a *tarArtifact) Params(*IContext) {}
|
||||
// Params writes compression encoded in little endian.
|
||||
func (a *tarArtifact) Params(ctx *IContext) { ctx.WriteUint32(a.compression) }
|
||||
|
||||
func init() {
|
||||
register(KindTar, func(r *IRReader) Artifact {
|
||||
a := NewTar(r.Next())
|
||||
a := NewTar(r.Next(), r.ReadUint32())
|
||||
if _, ok := r.Finalise(); ok {
|
||||
panic(ErrUnexpectedChecksum)
|
||||
}
|
||||
@@ -74,17 +98,42 @@ func (e DisallowedTypeflagError) Error() string {
|
||||
|
||||
// Cure cures the [Artifact], producing a directory located at work.
|
||||
func (a *tarArtifact) Cure(t *TContext) (err error) {
|
||||
var r io.ReadCloser
|
||||
if r, err = t.Open(a.f); err != nil {
|
||||
var tr io.ReadCloser
|
||||
if tr, err = t.Open(a.f); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closeErr := r.Close()
|
||||
defer func(f io.ReadCloser) {
|
||||
if err == nil {
|
||||
err = tr.Close()
|
||||
}
|
||||
|
||||
closeErr := f.Close()
|
||||
if err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
}(tr)
|
||||
br := t.cache.getReader(tr)
|
||||
defer t.cache.putReader(br)
|
||||
tr = io.NopCloser(br)
|
||||
|
||||
switch a.compression {
|
||||
case TarUncompressed:
|
||||
break
|
||||
|
||||
case TarGzip:
|
||||
if tr, err = gzip.NewReader(tr); err != nil {
|
||||
return
|
||||
}
|
||||
break
|
||||
|
||||
case TarBzip2:
|
||||
tr = io.NopCloser(bzip2.NewReader(tr))
|
||||
break
|
||||
|
||||
default:
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
type dirTargetPerm struct {
|
||||
path string
|
||||
@@ -107,8 +156,8 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
||||
}()
|
||||
|
||||
var header *tar.Header
|
||||
tr := tar.NewReader(r)
|
||||
for header, err = tr.Next(); err == nil; header, err = tr.Next() {
|
||||
r := tar.NewReader(tr)
|
||||
for header, err = r.Next(); err == nil; header, err = r.Next() {
|
||||
typeflag := header.Typeflag
|
||||
if typeflag == 0 {
|
||||
if len(header.Name) > 0 && header.Name[len(header.Name)-1] == '/' {
|
||||
@@ -134,7 +183,7 @@ func (a *tarArtifact) Cure(t *TContext) (err error) {
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = io.Copy(f, tr); err != nil {
|
||||
if _, err = io.Copy(f, r); err != nil {
|
||||
_ = f.Close()
|
||||
return
|
||||
} else if err = f.Close(); err != nil {
|
||||
|
||||
+61
-19
@@ -80,8 +80,8 @@ func TestTar(t *testing.T) {
|
||||
"checksum/" + wantEncode + "/work": {Mode: fs.ModeDir | 0500},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/T68YqeEW5moiwVe4J0wPvjERgyMUd4k_cKKxTcQx6AXqZaQAuuryu-Iv-qxDV6-T": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)},
|
||||
"identifier/WbWIIUGl6bqDBsjU-UB69JqOWLjG5waiUJBUBGeeRp3V7FBbwaBe3sG1qm7DlPSk": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)},
|
||||
"identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)},
|
||||
"identifier/rg7F1D5hwv6o4xctjD5zDq4i5MD0mArTsUIWfhUbik8xC6Bsyt3mjXXOm3goojTz": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
@@ -104,8 +104,8 @@ func TestTar(t *testing.T) {
|
||||
"checksum/" + wantExpandEncode + "/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
||||
|
||||
"identifier": {Mode: fs.ModeDir | 0700},
|
||||
"identifier/WbWIIUGl6bqDBsjU-UB69JqOWLjG5waiUJBUBGeeRp3V7FBbwaBe3sG1qm7DlPSk": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)},
|
||||
"identifier/e3BECw_x_vDyTvj2P48MJVgwpgALeXQ90Ye2Y8gr6SwIPtMup6SR2LVcCTDRGpVi": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)},
|
||||
"identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)},
|
||||
"identifier/_v1blm2h-_KA-dVaawdpLas6MjHc6rbhhFS8JWwx8iJxZGUu8EBbRrhr5AaZ9PJL": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)},
|
||||
|
||||
"substitute": {Mode: fs.ModeDir | 0700},
|
||||
|
||||
@@ -156,6 +156,49 @@ func checkTarHTTP(
|
||||
"testdata": {Data: []byte(testdata), Mode: 0400},
|
||||
}))
|
||||
|
||||
wantIdent := func() pkg.ID {
|
||||
h := sha512.New384()
|
||||
|
||||
// kind uint64
|
||||
h.Write([]byte{byte(pkg.KindTar), 0, 0, 0, 0, 0, 0, 0})
|
||||
// deps_sz uint64
|
||||
h.Write([]byte{1, 0, 0, 0, 0, 0, 0, 0})
|
||||
|
||||
// kind uint64
|
||||
h.Write([]byte{byte(pkg.KindHTTPGet), 0, 0, 0, 0, 0, 0, 0})
|
||||
// ident ID
|
||||
h0 := sha512.New384()
|
||||
// kind uint64
|
||||
h0.Write([]byte{byte(pkg.KindHTTPGet), 0, 0, 0, 0, 0, 0, 0})
|
||||
// deps_sz uint64
|
||||
h0.Write([]byte{0, 0, 0, 0, 0, 0, 0, 0})
|
||||
// url string
|
||||
h0.Write([]byte{byte(pkg.IRKindString), 0, 0, 0})
|
||||
h0.Write([]byte{0x10, 0, 0, 0})
|
||||
h0.Write([]byte("file:///testdata"))
|
||||
// end(KnownChecksum)
|
||||
h0.Write([]byte{byte(pkg.IRKindEnd), 0, 0, 0})
|
||||
h0.Write([]byte{byte(pkg.IREndKnownChecksum), 0, 0, 0})
|
||||
// checksum Checksum
|
||||
h0.Write(testdataChecksum[:])
|
||||
h.Write(h0.Sum(nil))
|
||||
// compression uint32
|
||||
h.Write([]byte{byte(pkg.IRKindUint32), 0, 0, 0})
|
||||
h.Write([]byte{pkg.TarGzip, 0, 0, 0})
|
||||
// end
|
||||
h.Write([]byte{byte(pkg.IRKindEnd), 0, 0, 0})
|
||||
h.Write([]byte{0, 0, 0, 0})
|
||||
|
||||
return pkg.ID(h.Sum(nil))
|
||||
}()
|
||||
|
||||
a := pkg.NewHTTPGetTar(
|
||||
&client,
|
||||
"file:///testdata",
|
||||
testdataChecksum,
|
||||
pkg.TarGzip,
|
||||
)
|
||||
|
||||
tarDir := stubArtifact{
|
||||
kind: pkg.KindExec,
|
||||
params: []byte("directory containing a single regular file"),
|
||||
@@ -208,28 +251,27 @@ func checkTarHTTP(
|
||||
defer newDestroyArtifactFunc(&tarDirType)(t, base, c)
|
||||
|
||||
cureMany(t, c, []cureStep{
|
||||
{"file", pkg.NewTar(pkg.NewDecompress(pkg.NewHTTPGet(
|
||||
&client,
|
||||
"file:///testdata",
|
||||
testdataChecksum,
|
||||
), pkg.Gzip)), ignorePathname, want, nil},
|
||||
{"file", a, base.Append(
|
||||
"identifier",
|
||||
pkg.Encode(wantIdent),
|
||||
), want, nil},
|
||||
|
||||
{"directory", pkg.NewTar(pkg.NewDecompress(
|
||||
{"directory", pkg.NewTar(
|
||||
&tarDir,
|
||||
pkg.Gzip,
|
||||
)), ignorePathname, want, nil},
|
||||
pkg.TarGzip,
|
||||
), ignorePathname, want, nil},
|
||||
|
||||
{"multiple entries", pkg.NewTar(pkg.NewDecompress(
|
||||
{"multiple entries", pkg.NewTar(
|
||||
&tarDirMulti,
|
||||
pkg.Gzip,
|
||||
)), nil, nil, errors.New(
|
||||
pkg.TarGzip,
|
||||
), nil, nil, errors.New(
|
||||
"input directory does not contain a single regular file",
|
||||
)},
|
||||
|
||||
{"bad type", pkg.NewTar(pkg.NewDecompress(
|
||||
{"bad type", pkg.NewTar(
|
||||
&tarDirType,
|
||||
pkg.Gzip,
|
||||
)), nil, nil, errors.New(
|
||||
pkg.TarGzip,
|
||||
), nil, nil, errors.New(
|
||||
"input directory does not contain a single regular file",
|
||||
)},
|
||||
|
||||
@@ -239,6 +281,6 @@ func checkTarHTTP(
|
||||
cure: func(t *pkg.TContext) error {
|
||||
return stub.UniqueError(0xcafe)
|
||||
},
|
||||
}), nil, nil, stub.UniqueError(0xcafe)},
|
||||
}, pkg.TarGzip), nil, nil, stub.UniqueError(0xcafe)},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"hakurei.app/check"
|
||||
)
|
||||
|
||||
const (
|
||||
// Trivial denotes an error that may happen frequently under normal operation.
|
||||
Trivial = iota
|
||||
// Inconsistent denotes an error diagnosed due to inconsistent state.
|
||||
Inconsistent
|
||||
// Degraded denotes an error condition causing a degraded state.
|
||||
Degraded
|
||||
// Fatal denotes an unrecoverable error. This is generally followed by
|
||||
// notifying the user and suggesting immediate restart, or an automatic
|
||||
// restart of a nonessential daemon process.
|
||||
Fatal
|
||||
)
|
||||
|
||||
// A Reporter represents an error reporting object backed by user-facing display
|
||||
// and optional persistent storage. A Reporter can be used simultaneously from
|
||||
// multiple goroutines.
|
||||
type Reporter struct {
|
||||
// Backing logger, the zero value implies the return value of [log.Default].
|
||||
log atomic.Pointer[log.Logger]
|
||||
// Backing persistent storage.
|
||||
pathname atomic.Pointer[check.Absolute]
|
||||
// Reporting behaviour.
|
||||
flag atomic.Uint64
|
||||
|
||||
// Caller-supplied reporting functions.
|
||||
notify []func(e Error)
|
||||
// Synchronises access to notify.
|
||||
notifyMu sync.RWMutex
|
||||
|
||||
// Synchronises access to persistent storage.
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// SetOutput sets the backing [log.Logger].
|
||||
func (r *Reporter) SetOutput(l *log.Logger) { r.log.Store(l) }
|
||||
|
||||
// SetPathname sets the pathname to persistent storage.
|
||||
func (r *Reporter) SetPathname(a *check.Absolute) { r.pathname.Store(a) }
|
||||
|
||||
const (
|
||||
// DStrict causes all dispatches to end in a call to panic.
|
||||
DStrict = 1 << iota
|
||||
// DNoRecover disallows recovering from error write to persistent storage.
|
||||
DNoRecover
|
||||
// DBypassEarly allows persistent storage to be skipped before it is ready.
|
||||
DBypassEarly
|
||||
)
|
||||
|
||||
// SetFlags sets flags for r.
|
||||
func (r *Reporter) SetFlags(flag uint64) { r.flag.Store(flag) }
|
||||
|
||||
// An Error represents an error reported via [Reporter.Dispatch].
|
||||
type Error struct {
|
||||
// Nature of error.
|
||||
Severity int
|
||||
// User-facing reporting message.
|
||||
Message string
|
||||
// Underlying error.
|
||||
Err error
|
||||
}
|
||||
|
||||
// RepresentableError is implemented by errors friendly to JSON serialisation.
|
||||
type RepresentableError interface {
|
||||
error
|
||||
Representable()
|
||||
}
|
||||
|
||||
// MarshalJSON returns an incomplete JSON representation of e.
|
||||
func (e Error) MarshalJSON() (data []byte, err error) {
|
||||
v := struct {
|
||||
Severity any `json:"kind"`
|
||||
Message string `json:"message"`
|
||||
Err json.RawMessage `json:"error"`
|
||||
}{Message: e.Message}
|
||||
|
||||
switch e.Severity {
|
||||
case Trivial:
|
||||
v.Severity = "trivial"
|
||||
case Inconsistent:
|
||||
v.Severity = "inconsistent"
|
||||
case Degraded:
|
||||
v.Severity = "degradation"
|
||||
case Fatal:
|
||||
v.Severity = "fatal"
|
||||
default:
|
||||
v.Severity = e.Severity
|
||||
}
|
||||
|
||||
if re, ok := errors.AsType[RepresentableError](e.Err); ok {
|
||||
v.Err, err = json.Marshal(re)
|
||||
} else {
|
||||
v.Err, err = json.Marshal(e.Err.Error())
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return json.Marshal(&v)
|
||||
}
|
||||
|
||||
// Error is generally only called during a [Reporter.Dispatch] call ending in
|
||||
// a panic or an otherwise unrecoverable condition.
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("%s: %v", e.Message, e.Err)
|
||||
}
|
||||
|
||||
// Notify arranges for f to be called for all future dispatched errors.
|
||||
func (r *Reporter) Notify(f func(Error)) {
|
||||
r.notifyMu.Lock()
|
||||
r.notify = append(r.notify, f)
|
||||
r.notifyMu.Unlock()
|
||||
}
|
||||
|
||||
// getLog returns the address of the underlying [log.Logger], or the return
|
||||
// value of [log.Default].
|
||||
func (r *Reporter) getLog() *log.Logger {
|
||||
l := r.log.Load()
|
||||
if l == nil {
|
||||
l = log.Default()
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// dispatch implements Dispatch but returns the reporting error.
|
||||
func (r *Reporter) dispatch(severity int, message string, e error) (err error) {
|
||||
p := Error{severity, message, e}
|
||||
r.getLog().Println(message+":", e)
|
||||
|
||||
flag := r.flag.Load()
|
||||
defer func() {
|
||||
if flag&DNoRecover != 0 && err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
if a := r.pathname.Load(); a != nil {
|
||||
var w *os.File
|
||||
r.mu.Lock()
|
||||
w, err = os.OpenFile(
|
||||
a.Append(strconv.FormatUint(uint64(time.Now().UnixNano()), 10)).String(),
|
||||
os.O_CREATE|os.O_EXCL|os.O_WRONLY,
|
||||
0400,
|
||||
)
|
||||
if err != nil {
|
||||
r.mu.Unlock()
|
||||
return
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(p)
|
||||
if _err := w.Close(); err == nil {
|
||||
err = _err
|
||||
}
|
||||
r.mu.Unlock()
|
||||
} else if flag&DBypassEarly == 0 && severity != Trivial {
|
||||
panic(p)
|
||||
}
|
||||
|
||||
if flag&DStrict != 0 && severity != Trivial {
|
||||
panic(p)
|
||||
}
|
||||
|
||||
r.notifyMu.RLock()
|
||||
defer r.notifyMu.RUnlock()
|
||||
for _, f := range r.notify {
|
||||
f(p)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Dispatch reports an error and saves it to backing storage if available.
|
||||
func (r *Reporter) Dispatch(severity int, message string, e error) {
|
||||
if err := r.dispatch(severity, message, e); err != nil {
|
||||
r.getLog().Println(err)
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
package report_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"slices"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"hakurei.app/check"
|
||||
"hakurei.app/internal/kobject"
|
||||
"hakurei.app/internal/report"
|
||||
"hakurei.app/internal/stub"
|
||||
"hakurei.app/internal/uevent"
|
||||
)
|
||||
|
||||
type representableUE struct{ stub.UniqueError }
|
||||
|
||||
func (representableUE) Representable() {}
|
||||
|
||||
func TestDispatch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
flag uint64
|
||||
errs []report.Error
|
||||
|
||||
persist bool
|
||||
wantFiles []string
|
||||
wantLog string
|
||||
wantPanic error
|
||||
}{
|
||||
{"default", 0, []report.Error{
|
||||
{
|
||||
Severity: report.Fatal,
|
||||
Message: "rejecting coldboot loop",
|
||||
Err: stub.UniqueError(0xcafe),
|
||||
},
|
||||
{
|
||||
Severity: report.Inconsistent,
|
||||
Message: "processed inconsistent uevent",
|
||||
Err: (&kobject.Event{
|
||||
Action: uevent.KOBJ_ADD,
|
||||
DevPath: "\x00",
|
||||
Env: map[string]string{"V": "\xfd"},
|
||||
Sequence: 2,
|
||||
}).NewError(kobject.EDuplicateAdd, &kobject.Object{
|
||||
State: kobject.StateNew,
|
||||
DevPath: "\x00",
|
||||
Env: map[string]string{"V": "\xff"},
|
||||
}),
|
||||
},
|
||||
}, true, []string{`{"kind":"fatal","message":"rejecting coldboot loop","error":"unique error 51966 injected by the test suite"}
|
||||
`, `{"kind":"inconsistent","message":"processed inconsistent uevent","error":{"fault":1,"event":{"action":"add","devpath":"\u0000","env":{"V":"\ufffd"},"seqnum":2,"subsystem":""},"object":{"state":1,"devpath":"\u0000","subsystem":"","env":{"V":"\ufffd"}}}}
|
||||
`}, `dispatch: rejecting coldboot loop: unique error 51966 injected by the test suite
|
||||
dispatch: processed inconsistent uevent: duplicate add event on devpath "\x00"
|
||||
`, nil},
|
||||
|
||||
{"strict", report.DStrict, []report.Error{
|
||||
{
|
||||
Severity: report.Degraded,
|
||||
Message: "rejecting coldboot loop",
|
||||
Err: stub.UniqueError(0xcafe),
|
||||
},
|
||||
}, true, []string{
|
||||
`{"kind":"degradation","message":"rejecting coldboot loop","error":"unique error 51966 injected by the test suite"}
|
||||
`,
|
||||
}, "dispatch: rejecting coldboot loop: unique error 51966 injected by the test suite\n", report.Error{
|
||||
Severity: report.Degraded,
|
||||
Message: "rejecting coldboot loop",
|
||||
Err: stub.UniqueError(0xcafe),
|
||||
}},
|
||||
|
||||
{"strict no recover", report.DStrict | report.DNoRecover, []report.Error{
|
||||
{
|
||||
Severity: report.Inconsistent,
|
||||
Message: "rejecting coldboot loop",
|
||||
Err: stub.UniqueError(0xcafe),
|
||||
},
|
||||
}, true, []string{
|
||||
`{"kind":"inconsistent","message":"rejecting coldboot loop","error":"unique error 51966 injected by the test suite"}
|
||||
`,
|
||||
}, "dispatch: rejecting coldboot loop: unique error 51966 injected by the test suite\n", report.Error{
|
||||
Severity: report.Inconsistent,
|
||||
Message: "rejecting coldboot loop",
|
||||
Err: stub.UniqueError(0xcafe),
|
||||
}},
|
||||
|
||||
{"no recover", report.DNoRecover, []report.Error{
|
||||
{
|
||||
Severity: 0xbeef,
|
||||
Message: "rejecting coldboot loop",
|
||||
Err: representableUE{stub.UniqueError(0xcafe)},
|
||||
},
|
||||
}, true, []string{
|
||||
`{"kind":48879,"message":"rejecting coldboot loop","error":{"UniqueError":51966}}
|
||||
`,
|
||||
}, "dispatch: rejecting coldboot loop: unique error 51966 injected by the test suite\n", nil},
|
||||
|
||||
{"early", 0, []report.Error{
|
||||
{
|
||||
Severity: report.Fatal,
|
||||
Message: "rejecting coldboot loop",
|
||||
Err: stub.UniqueError(0xcafe),
|
||||
},
|
||||
}, false, nil, "dispatch: rejecting coldboot loop: unique error 51966 injected by the test suite\n", report.Error{
|
||||
Severity: report.Fatal,
|
||||
Message: "rejecting coldboot loop",
|
||||
Err: stub.UniqueError(0xcafe),
|
||||
}},
|
||||
|
||||
{"bypass early", report.DBypassEarly, []report.Error{
|
||||
{
|
||||
Severity: report.Fatal,
|
||||
Message: "rejecting coldboot loop",
|
||||
Err: stub.UniqueError(0xcafe),
|
||||
},
|
||||
}, false, nil, "dispatch: rejecting coldboot loop: unique error 51966 injected by the test suite\n", nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var r report.Reporter
|
||||
r.SetFlags(tc.flag)
|
||||
var buf bytes.Buffer
|
||||
l := log.New(&buf, "dispatch: ", 0)
|
||||
r.SetOutput(l)
|
||||
|
||||
a := check.MustAbs(t.TempDir())
|
||||
if tc.persist {
|
||||
r.SetPathname(a)
|
||||
}
|
||||
|
||||
var got []report.Error
|
||||
r.Notify(func(p report.Error) { got = append(got, p) })
|
||||
|
||||
var gotPanic any
|
||||
func() {
|
||||
defer func() { gotPanic = recover() }()
|
||||
for _, p := range tc.errs {
|
||||
r.Dispatch(p.Severity, p.Message, p.Err)
|
||||
}
|
||||
}()
|
||||
|
||||
if gotPanic == nil && !reflect.DeepEqual(got, tc.errs) {
|
||||
t.Errorf("Dispatch: %#v, want %#v", got, tc.errs)
|
||||
}
|
||||
if !reflect.DeepEqual(gotPanic, tc.wantPanic) {
|
||||
t.Errorf("Dispatch: panic = %v, want %v", gotPanic, tc.wantPanic)
|
||||
}
|
||||
if gotLog := buf.String(); gotLog != tc.wantLog {
|
||||
t.Errorf("Dispatch: log =\n%s\nwant\n%s", gotLog, tc.wantLog)
|
||||
}
|
||||
|
||||
if tc.persist {
|
||||
var names []string
|
||||
if dents, err := os.ReadDir(a.String()); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
names = make([]string, len(dents))
|
||||
for i, dent := range dents {
|
||||
names[i] = dent.Name()
|
||||
}
|
||||
slices.Sort(names)
|
||||
}
|
||||
|
||||
gotFiles := make([]string, len(names))
|
||||
for i, name := range names {
|
||||
if p, err := os.ReadFile(a.Append(name).String()); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
gotFiles[i] = unsafe.String(unsafe.SliceData(p), len(p))
|
||||
}
|
||||
}
|
||||
|
||||
if !slices.Equal(gotFiles, tc.wantFiles) {
|
||||
t.Errorf("Dispatch: persist = %s, want %s", gotFiles, tc.wantFiles)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadPersist(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var r report.Reporter
|
||||
r.SetFlags(report.DNoRecover)
|
||||
r.SetPathname(check.MustAbs("/proc/nonexistent"))
|
||||
|
||||
var pathError *os.PathError
|
||||
func() {
|
||||
defer func() {
|
||||
if !errors.As(recover().(error), &pathError) {
|
||||
t.Fatal("invalid panic kind")
|
||||
}
|
||||
}()
|
||||
r.Dispatch(report.Fatal, "\x00", stub.UniqueError(0xbad))
|
||||
}()
|
||||
if !errors.Is(pathError.Err, os.ErrNotExist) {
|
||||
t.Fatalf("Dispatch: panic = %v", pathError)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
l := log.New(&buf, "persist: ", 0)
|
||||
r.SetOutput(l)
|
||||
r.SetFlags(0)
|
||||
r.Dispatch(report.Fatal, "\x00", stub.UniqueError(0xbad))
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newAttr() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.5.2"
|
||||
checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l"
|
||||
)
|
||||
return t.NewPackage("attr", version, newTar(
|
||||
"https://download.savannah.nongnu.org/releases/attr/"+
|
||||
"attr-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"libgen-basename", `From 8a80d895dfd779373363c3a4b62ecce5a549efb2 Mon Sep 17 00:00:00 2001
|
||||
From: "Haelwenn (lanodan) Monnier" <contact@hacktivis.me>
|
||||
Date: Sat, 30 Mar 2024 10:17:10 +0100
|
||||
Subject: tools/attr.c: Add missing libgen.h include for basename(3)
|
||||
|
||||
Fixes compilation issue with musl and modern C99 compilers.
|
||||
|
||||
See: https://bugs.gentoo.org/926294
|
||||
---
|
||||
tools/attr.c | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/tools/attr.c b/tools/attr.c
|
||||
index f12e4af..6a3c1e9 100644
|
||||
--- a/tools/attr.c
|
||||
+++ b/tools/attr.c
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <locale.h>
|
||||
+#include <libgen.h>
|
||||
|
||||
#include <attr/attributes.h>
|
||||
|
||||
--
|
||||
cgit v1.1`},
|
||||
|
||||
{"musl-errno", `diff --git a/test/attr.test b/test/attr.test
|
||||
index 6ce2f9b..e9bde92 100644
|
||||
--- a/test/attr.test
|
||||
+++ b/test/attr.test
|
||||
@@ -11,7 +11,7 @@ Try various valid and invalid names
|
||||
|
||||
$ touch f
|
||||
$ setfattr -n user -v value f
|
||||
- > setfattr: f: Operation not supported
|
||||
+ > setfattr: f: Not supported
|
||||
|
||||
$ setfattr -n user. -v value f
|
||||
> setfattr: f: Invalid argument
|
||||
`},
|
||||
},
|
||||
|
||||
ScriptEarly: `
|
||||
ln -s ../../system/bin/perl /usr/bin
|
||||
`,
|
||||
}, (*MakeHelper)(nil),
|
||||
Perl,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Attr] = Metadata{
|
||||
f: Toolchain.newAttr,
|
||||
|
||||
Name: "attr",
|
||||
Description: "Commands for Manipulating Filesystem Extended Attributes",
|
||||
Website: "https://savannah.nongnu.org/projects/attr/",
|
||||
|
||||
ID: 137,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newACL() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.3.2"
|
||||
checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P"
|
||||
)
|
||||
return t.NewPackage("acl", version, newTar(
|
||||
"https://download.savannah.nongnu.org/releases/acl/"+
|
||||
"acl-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), nil, &MakeHelper{
|
||||
// makes assumptions about uid_map/gid_map
|
||||
SkipCheck: true,
|
||||
},
|
||||
Attr,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[ACL] = Metadata{
|
||||
f: Toolchain.newACL,
|
||||
|
||||
Name: "acl",
|
||||
Description: "Commands for Manipulating POSIX Access Control Lists",
|
||||
Website: "https://savannah.nongnu.org/projects/acl/",
|
||||
|
||||
Dependencies: P{
|
||||
Attr,
|
||||
},
|
||||
|
||||
ID: 16,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
// PArtifact is a lazily-initialised [pkg.Artifact] preset.
|
||||
type PArtifact int
|
||||
|
||||
const (
|
||||
LLVM PArtifact = iota
|
||||
|
||||
// EarlyInit is the Rosa OS init program.
|
||||
EarlyInit
|
||||
// ImageSystem is the Rosa OS /system image.
|
||||
ImageSystem
|
||||
// ImageInitramfs is the Rosa OS initramfs archive.
|
||||
ImageInitramfs
|
||||
|
||||
// Kernel is the generic Rosa OS Linux kernel.
|
||||
Kernel
|
||||
// KernelHeaders is an installation of kernel headers for [Kernel].
|
||||
KernelHeaders
|
||||
// KernelSource is a writable kernel source tree installed to [AbsUsrSrc].
|
||||
KernelSource
|
||||
// Firmware is firmware blobs for use with the Linux kernel.
|
||||
Firmware
|
||||
|
||||
ACL
|
||||
ArgpStandalone
|
||||
Attr
|
||||
Autoconf
|
||||
Automake
|
||||
BC
|
||||
Bash
|
||||
Binutils
|
||||
Bison
|
||||
Bzip2
|
||||
Cairo
|
||||
CMake
|
||||
Connman
|
||||
Coreutils
|
||||
Curl
|
||||
DBus
|
||||
DTC
|
||||
Diffutils
|
||||
Elfutils
|
||||
Fakeroot
|
||||
Findutils
|
||||
Flex
|
||||
FontConfig
|
||||
FontUtil
|
||||
Freetype
|
||||
Fribidi
|
||||
Fuse
|
||||
Gconf
|
||||
GMP
|
||||
GLib
|
||||
Gawk
|
||||
GenInitCPIO
|
||||
Gettext
|
||||
Git
|
||||
Glslang
|
||||
GnuTLS
|
||||
Go
|
||||
GObjectIntrospection
|
||||
Gperf
|
||||
Grep
|
||||
Gzip
|
||||
Hakurei
|
||||
HakureiDist
|
||||
HarfBuzz
|
||||
Hwdata
|
||||
IPTables
|
||||
JDK
|
||||
Kmod
|
||||
LIT
|
||||
LibX11
|
||||
LibXau
|
||||
LibXdmcp
|
||||
LibXext
|
||||
LibXfixes
|
||||
LibXfont2
|
||||
LibXft
|
||||
LibXi
|
||||
LibXrandr
|
||||
LibXrender
|
||||
LibXtst
|
||||
LibXxf86vm
|
||||
Libarchive
|
||||
Libbsd
|
||||
Libcap
|
||||
Libconfig
|
||||
Libdatrie
|
||||
LibdisplayInfo
|
||||
Libdrm
|
||||
Libepoxy
|
||||
Libev
|
||||
Libexpat
|
||||
Libffi
|
||||
Libfontenc
|
||||
Libgd
|
||||
Libglvnd
|
||||
Libiconv
|
||||
Libjpeg
|
||||
Libmd
|
||||
Libmnl
|
||||
Libnftnl
|
||||
Libpciaccess
|
||||
Libpng
|
||||
Libpsl
|
||||
Libseccomp
|
||||
Libtasn1
|
||||
Libthai
|
||||
Libtirpc
|
||||
Libtool
|
||||
Libucontext
|
||||
Libunistring
|
||||
Libva
|
||||
LibxcbRenderUtil
|
||||
LibxcbUtil
|
||||
LibxcbUtilImage
|
||||
LibxcbUtilKeysyms
|
||||
LibxcbUtilWM
|
||||
Libxcvt
|
||||
Libxkbfile
|
||||
Libxml2
|
||||
Libxshmfence
|
||||
Libxslt
|
||||
Libxtrans
|
||||
LMSensors
|
||||
M4
|
||||
MPC
|
||||
MPFR
|
||||
Make
|
||||
Mesa
|
||||
Meson
|
||||
Mksh
|
||||
MuslFts
|
||||
MuslObstack
|
||||
NSS
|
||||
NSSCACert
|
||||
Ncurses
|
||||
Nettle
|
||||
Ninja
|
||||
OpenSSL
|
||||
P11Kit
|
||||
PCRE2
|
||||
Pango
|
||||
Parallel
|
||||
Patch
|
||||
Perl
|
||||
PerlLocaleGettext
|
||||
PerlMIMECharset
|
||||
PerlModuleBuild
|
||||
PerlPodParser
|
||||
PerlSGMLS
|
||||
PerlTermReadKey
|
||||
PerlTestCmd
|
||||
PerlTextCharWidth
|
||||
PerlTextWrapI18N
|
||||
PerlUnicodeLineBreak
|
||||
PerlYAMLTiny
|
||||
Pixman
|
||||
PkgConfig
|
||||
Procps
|
||||
Python
|
||||
PythonFlitCore
|
||||
PythonHatchling
|
||||
PythonIniConfig
|
||||
PythonMako
|
||||
PythonMarkupSafe
|
||||
PythonPackaging
|
||||
PythonPathspec
|
||||
PythonPluggy
|
||||
PythonPyTest
|
||||
PythonPyYAML
|
||||
PythonPycparser
|
||||
PythonPygments
|
||||
PythonSetuptools
|
||||
PythonSetuptoolsSCM
|
||||
PythonTroveClassifiers
|
||||
PythonVCSVersioning
|
||||
PythonWheel
|
||||
QEMU
|
||||
Rdfind
|
||||
Readline
|
||||
Rsync
|
||||
Sed
|
||||
SharedMimeInfo
|
||||
SPIRVHeaders
|
||||
SPIRVLLVMTranslator
|
||||
SPIRVTools
|
||||
SquashfsTools
|
||||
Strace
|
||||
TamaGo
|
||||
Tar
|
||||
Texinfo
|
||||
Toybox
|
||||
toyboxEarly
|
||||
Unzip
|
||||
UtilLinux
|
||||
VIM
|
||||
Wayland
|
||||
WaylandProtocols
|
||||
XCB
|
||||
XCBProto
|
||||
XDGDBusProxy
|
||||
XZ
|
||||
Xkbcomp
|
||||
XkeyboardConfig
|
||||
XorgProto
|
||||
Xserver
|
||||
Zip
|
||||
Zlib
|
||||
Zstd
|
||||
|
||||
// PresetUnexportedStart is the first unexported preset.
|
||||
PresetUnexportedStart
|
||||
|
||||
stage0Dist = iota - 1
|
||||
llvmSource
|
||||
// earlyCompilerRT is an early, standalone compiler-rt installation for the
|
||||
// standalone runtimes build.
|
||||
//
|
||||
// earlyCompilerRT must only be loaded by [LLVM].
|
||||
earlyCompilerRT
|
||||
// earlyRuntimes is an early, standalone installation of LLVM runtimes to
|
||||
// work around the cmake build system leaking the system LLVM installation
|
||||
// when invoking the newly built toolchain.
|
||||
//
|
||||
// earlyRuntimes must only be loaded by [LLVM].
|
||||
earlyRuntimes
|
||||
|
||||
buildcatrust
|
||||
utilMacros
|
||||
|
||||
// Musl is a standalone libc that does not depend on the toolchain.
|
||||
Musl
|
||||
// muslHeaders is a system installation of [Musl] headers.
|
||||
muslHeaders
|
||||
|
||||
// gcc is a hacked-to-pieces GCC toolchain meant for use in intermediate
|
||||
// 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
|
||||
|
||||
// PresetEnd is the total number of presets and does not denote a preset.
|
||||
PresetEnd
|
||||
)
|
||||
|
||||
// P represents multiple [PArtifact] and is stable through JSON.
|
||||
type P []PArtifact
|
||||
|
||||
// MarshalJSON represents [PArtifact] by their [Metadata.Name].
|
||||
func (s P) MarshalJSON() ([]byte, error) {
|
||||
names := make([]string, len(s))
|
||||
for i, p := range s {
|
||||
names[i] = GetMetadata(p).Name
|
||||
}
|
||||
return json.Marshal(names)
|
||||
}
|
||||
|
||||
// UnmarshalJSON resolves the value created by MarshalJSON back to [P].
|
||||
func (s *P) UnmarshalJSON(data []byte) error {
|
||||
var names []string
|
||||
if err := json.Unmarshal(data, &names); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*s = make(P, len(names))
|
||||
for i, name := range names {
|
||||
if p, ok := ResolveName(name); !ok {
|
||||
return fmt.Errorf("unknown artifact %q", name)
|
||||
} else {
|
||||
(*s)[i] = p
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Metadata is stage-agnostic information of a [PArtifact] not directly
|
||||
// representable in the resulting [pkg.Artifact].
|
||||
type Metadata struct {
|
||||
f func(t Toolchain) (a pkg.Artifact, version string)
|
||||
|
||||
// Unique package name.
|
||||
Name string `json:"name"`
|
||||
// Short user-facing description.
|
||||
Description string `json:"description"`
|
||||
// Project home page.
|
||||
Website string `json:"website,omitempty"`
|
||||
|
||||
// Runtime dependencies.
|
||||
Dependencies P `json:"dependencies"`
|
||||
|
||||
// Project identifier on [Anitya].
|
||||
//
|
||||
// [Anitya]: https://release-monitoring.org/
|
||||
ID int `json:"-"`
|
||||
|
||||
// Optional custom version checking behaviour.
|
||||
latest func(v *Versions) string
|
||||
}
|
||||
|
||||
// GetLatest returns the latest version described by v.
|
||||
func (meta *Metadata) GetLatest(v *Versions) string {
|
||||
if meta.latest != nil {
|
||||
return meta.latest(v)
|
||||
}
|
||||
return v.Latest
|
||||
}
|
||||
|
||||
// Unversioned denotes an unversioned [PArtifact].
|
||||
const Unversioned = "\x00"
|
||||
|
||||
// UnpopulatedIDError is returned by [Metadata.GetLatest] for an instance of
|
||||
// [Metadata] where ID is not populated.
|
||||
type UnpopulatedIDError struct{}
|
||||
|
||||
func (UnpopulatedIDError) Unwrap() error { return errors.ErrUnsupported }
|
||||
func (UnpopulatedIDError) Error() string { return "Anitya ID is not populated" }
|
||||
|
||||
// Versions are package versions returned by Anitya.
|
||||
type Versions struct {
|
||||
// The latest version for the project, as determined by the version sorting algorithm.
|
||||
Latest string `json:"latest_version"`
|
||||
// List of all versions that aren’t flagged as pre-release.
|
||||
Stable []string `json:"stable_versions"`
|
||||
// List of all versions stored, sorted from newest to oldest.
|
||||
All []string `json:"versions"`
|
||||
}
|
||||
|
||||
// getStable returns the first Stable version, or Latest if that is unavailable.
|
||||
func (v *Versions) getStable() string {
|
||||
if len(v.Stable) == 0 {
|
||||
return v.Latest
|
||||
}
|
||||
return v.Stable[0]
|
||||
}
|
||||
|
||||
// GetVersions returns versions fetched from Anitya.
|
||||
func (meta *Metadata) GetVersions(ctx context.Context) (*Versions, error) {
|
||||
if meta.ID == 0 {
|
||||
return nil, UnpopulatedIDError{}
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
if req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodGet,
|
||||
"https://release-monitoring.org/api/v2/versions/?project_id="+
|
||||
strconv.Itoa(meta.ID),
|
||||
nil,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
req.Header.Set("User-Agent", "Rosa/1.1")
|
||||
if resp, err = http.DefaultClient.Do(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var v Versions
|
||||
err := json.NewDecoder(resp.Body).Decode(&v)
|
||||
return &v, errors.Join(err, resp.Body.Close())
|
||||
}
|
||||
|
||||
var (
|
||||
// artifactsM is an array of [PArtifact] metadata.
|
||||
artifactsM [PresetEnd]Metadata
|
||||
|
||||
// artifacts stores the result of Metadata.f.
|
||||
artifacts [_toolchainEnd][len(artifactsM)]struct {
|
||||
a pkg.Artifact
|
||||
v string
|
||||
}
|
||||
// artifactsOnce is for lazy initialisation of artifacts.
|
||||
artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once
|
||||
|
||||
// arch is the target architecture.
|
||||
arch = runtime.GOARCH
|
||||
|
||||
// presetOpts globally modifies behaviour of presets.
|
||||
presetOpts int
|
||||
)
|
||||
|
||||
const (
|
||||
// OptSkipCheck skips running all test suites.
|
||||
OptSkipCheck = 1 << iota
|
||||
// OptLLVMNoLTO disables LTO in all [LLVM] stages.
|
||||
OptLLVMNoLTO
|
||||
)
|
||||
|
||||
// Arch returns the target architecture.
|
||||
func Arch() string { return arch }
|
||||
|
||||
// Flags returns the current preset flags
|
||||
func Flags() int { return presetOpts }
|
||||
|
||||
// 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(targetArch string, flags int) {
|
||||
if targetArch == "" {
|
||||
targetArch = runtime.GOARCH
|
||||
}
|
||||
|
||||
arch = targetArch
|
||||
presetOpts = flags
|
||||
zero(&artifacts)
|
||||
zero(&artifactsOnce)
|
||||
}
|
||||
|
||||
// GetMetadata returns [Metadata] of a [PArtifact].
|
||||
func GetMetadata(p PArtifact) *Metadata { return &artifactsM[p] }
|
||||
|
||||
// construct constructs a [pkg.Artifact] corresponding to a [PArtifact] once.
|
||||
func (t Toolchain) construct(p PArtifact) {
|
||||
artifactsOnce[t][p].Do(func() {
|
||||
artifacts[t][p].a, artifacts[t][p].v = artifactsM[p].f(t)
|
||||
})
|
||||
}
|
||||
|
||||
// Load returns the resulting [pkg.Artifact] of [PArtifact].
|
||||
func (t Toolchain) Load(p PArtifact) pkg.Artifact {
|
||||
t.construct(p)
|
||||
return artifacts[t][p].a
|
||||
}
|
||||
|
||||
// Version returns the version string of [PArtifact].
|
||||
func (t Toolchain) Version(p PArtifact) string {
|
||||
t.construct(p)
|
||||
return artifacts[t][p].v
|
||||
}
|
||||
|
||||
// ResolveName returns a [PArtifact] by name.
|
||||
func ResolveName(name string) (p PArtifact, ok bool) {
|
||||
for i := range PresetUnexportedStart {
|
||||
if name == artifactsM[i].Name {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package rosa_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"hakurei.app/internal/rosa"
|
||||
)
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i := range rosa.PresetEnd {
|
||||
p := rosa.PArtifact(i)
|
||||
t.Run(rosa.GetMetadata(p).Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
rosa.Std.Load(p)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAll(b *testing.B) {
|
||||
arch, flags := rosa.Arch(), rosa.Flags()
|
||||
b.Cleanup(func() { rosa.DropCaches(arch, flags) })
|
||||
|
||||
for b.Loop() {
|
||||
for i := range rosa.PresetEnd {
|
||||
rosa.Std.Load(rosa.PArtifact(i))
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
rosa.DropCaches("", 0)
|
||||
b.StartTimer()
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i := range rosa.PresetUnexportedStart {
|
||||
p := i
|
||||
name := rosa.GetMetadata(p).Name
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got, ok := rosa.ResolveName(name); !ok {
|
||||
t.Fatal("ResolveName: ok = false")
|
||||
} else if got != p {
|
||||
t.Fatalf("ResolveName: %d, want %d", got, p)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestResolveNameUnexported(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for i := rosa.PresetUnexportedStart; i < rosa.PresetEnd; i++ {
|
||||
p := i
|
||||
name := rosa.GetMetadata(p).Name
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if got, ok := rosa.ResolveName(name); ok {
|
||||
t.Fatalf("ResolveName: resolved unexported preset %d", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnique(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
names := make(map[string]struct{})
|
||||
for i := range rosa.PresetEnd {
|
||||
name := rosa.GetMetadata(rosa.PArtifact(i)).Name
|
||||
if _, ok := names[name]; ok {
|
||||
t.Fatalf("name %s is not unique", name)
|
||||
}
|
||||
names[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newArgpStandalone() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.3"
|
||||
checksum = "vtW0VyO2pJ-hPyYmDI2zwSLS8QL0sPAUKC1t3zNYbwN2TmsaE-fADhaVtNd3eNFl"
|
||||
)
|
||||
return t.NewPackage("argp-standalone", version, newTar(
|
||||
"http://www.lysator.liu.se/~nisse/misc/"+
|
||||
"argp-standalone-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Env: []string{
|
||||
"CC=cc -std=gnu89 -fPIC",
|
||||
},
|
||||
}, &MakeHelper{
|
||||
Install: `
|
||||
install -D -m644 /usr/src/argp-standalone/argp.h /work/system/include/argp.h
|
||||
install -D -m755 libargp.a /work/system/lib/libargp.a
|
||||
`,
|
||||
},
|
||||
Diffutils,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[ArgpStandalone] = Metadata{
|
||||
f: Toolchain.newArgpStandalone,
|
||||
|
||||
Name: "argp-standalone",
|
||||
Description: "hierarchical argument parsing library broken out from glibc",
|
||||
Website: "http://www.lysator.liu.se/~nisse/misc/",
|
||||
}
|
||||
}
|
||||
@@ -1,348 +0,0 @@
|
||||
// Package azalea implements a proof-of-concept, domain-specific language for
|
||||
// Rosa OS software packaging.
|
||||
package azalea
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
"text/scanner"
|
||||
)
|
||||
|
||||
// idents are runes accepted in an identifier.
|
||||
var idents = [...]bool{
|
||||
'0': true, '1': true, '2': true, '3': true, '4': true, '5': true, '6': true,
|
||||
'7': true, '8': true, '9': true,
|
||||
|
||||
'A': true, 'B': true, 'C': true, 'D': true, 'E': true, 'F': true, 'G': true,
|
||||
'H': true, 'I': true, 'J': true, 'K': true, 'L': true, 'M': true, 'N': true,
|
||||
'O': true, 'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true,
|
||||
'V': true, 'W': true, 'X': true, 'Y': true, 'Z': true,
|
||||
|
||||
'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true,
|
||||
'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true,
|
||||
'o': true, 'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true,
|
||||
'v': true, 'w': true, 'x': true, 'y': true, 'z': true,
|
||||
|
||||
'-': true, '_': true,
|
||||
}
|
||||
|
||||
// TokenError describes an unexpected token.
|
||||
type TokenError [2]rune
|
||||
|
||||
func (e TokenError) Error() string {
|
||||
return "expected " + scanner.TokenString(e[0]) +
|
||||
", found " + scanner.TokenString(e[1])
|
||||
}
|
||||
|
||||
// ExprError is an unexpected token encountered while parsing an expression.
|
||||
type ExprError rune
|
||||
|
||||
func (e ExprError) Error() string {
|
||||
return "unexpected token " + scanner.TokenString(rune(e))
|
||||
}
|
||||
|
||||
// must1 returns v, or panics if err is not nil.
|
||||
func must1[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// parser retains the current token.
|
||||
type parser struct {
|
||||
s scanner.Scanner
|
||||
tok rune
|
||||
}
|
||||
|
||||
// scan advances the underlying scanner to the next token, storing its result.
|
||||
func (p *parser) scan() rune { p.tok = p.s.Scan(); return p.tok }
|
||||
|
||||
// expects panics with [TokenError] for an unexpected tok.
|
||||
func (p *parser) expects(expects rune) {
|
||||
if p.tok != expects {
|
||||
panic(TokenError{expects, p.tok})
|
||||
}
|
||||
}
|
||||
|
||||
// scanAs advances the scanner for an expected token.
|
||||
func (p *parser) scanAs(expects rune) { p.scan(); p.expects(expects) }
|
||||
|
||||
// An Int is the value represented by an integer literal.
|
||||
type Int int64
|
||||
|
||||
func (v Int) GoString() string {
|
||||
return "azalea.Int(" + strconv.FormatInt(int64(v), 10) + ")"
|
||||
}
|
||||
|
||||
// parseInt parses the current token as a base 10 representation of a 64-bit
|
||||
// signed integer.
|
||||
func (p *parser) parseInt() Int {
|
||||
v, err := strconv.ParseInt(p.s.TokenText(), 10, 64)
|
||||
return must1(Int(v), err)
|
||||
}
|
||||
|
||||
// A String holds the unquoted content of a string literal.
|
||||
type String string
|
||||
|
||||
func (v String) GoString() string {
|
||||
return "azalea.String(" + strconv.Quote(string(v)) + ")"
|
||||
}
|
||||
|
||||
// parseString parses the current token as a string.
|
||||
func (p *parser) parseString() String {
|
||||
s, err := strconv.Unquote(p.s.TokenText())
|
||||
return must1(String(s), err)
|
||||
}
|
||||
|
||||
// An Ident holds the name of an identifier.
|
||||
type Ident string
|
||||
|
||||
func (v Ident) GoString() string {
|
||||
return "azalea.Ident(" + strconv.Quote(string(v)) + ")"
|
||||
}
|
||||
|
||||
// A Val are statements joined by the '+' operator. Only the [String] type
|
||||
// supports concatenation.
|
||||
type Val []any
|
||||
|
||||
// parseVal parses until the end of the [Val].
|
||||
func (p *parser) parseVal() (v Val) {
|
||||
v = append(v, p.parseExpr())
|
||||
for p.tok == '+' {
|
||||
p.scan()
|
||||
v = append(v, p.parseExpr())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// An Array holds statements in an array.
|
||||
type Array []Val
|
||||
|
||||
// A KV holds a key/value pair.
|
||||
type KV struct {
|
||||
K String
|
||||
V Val
|
||||
}
|
||||
|
||||
// An Arg represents an argument of [Func].
|
||||
type Arg struct {
|
||||
K []Ident
|
||||
V Val
|
||||
R bool
|
||||
}
|
||||
|
||||
// Func is a function call or package declaration.
|
||||
type Func struct {
|
||||
// Function or package identifier.
|
||||
Ident Ident
|
||||
// Whether this is a package declaration.
|
||||
Package bool
|
||||
// Key-value arguments.
|
||||
Args []Arg
|
||||
}
|
||||
|
||||
// parseExpr parses the current expression.
|
||||
func (p *parser) parseExpr() any {
|
||||
switch p.tok {
|
||||
case scanner.Int:
|
||||
v := p.parseInt()
|
||||
p.scan()
|
||||
return v
|
||||
|
||||
case scanner.String, scanner.RawString:
|
||||
v := p.parseString()
|
||||
p.scan()
|
||||
return v
|
||||
|
||||
case scanner.Ident:
|
||||
var v Func
|
||||
v.Ident = Ident(p.s.TokenText())
|
||||
if v.Package = v.Ident == "package"; v.Package {
|
||||
p.scanAs(scanner.Ident)
|
||||
v.Ident = Ident(p.s.TokenText())
|
||||
}
|
||||
|
||||
p.scan()
|
||||
switch p.tok {
|
||||
case '{':
|
||||
for {
|
||||
p.scan()
|
||||
switch p.tok {
|
||||
case '}':
|
||||
p.scan()
|
||||
return v
|
||||
|
||||
case scanner.Ident:
|
||||
break
|
||||
|
||||
default:
|
||||
panic(TokenError{scanner.Ident, p.tok})
|
||||
}
|
||||
|
||||
arg := Arg{K: []Ident{Ident(p.s.TokenText())}}
|
||||
delim := true
|
||||
arg:
|
||||
for {
|
||||
p.scan()
|
||||
switch p.tok {
|
||||
case ',':
|
||||
if delim {
|
||||
delim = false
|
||||
continue
|
||||
}
|
||||
panic(ExprError(p.tok))
|
||||
|
||||
case scanner.Ident:
|
||||
if delim {
|
||||
panic(TokenError{',', p.tok})
|
||||
}
|
||||
delim = true
|
||||
arg.K = append(arg.K, Ident(p.s.TokenText()))
|
||||
|
||||
default:
|
||||
break arg
|
||||
}
|
||||
}
|
||||
switch p.tok {
|
||||
case '=':
|
||||
break
|
||||
|
||||
case '#':
|
||||
arg.R = true
|
||||
p.scanAs('=')
|
||||
|
||||
default:
|
||||
panic(TokenError{'=', p.tok})
|
||||
}
|
||||
p.scan()
|
||||
arg.V = p.parseVal()
|
||||
v.Args = append(v.Args, arg)
|
||||
p.expects(';')
|
||||
}
|
||||
|
||||
default:
|
||||
return v.Ident
|
||||
}
|
||||
|
||||
case '{':
|
||||
var v []KV
|
||||
for {
|
||||
p.scan()
|
||||
switch p.tok {
|
||||
case '}':
|
||||
p.scan()
|
||||
return v
|
||||
|
||||
case scanner.String:
|
||||
pair := KV{K: p.parseString()}
|
||||
p.scan()
|
||||
switch p.tok {
|
||||
case ';':
|
||||
break
|
||||
|
||||
case ':':
|
||||
p.scan()
|
||||
pair.V = p.parseVal()
|
||||
p.expects(';')
|
||||
break
|
||||
|
||||
default:
|
||||
panic(ExprError(p.tok))
|
||||
}
|
||||
v = append(v, pair)
|
||||
|
||||
default:
|
||||
panic(ExprError(p.tok))
|
||||
}
|
||||
}
|
||||
|
||||
case '[':
|
||||
var (
|
||||
v Array
|
||||
delim bool
|
||||
)
|
||||
p.scan()
|
||||
for {
|
||||
switch p.tok {
|
||||
case ',':
|
||||
if delim {
|
||||
p.scan()
|
||||
delim = false
|
||||
continue
|
||||
}
|
||||
panic(ExprError(','))
|
||||
case ']':
|
||||
p.scan()
|
||||
return v
|
||||
case scanner.EOF:
|
||||
panic(ExprError(scanner.EOF))
|
||||
default:
|
||||
if delim {
|
||||
panic(TokenError{',', p.tok})
|
||||
}
|
||||
delim = true
|
||||
break
|
||||
}
|
||||
v = append(v, p.parseVal())
|
||||
}
|
||||
|
||||
default:
|
||||
panic(ExprError(p.tok))
|
||||
}
|
||||
}
|
||||
|
||||
// ScanError is the error count parsing all expressions.
|
||||
type ScanError int
|
||||
|
||||
func (ScanError) Error() string {
|
||||
return "aborting due to scanning errors"
|
||||
}
|
||||
|
||||
// Parse parses expressions from r.
|
||||
func Parse(r io.Reader) (e []any, err error) {
|
||||
var p parser
|
||||
p.s.Init(r)
|
||||
|
||||
p.s.Mode = scanner.ScanIdents |
|
||||
scanner.ScanInts |
|
||||
scanner.ScanStrings |
|
||||
scanner.ScanRawStrings |
|
||||
scanner.ScanComments |
|
||||
scanner.SkipComments
|
||||
p.s.IsIdentRune = func(ch rune, i int) bool {
|
||||
if i == 0 && ch >= '0' && ch <= '9' {
|
||||
return false
|
||||
}
|
||||
return ch > 0 && ch < rune(len(idents)) && idents[ch]
|
||||
}
|
||||
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_err, ok := v.(error)
|
||||
if !ok {
|
||||
panic(v)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = _err
|
||||
return
|
||||
}
|
||||
err = errors.Join(err, _err)
|
||||
}()
|
||||
|
||||
p.scan()
|
||||
for p.tok != scanner.EOF {
|
||||
e = append(e, p.parseExpr())
|
||||
}
|
||||
|
||||
if p.s.ErrorCount != 0 {
|
||||
err = ScanError(p.s.ErrorCount)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package azalea_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"text/scanner"
|
||||
|
||||
. "hakurei.app/internal/rosa/azalea"
|
||||
)
|
||||
|
||||
//go:embed testdata/gcc.az
|
||||
var sample string
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
data string
|
||||
want []any
|
||||
err error
|
||||
}{
|
||||
{"invalid", "}", nil, ExprError('}')},
|
||||
{"bad sep", "f{v?}", nil, TokenError{'=', '?'}},
|
||||
{"bad ident", "f{9}", nil, TokenError{scanner.Ident, scanner.Int}},
|
||||
{"share bad sep", "f { v,,v = v; }", nil, ExprError(',')},
|
||||
{"share missing sep", "f { v v }", nil, TokenError{',', scanner.Ident}},
|
||||
|
||||
{"ident", `v`, []any{Ident("v")}, nil},
|
||||
{"concat", `f { v = v+"\xfd"+p{}+9; }`, []any{Func{
|
||||
Ident: "f",
|
||||
|
||||
Args: []Arg{{K: []Ident{"v"}, V: Val{
|
||||
Ident("v"),
|
||||
String("\xfd"),
|
||||
Func{Ident: "p"},
|
||||
Int(9),
|
||||
}}},
|
||||
}}, nil},
|
||||
{"truncated string concat", `f { v = v+; }`, nil,
|
||||
ExprError(';')},
|
||||
|
||||
{"empty pairs", `{}`, []any{[]KV(nil)}, nil},
|
||||
{"short kv", `{"\x00":v;}`, []any{[]KV{
|
||||
{K: "\x00", V: Val{Ident("v")}},
|
||||
}}, nil},
|
||||
{"truncated kv", `{"\x00"`, nil, ExprError(scanner.EOF)},
|
||||
{"ident kv", `{v="";}`, nil, ExprError(scanner.Ident)},
|
||||
|
||||
{"empty array", `[]`, []any{Array(nil)}, nil},
|
||||
{"integer array", `[9]`, []any{Array{{Int(9)}}}, nil},
|
||||
{"short array", `[ "\x00" ]`, []any{
|
||||
Array{{String("\x00")}},
|
||||
}, nil},
|
||||
{"short array delim", `[ "\x00", ]`, []any{
|
||||
Array{{String("\x00")}},
|
||||
}, nil},
|
||||
{"missing array value", `[ "\x00", , v ]`, nil, ExprError(',')},
|
||||
{"missing array delimiter", `[ v0 v1 ]`, nil, TokenError{',', scanner.Ident}},
|
||||
{"truncated array", `[ "\x00"`, nil,
|
||||
ExprError(scanner.EOF)},
|
||||
|
||||
{"gcc", sample, []any{Func{
|
||||
Ident: Ident("gcc"),
|
||||
Package: true,
|
||||
|
||||
Args: []Arg{
|
||||
{K: []Ident{Ident("description")}, V: Val{String("The GNU Compiler Collection")}},
|
||||
{K: []Ident{Ident("website")}, V: Val{String("https://www.gnu.org/software/gcc")}},
|
||||
{K: []Ident{Ident("anitya")}, V: Val{Int(6502)}},
|
||||
|
||||
{K: []Ident{Ident("version")}, V: Val{String("16.1.0")}, R: true},
|
||||
{K: []Ident{Ident("source")}, V: Val{Func{
|
||||
Ident: Ident("remoteTar"),
|
||||
|
||||
Args: []Arg{
|
||||
{K: []Ident{Ident("url")}, V: Val{
|
||||
String("https://ftp.tsukuba.wide.ad.jp/software/gcc/releases/"),
|
||||
String("gcc-"),
|
||||
Ident("version"),
|
||||
String("/gcc-"),
|
||||
Ident("version"),
|
||||
String(".tar.gz"),
|
||||
}},
|
||||
{K: []Ident{Ident("checksum")}, V: Val{String("4ASoWbxaA2FW7PAB0zzHDPC5XnNhyaAyjtDPpGzceSLeYnEIXsNYZR3PA_Zu5P0K")}},
|
||||
{K: []Ident{Ident("compress")}, V: Val{Ident("gzip")}},
|
||||
},
|
||||
}}},
|
||||
{K: []Ident{Ident("patches")}, V: Val{Array{
|
||||
{String("musl-off64_t-loff_t.patch")},
|
||||
{String("musl-legacy-lfs.patch")},
|
||||
}}},
|
||||
|
||||
{K: []Ident{Ident("exclusive")}, V: Val{Ident("true")}},
|
||||
|
||||
{K: []Ident{Ident("exec")}, V: Val{Func{
|
||||
Ident: Ident("make"),
|
||||
|
||||
Args: []Arg{
|
||||
{K: []Ident{Ident("configure")}, V: Val{[]KV{
|
||||
{K: String("disable-multilib")},
|
||||
{K: String("enable-default-pie")},
|
||||
{K: String("disable-nls")},
|
||||
{K: String("with-gnu-as")},
|
||||
{K: String("with-gnu-ld")},
|
||||
{K: String("with-system-zlib")},
|
||||
{K: String("enable-languages"), V: Val{String("c,c++,go")}},
|
||||
{K: String("with-native-system-header-dir"), V: Val{String("/system/include")}},
|
||||
{K: String("with-multilib-list"), V: Val{Func{
|
||||
Ident: Ident("arch"),
|
||||
|
||||
Args: []Arg{
|
||||
{K: []Ident{Ident("amd64"), Ident("arm64")}, V: Val{String("''")}},
|
||||
{K: []Ident{Ident("default")}, V: Val{Ident("unset")}},
|
||||
},
|
||||
}}},
|
||||
}}},
|
||||
{K: []Ident{Ident("make")}, V: Val{Array{
|
||||
{String("BOOT_CFLAGS='-O2 -g'")},
|
||||
{
|
||||
Func{Ident: Ident("noop"), Args: []Arg{{K: []Ident{Ident("key")}, V: Val{Ident("value")}}}},
|
||||
String("\x00"),
|
||||
},
|
||||
{String("bootstrap")},
|
||||
}}},
|
||||
|
||||
{K: []Ident{Ident("skip-check")}, V: Val{Ident("true")}},
|
||||
},
|
||||
}}},
|
||||
|
||||
{K: []Ident{Ident("inputs")}, V: Val{Array{
|
||||
{Ident("binutils")},
|
||||
{Ident("mpc")},
|
||||
{Ident("zlib")},
|
||||
{Ident("libucontext")},
|
||||
{Ident("kernel-headers")},
|
||||
}}},
|
||||
},
|
||||
}}, nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p, err := Parse(strings.NewReader(tc.data))
|
||||
if !reflect.DeepEqual(p, tc.want) {
|
||||
t.Errorf("Parse: %#v, want %#v", p, tc.want)
|
||||
}
|
||||
if !reflect.DeepEqual(err, tc.err) {
|
||||
t.Errorf("Parse: error = %v, want %v", err, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParse(b *testing.B) {
|
||||
r := strings.NewReader(sample)
|
||||
for b.Loop() {
|
||||
if _, err := Parse(r); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
b.StopTimer()
|
||||
r.Reset(sample)
|
||||
b.StartTimer()
|
||||
}
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
package azalea
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"unique"
|
||||
)
|
||||
|
||||
// Value are types supported by the language.
|
||||
type Value interface {
|
||||
bool | int64 | string | []string | []int64 | [][2]string
|
||||
}
|
||||
|
||||
type (
|
||||
// FArg is an argument passed to [F].
|
||||
FArg struct {
|
||||
K unique.Handle[Ident]
|
||||
V any
|
||||
R bool
|
||||
}
|
||||
// FArgs are arguments passed to [F].
|
||||
FArgs []FArg
|
||||
|
||||
// PF implements the package declaration function.
|
||||
PF func(name Ident, args FArgs) (v any, set bool, err error)
|
||||
|
||||
// F is the implementation of a [Func].
|
||||
F struct {
|
||||
F func(args FArgs) (v any, set bool, err error)
|
||||
V map[unique.Handle[Ident]]any
|
||||
}
|
||||
)
|
||||
|
||||
// Apply applies named arguments and rejects unused arguments.
|
||||
func (args FArgs) Apply(v map[unique.Handle[Ident]]any) error {
|
||||
for _, arg := range args {
|
||||
if arg.V == nil {
|
||||
// unset
|
||||
continue
|
||||
}
|
||||
|
||||
r, ok := v[arg.K]
|
||||
if !ok {
|
||||
if arg.R {
|
||||
continue
|
||||
}
|
||||
return UndefinedError(arg.K.Value())
|
||||
}
|
||||
err := storeE(r, arg.V)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A Frame refers to local variables and debugging information.
|
||||
type Frame struct {
|
||||
// Local constants.
|
||||
Val map[unique.Handle[Ident]]any
|
||||
// Functions.
|
||||
Func map[unique.Handle[Ident]]F
|
||||
}
|
||||
|
||||
// UnsupportedExprError is an expression with invalid concrete type.
|
||||
type UnsupportedExprError struct{ E any }
|
||||
|
||||
func (e UnsupportedExprError) Error() string {
|
||||
return fmt.Sprintf("unsupported expression %#v", e.E)
|
||||
}
|
||||
|
||||
// UndefinedError is an identifier not defined in any stack frame visible to the
|
||||
// expression containing it.
|
||||
type UndefinedError Ident
|
||||
|
||||
func (e UndefinedError) Error() string {
|
||||
return "undefined: " + string(e)
|
||||
}
|
||||
|
||||
// evaluate is evaluateAny with a type parameter.
|
||||
func evaluate[T Value](d PF, s []Frame, expr any, rp *T) bool {
|
||||
return evaluateAny(d, s, expr, rp)
|
||||
}
|
||||
|
||||
// evaluateArray evaluates expr and returns its values as a slice.
|
||||
func evaluateArray[T Value](d PF, s []Frame, expr Array) []T {
|
||||
r := make([]T, 0, len(expr))
|
||||
for i := range expr {
|
||||
var _r T
|
||||
if evaluate(d, s, expr[i], &_r) {
|
||||
r = append(r, _r)
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// TypeError is an unexpected type during evaluation.
|
||||
type TypeError struct {
|
||||
Concrete, Asserted reflect.Type
|
||||
}
|
||||
|
||||
func (e TypeError) Error() string {
|
||||
return fmt.Sprintf("expected %s, got %s", e.Asserted, e.Concrete)
|
||||
}
|
||||
|
||||
func (e TypeError) Is(err error) bool {
|
||||
v, ok := errors.AsType[TypeError](err)
|
||||
return ok &&
|
||||
e.Asserted == v.Asserted &&
|
||||
e.Concrete == v.Concrete
|
||||
}
|
||||
|
||||
// storeE is a convenience function to set the value of a result pointer.
|
||||
func storeE(rp any, r any) error {
|
||||
pv := reflect.ValueOf(rp).Elem()
|
||||
v := reflect.ValueOf(r)
|
||||
pt, vt := pv.Type(), v.Type()
|
||||
if !vt.AssignableTo(pt) {
|
||||
return TypeError{vt, pt}
|
||||
}
|
||||
pv.Set(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
// store is like storeE, but panics if error is non-nil.
|
||||
func store[T Value](rp any, r T) {
|
||||
err := storeE(rp, r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// EvaluationError is an error and the expression it occurred in.
|
||||
type EvaluationError struct {
|
||||
Expr any
|
||||
Err error
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying error.
|
||||
func (e EvaluationError) Unwrap() error { return e.Err }
|
||||
|
||||
// Error returns a very long error description that should not be presented
|
||||
// to the user directly.
|
||||
func (e EvaluationError) Error() string {
|
||||
return fmt.Sprintf("expression %#v: %v", e.Expr, e.Err)
|
||||
}
|
||||
|
||||
var (
|
||||
// IdentInputs is a special array argument in a package declaration whose
|
||||
// values of [Ident] are kept as is when passed to a [PF].
|
||||
IdentInputs = unique.Make(Ident("inputs"))
|
||||
// IdentRuntime has the same semantics as [IdentInputs].
|
||||
IdentRuntime = unique.Make(Ident("runtime"))
|
||||
// IdentExtra has the same semantics as [IdentInputs].
|
||||
IdentExtra = unique.Make(Ident("extra"))
|
||||
// IdentSource is a special argument in a package declaration where an
|
||||
// assignment of a [Val] with a single [Ident] passes through the evaluator.
|
||||
IdentSource = unique.Make(Ident("source"))
|
||||
|
||||
// ErrInvalidSpecial is panicked for a special [PF] argument sharing its
|
||||
// value or set for R.
|
||||
ErrInvalidSpecial = errors.New("special must not be common or bound to scope")
|
||||
)
|
||||
|
||||
// evaluateAny implements [Evaluate].
|
||||
func evaluateAny(d PF, s []Frame, expr, rp any) bool {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err, ok := r.(error)
|
||||
if !ok {
|
||||
panic(r)
|
||||
}
|
||||
|
||||
if _, ok = err.(EvaluationError); ok {
|
||||
panic(err)
|
||||
}
|
||||
panic(EvaluationError{expr, err})
|
||||
}()
|
||||
|
||||
switch e := expr.(type) {
|
||||
case Int:
|
||||
store(rp, int64(e))
|
||||
return true
|
||||
|
||||
case String:
|
||||
store(rp, string(e))
|
||||
return true
|
||||
|
||||
case Ident:
|
||||
var (
|
||||
v any
|
||||
ok bool
|
||||
)
|
||||
for i := range s {
|
||||
v, ok = s[len(s)-1-i].Val[unique.Make(e)]
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
panic(UndefinedError(e))
|
||||
}
|
||||
if err := storeE(rp, v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return true
|
||||
|
||||
case Val:
|
||||
if len(e) == 1 {
|
||||
switch v := e[0].(type) {
|
||||
case Ident:
|
||||
switch v {
|
||||
case "unset":
|
||||
return false
|
||||
case "true":
|
||||
store(rp, true)
|
||||
return true
|
||||
case "false":
|
||||
store(rp, false)
|
||||
return true
|
||||
default:
|
||||
return evaluateAny(d, s, v, rp)
|
||||
}
|
||||
|
||||
default:
|
||||
return evaluateAny(d, s, e[0], rp)
|
||||
}
|
||||
}
|
||||
var v strings.Builder
|
||||
for i := range e {
|
||||
var _r string
|
||||
if evaluate(d, s, e[i], &_r) {
|
||||
v.WriteString(_r)
|
||||
}
|
||||
}
|
||||
store(rp, v.String())
|
||||
return true
|
||||
|
||||
case Array:
|
||||
if len(e) > 0 && len(e[0]) == 1 {
|
||||
if _, ok := e[0][0].(Int); ok {
|
||||
store(rp, evaluateArray[int64](d, s, e))
|
||||
return true
|
||||
}
|
||||
}
|
||||
store(rp, evaluateArray[string](d, s, e))
|
||||
return true
|
||||
|
||||
case []KV:
|
||||
r := make([][2]string, 0, len(e))
|
||||
for i := range e {
|
||||
var _r string
|
||||
if e[i].V == nil || evaluate(d, s, e[i].V, &_r) {
|
||||
r = append(r, [2]string{string(e[i].K), _r})
|
||||
}
|
||||
}
|
||||
store(rp, r)
|
||||
return true
|
||||
|
||||
case Func:
|
||||
var (
|
||||
f F
|
||||
ok bool
|
||||
)
|
||||
if !e.Package {
|
||||
for i := range s {
|
||||
f, ok = s[len(s)-1-i].Func[unique.Make(e.Ident)]
|
||||
if ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
panic(UndefinedError(e.Ident))
|
||||
}
|
||||
}
|
||||
|
||||
argc := len(e.Args)
|
||||
for _, arg := range e.Args {
|
||||
argc += len(arg.K) - 1
|
||||
}
|
||||
fargs := make([]FArg, 0, len(e.Args))
|
||||
s = append(s, Frame{})
|
||||
fp := &s[len(s)-1]
|
||||
if !e.Package {
|
||||
fp.Val = maps.Clone(f.V)
|
||||
}
|
||||
|
||||
args:
|
||||
for _, arg := range e.Args {
|
||||
names := make([]unique.Handle[Ident], len(arg.K))
|
||||
for i, name := range arg.K {
|
||||
names[i] = unique.Make(name)
|
||||
}
|
||||
|
||||
farg := FArg{R: arg.R}
|
||||
if e.Package {
|
||||
for _, special := range [...]unique.Handle[Ident]{
|
||||
IdentInputs,
|
||||
IdentRuntime,
|
||||
IdentExtra,
|
||||
} {
|
||||
if slices.Contains(names, special) {
|
||||
if len(names) != 1 || len(arg.V) != 1 || arg.R {
|
||||
panic(ErrInvalidSpecial)
|
||||
}
|
||||
farg.K = names[0]
|
||||
if err := storeE(&farg.V, arg.V[0]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fargs = append(fargs, farg)
|
||||
continue args
|
||||
}
|
||||
}
|
||||
|
||||
if slices.Contains(names, IdentSource) {
|
||||
if len(names) != 1 || len(arg.V) != 1 || arg.R {
|
||||
panic(ErrInvalidSpecial)
|
||||
}
|
||||
if _, ok = arg.V[0].(Ident); ok {
|
||||
farg.K = names[0]
|
||||
if err := storeE(&farg.V, arg.V[0]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fargs = append(fargs, farg)
|
||||
continue args
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !evaluateAny(d, s, arg.V, &farg.V) {
|
||||
farg.V = nil
|
||||
}
|
||||
for _, name := range names {
|
||||
farg.K = name
|
||||
fargs = append(fargs, farg)
|
||||
|
||||
if arg.R && farg.V != nil {
|
||||
if fp.Val == nil {
|
||||
fp.Val = make(map[unique.Handle[Ident]]any)
|
||||
}
|
||||
(*fp).Val[name] = farg.V
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
v any
|
||||
err error
|
||||
)
|
||||
if !e.Package {
|
||||
v, ok, err = f.F(fargs)
|
||||
} else {
|
||||
v, ok, err = d(e.Ident, fargs)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
} else if v != nil {
|
||||
if err = storeE(rp, v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return ok
|
||||
|
||||
default:
|
||||
panic(UnsupportedExprError{expr})
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate evaluates a statement and returns its value.
|
||||
func Evaluate[T any](d PF, s []Frame, expr any) (v T, set bool, err error) {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
_err, ok := r.(error)
|
||||
if !ok {
|
||||
panic(r)
|
||||
}
|
||||
err = _err
|
||||
}()
|
||||
set = evaluateAny(d, s, expr, &v)
|
||||
return
|
||||
}
|
||||
@@ -1,407 +0,0 @@
|
||||
package azalea_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"unique"
|
||||
|
||||
. "hakurei.app/internal/rosa/azalea"
|
||||
)
|
||||
|
||||
// makeStackCheck creates a stack with a single frame with a single function "f"
|
||||
// which calls the check function internally.
|
||||
func makeStackCheck(check func(args FArgs) (any, error)) []Frame {
|
||||
return []Frame{{Func: map[unique.Handle[Ident]]F{
|
||||
unique.Make(Ident("f")): {F: func(
|
||||
args FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
set = true
|
||||
v, err = check(args)
|
||||
return
|
||||
}},
|
||||
}}}
|
||||
}
|
||||
|
||||
// checkArgs is like makeStackCheck, but the resulting function asserts that its
|
||||
// args match the expected value.
|
||||
func checkArgs(want FArgs) []Frame {
|
||||
return makeStackCheck(func(args FArgs) (any, error) {
|
||||
if !reflect.DeepEqual(args, want) {
|
||||
return nil, fmt.Errorf("%#v, want %#v", args, want)
|
||||
}
|
||||
return "\xfd", nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestEvaluate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
data string
|
||||
s []Frame
|
||||
want any
|
||||
err error
|
||||
}{
|
||||
{"apply unset", `f { v = unset; }`, makeStackCheck(func(
|
||||
args FArgs,
|
||||
) (v any, err error) {
|
||||
v = "\xfd"
|
||||
err = args.Apply(map[unique.Handle[Ident]]any{
|
||||
unique.Make(Ident("v")): &v,
|
||||
})
|
||||
return
|
||||
}), "\xfd", nil},
|
||||
|
||||
{"apply bad type", `f { v = 9; }`, makeStackCheck(func(
|
||||
args FArgs,
|
||||
) (v any, err error) {
|
||||
v = "\xfd"
|
||||
err = args.Apply(map[unique.Handle[Ident]]any{
|
||||
unique.Make(Ident("v")): &v,
|
||||
})
|
||||
return
|
||||
}), "", TypeError{
|
||||
Concrete: reflect.TypeFor[int64](),
|
||||
Asserted: reflect.TypeFor[string](),
|
||||
}},
|
||||
|
||||
{"apply undefined", `f { v = 9; }`, makeStackCheck(func(
|
||||
args FArgs,
|
||||
) (v any, err error) {
|
||||
v = "\xfd"
|
||||
err = args.Apply(map[unique.Handle[Ident]]any{})
|
||||
return
|
||||
}), "", EvaluationError{
|
||||
Expr: Func{
|
||||
Ident: Ident("f"),
|
||||
Args: []Arg{
|
||||
{K: []Ident{"v"}, V: Val{Int(9)}},
|
||||
},
|
||||
},
|
||||
Err: UndefinedError("v"),
|
||||
}},
|
||||
|
||||
{"apply bound undefined", `f { _v# = "\x00"; v = _v; }`, makeStackCheck(func(
|
||||
args FArgs,
|
||||
) (v any, err error) {
|
||||
v = "\xfd"
|
||||
err = args.Apply(map[unique.Handle[Ident]]any{
|
||||
unique.Make(Ident("v")): &v,
|
||||
})
|
||||
return
|
||||
}), "\x00", nil},
|
||||
|
||||
{"undefined function", `f {}`, nil, "", EvaluationError{
|
||||
Expr: Func{Ident: "f"},
|
||||
Err: UndefinedError("f"),
|
||||
}},
|
||||
|
||||
{"error wrap deep", `f { v = nil; }`, makeStackCheck(func(
|
||||
FArgs,
|
||||
) (any, error) {
|
||||
panic("unreachable")
|
||||
}), "", EvaluationError{
|
||||
Expr: Ident("nil"),
|
||||
Err: UndefinedError("nil"),
|
||||
}},
|
||||
|
||||
{"common inputs", `package name { inputs, v = []; }`, nil, "", EvaluationError{
|
||||
Expr: Func{
|
||||
Ident: Ident("name"),
|
||||
Package: true,
|
||||
|
||||
Args: []Arg{
|
||||
{K: []Ident{
|
||||
"inputs",
|
||||
"v",
|
||||
}, V: Val{Array(nil)}},
|
||||
},
|
||||
},
|
||||
Err: ErrInvalidSpecial,
|
||||
}},
|
||||
|
||||
{"bound inputs", `package name { inputs# = []; }`, nil, "", EvaluationError{
|
||||
Expr: Func{
|
||||
Ident: Ident("name"),
|
||||
Package: true,
|
||||
|
||||
Args: []Arg{
|
||||
{K: []Ident{"inputs"}, V: Val{Array(nil)}, R: true},
|
||||
},
|
||||
},
|
||||
Err: ErrInvalidSpecial,
|
||||
}},
|
||||
|
||||
{"bound runtime", `package name { runtime# = []; }`, nil, "", EvaluationError{
|
||||
Expr: Func{
|
||||
Ident: Ident("name"),
|
||||
Package: true,
|
||||
|
||||
Args: []Arg{
|
||||
{K: []Ident{"runtime"}, V: Val{Array(nil)}, R: true},
|
||||
},
|
||||
},
|
||||
Err: ErrInvalidSpecial,
|
||||
}},
|
||||
|
||||
{"concat inputs", `package name { inputs = ""+""; }`, nil, "", EvaluationError{
|
||||
Expr: Func{
|
||||
Ident: Ident("name"),
|
||||
Package: true,
|
||||
|
||||
Args: []Arg{
|
||||
{K: []Ident{"inputs"}, V: Val{String(""), String("")}},
|
||||
},
|
||||
},
|
||||
Err: ErrInvalidSpecial,
|
||||
}},
|
||||
|
||||
{"concat source", `package name { source = ""+""; }`, nil, "", EvaluationError{
|
||||
Expr: Func{
|
||||
Ident: Ident("name"),
|
||||
Package: true,
|
||||
|
||||
Args: []Arg{
|
||||
{K: []Ident{"source"}, V: Val{String(""), String("")}},
|
||||
},
|
||||
},
|
||||
Err: ErrInvalidSpecial,
|
||||
}},
|
||||
|
||||
{"source handle", `package name { source = name; }`, nil, FArgs{
|
||||
{K: unique.Make(Ident("source")), V: Ident("name")},
|
||||
}, nil},
|
||||
|
||||
{"integer array", `f { v = [ 0 ]; _v = [ 0, 9 ]; }`, checkArgs(FArgs{
|
||||
{K: unique.Make(Ident("v")), V: []int64{0}},
|
||||
{K: unique.Make(Ident("_v")), V: []int64{0, 9}},
|
||||
}), "\xfd", nil},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var expr Func
|
||||
if e, err := Parse(strings.NewReader(tc.data)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if len(e) != 1 {
|
||||
t.Fatalf("got expression %#v", e)
|
||||
} else {
|
||||
expr = e[0].(Func)
|
||||
}
|
||||
const rPackage = "\xff\xff\xff\xff"
|
||||
r, set, err := Evaluate[string](func(
|
||||
name Ident,
|
||||
args FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
v = rPackage
|
||||
if !reflect.DeepEqual(args, tc.want) {
|
||||
err = fmt.Errorf("%#v, want %#v", args, tc.want)
|
||||
}
|
||||
set = true
|
||||
return
|
||||
}, tc.s, expr)
|
||||
if set != (err == nil) {
|
||||
t.Error("Evaluate: unexpected unset")
|
||||
}
|
||||
|
||||
if r != rPackage && r != tc.want {
|
||||
t.Errorf("Evaluate: %q, want %q", r, tc.want)
|
||||
}
|
||||
|
||||
var errEquals bool
|
||||
if _, ok := errors.AsType[TypeError](err); ok {
|
||||
errEquals = errors.Is(err, tc.err)
|
||||
} else {
|
||||
errEquals = reflect.DeepEqual(err, tc.err)
|
||||
}
|
||||
if !errEquals {
|
||||
t.Errorf("Evaluate: error = %v, want %v", err, tc.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvaluateGCC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var gcc Func
|
||||
if e, err := Parse(strings.NewReader(sample)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
gcc = e[0].(Func)
|
||||
}
|
||||
|
||||
var got [3]FArgs
|
||||
if r, set, err := Evaluate[string](func(
|
||||
name Ident,
|
||||
args FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
v = "\x00"
|
||||
set = true
|
||||
got[0] = args
|
||||
return
|
||||
}, []Frame{{
|
||||
Func: map[unique.Handle[Ident]]F{
|
||||
unique.Make(Ident("remoteTar")): {F: func(
|
||||
args FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
var url, checksum string
|
||||
var compress int
|
||||
if err = args.Apply(map[unique.Handle[Ident]]any{
|
||||
unique.Make(Ident("url")): &url,
|
||||
unique.Make(Ident("checksum")): &checksum,
|
||||
unique.Make(Ident("compress")): &compress,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if compress != 0xcafe {
|
||||
err = fmt.Errorf("unexpected compress %#v", compress)
|
||||
}
|
||||
set = true
|
||||
v = url + "?checksum=" + checksum
|
||||
return
|
||||
}, V: map[unique.Handle[Ident]]any{
|
||||
unique.Make(Ident("gzip")): 0xcafe,
|
||||
}},
|
||||
|
||||
unique.Make(Ident("make")): {F: func(
|
||||
args FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
v = args
|
||||
set = true
|
||||
return
|
||||
}},
|
||||
|
||||
unique.Make(Ident("arch")): {F: func(
|
||||
args FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
set = false
|
||||
got[1] = args
|
||||
return
|
||||
}},
|
||||
|
||||
unique.Make(Ident("noop")): {F: func(
|
||||
args FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
set = false
|
||||
set = true
|
||||
got[2] = args
|
||||
return
|
||||
}, V: map[unique.Handle[Ident]]any{
|
||||
unique.Make(Ident("value")): "\xfd",
|
||||
}},
|
||||
},
|
||||
}}, gcc); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if r != "\x00" {
|
||||
t.Fatalf("package: %q", r)
|
||||
} else if !set {
|
||||
t.Fatal("package: unset")
|
||||
}
|
||||
|
||||
want := [...]FArgs{
|
||||
{
|
||||
{K: unique.Make(Ident("description")), V: "The GNU Compiler Collection"},
|
||||
{K: unique.Make(Ident("website")), V: "https://www.gnu.org/software/gcc"},
|
||||
{K: unique.Make(Ident("anitya")), V: int64(6502)},
|
||||
|
||||
{K: unique.Make(Ident("version")), V: "16.1.0", R: true},
|
||||
{K: unique.Make(Ident("source")), V: "https://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-16.1.0/gcc-16.1.0.tar.gz?checksum=4ASoWbxaA2FW7PAB0zzHDPC5XnNhyaAyjtDPpGzceSLeYnEIXsNYZR3PA_Zu5P0K"},
|
||||
{K: unique.Make(Ident("patches")), V: []string{"musl-off64_t-loff_t.patch", "musl-legacy-lfs.patch"}},
|
||||
|
||||
{K: unique.Make(Ident("exclusive")), V: true},
|
||||
{K: unique.Make(Ident("exec")), V: FArgs{
|
||||
{K: unique.Make(Ident("configure")), V: [][2]string{
|
||||
{"disable-multilib", ""},
|
||||
{"enable-default-pie", ""},
|
||||
{"disable-nls", ""},
|
||||
{"with-gnu-as", ""},
|
||||
{"with-gnu-ld", ""},
|
||||
{"with-system-zlib", ""},
|
||||
{"enable-languages", "c,c++,go"},
|
||||
{"with-native-system-header-dir", "/system/include"},
|
||||
}},
|
||||
{K: unique.Make(Ident("make")), V: []string{
|
||||
"BOOT_CFLAGS='-O2 -g'",
|
||||
"\x00",
|
||||
"bootstrap",
|
||||
}},
|
||||
{K: unique.Make(Ident("skip-check")), V: true},
|
||||
}},
|
||||
|
||||
{K: unique.Make(Ident("inputs")), V: Array{
|
||||
{Ident("binutils")},
|
||||
{Ident("mpc")},
|
||||
{Ident("zlib")},
|
||||
{Ident("libucontext")},
|
||||
{Ident("kernel-headers")},
|
||||
}},
|
||||
},
|
||||
{
|
||||
{K: unique.Make(Ident("amd64")), V: "''"},
|
||||
{K: unique.Make(Ident("arm64")), V: "''"},
|
||||
{K: unique.Make(Ident("default"))},
|
||||
},
|
||||
{{K: unique.Make(Ident("key")), V: "\xfd"}},
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("package: args = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEvaluate(b *testing.B) {
|
||||
var gcc Func
|
||||
if e, err := Parse(strings.NewReader(sample)); err != nil {
|
||||
b.Fatal(err)
|
||||
} else {
|
||||
gcc = e[0].(Func)
|
||||
}
|
||||
|
||||
s := []Frame{{
|
||||
Func: map[unique.Handle[Ident]]F{
|
||||
unique.Make(Ident("remoteTar")): {F: func(
|
||||
FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
return
|
||||
}, V: map[unique.Handle[Ident]]any{
|
||||
unique.Make(Ident("gzip")): 0xcafe,
|
||||
}},
|
||||
|
||||
unique.Make(Ident("make")): {F: func(
|
||||
FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
return
|
||||
}},
|
||||
|
||||
unique.Make(Ident("arch")): {F: func(
|
||||
FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
return
|
||||
}},
|
||||
|
||||
unique.Make(Ident("noop")): {F: func(
|
||||
FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
return
|
||||
}, V: map[unique.Handle[Ident]]any{
|
||||
unique.Make(Ident("value")): "\xfd",
|
||||
}},
|
||||
},
|
||||
}}
|
||||
for b.Loop() {
|
||||
if _, _, err := Evaluate[string](func(
|
||||
Ident,
|
||||
FArgs,
|
||||
) (v any, set bool, err error) {
|
||||
return
|
||||
}, s, gcc); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-57
@@ -1,57 +0,0 @@
|
||||
package gcc {
|
||||
description = "The GNU Compiler Collection";
|
||||
website = "https://www.gnu.org/software/gcc";
|
||||
anitya = 6502;
|
||||
|
||||
version# = "16.1.0";
|
||||
source = remoteTar {
|
||||
url = "https://ftp.tsukuba.wide.ad.jp/software/gcc/releases/"+
|
||||
"gcc-"+version+"/gcc-"+version+".tar.gz";
|
||||
checksum = "4ASoWbxaA2FW7PAB0zzHDPC5XnNhyaAyjtDPpGzceSLeYnEIXsNYZR3PA_Zu5P0K";
|
||||
compress = gzip;
|
||||
};
|
||||
patches = [
|
||||
"musl-off64_t-loff_t.patch",
|
||||
"musl-legacy-lfs.patch",
|
||||
];
|
||||
|
||||
// GCC spends most of its time in its many configure scripts, however
|
||||
// it also saturates the CPU for a consequential amount of time.
|
||||
exclusive = true;
|
||||
|
||||
exec = make {
|
||||
configure = {
|
||||
"disable-multilib";
|
||||
"enable-default-pie";
|
||||
"disable-nls";
|
||||
"with-gnu-as";
|
||||
"with-gnu-ld";
|
||||
"with-system-zlib";
|
||||
"enable-languages": "c,c++,go";
|
||||
"with-native-system-header-dir": "/system/include";
|
||||
"with-multilib-list": arch {
|
||||
amd64, arm64 = "''";
|
||||
default = unset;
|
||||
};
|
||||
};
|
||||
make = [
|
||||
"BOOT_CFLAGS='-O2 -g'",
|
||||
noop { key = value; } + "\x00",
|
||||
"bootstrap",
|
||||
];
|
||||
|
||||
// This toolchain is hacked to pieces, it is not expected to ever work
|
||||
// well in its current state. That does not matter as long as the
|
||||
// toolchain it produces passes its own test suite.
|
||||
skip-check = true;
|
||||
};
|
||||
|
||||
inputs = [
|
||||
binutils,
|
||||
|
||||
mpc,
|
||||
zlib,
|
||||
libucontext,
|
||||
kernel-headers,
|
||||
];
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"path"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
const (
|
||||
// jobsE is expression for preferred job count set by [pkg].
|
||||
jobsE = `"$` + pkg.EnvJobs + `"`
|
||||
// jobsFlagE is expression for flag with preferred job count.
|
||||
jobsFlagE = `"-j$` + pkg.EnvJobs + `"`
|
||||
// jobsLE is expression for twice of preferred job count set by [pkg].
|
||||
jobsLE = `"$(expr ` + jobsE + ` '*' 2)"`
|
||||
// jobsLFlagE is expression for flag with double of preferred job count.
|
||||
jobsLFlagE = `"-j$(expr ` + jobsE + ` '*' 2)"`
|
||||
)
|
||||
|
||||
// newTar wraps [pkg.NewHTTPGetTar] with a simpler function signature.
|
||||
func newTar(url, checksum string, compress uint32) pkg.Artifact {
|
||||
return pkg.NewTar(
|
||||
pkg.NewDecompress(
|
||||
pkg.NewHTTPGet(nil, url, mustDecode(checksum)),
|
||||
compress,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// newFromCPAN is a helper for downloading release from CPAN.
|
||||
func newFromCPAN(author, name, version, checksum string) pkg.Artifact {
|
||||
return newTar(
|
||||
"https://cpan.metacpan.org/authors/id/"+
|
||||
author[:1]+"/"+author[:2]+"/"+author+"/"+
|
||||
name+"-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.Gzip,
|
||||
)
|
||||
}
|
||||
|
||||
// newFromGitLab is a helper for downloading source from GitLab.
|
||||
func newFromGitLab(domain, suffix, ref, checksum string) pkg.Artifact {
|
||||
return newTar(
|
||||
"https://"+domain+"/"+suffix+"/-/archive/"+
|
||||
ref+"/"+path.Base(suffix)+"-"+
|
||||
strings.ReplaceAll(ref, "/", "-")+".tar.bz2",
|
||||
checksum,
|
||||
pkg.Bzip2,
|
||||
)
|
||||
}
|
||||
|
||||
// newFromGitHub is a helper for downloading source from Microsoft Github.
|
||||
func newFromGitHub(suffix, tag, checksum string) pkg.Artifact {
|
||||
return newTar(
|
||||
"https://github.com/"+suffix+
|
||||
"/archive/refs/tags/"+tag+".tar.gz",
|
||||
checksum,
|
||||
pkg.Gzip,
|
||||
)
|
||||
}
|
||||
|
||||
// newFromGitHubRelease is a helper for downloading release tarball from
|
||||
// Microsoft Github.
|
||||
func newFromGitHubRelease(
|
||||
suffix, tag, name, checksum string,
|
||||
compression uint32,
|
||||
) pkg.Artifact {
|
||||
return newTar(
|
||||
"https://github.com/"+suffix+
|
||||
"/releases/download/"+tag+"/"+name,
|
||||
checksum,
|
||||
compression,
|
||||
)
|
||||
}
|
||||
|
||||
// skipGNUTests generates a string for skipping specific tests by number in a
|
||||
// GNU test suite. This is nontrivial because the test suite does not support
|
||||
// excluding tests in any way, so ranges for all but the skipped tests have to
|
||||
// be specified instead.
|
||||
//
|
||||
// For example, to skip test 764, ranges around the skipped test must be
|
||||
// specified:
|
||||
//
|
||||
// 1-763 765-
|
||||
//
|
||||
// Tests are numbered starting from 1. The resulting string is unquoted.
|
||||
func skipGNUTests(tests ...int64) string {
|
||||
tests = slices.Clone(tests)
|
||||
slices.Sort(tests)
|
||||
|
||||
var buf strings.Builder
|
||||
|
||||
if tests[0] != 1 {
|
||||
buf.WriteString("1-")
|
||||
}
|
||||
|
||||
for i, n := range tests {
|
||||
if n != 1 && (i == 0 || tests[i-1] != n-1) {
|
||||
buf.WriteString(strconv.Itoa(int(n - 1)))
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
if i == len(tests)-1 || tests[i+1] != n+1 {
|
||||
buf.WriteString(strconv.Itoa(int(n + 1)))
|
||||
buf.WriteString("-")
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
@@ -26,7 +26,7 @@ func (a busyboxBin) Params(*pkg.IContext) {}
|
||||
// IsExclusive returns false: Cure performs a trivial filesystem write.
|
||||
func (busyboxBin) IsExclusive() bool { return false }
|
||||
|
||||
// Dependencies returns the underlying busybox [pkg.FileArtifact].
|
||||
// Dependencies returns the underlying busybox [pkg.File].
|
||||
func (a busyboxBin) Dependencies() []pkg.Artifact {
|
||||
return []pkg.Artifact{a.bin}
|
||||
}
|
||||
@@ -85,13 +85,13 @@ func (a busyboxBin) Cure(t *pkg.TContext) (err error) {
|
||||
|
||||
// newBusyboxBin returns a [pkg.Artifact] containing a busybox installation from
|
||||
// the https://busybox.net/downloads/binaries/ binary release.
|
||||
func (s *S) newBusyboxBin() pkg.Artifact {
|
||||
func newBusyboxBin() pkg.Artifact {
|
||||
var version, url, checksum string
|
||||
switch s.arch {
|
||||
switch arch {
|
||||
case "amd64":
|
||||
version = "1.35.0"
|
||||
url = "https://busybox.net/downloads/binaries/" +
|
||||
version + "-" + s.linuxArch() + "-linux-musl/busybox"
|
||||
version + "-" + linuxArch() + "-linux-musl/busybox"
|
||||
checksum = "L7OBIsPu9enNHn7FqpBT1kOg_mCLNmetSeNMA3i4Y60Z5jTgnlX3qX3zcQtLx5AB"
|
||||
case "arm64":
|
||||
version = "1.31.0"
|
||||
@@ -100,11 +100,11 @@ func (s *S) newBusyboxBin() pkg.Artifact {
|
||||
checksum = "npJjBO7iwhjW6Kx2aXeSxf8kXhVgTCDChOZTTsI8ZfFfa3tbsklxRiidZQdrVERg"
|
||||
|
||||
default:
|
||||
panic("unsupported target " + s.arch)
|
||||
panic("unsupported target " + arch)
|
||||
}
|
||||
|
||||
return pkg.NewExec(
|
||||
"busybox-bin-"+version, s.arch, nil, pkg.ExecTimeoutMax, false, false,
|
||||
"busybox-bin-"+version, arch, nil, pkg.ExecTimeoutMax, false, false,
|
||||
fhs.AbsRoot, []string{
|
||||
"PATH=/system/bin",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newBzip2() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.0.8"
|
||||
checksum = "cTLykcco7boom-s05H1JVsQi1AtChYL84nXkg_92Dm1Xt94Ob_qlMg_-NSguIK-c"
|
||||
)
|
||||
return t.NewPackage("bzip2", version, newTar(
|
||||
"https://sourceware.org/pub/bzip2/bzip2-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Writable: true,
|
||||
EnterSource: true,
|
||||
}, &MakeHelper{
|
||||
// uses source tree as scratch space
|
||||
SkipConfigure: true,
|
||||
SkipCheck: true,
|
||||
InPlace: true,
|
||||
Make: []string{
|
||||
"CC=cc",
|
||||
},
|
||||
Install: "make PREFIX=/work/system install",
|
||||
}), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Bzip2] = Metadata{
|
||||
f: Toolchain.newBzip2,
|
||||
|
||||
Name: "bzip2",
|
||||
Description: "a freely available, patent free, high-quality data compressor",
|
||||
Website: "https://sourceware.org/bzip2/",
|
||||
|
||||
ID: 237,
|
||||
}
|
||||
}
|
||||
+121
-12
@@ -4,14 +4,120 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
var (
|
||||
_cmake = H("cmake")
|
||||
_ninja = H("ninja")
|
||||
)
|
||||
func (t Toolchain) newCMake() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "4.3.2"
|
||||
checksum = "6QylwRVKletndTSkZTV2YBRwgd_9rUVgav_QW23HpjUgV21AVYZOUOal8tdBDmO7"
|
||||
)
|
||||
return t.NewPackage("cmake", version, newFromGitHubRelease(
|
||||
"Kitware/CMake",
|
||||
"v"+version,
|
||||
"cmake-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
// test suite expects writable source tree
|
||||
Writable: true,
|
||||
|
||||
// CMakeHelper builds and tests a CMake project with specified CACHE entries.
|
||||
// expected to be writable in the copy made during bootstrap
|
||||
Chmod: true,
|
||||
|
||||
Patches: []KV{
|
||||
{"bootstrap-test-no-openssl", `diff --git a/Tests/BootstrapTest.cmake b/Tests/BootstrapTest.cmake
|
||||
index 137de78bc1..b4da52e664 100644
|
||||
--- a/Tests/BootstrapTest.cmake
|
||||
+++ b/Tests/BootstrapTest.cmake
|
||||
@@ -9,7 +9,7 @@ if(NOT nproc EQUAL 0)
|
||||
endif()
|
||||
message(STATUS "running bootstrap: ${bootstrap} ${ninja_arg} ${parallel_arg}")
|
||||
execute_process(
|
||||
- COMMAND ${bootstrap} ${ninja_arg} ${parallel_arg}
|
||||
+ COMMAND ${bootstrap} ${ninja_arg} ${parallel_arg} -- -DCMAKE_USE_OPENSSL=OFF
|
||||
WORKING_DIRECTORY "${bin_dir}"
|
||||
RESULT_VARIABLE result
|
||||
)
|
||||
`},
|
||||
|
||||
{"disable-broken-tests-musl", `diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt
|
||||
index 2ead810437..f85cbb8b1c 100644
|
||||
--- a/Tests/CMakeLists.txt
|
||||
+++ b/Tests/CMakeLists.txt
|
||||
@@ -384,7 +384,6 @@ if(BUILD_TESTING)
|
||||
add_subdirectory(CMakeLib)
|
||||
endif()
|
||||
add_subdirectory(CMakeOnly)
|
||||
- add_subdirectory(RunCMake)
|
||||
|
||||
add_subdirectory(FindPackageModeMakefileTest)
|
||||
|
||||
@@ -528,9 +527,6 @@ if(BUILD_TESTING)
|
||||
-DCMake_TEST_CUDA:BOOL=${CMake_TEST_CUDA}
|
||||
-DCMake_INSTALL_NAME_TOOL_BUG:BOOL=${CMake_INSTALL_NAME_TOOL_BUG}
|
||||
)
|
||||
- ADD_TEST_MACRO(ExportImport ExportImport)
|
||||
- set_property(TEST ExportImport APPEND
|
||||
- PROPERTY LABELS "CUDA")
|
||||
ADD_TEST_MACRO(Unset Unset)
|
||||
ADD_TEST_MACRO(PolicyScope PolicyScope)
|
||||
ADD_TEST_MACRO(EmptyLibrary EmptyLibrary)
|
||||
@@ -624,7 +620,6 @@ if(BUILD_TESTING)
|
||||
# run test for BundleUtilities on supported platforms/compilers
|
||||
if((MSVC OR
|
||||
MINGW OR
|
||||
- CMAKE_SYSTEM_NAME MATCHES "Linux" OR
|
||||
CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
AND NOT CMAKE_GENERATOR STREQUAL "Watcom WMake")
|
||||
|
||||
@@ -3095,10 +3090,6 @@ if(BUILD_TESTING)
|
||||
"${CMake_SOURCE_DIR}/Tests/CTestTestFdSetSize/test.cmake.in"
|
||||
"${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/test.cmake"
|
||||
@ONLY ESCAPE_QUOTES)
|
||||
- add_test(CTestTestFdSetSize ${CMAKE_CTEST_COMMAND}
|
||||
- -S "${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/test.cmake" -j20 -V --timeout 120
|
||||
- --output-log "${CMake_BINARY_DIR}/Tests/CTestTestFdSetSize/testOutput.log"
|
||||
- )
|
||||
|
||||
if(CMAKE_TESTS_CDASH_SERVER)
|
||||
set(regex "^([^:]+)://([^/]+)(.*)$")
|
||||
`},
|
||||
},
|
||||
}, &MakeHelper{
|
||||
OmitDefaults: true,
|
||||
|
||||
ConfigureName: "/usr/src/cmake/bootstrap",
|
||||
Configure: []KV{
|
||||
{"prefix", "/system"},
|
||||
{"parallel", jobsE},
|
||||
{"--"},
|
||||
{"-DCMAKE_USE_OPENSSL", "OFF"},
|
||||
{"-DCMake_TEST_NO_NETWORK", "ON"},
|
||||
},
|
||||
Check: []string{
|
||||
"CTEST_OUTPUT_ON_FAILURE=1",
|
||||
"CTEST_PARALLEL_LEVEL=128",
|
||||
"test",
|
||||
},
|
||||
},
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[CMake] = Metadata{
|
||||
f: Toolchain.newCMake,
|
||||
|
||||
Name: "cmake",
|
||||
Description: "cross-platform, open-source build system",
|
||||
Website: "https://cmake.org/",
|
||||
|
||||
ID: 306,
|
||||
}
|
||||
}
|
||||
|
||||
// CMakeHelper is the [CMake] build system helper.
|
||||
type CMakeHelper struct {
|
||||
// Path elements joined with source.
|
||||
Append []string
|
||||
@@ -34,12 +140,12 @@ type CMakeHelper struct {
|
||||
|
||||
var _ Helper = new(CMakeHelper)
|
||||
|
||||
// extra returns the cmake handle alongside either ninja or make.
|
||||
// extra returns a hardcoded slice of [CMake] and [Ninja].
|
||||
func (attr *CMakeHelper) extra(int) P {
|
||||
if attr != nil && attr.Make {
|
||||
return P{_cmake, _make}
|
||||
return P{CMake, Make}
|
||||
}
|
||||
return P{_cmake, _ninja}
|
||||
return P{CMake, Ninja}
|
||||
}
|
||||
|
||||
// wantsChmod returns false.
|
||||
@@ -51,11 +157,14 @@ func (*CMakeHelper) wantsWrite() bool { return false }
|
||||
// scriptEarly returns the zero value.
|
||||
func (*CMakeHelper) scriptEarly() string { return "" }
|
||||
|
||||
// createDir returns true.
|
||||
func (*CMakeHelper) createDir() bool { return true }
|
||||
|
||||
// wantsDir returns a hardcoded, deterministic pathname.
|
||||
func (*CMakeHelper) wantsDir() (string, bool) { return "/cure/", true }
|
||||
func (*CMakeHelper) wantsDir() string { return "/cure/" }
|
||||
|
||||
// script generates the cure script.
|
||||
func (attr *CMakeHelper) script(t Toolchain, name string) string {
|
||||
func (attr *CMakeHelper) script(name string) string {
|
||||
if attr == nil {
|
||||
attr = new(CMakeHelper)
|
||||
}
|
||||
@@ -71,7 +180,7 @@ func (attr *CMakeHelper) script(t Toolchain, name string) string {
|
||||
}
|
||||
|
||||
script := attr.Script
|
||||
if !attr.SkipTest && t.opts&OptSkipCheck == 0 {
|
||||
if !attr.SkipTest && presetOpts&OptSkipCheck == 0 {
|
||||
script += "\n" + test
|
||||
}
|
||||
|
||||
@@ -98,6 +207,6 @@ cmake -G ` + generate + ` \
|
||||
-DCMAKE_INSTALL_PREFIX=/system \
|
||||
'/usr/src/` + name + `/` + filepath.Join(attr.Append...) + `'
|
||||
cmake --build . --parallel=` + jobsE + `
|
||||
DESTDIR=/work cmake --install .
|
||||
cmake --install . --prefix=/work/system
|
||||
` + script
|
||||
}
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
musl does not implement res_ninit
|
||||
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, newTar(
|
||||
"https://git.kernel.org/pub/scm/network/connman/connman.git/"+
|
||||
"snapshot/connman-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"alpine-musl-res", `musl does not implement res_ninit
|
||||
|
||||
--- a/gweb/gresolv.c
|
||||
+++ b/gweb/gresolv.c
|
||||
@@ -57,3 +73,37 @@ musl does not implement res_ninit
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newCurl() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "8.20.0"
|
||||
checksum = "xyHXwrngIRGMasuzhn-I5MSCOhktwINbsWt1f_LuR-5jRVvyx_g6U1EQfDLEbr9r"
|
||||
)
|
||||
return t.NewPackage("curl", version, newTar(
|
||||
"https://curl.se/download/curl-"+version+".tar.bz2",
|
||||
checksum,
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
// remove broken test
|
||||
Writable: true,
|
||||
ScriptEarly: `
|
||||
chmod +w tests/data && rm -f tests/data/test459
|
||||
`,
|
||||
}, &MakeHelper{
|
||||
Configure: []KV{
|
||||
{"with-openssl"},
|
||||
{"with-ca-bundle", "/system/etc/ssl/certs/ca-bundle.crt"},
|
||||
|
||||
{"disable-smb"},
|
||||
},
|
||||
Check: []string{
|
||||
"TFLAGS=" + jobsLFlagE,
|
||||
"test-nonflaky",
|
||||
},
|
||||
},
|
||||
Perl,
|
||||
Python,
|
||||
PkgConfig,
|
||||
Diffutils,
|
||||
|
||||
Libpsl,
|
||||
OpenSSL,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Curl] = Metadata{
|
||||
f: Toolchain.newCurl,
|
||||
|
||||
Name: "curl",
|
||||
Description: "command line tool and library for transferring data with URLs",
|
||||
Website: "https://curl.se/",
|
||||
|
||||
Dependencies: P{
|
||||
Libpsl,
|
||||
OpenSSL,
|
||||
},
|
||||
|
||||
ID: 381,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
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, newFromGitLab(
|
||||
"gitlab.freedesktop.org",
|
||||
"dbus/dbus",
|
||||
"dbus-"+version,
|
||||
checksum,
|
||||
), &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,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newXDGDBusProxy() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.1.7"
|
||||
checksum = "UW5Pe-TP-XAaN-kTbxrkOQ7eYdmlAQlr2pdreLtPT0uwdAz-7rzDP8V_8PWuZBup"
|
||||
)
|
||||
return t.NewPackage("xdg-dbus-proxy", version, newFromGitHub(
|
||||
"flatpak/xdg-dbus-proxy",
|
||||
version,
|
||||
checksum,
|
||||
), nil, &MesonHelper{
|
||||
Setup: []KV{
|
||||
{"Dman", "disabled"},
|
||||
},
|
||||
},
|
||||
DBus,
|
||||
|
||||
GLib,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[XDGDBusProxy] = Metadata{
|
||||
f: Toolchain.newXDGDBusProxy,
|
||||
|
||||
Name: "xdg-dbus-proxy",
|
||||
Description: "a filtering proxy for D-Bus connections",
|
||||
Website: "https://github.com/flatpak/xdg-dbus-proxy",
|
||||
|
||||
Dependencies: P{
|
||||
GLib,
|
||||
},
|
||||
|
||||
ID: 58434,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newDTC() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.7.2"
|
||||
checksum = "vUoiRynPyYRexTpS6USweT5p4SVHvvVJs8uqFkkVD-YnFjwf6v3elQ0-Etrh00Dt"
|
||||
)
|
||||
return t.NewPackage("dtc", version, newTar(
|
||||
"https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/"+
|
||||
"dtc-v"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
// works around buggy test:
|
||||
// fdtdump-runtest.sh /usr/src/dtc/tests/fdtdump.dts
|
||||
Writable: true,
|
||||
Chmod: true,
|
||||
}, &MesonHelper{
|
||||
Setup: []KV{
|
||||
{"Dyaml", "disabled"},
|
||||
{"Dstatic-build", "true"},
|
||||
},
|
||||
},
|
||||
Flex,
|
||||
Bison,
|
||||
M4,
|
||||
Coreutils,
|
||||
Diffutils,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[DTC] = Metadata{
|
||||
f: Toolchain.newDTC,
|
||||
|
||||
Name: "dtc",
|
||||
Description: "The Device Tree Compiler",
|
||||
Website: "https://git.kernel.org/pub/scm/utils/dtc/dtc.git/",
|
||||
|
||||
ID: 16911,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newElfutils() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.195"
|
||||
checksum = "JrGnBD38w8Mj0ZxDw3fKlRBFcLvRKu8rcYnX35R9yTlUSYnzTazyLboG-a2CsJlu"
|
||||
)
|
||||
return t.NewPackage("elfutils", version, newTar(
|
||||
"https://sourceware.org/elfutils/ftp/"+
|
||||
version+"/elfutils-"+version+".tar.bz2",
|
||||
checksum,
|
||||
pkg.TarBzip2,
|
||||
), &PackageAttr{
|
||||
Env: []string{
|
||||
"CC=cc" +
|
||||
// nonstandard glibc extension
|
||||
" -DFNM_EXTMATCH=0",
|
||||
},
|
||||
}, &MakeHelper{
|
||||
// nonstandard glibc extension
|
||||
SkipCheck: true,
|
||||
|
||||
Configure: []KV{
|
||||
{"enable-deterministic-archives"},
|
||||
},
|
||||
},
|
||||
M4,
|
||||
PkgConfig,
|
||||
|
||||
Zlib,
|
||||
Bzip2,
|
||||
Zstd,
|
||||
ArgpStandalone,
|
||||
MuslFts,
|
||||
MuslObstack,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Elfutils] = Metadata{
|
||||
f: Toolchain.newElfutils,
|
||||
|
||||
Name: "elfutils",
|
||||
Description: "utilities and libraries to handle ELF files and DWARF data",
|
||||
Website: "https://sourceware.org/elfutils/",
|
||||
|
||||
Dependencies: P{
|
||||
Zlib,
|
||||
Bzip2,
|
||||
Zstd,
|
||||
MuslFts,
|
||||
MuslObstack,
|
||||
},
|
||||
|
||||
ID: 5679,
|
||||
}
|
||||
}
|
||||
+24
-23
@@ -15,13 +15,8 @@ import (
|
||||
// [Toolchain]. This silences test suites expecting certain standard files to be
|
||||
// available in /etc.
|
||||
type cureEtc struct {
|
||||
// Whether to exclude ianaEtc.
|
||||
minimal bool
|
||||
}
|
||||
|
||||
// NewEtc returns a [pkg.Artifact] containing deterministic elements of /etc.
|
||||
func NewEtc(minimal bool) pkg.Artifact {
|
||||
return cureEtc{minimal}
|
||||
// Optional via newIANAEtc.
|
||||
iana pkg.Artifact
|
||||
}
|
||||
|
||||
// Cure writes hardcoded configuration to files under etc.
|
||||
@@ -50,8 +45,8 @@ nobody:x:65534:
|
||||
}
|
||||
}
|
||||
|
||||
if !a.minimal {
|
||||
iana, _ := t.GetArtifact(ianaEtc)
|
||||
if a.iana != nil {
|
||||
iana, _ := t.GetArtifact(a.iana)
|
||||
|
||||
buf := make([]byte, syscall.Getpagesize()<<3)
|
||||
for _, name := range []string{
|
||||
@@ -95,7 +90,7 @@ func (cureEtc) Kind() pkg.Kind { return kindEtc }
|
||||
|
||||
// Params writes whether iana-etc is populated.
|
||||
func (a cureEtc) Params(ctx *pkg.IContext) {
|
||||
if !a.minimal {
|
||||
if a.iana != nil {
|
||||
ctx.WriteUint32(1)
|
||||
} else {
|
||||
ctx.WriteUint32(0)
|
||||
@@ -105,8 +100,8 @@ func (a cureEtc) Params(ctx *pkg.IContext) {
|
||||
func init() {
|
||||
pkg.Register(kindEtc, func(r *pkg.IRReader) pkg.Artifact {
|
||||
a := cureEtc{}
|
||||
if r.ReadUint32() == 0 {
|
||||
a.minimal = true
|
||||
if r.ReadUint32() != 0 {
|
||||
a.iana = r.Next()
|
||||
}
|
||||
if _, ok := r.Finalise(); ok {
|
||||
panic(pkg.ErrUnexpectedChecksum)
|
||||
@@ -120,28 +115,34 @@ func (cureEtc) IsExclusive() bool { return false }
|
||||
|
||||
// Dependencies returns a slice containing the backing iana-etc release.
|
||||
func (a cureEtc) Dependencies() []pkg.Artifact {
|
||||
if !a.minimal {
|
||||
return []pkg.Artifact{ianaEtc}
|
||||
if a.iana != nil {
|
||||
return []pkg.Artifact{a.iana}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a hardcoded reporting name.
|
||||
func (a cureEtc) String() string {
|
||||
if a.minimal {
|
||||
if a.iana == nil {
|
||||
return "cure-etc-minimal"
|
||||
}
|
||||
return "cure-etc"
|
||||
}
|
||||
|
||||
// ianaEtc is an unpacked iana-etc release.
|
||||
var ianaEtc = newFromGitHubRelease(
|
||||
"Mic92/iana-etc",
|
||||
"20251215",
|
||||
"iana-etc-20251215.tar.gz",
|
||||
"kvKz0gW_rGG5QaNK9ZWmWu1IEgYAdmhj_wR7DYrh3axDfIql_clGRHmelP7525NJ",
|
||||
pkg.Gzip,
|
||||
)
|
||||
// newIANAEtc returns an unpacked iana-etc release.
|
||||
func newIANAEtc() pkg.Artifact {
|
||||
const (
|
||||
version = "20251215"
|
||||
checksum = "kvKz0gW_rGG5QaNK9ZWmWu1IEgYAdmhj_wR7DYrh3axDfIql_clGRHmelP7525NJ"
|
||||
)
|
||||
return newFromGitHubRelease(
|
||||
"Mic92/iana-etc",
|
||||
version,
|
||||
"iana-etc-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
resolvconfPath pkg.ExecPath
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newFakeroot() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.37.2"
|
||||
checksum = "4ve-eDqVspzQ6VWDhPS0NjW3aSenBJcPAJq_BFT7OOFgUdrQzoTBxZWipDAGWxF8"
|
||||
)
|
||||
return t.NewPackage("fakeroot", version, newFromGitLab(
|
||||
"salsa.debian.org",
|
||||
"clint/fakeroot",
|
||||
"upstream/"+version,
|
||||
checksum,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"remove-broken-docs", `diff --git a/doc/Makefile.am b/doc/Makefile.am
|
||||
index f135ad9..85c784c 100644
|
||||
--- a/doc/Makefile.am
|
||||
+++ b/doc/Makefile.am
|
||||
@@ -1,5 +1,4 @@
|
||||
AUTOMAKE_OPTIONS=foreign
|
||||
-SUBDIRS = de es fr nl pt ro sv
|
||||
|
||||
man_MANS = faked.1 fakeroot.1
|
||||
|
||||
`},
|
||||
},
|
||||
|
||||
Env: []string{
|
||||
"CONFIG_SHELL=/bin/sh",
|
||||
},
|
||||
}, &MakeHelper{
|
||||
Generate: "./bootstrap",
|
||||
|
||||
// makes assumptions about /etc/passwd
|
||||
SkipCheck: true,
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
PkgConfig,
|
||||
|
||||
Attr,
|
||||
Libcap,
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Fakeroot] = Metadata{
|
||||
f: Toolchain.newFakeroot,
|
||||
|
||||
Name: "fakeroot",
|
||||
Description: "tool for simulating superuser privileges",
|
||||
Website: "https://salsa.debian.org/clint/fakeroot",
|
||||
|
||||
ID: 12048,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func (t Toolchain) newFlex() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.6.4"
|
||||
checksum = "p9POjQU7VhgOf3x5iFro8fjhy0NOanvA7CTeuWS_veSNgCixIJshTrWVkc5XLZkB"
|
||||
)
|
||||
return t.NewPackage("flex", version, newFromGitHubRelease(
|
||||
"westes/flex",
|
||||
"v"+version,
|
||||
"flex-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil),
|
||||
M4,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Flex] = Metadata{
|
||||
f: Toolchain.newFlex,
|
||||
|
||||
Name: "flex",
|
||||
Description: "scanner generator for lexing in C and C++",
|
||||
Website: "https://github.com/westes/flex/",
|
||||
|
||||
ID: 819,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newFontConfig() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.15.0"
|
||||
checksum = "rvlTL_zn-RwVkTBFdsOacUmEdLmwa3whTkuTzV8JH7kKbsFmnbG2qof3hN9ghr9d"
|
||||
)
|
||||
return t.NewPackage("fontconfig", version, newFromGitLab("gitlab.freedesktop.org", "fontconfig/fontconfig", version, checksum),
|
||||
nil,
|
||||
(*MesonHelper)(nil),
|
||||
Gperf,
|
||||
Freetype,
|
||||
Libexpat,
|
||||
Python,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[FontConfig] = Metadata{
|
||||
f: Toolchain.newFontConfig,
|
||||
Name: "fontconfig",
|
||||
Description: "freedesktop font discovery",
|
||||
Website: "https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-user.html",
|
||||
ID: 827,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newCairo() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.18.4"
|
||||
checksum = "_Ztw0ttmYLIiElqS27x2Iu4EneXOMNHp2or6bJA6suLeF3NRvORzUMPPQy9Wr5gT"
|
||||
)
|
||||
return t.NewPackage("cairo", version, newFromGitLab("gitlab.freedesktop.org", "cairo/cairo", version, checksum),
|
||||
nil,
|
||||
&MesonHelper{
|
||||
SkipTest: true,
|
||||
Setup: []KV{{"Dfreetype", "enabled"}, {"Dfontconfig", "enabled"}},
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
Libpng,
|
||||
PkgConfig,
|
||||
FontConfig,
|
||||
Libexpat,
|
||||
Freetype,
|
||||
Pixman,
|
||||
Gawk,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Cairo] = Metadata{
|
||||
f: Toolchain.newCairo,
|
||||
Name: "cairo",
|
||||
Description: "freedesktop 2d rendering library",
|
||||
Website: "https://cairographics.org",
|
||||
ID: 247,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newHarfbuzz() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "14.2.0"
|
||||
checksum = "2VgjUmcPeIbleafZaGk5l7iGnag2qj0HTqrJ5770GzivebryZ2pbwVIha5j_24PH"
|
||||
)
|
||||
return t.NewPackage("harfbuzz", version, newFromGitHub("harfbuzz/harfbuzz", version, checksum),
|
||||
nil,
|
||||
(*MesonHelper)(nil),
|
||||
Freetype,
|
||||
GLib,
|
||||
Cairo,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[HarfBuzz] = Metadata{
|
||||
f: Toolchain.newHarfbuzz,
|
||||
Name: "harfbuzz",
|
||||
Description: "text shaping library",
|
||||
Website: "https://harfbuzz.github.io",
|
||||
ID: 1299,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newFribidi() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.0.16"
|
||||
checksum = "Sn9hwdgViXW9_MOi8ovAcBgQy6s6o4cc0o260k76nqCRzxmxxD1-B37GVkJ2EoMy"
|
||||
)
|
||||
return t.NewPackage("fribidi", version, newFromGitHub("fribidi/fribidi", "v"+version, checksum),
|
||||
nil,
|
||||
&MesonHelper{
|
||||
Setup: []KV{[2]string{"Ddocs", "false"}},
|
||||
},
|
||||
PkgConfig,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Fribidi] = Metadata{
|
||||
f: Toolchain.newFribidi,
|
||||
Name: "fribidi",
|
||||
Description: "bidirectional text library",
|
||||
Website: "https://www.gnu.org/software/fribidi/",
|
||||
ID: 857,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newLibdatrie() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.2.14"
|
||||
checksum = "JnJBEFrA8xYscXOSRJNGb1ZyRqrdsfP1_sdRIQeeSujdcsxmYT5Yl9m4byTsLozp"
|
||||
)
|
||||
return t.NewPackage("libdatrie", version, newFromGitHub("tlwg/libdatrie", "v"+version, checksum),
|
||||
nil,
|
||||
&MakeHelper{
|
||||
Generate: "sh autogen.sh",
|
||||
},
|
||||
Libtool,
|
||||
Automake,
|
||||
M4,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libdatrie] = Metadata{
|
||||
f: Toolchain.newLibdatrie,
|
||||
Name: "libdatrie",
|
||||
Description: "double-array trie library",
|
||||
Website: "https://linux.thai.net/~thep/datrie/datrie.html",
|
||||
ID: 7342,
|
||||
}
|
||||
}
|
||||
func (t Toolchain) newLibThai() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "0.1.30"
|
||||
checksum = "jQxUviF-lSiVrnqXebc4kROp0hrIeQbke2gvaeTfkCDp-wHkpxb3xP18fsNOF_F-"
|
||||
)
|
||||
return t.NewPackage("libthai", version, newFromGitHub("tlwg/libthai", "v"+version, checksum),
|
||||
nil,
|
||||
&MakeHelper{
|
||||
Generate: "sh autogen.sh",
|
||||
},
|
||||
Libtool,
|
||||
Automake,
|
||||
M4,
|
||||
Libdatrie,
|
||||
PkgConfig,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Libthai] = Metadata{
|
||||
f: Toolchain.newLibThai,
|
||||
Name: "libthai",
|
||||
Description: "thai language handling library",
|
||||
Website: "https://linux.thai.net/",
|
||||
|
||||
ID: 1737,
|
||||
}
|
||||
}
|
||||
func (t Toolchain) newPango() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.56.3"
|
||||
checksum = "O73sc8axQyjIFpW4XkV7wi4HVSvmdUnx1uTn-HtbewXgrCVWX9Du-pssL5BK7rf1"
|
||||
)
|
||||
return t.NewPackage("pango", version, newFromGitLab("gitlab.gnome.org", "GNOME/pango", version, checksum),
|
||||
nil,
|
||||
&MesonHelper{
|
||||
SkipTest: true,
|
||||
},
|
||||
GLib,
|
||||
GObjectIntrospection,
|
||||
Freetype,
|
||||
PkgConfig,
|
||||
FontConfig,
|
||||
HarfBuzz,
|
||||
Fribidi,
|
||||
Cairo,
|
||||
Pixman,
|
||||
Libpng,
|
||||
Libexpat,
|
||||
Libdatrie,
|
||||
Libthai,
|
||||
Libtool,
|
||||
LibX11,
|
||||
LibXrender,
|
||||
LibXft,
|
||||
Diffutils,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Pango] = Metadata{
|
||||
f: Toolchain.newPango,
|
||||
Name: "pango",
|
||||
Description: "text rendering library",
|
||||
Website: "https://pango.org",
|
||||
ID: 11783,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newFreetype() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.14.3"
|
||||
checksum = "-WfLv8fVJNyCHpP_lriiDzOcVbBL9ajdQ3tl8AzIIUa9-8sVpU9irxOmSMgRHWYz"
|
||||
)
|
||||
return t.NewPackage("freetype", version, newTar(
|
||||
"https://download.savannah.gnu.org/releases/freetype/"+
|
||||
"freetype-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), nil, (*MakeHelper)(nil)), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Freetype] = Metadata{
|
||||
f: Toolchain.newFreetype,
|
||||
|
||||
Name: "freetype",
|
||||
Description: "a freely available software library to render fonts",
|
||||
Website: "http://www.freetype.org/",
|
||||
|
||||
ID: 854,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package rosa
|
||||
|
||||
import "hakurei.app/internal/pkg"
|
||||
|
||||
func (t Toolchain) newFuse() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "3.18.2"
|
||||
checksum = "iL-7b7eUtmlVSf5cSq0dzow3UiqSjBmzV3cI_ENPs1tXcHdktkG45j1V12h-4jZe"
|
||||
)
|
||||
return t.NewPackage("fuse", version, newFromGitHubRelease(
|
||||
"libfuse/libfuse",
|
||||
"fuse-"+version,
|
||||
"fuse-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), nil, &MesonHelper{
|
||||
Setup: []KV{
|
||||
{"Ddefault_library", "both"},
|
||||
{"Dtests", "true"},
|
||||
{"Duseroot", "false"},
|
||||
{"Dinitscriptdir", "/system/etc"},
|
||||
},
|
||||
|
||||
ScriptCompiled: "python3 -m pytest test/",
|
||||
// this project uses pytest
|
||||
SkipTest: true,
|
||||
},
|
||||
PythonPyTest,
|
||||
|
||||
KernelHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Fuse] = Metadata{
|
||||
f: Toolchain.newFuse,
|
||||
|
||||
Name: "fuse",
|
||||
Description: "the reference implementation of the Linux FUSE interface",
|
||||
Website: "https://github.com/libfuse/libfuse/",
|
||||
|
||||
ID: 861,
|
||||
}
|
||||
}
|
||||
+105
-7
@@ -7,10 +7,103 @@ import (
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
var (
|
||||
_git = H("git")
|
||||
_nssCACert = H("nss-cacert")
|
||||
)
|
||||
func (t Toolchain) newGit() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.54.0"
|
||||
checksum = "7vGKtFOJGqY8DO4e8UMRax7dLgImXKQz5MMalec6MlgYrsarffSJjgOughwRFpSH"
|
||||
)
|
||||
return t.NewPackage("git", version, newTar(
|
||||
"https://www.kernel.org/pub/software/scm/git/"+
|
||||
"git-"+version+".tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
), &PackageAttr{
|
||||
ScriptEarly: `
|
||||
ln -s ../../system/bin/perl /usr/bin/ || true
|
||||
|
||||
# test suite assumes apache
|
||||
rm -f /system/bin/httpd
|
||||
`,
|
||||
|
||||
// uses source tree as scratch space
|
||||
EnterSource: true,
|
||||
}, &MakeHelper{
|
||||
InPlace: true,
|
||||
Generate: "make configure",
|
||||
ScriptMakeEarly: `
|
||||
function disable_test {
|
||||
local test=$1 pattern=${2:-''}
|
||||
if [ $# -eq 1 ]; then
|
||||
rm "t/${test}.sh"
|
||||
else
|
||||
sed -i "t/${test}.sh" \
|
||||
-e "/^\s*test_expect_.*$pattern/,/^\s*' *\$/{s/^/: #/}"
|
||||
fi
|
||||
}
|
||||
|
||||
disable_test t1800-hook
|
||||
disable_test t5319-multi-pack-index
|
||||
disable_test t1305-config-include
|
||||
disable_test t3900-i18n-commit
|
||||
disable_test t3507-cherry-pick-conflict
|
||||
disable_test t4201-shortlog
|
||||
disable_test t5303-pack-corruption-resilience
|
||||
disable_test t4301-merge-tree-write-tree
|
||||
disable_test t8005-blame-i18n
|
||||
disable_test t9350-fast-export
|
||||
disable_test t9300-fast-import
|
||||
disable_test t0211-trace2-perf
|
||||
disable_test t1517-outside-repo
|
||||
disable_test t2200-add-update
|
||||
disable_test t0027-auto-crlf
|
||||
disable_test t7513-interpret-trailers
|
||||
disable_test t7703-repack-geometric
|
||||
disable_test t7002-mv-sparse-checkout
|
||||
disable_test t1451-fsck-buffer
|
||||
disable_test t4104-apply-boundary
|
||||
disable_test t4200-rerere
|
||||
disable_test t5515-fetch-merge-logic
|
||||
`,
|
||||
Check: []string{
|
||||
"-C t",
|
||||
`GIT_PROVE_OPTS="--jobs 32 --failures"`,
|
||||
"prove",
|
||||
},
|
||||
Install: `make \
|
||||
` + jobsFlagE + ` \
|
||||
DESTDIR=/work \
|
||||
NO_INSTALL_HARDLINKS=1 \
|
||||
install`,
|
||||
},
|
||||
// test suite hangs on mksh
|
||||
Bash,
|
||||
|
||||
Diffutils,
|
||||
Autoconf,
|
||||
Gettext,
|
||||
|
||||
Zlib,
|
||||
Curl,
|
||||
Libexpat,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Git] = Metadata{
|
||||
f: Toolchain.newGit,
|
||||
|
||||
Name: "git",
|
||||
Description: "distributed version control system",
|
||||
Website: "https://www.git-scm.com/",
|
||||
|
||||
Dependencies: P{
|
||||
Zlib,
|
||||
Curl,
|
||||
Libexpat,
|
||||
},
|
||||
|
||||
ID: 5350,
|
||||
}
|
||||
}
|
||||
|
||||
// NewViaGit returns a [pkg.Artifact] for cloning a git repository.
|
||||
func (t Toolchain) NewViaGit(
|
||||
@@ -20,9 +113,9 @@ func (t Toolchain) NewViaGit(
|
||||
return t.New(strings.TrimSuffix(
|
||||
path.Base(url),
|
||||
".git",
|
||||
)+"-src-"+path.Base(rev), THostNet, t.Append(nil,
|
||||
_nssCACert,
|
||||
_git,
|
||||
)+"-src-"+path.Base(rev), THostNet, t.AppendPresets(nil,
|
||||
NSSCACert,
|
||||
Git,
|
||||
), &checksum, nil, `
|
||||
git \
|
||||
-c advice.detachedHead=false \
|
||||
@@ -36,3 +129,8 @@ git \
|
||||
rm -rf /work/.git
|
||||
`, resolvconf())
|
||||
}
|
||||
|
||||
// newTagRemote is a helper around NewViaGit for a tag on a git remote.
|
||||
func (t Toolchain) newTagRemote(url, tag, checksum string) pkg.Artifact {
|
||||
return t.NewViaGit(url, "refs/tags/"+tag, mustDecode(checksum))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func (t Toolchain) newSPIRVHeaders() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.4.341.0"
|
||||
checksum = "0PL43-19Iaw4k7_D8J8BvoJ-iLgCVSYZ2ThgDPGfAJwIJFtre7l0cnQtLjcY-JvD"
|
||||
)
|
||||
return t.NewPackage("spirv-headers", version, newFromGitHub(
|
||||
"KhronosGroup/SPIRV-Headers",
|
||||
"vulkan-sdk-"+version,
|
||||
checksum,
|
||||
), nil, &CMakeHelper{
|
||||
// upstream has no tests
|
||||
SkipTest: true,
|
||||
}), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[SPIRVHeaders] = Metadata{
|
||||
f: Toolchain.newSPIRVHeaders,
|
||||
|
||||
Name: "spirv-headers",
|
||||
Description: "machine-readable files for the SPIR-V Registry",
|
||||
Website: "https://github.com/KhronosGroup/SPIRV-Headers",
|
||||
|
||||
ID: 230542,
|
||||
|
||||
// upstream changed version scheme, anitya incapable of filtering them
|
||||
latest: func(v *Versions) string {
|
||||
for _, s := range v.Stable {
|
||||
fields := strings.SplitN(s, ".", 4)
|
||||
if len(fields) != 4 {
|
||||
continue
|
||||
}
|
||||
if slices.ContainsFunc(fields, func(f string) bool {
|
||||
return slices.ContainsFunc([]byte(f), func(d byte) bool {
|
||||
return d < '0' || d > '9'
|
||||
})
|
||||
}) {
|
||||
continue
|
||||
}
|
||||
return s
|
||||
}
|
||||
return v.Latest
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newSPIRVTools() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2026.1"
|
||||
checksum = "ZSQPQx8NltCDzQLk4qlaVxyWRWeI_JtsjEpeFt3kezTanl9DTHfLixSUCezMFBjv"
|
||||
)
|
||||
return t.NewPackage("spirv-tools", version, newFromGitHub(
|
||||
"KhronosGroup/SPIRV-Tools",
|
||||
"v"+version,
|
||||
checksum,
|
||||
), nil, &CMakeHelper{
|
||||
Cache: []KV{
|
||||
{"SPIRV-Headers_SOURCE_DIR", "/system"},
|
||||
},
|
||||
},
|
||||
Python,
|
||||
|
||||
SPIRVHeaders,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[SPIRVTools] = Metadata{
|
||||
f: Toolchain.newSPIRVTools,
|
||||
|
||||
Name: "spirv-tools",
|
||||
Description: "an API and commands for processing SPIR-V modules",
|
||||
Website: "https://github.com/KhronosGroup/SPIRV-Tools",
|
||||
|
||||
Dependencies: P{
|
||||
SPIRVHeaders,
|
||||
},
|
||||
|
||||
ID: 14894,
|
||||
|
||||
latest: (*Versions).getStable,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newGlslang() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "16.3.0"
|
||||
checksum = "xyqDf8k3-D0_BXHGi0uLgMglnJ05Rf3j73QgbDs3sGtKNdBIQhY8JfqX1NcNoJQN"
|
||||
)
|
||||
return t.NewPackage("glslang", version, newFromGitHub(
|
||||
"KhronosGroup/glslang",
|
||||
version,
|
||||
checksum,
|
||||
), &PackageAttr{
|
||||
// test suite writes to source
|
||||
Writable: true,
|
||||
Chmod: true,
|
||||
}, &CMakeHelper{
|
||||
Cache: []KV{
|
||||
{"BUILD_SHARED_LIBS", "ON"},
|
||||
{"ALLOW_EXTERNAL_SPIRV_TOOLS", "ON"},
|
||||
},
|
||||
},
|
||||
Python,
|
||||
Bash,
|
||||
Diffutils,
|
||||
|
||||
SPIRVTools,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Glslang] = Metadata{
|
||||
f: Toolchain.newGlslang,
|
||||
|
||||
Name: "glslang",
|
||||
Description: "reference front end for GLSL/ESSL",
|
||||
Website: "https://github.com/KhronosGroup/glslang",
|
||||
|
||||
ID: 205796,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newSPIRVLLVMTranslator() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "22.1.2"
|
||||
checksum = "JZAaV5ewYcm-35YA_U2BM2IcsQouZtX1BLZR0zh2vSlfEXMsT5OCtY4Gh5RJkcGy"
|
||||
)
|
||||
skipChecks := []string{
|
||||
// error: line 13: OpTypeCooperativeMatrixKHR Scope is limited to Workgroup and Subgroup
|
||||
"cooperative_matrix_constant_null.spvasm",
|
||||
}
|
||||
|
||||
switch arch {
|
||||
case "arm64":
|
||||
skipChecks = append(skipChecks,
|
||||
// LLVM ERROR: unsupported calling convention
|
||||
"DebugInfo/COFF/no-cus.ll",
|
||||
"DebugInfo/Generic/2009-11-05-DeadGlobalVariable.ll",
|
||||
"DebugInfo/Generic/2009-11-10-CurrentFn.ll",
|
||||
"DebugInfo/Generic/2010-01-05-DbgScope.ll",
|
||||
"DebugInfo/Generic/2010-03-12-llc-crash.ll",
|
||||
"DebugInfo/Generic/2010-03-24-MemberFn.ll",
|
||||
"DebugInfo/Generic/2010-04-19-FramePtr.ll",
|
||||
"DebugInfo/Generic/2010-06-29-InlinedFnLocalVar.ll",
|
||||
"DebugInfo/Generic/2010-10-01-crash.ll",
|
||||
"DebugInfo/Generic/PR20038.ll",
|
||||
"DebugInfo/Generic/constant-pointers.ll",
|
||||
"DebugInfo/Generic/dead-argument-order.ll",
|
||||
"DebugInfo/Generic/debug-info-eis-option.ll",
|
||||
"DebugInfo/Generic/def-line.ll",
|
||||
"DebugInfo/Generic/discriminator.ll",
|
||||
"DebugInfo/Generic/dwarf-public-names.ll",
|
||||
"DebugInfo/Generic/enum.ll",
|
||||
"DebugInfo/Generic/func-using-decl.ll",
|
||||
"DebugInfo/Generic/global.ll",
|
||||
"DebugInfo/Generic/imported-name-inlined.ll",
|
||||
"DebugInfo/Generic/incorrect-variable-debugloc1.ll",
|
||||
"DebugInfo/Generic/inline-scopes.ll",
|
||||
"DebugInfo/Generic/inlined-arguments.ll",
|
||||
"DebugInfo/Generic/inlined-vars.ll",
|
||||
"DebugInfo/Generic/linear-dbg-value.ll",
|
||||
"DebugInfo/Generic/linkage-name-abstract.ll",
|
||||
"DebugInfo/Generic/member-order.ll",
|
||||
"DebugInfo/Generic/missing-abstract-variable.ll",
|
||||
"DebugInfo/Generic/multiline.ll",
|
||||
"DebugInfo/Generic/namespace_function_definition.ll",
|
||||
"DebugInfo/Generic/namespace_inline_function_definition.ll",
|
||||
"DebugInfo/Generic/noscopes.ll",
|
||||
"DebugInfo/Generic/ptrsize.ll",
|
||||
"DebugInfo/Generic/restrict.ll",
|
||||
"DebugInfo/Generic/two-cus-from-same-file.ll",
|
||||
"DebugInfo/Generic/version.ll",
|
||||
"DebugInfo/LocalAddressSpace.ll",
|
||||
"DebugInfo/UnknownBaseType.ll",
|
||||
"DebugInfo/expr-opcode.ll",
|
||||
)
|
||||
}
|
||||
|
||||
return t.NewPackage("spirv-llvm-translator", version, newFromGitHub(
|
||||
"KhronosGroup/SPIRV-LLVM-Translator",
|
||||
"v"+version, checksum,
|
||||
), &PackageAttr{
|
||||
Patches: []KV{
|
||||
{"remove-early-prefix", `diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index c000a77e..f18f3fde 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -164,7 +164,7 @@ install(
|
||||
${LLVM_SPIRV_INCLUDE_DIRS}/LLVMSPIRVOpts.h
|
||||
${LLVM_SPIRV_INCLUDE_DIRS}/LLVMSPIRVExtensions.inc
|
||||
DESTINATION
|
||||
- ${CMAKE_INSTALL_PREFIX}/include/LLVMSPIRVLib
|
||||
+ include/LLVMSPIRVLib
|
||||
)
|
||||
|
||||
configure_file(LLVMSPIRVLib.pc.in ${CMAKE_BINARY_DIR}/LLVMSPIRVLib.pc @ONLY)
|
||||
@@ -172,5 +172,5 @@ install(
|
||||
FILES
|
||||
${CMAKE_BINARY_DIR}/LLVMSPIRVLib.pc
|
||||
DESTINATION
|
||||
- ${CMAKE_INSTALL_PREFIX}/lib${LLVM_LIBDIR_SUFFIX}/pkgconfig
|
||||
+ lib${LLVM_LIBDIR_SUFFIX}/pkgconfig
|
||||
)
|
||||
;`},
|
||||
},
|
||||
|
||||
// litArgs emits shell syntax
|
||||
ScriptEarly: `
|
||||
export LIT_OPTS=` + litArgs(true, skipChecks...) + `
|
||||
`,
|
||||
}, &CMakeHelper{
|
||||
Cache: []KV{
|
||||
{"CMAKE_SKIP_BUILD_RPATH", "ON"},
|
||||
{"BUILD_SHARED_LIBS", "ON"},
|
||||
{"LLVM_SPIRV_ENABLE_LIBSPIRV_DIS", "ON"},
|
||||
{"LLVM_EXTERNAL_SPIRV_HEADERS_SOURCE_DIR", "/system"},
|
||||
{"LLVM_EXTERNAL_LIT", "/system/bin/lit"},
|
||||
{"LLVM_INCLUDE_TESTS", "ON"},
|
||||
},
|
||||
},
|
||||
Bash,
|
||||
LIT,
|
||||
|
||||
SPIRVTools,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[SPIRVLLVMTranslator] = Metadata{
|
||||
f: Toolchain.newSPIRVLLVMTranslator,
|
||||
|
||||
Name: "spirv-llvm-translator",
|
||||
Description: "bi-directional translation between SPIR-V and LLVM IR",
|
||||
Website: "https://github.com/KhronosGroup/SPIRV-LLVM-Translator",
|
||||
|
||||
Dependencies: P{
|
||||
SPIRVTools,
|
||||
},
|
||||
|
||||
ID: 227273,
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,18 +11,18 @@ func TestSkipGNUTests(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
tests []int64
|
||||
tests []int
|
||||
want string
|
||||
}{
|
||||
{[]int64{764}, "1-763 765-"},
|
||||
{[]int64{764, 0xcafe, 37, 9}, "1-8 10-36 38-763 765-51965 51967-"},
|
||||
{[]int64{1, 2, 0xbed}, "3-3052 3054-"},
|
||||
{[]int64{3, 4}, "1-2 5-"},
|
||||
{[]int{764}, "1-763 765-"},
|
||||
{[]int{764, 0xcafe, 37, 9}, "1-8 10-36 38-763 765-51965 51967-"},
|
||||
{[]int{1, 2, 0xbed}, "3-3052 3054-"},
|
||||
{[]int{3, 4}, "1-2 5-"},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(strings.Join(slices.Collect(func(yield func(string) bool) {
|
||||
for _, n := range tc.tests {
|
||||
yield(strconv.Itoa(int(n)))
|
||||
yield(strconv.Itoa(n))
|
||||
}
|
||||
}), ","), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
+107
-116
@@ -6,111 +6,89 @@ import (
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
// newGoBootstrap returns the Go bootstrap toolchain.
|
||||
func (t Toolchain) newGoBootstrap() pkg.Artifact {
|
||||
const checksum = "8o9JL_ToiQKadCTb04nvBDkp8O1xiWOolAxVEqaTGodieNe4lOFEjlOxN3bwwe23"
|
||||
return t.New("go1.4-bootstrap", 0, t.AppendPresets(nil,
|
||||
Bash,
|
||||
), nil, []string{
|
||||
"CGO_ENABLED=0",
|
||||
}, `
|
||||
mkdir -p /var/tmp/ /work/system/
|
||||
cp -r /usr/src/go /work/system/
|
||||
cd /work/system/go/src
|
||||
chmod -R +w ..
|
||||
|
||||
./make.bash
|
||||
`, pkg.Path(AbsUsrSrc.Append("go"), false, newTar(
|
||||
"https://dl.google.com/go/go1.4-bootstrap-20171003.tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
)))
|
||||
}
|
||||
|
||||
// newGo returns a specific version of the Go toolchain.
|
||||
func (t Toolchain) newGo(
|
||||
version, checksum string,
|
||||
env []string,
|
||||
script string,
|
||||
boot ...pkg.Artifact,
|
||||
extra ...pkg.Artifact,
|
||||
) pkg.Artifact {
|
||||
return t.NewPackage("go", version, newTar(
|
||||
"https://go.dev/dl/go"+version+".src.tar.gz",
|
||||
checksum,
|
||||
pkg.Gzip,
|
||||
), &PackageAttr{
|
||||
EnterSource: true,
|
||||
Env: slices.Concat([]string{
|
||||
"CC=cc",
|
||||
"GOCACHE=/tmp/gocache",
|
||||
"GOROOT_BOOTSTRAP=/system/go",
|
||||
"TMPDIR=/dev/shm/go",
|
||||
}, env),
|
||||
|
||||
Extra: boot,
|
||||
}, &GenericHelper{
|
||||
InPlace: true,
|
||||
Build: `
|
||||
mkdir /work/system/ "${TMPDIR}"
|
||||
cp -r . /work/system/go
|
||||
cd /work/system/go/src/
|
||||
name := "all"
|
||||
if presetOpts&OptSkipCheck != 0 {
|
||||
name = "make"
|
||||
}
|
||||
return t.New("go"+version, 0, t.AppendPresets(extra,
|
||||
Bash,
|
||||
), nil, slices.Concat([]string{
|
||||
"CC=cc",
|
||||
"GOCACHE=/tmp/gocache",
|
||||
"GOROOT_BOOTSTRAP=/system/go",
|
||||
"TMPDIR=/dev/shm/go",
|
||||
}, env), `
|
||||
mkdir /work/system "${TMPDIR}"
|
||||
cp -r /usr/src/go /work/system
|
||||
cd /work/system/go/src
|
||||
chmod -R +w ..
|
||||
` + script + `
|
||||
set +u
|
||||
. ./make.bash "$@" --no-banner
|
||||
set -u
|
||||
`,
|
||||
Check: "bash run.bash --no-rebuild\n",
|
||||
Install: `
|
||||
../bin/go tool dist banner # print build info
|
||||
`+script+`
|
||||
./`+name+`.bash
|
||||
|
||||
mkdir /work/system/bin
|
||||
ln -s \
|
||||
../go/bin/go \
|
||||
../go/bin/gofmt \
|
||||
/work/system/bin
|
||||
`,
|
||||
},
|
||||
_bash,
|
||||
)
|
||||
`, pkg.Path(AbsUsrSrc.Append("go"), false, newTar(
|
||||
"https://go.dev/dl/go"+version+".src.tar.gz",
|
||||
checksum,
|
||||
pkg.TarGzip,
|
||||
)))
|
||||
}
|
||||
|
||||
func init() {
|
||||
const (
|
||||
version = "1.26.4"
|
||||
checksum = "fpqJlxa41wKRtbnviYNLk9VxgcL-5oIEDxsriF8svU6kNwQW70oRRA9gTz_ImVoB"
|
||||
func (t Toolchain) newGoLatest() (pkg.Artifact, string) {
|
||||
var (
|
||||
bootstrapEnv []string
|
||||
bootstrapExtra []pkg.Artifact
|
||||
|
||||
finalEnv []string
|
||||
)
|
||||
meta := Metadata{
|
||||
Name: "go",
|
||||
Description: "the Go programming language toolchain",
|
||||
Website: "https://go.dev",
|
||||
Version: version,
|
||||
switch arch {
|
||||
case "amd64":
|
||||
bootstrapExtra = append(bootstrapExtra, t.newGoBootstrap())
|
||||
|
||||
ID: 1227,
|
||||
case "arm64", "riscv64":
|
||||
bootstrapEnv = append(bootstrapEnv, "GOROOT_BOOTSTRAP=/system")
|
||||
bootstrapExtra = t.AppendPresets(bootstrapExtra, gcc)
|
||||
finalEnv = append(finalEnv, "CGO_ENABLED=0")
|
||||
|
||||
default:
|
||||
panic("unsupported target " + arch)
|
||||
}
|
||||
native.MustRegister(meta.Name, func(t Toolchain) (*Metadata, pkg.Artifact) {
|
||||
var (
|
||||
bootstrapEnv []string
|
||||
bootstrapEarly []pkg.Artifact
|
||||
|
||||
finalEnv []string
|
||||
)
|
||||
switch t.arch {
|
||||
case "amd64":
|
||||
bootstrapEarly = []pkg.Artifact{t.NewPackage("go", "1.4-bootstrap", newTar(
|
||||
"https://dl.google.com/go/go1.4-bootstrap-20171003.tar.gz",
|
||||
"8o9JL_ToiQKadCTb04nvBDkp8O1xiWOolAxVEqaTGodieNe4lOFEjlOxN3bwwe23",
|
||||
pkg.Gzip,
|
||||
), &PackageAttr{
|
||||
EnterSource: true,
|
||||
Env: []string{
|
||||
"CGO_ENABLED=0",
|
||||
},
|
||||
}, &GenericHelper{
|
||||
InPlace: true,
|
||||
Build: `
|
||||
mkdir /work/system/
|
||||
cp -r . /work/system/go
|
||||
cd /work/system/go/src/
|
||||
mkdir -p /var/tmp/
|
||||
./make.bash
|
||||
`,
|
||||
},
|
||||
_bash,
|
||||
)}
|
||||
|
||||
case "arm64", "riscv64":
|
||||
bootstrapEnv = append(bootstrapEnv, "GOROOT_BOOTSTRAP=/system")
|
||||
bootstrapEarly = t.Append(bootstrapEarly, H("gcc"))
|
||||
finalEnv = append(finalEnv, "CGO_ENABLED=0")
|
||||
|
||||
default:
|
||||
panic("unsupported target " + t.arch)
|
||||
}
|
||||
|
||||
go119 := t.newGo(
|
||||
"1.19",
|
||||
"9_e0aFHsIkVxWVGsp9T2RvvjOc3p4n9o9S8tkNe9Cvgzk_zI2FhRQB7ioQkeAAro",
|
||||
append(bootstrapEnv, "CGO_ENABLED=0"), `
|
||||
go119 := t.newGo(
|
||||
"1.19",
|
||||
"9_e0aFHsIkVxWVGsp9T2RvvjOc3p4n9o9S8tkNe9Cvgzk_zI2FhRQB7ioQkeAAro",
|
||||
append(bootstrapEnv, "CGO_ENABLED=0"), `
|
||||
rm \
|
||||
crypto/tls/handshake_client_test.go \
|
||||
cmd/pprof/pprof_test.go \
|
||||
@@ -121,15 +99,15 @@ sed -i \
|
||||
echo \
|
||||
'type syscallDescriptor = int' >> \
|
||||
os/rawconn_test.go
|
||||
`, bootstrapEarly...)
|
||||
`, bootstrapExtra...)
|
||||
|
||||
go121 := t.newGo(
|
||||
"1.21.13",
|
||||
"YtrDka402BOAEwywx03Vz4QlVwoBiguJHzG7PuythMCPHXS8CVMLvzmvgEbu4Tzu",
|
||||
[]string{"CGO_ENABLED=0"}, `
|
||||
go121 := t.newGo(
|
||||
"1.21.13",
|
||||
"YtrDka402BOAEwywx03Vz4QlVwoBiguJHzG7PuythMCPHXS8CVMLvzmvgEbu4Tzu",
|
||||
[]string{"CGO_ENABLED=0"}, `
|
||||
sed -i \
|
||||
's,/lib/ld-musl-`+t.linuxArch()+`.so.1,/system/bin/linker,' \
|
||||
cmd/link/internal/`+t.arch+`/obj.go
|
||||
's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \
|
||||
cmd/link/internal/`+arch+`/obj.go
|
||||
|
||||
rm \
|
||||
crypto/tls/handshake_client_test.go \
|
||||
@@ -139,39 +117,43 @@ echo \
|
||||
'type syscallDescriptor = int' >> \
|
||||
os/rawconn_test.go
|
||||
`, go119,
|
||||
)
|
||||
)
|
||||
|
||||
go123 := t.newGo(
|
||||
"1.23.12",
|
||||
"wcI32bl1tkqbgcelGtGWPI4RtlEddd-PTd76Eb-k7nXA5LbE9yTNdIL9QSOOxMOs",
|
||||
[]string{"CGO_ENABLED=0"}, `
|
||||
go123 := t.newGo(
|
||||
"1.23.12",
|
||||
"wcI32bl1tkqbgcelGtGWPI4RtlEddd-PTd76Eb-k7nXA5LbE9yTNdIL9QSOOxMOs",
|
||||
[]string{"CGO_ENABLED=0"}, `
|
||||
sed -i \
|
||||
's,/lib/ld-musl-`+t.linuxArch()+`.so.1,/system/bin/linker,' \
|
||||
cmd/link/internal/`+t.arch+`/obj.go
|
||||
's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \
|
||||
cmd/link/internal/`+arch+`/obj.go
|
||||
`, go121,
|
||||
)
|
||||
)
|
||||
|
||||
go125 := t.newGo(
|
||||
"1.25.11",
|
||||
"cnqAQ8wpVZXcM6nHAfSqFkIW4L3H73ALB9rBfKUBbfgg4_pOF3I9zOZARgYQ6MR2",
|
||||
[]string{"CGO_ENABLED=0"}, `
|
||||
go125 := t.newGo(
|
||||
"1.25.10",
|
||||
"TwKwatkpwal-j9U2sDSRPEdM3YesI4Gm88YgGV59wtU-L85K9gA7UPy9SCxn6PMb",
|
||||
[]string{"CGO_ENABLED=0"}, `
|
||||
sed -i \
|
||||
's,/lib/ld-musl-`+t.linuxArch()+`.so.1,/system/bin/linker,' \
|
||||
cmd/link/internal/`+t.arch+`/obj.go
|
||||
's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \
|
||||
cmd/link/internal/`+arch+`/obj.go
|
||||
|
||||
rm \
|
||||
os/root_unix_test.go \
|
||||
net/smtp/smtp_test.go
|
||||
`, go123,
|
||||
)
|
||||
)
|
||||
|
||||
return &meta, t.newGo(
|
||||
version,
|
||||
checksum,
|
||||
finalEnv, `
|
||||
const (
|
||||
version = "1.26.3"
|
||||
checksum = "lEiFocZFnN5fKvZzmwVdqc9pYUjAuhzqZGbuiOqxUP4XdcY8yECisKcqsQ_eNn1N"
|
||||
)
|
||||
return t.newGo(
|
||||
version,
|
||||
checksum,
|
||||
finalEnv, `
|
||||
sed -i \
|
||||
's,/lib/ld-musl-`+t.linuxArch()+`.so.1,/system/bin/linker,' \
|
||||
cmd/link/internal/`+t.arch+`/obj.go
|
||||
's,/lib/ld-musl-`+linuxArch()+`.so.1,/system/bin/linker,' \
|
||||
cmd/link/internal/`+arch+`/obj.go
|
||||
sed -i \
|
||||
's/cpu.X86.HasAVX512VBMI/& \&\& cpu.X86.HasPOPCNT/' \
|
||||
internal/runtime/gc/scan/scan_amd64.go
|
||||
@@ -181,7 +163,16 @@ rm \
|
||||
cmd/cgo/internal/testsanitizers/tsan_test.go \
|
||||
cmd/cgo/internal/testsanitizers/cshared_test.go
|
||||
`, go125,
|
||||
)
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Go] = Metadata{
|
||||
f: Toolchain.newGoLatest,
|
||||
|
||||
})
|
||||
Name: "go",
|
||||
Description: "the Go programming language toolchain",
|
||||
Website: "https://go.dev/",
|
||||
|
||||
ID: 1227,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package rosa
|
||||
|
||||
import (
|
||||
"hakurei.app/fhs"
|
||||
"hakurei.app/internal/pkg"
|
||||
)
|
||||
|
||||
func (t Toolchain) newGLib() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.88.1"
|
||||
checksum = "Rkszn6W4RHjyspyqfXdVAVawdwDJCuS0Zu0f7qot7tbJhnw2fUDoUUJB40m-1MCX"
|
||||
)
|
||||
return t.NewPackage("glib", version, t.newTagRemote(
|
||||
"https://gitlab.gnome.org/GNOME/glib.git",
|
||||
version, checksum,
|
||||
), &PackageAttr{
|
||||
Paths: []pkg.ExecPath{
|
||||
pkg.Path(fhs.AbsEtc.Append(
|
||||
"machine-id",
|
||||
), false, pkg.NewFile(
|
||||
"glib-machine-id",
|
||||
[]byte("ffffffffffffffffffffffffffffffff\n"),
|
||||
)),
|
||||
pkg.Path(AbsSystem.Append(
|
||||
"var/lib/dbus/machine-id",
|
||||
), false, pkg.NewFile(
|
||||
"glib-machine-id",
|
||||
[]byte("fefefefefefefefefefefefefefefefe\n"),
|
||||
)),
|
||||
},
|
||||
}, &MesonHelper{
|
||||
Setup: []KV{
|
||||
{"Ddefault_library", "both"},
|
||||
},
|
||||
},
|
||||
PythonPackaging,
|
||||
Bash,
|
||||
|
||||
PCRE2,
|
||||
Libffi,
|
||||
Zlib,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[GLib] = Metadata{
|
||||
f: Toolchain.newGLib,
|
||||
|
||||
Name: "glib",
|
||||
Description: "the GNU library of miscellaneous stuff",
|
||||
Website: "https://developer.gnome.org/glib/",
|
||||
|
||||
Dependencies: P{
|
||||
PCRE2,
|
||||
Libffi,
|
||||
Zlib,
|
||||
},
|
||||
|
||||
ID: 10024,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newSharedMimeInfo() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "2.4"
|
||||
checksum = "RrqfOT1gGuYfN7pkwgeLPfljkk9eL5BwRsNTuJNZjOk1vT5frTJ1EshDvaSqKwiL"
|
||||
)
|
||||
return t.NewPackage("shared-mime-info", version, newFromGitLab("gitlab.freedesktop.org", "xdg/shared-mime-info", version, checksum),
|
||||
nil,
|
||||
&MesonHelper{
|
||||
SkipTest: true,
|
||||
},
|
||||
GLib,
|
||||
Meson,
|
||||
Libxml2,
|
||||
Gettext,
|
||||
Libpng,
|
||||
Libjpeg,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[SharedMimeInfo] = Metadata{
|
||||
f: Toolchain.newSharedMimeInfo,
|
||||
Name: "shared-mime-info",
|
||||
Description: "xdg MIME repository",
|
||||
Website: "https://www.freedesktop.org/wiki/Software/shared-mime-info/",
|
||||
ID: 5524,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newGobjectIntrospection() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "1.86.0"
|
||||
checksum = "M-eq1kEEX215x0VMfTIfJXNfVnZwiC41QK33tlCeK0_GDwGUILQm0SqvRSySNoka"
|
||||
)
|
||||
return t.NewPackage("gobject-introspection", version, t.NewViaGit("https://gitlab.gnome.org/GNOME/gobject-introspection.git", version, mustDecode(checksum)),
|
||||
&PackageAttr{
|
||||
Writable: true,
|
||||
},
|
||||
&MesonHelper{
|
||||
SkipTest: true,
|
||||
},
|
||||
GLib,
|
||||
PythonSetuptools,
|
||||
Flex,
|
||||
Bison,
|
||||
Cairo,
|
||||
Libffi,
|
||||
Libpng,
|
||||
M4,
|
||||
PythonMako,
|
||||
Pixman,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[GObjectIntrospection] = Metadata{
|
||||
f: Toolchain.newGobjectIntrospection,
|
||||
Name: "gobject-introspection",
|
||||
Description: "introspection toolkit",
|
||||
Website: "https://gi.readthedocs.io/",
|
||||
ID: 1223,
|
||||
}
|
||||
}
|
||||
|
||||
func (t Toolchain) newGconf() (pkg.Artifact, string) {
|
||||
const (
|
||||
version = "3.2.6"
|
||||
checksum = "oSoPCk8J4Xd1gF1RLPqzdvHQfEn2eOE9qpLoT2CyDwnkUERgg2990dHN_AKhVgJp"
|
||||
)
|
||||
return t.NewPackage("gconf", version, newFromGitLab("gitlab.gnome.org", "Archive/gconf", version, checksum),
|
||||
nil,
|
||||
&MakeHelper{
|
||||
Configure: []KV{{"disable-orbit"}, {"disable-introspection"}},
|
||||
Generate: "autoreconf -if",
|
||||
},
|
||||
Automake,
|
||||
Libtool,
|
||||
GLib,
|
||||
), version
|
||||
}
|
||||
func init() {
|
||||
artifactsM[Gconf] = Metadata{
|
||||
f: Toolchain.newGconf,
|
||||
Name: "gconf",
|
||||
Description: "outdated configuration database library",
|
||||
Website: "http://projects.gnome.org/gconf/",
|
||||
ID: 8423,
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user