Compare commits
42 Commits
wip-binfmt
...
63f7bed2ed
| Author | SHA1 | Date | |
|---|---|---|---|
|
63f7bed2ed
|
|||
|
7e8e7d06ed
|
|||
|
1b55385913
|
|||
|
c04e7d5717
|
|||
|
7643945fe7
|
|||
|
ca9153f4ea
|
|||
|
664a4c69e0
|
|||
| da00d149bd | |||
| 0db8da9cba | |||
| 4ae5468cf6 | |||
| 226b86fe38 | |||
| 23b5d0dd3e | |||
| ba06769c5f | |||
| 28e84f0445 | |||
| 51ae9496e9 | |||
| 3cd8abc451 | |||
| edf4d0d32a | |||
| 97657ad9fc | |||
| 81344a1c8a | |||
| 50514edc00 | |||
| 7affd5735a | |||
| 0a2d9dbe42 | |||
| 049f212a02 | |||
| ccecaee7ca | |||
| 365602c9b6 | |||
| 85e066ccc4 | |||
| ffe37ae439 | |||
| 222a2aa8b4 | |||
| 29384553bf | |||
| cb0c652b18 | |||
| 5552598bc4 | |||
| 3db5603e78 | |||
| 08d120a84d | |||
| 1ecdcdc243 | |||
| e425c3b769 | |||
| 3d8b89e1ab | |||
| b2777de621 | |||
| 529a641fcd | |||
| 0e34ec3093 | |||
| 31b2d5431c | |||
| d6954e6bdb | |||
| 1cda0d83c3 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -7,8 +7,12 @@
|
|||||||
|
|
||||||
# go generate
|
# go generate
|
||||||
/cmd/hakurei/LICENSE
|
/cmd/hakurei/LICENSE
|
||||||
/cmd/mbf/internal/pkgserver/ui/static
|
/cmd/pkgserver/.sass-cache
|
||||||
/internal/pkg/internal/testtool/testtool
|
/cmd/pkgserver/ui/static/*.js
|
||||||
|
/cmd/pkgserver/ui/static/*.css*
|
||||||
|
/cmd/pkgserver/ui/static/*.css.map
|
||||||
|
/cmd/pkgserver/ui_test/static
|
||||||
|
/internal/pkg/testdata/testtool
|
||||||
/internal/rosa/hakurei_current.tar.gz
|
/internal/rosa/hakurei_current.tar.gz
|
||||||
|
|
||||||
# cmd/dist default destination
|
# cmd/dist default destination
|
||||||
|
|||||||
8
all.sh
8
all.sh
@@ -2,9 +2,5 @@
|
|||||||
|
|
||||||
TOOLCHAIN_VERSION="$(go version)"
|
TOOLCHAIN_VERSION="$(go version)"
|
||||||
cd "$(dirname -- "$0")/"
|
cd "$(dirname -- "$0")/"
|
||||||
echo "Building cmd/dist using ${TOOLCHAIN_VERSION}."
|
echo "# Building cmd/dist using ${TOOLCHAIN_VERSION}."
|
||||||
FLAGS=''
|
go run -v --tags=dist ./cmd/dist
|
||||||
if test -n "$VERBOSE"; then
|
|
||||||
FLAGS="$FLAGS -v"
|
|
||||||
fi
|
|
||||||
go run $FLAGS --tags=dist ./cmd/dist
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
package check
|
package check
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -30,16 +30,6 @@ func (e AbsoluteError) Is(target error) bool {
|
|||||||
// Absolute holds a pathname checked to be absolute.
|
// Absolute holds a pathname checked to be absolute.
|
||||||
type Absolute struct{ pathname unique.Handle[string] }
|
type Absolute struct{ pathname unique.Handle[string] }
|
||||||
|
|
||||||
var (
|
|
||||||
_ encoding.TextAppender = new(Absolute)
|
|
||||||
_ encoding.TextMarshaler = new(Absolute)
|
|
||||||
_ encoding.TextUnmarshaler = new(Absolute)
|
|
||||||
|
|
||||||
_ encoding.BinaryAppender = new(Absolute)
|
|
||||||
_ encoding.BinaryMarshaler = new(Absolute)
|
|
||||||
_ encoding.BinaryUnmarshaler = new(Absolute)
|
|
||||||
)
|
|
||||||
|
|
||||||
// ok returns whether [Absolute] is not the zero value.
|
// ok returns whether [Absolute] is not the zero value.
|
||||||
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
|
func (a *Absolute) ok() bool { return a != nil && *a != (Absolute{}) }
|
||||||
|
|
||||||
@@ -94,16 +84,13 @@ func (a *Absolute) Append(elem ...string) *Absolute {
|
|||||||
// Dir calls [filepath.Dir] with [Absolute] as its argument.
|
// Dir calls [filepath.Dir] with [Absolute] as its argument.
|
||||||
func (a *Absolute) Dir() *Absolute { return unsafeAbs(filepath.Dir(a.String())) }
|
func (a *Absolute) Dir() *Absolute { return unsafeAbs(filepath.Dir(a.String())) }
|
||||||
|
|
||||||
// AppendText appends the checked pathname.
|
// GobEncode returns the checked pathname.
|
||||||
func (a *Absolute) AppendText(data []byte) ([]byte, error) {
|
func (a *Absolute) GobEncode() ([]byte, error) {
|
||||||
return append(data, a.String()...), nil
|
return []byte(a.String()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalText returns the checked pathname.
|
// GobDecode stores data if it represents an absolute pathname.
|
||||||
func (a *Absolute) MarshalText() ([]byte, error) { return a.AppendText(nil) }
|
func (a *Absolute) GobDecode(data []byte) error {
|
||||||
|
|
||||||
// UnmarshalText stores data if it represents an absolute pathname.
|
|
||||||
func (a *Absolute) UnmarshalText(data []byte) error {
|
|
||||||
pathname := string(data)
|
pathname := string(data)
|
||||||
if !filepath.IsAbs(pathname) {
|
if !filepath.IsAbs(pathname) {
|
||||||
return AbsoluteError(pathname)
|
return AbsoluteError(pathname)
|
||||||
@@ -112,9 +99,23 @@ func (a *Absolute) UnmarshalText(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Absolute) AppendBinary(data []byte) ([]byte, error) { return a.AppendText(data) }
|
// MarshalJSON returns a JSON representation of the checked pathname.
|
||||||
func (a *Absolute) MarshalBinary() ([]byte, error) { return a.MarshalText() }
|
func (a *Absolute) MarshalJSON() ([]byte, error) {
|
||||||
func (a *Absolute) UnmarshalBinary(data []byte) error { return a.UnmarshalText(data) }
|
return json.Marshal(a.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON stores data if it represents an absolute pathname.
|
||||||
|
func (a *Absolute) UnmarshalJSON(data []byte) error {
|
||||||
|
var pathname string
|
||||||
|
if err := json.Unmarshal(data, &pathname); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !filepath.IsAbs(pathname) {
|
||||||
|
return AbsoluteError(pathname)
|
||||||
|
}
|
||||||
|
a.pathname = unique.Make(pathname)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// SortAbs calls [slices.SortFunc] for a slice of [Absolute].
|
// SortAbs calls [slices.SortFunc] for a slice of [Absolute].
|
||||||
func SortAbs(x []*Absolute) {
|
func SortAbs(x []*Absolute) {
|
||||||
|
|||||||
@@ -170,20 +170,20 @@ func TestCodecAbsolute(t *testing.T) {
|
|||||||
|
|
||||||
{"good", MustAbs("/etc"),
|
{"good", MustAbs("/etc"),
|
||||||
nil,
|
nil,
|
||||||
"\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\b\xff\x80\x00\x04/etc",
|
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\b\xff\x80\x00\x04/etc",
|
||||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x04/etc\x01\xfc\xc0\xed\x00\x00\x00",
|
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x04/etc\x01\xfc\xc0\xed\x00\x00\x00",
|
||||||
|
|
||||||
`"/etc"`, `{"val":"/etc","magic":3236757504}`},
|
`"/etc"`, `{"val":"/etc","magic":3236757504}`},
|
||||||
{"not absolute", nil,
|
{"not absolute", nil,
|
||||||
AbsoluteError("etc"),
|
AbsoluteError("etc"),
|
||||||
"\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
|
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\a\xff\x80\x00\x03etc",
|
||||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
|
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x0f\xff\x84\x01\x03etc\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||||
|
|
||||||
`"etc"`, `{"val":"etc","magic":3236757504}`},
|
`"etc"`, `{"val":"etc","magic":3236757504}`},
|
||||||
{"zero", nil,
|
{"zero", nil,
|
||||||
new(AbsoluteError),
|
new(AbsoluteError),
|
||||||
"\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\x04\xff\x80\x00\x00",
|
"\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\x04\xff\x80\x00\x00",
|
||||||
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x06\x01\x02\xff\x82\x00\x00\x00\f\xff\x84\x01\x00\x01\xfb\x01\x81\xda\x00\x00\x00",
|
",\xff\x83\x03\x01\x01\x06sCheck\x01\xff\x84\x00\x01\x02\x01\bPathname\x01\xff\x80\x00\x01\x05Magic\x01\x06\x00\x00\x00\t\x7f\x05\x01\x02\xff\x82\x00\x00\x00\f\xff\x84\x01\x00\x01\xfb\x01\x81\xda\x00\x00\x00",
|
||||||
`""`, `{"val":"","magic":3236757504}`},
|
`""`, `{"val":"","magic":3236757504}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +347,15 @@ func TestCodecAbsolute(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("json passthrough", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
wantErr := "invalid character ':' looking for beginning of value"
|
||||||
|
if err := new(Absolute).UnmarshalJSON([]byte(":3")); err == nil || err.Error() != wantErr {
|
||||||
|
t.Errorf("UnmarshalJSON: error = %v, want %s", err, wantErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAbsoluteWrap(t *testing.T) {
|
func TestAbsoluteWrap(t *testing.T) {
|
||||||
|
|||||||
@@ -4,23 +4,15 @@ import "strings"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// SpecialOverlayEscape is the escape string for overlay mount options.
|
// SpecialOverlayEscape is the escape string for overlay mount options.
|
||||||
//
|
|
||||||
// Deprecated: This is no longer used and will be removed in 0.5.
|
|
||||||
SpecialOverlayEscape = `\`
|
SpecialOverlayEscape = `\`
|
||||||
// SpecialOverlayOption is the separator string between overlay mount options.
|
// SpecialOverlayOption is the separator string between overlay mount options.
|
||||||
//
|
|
||||||
// Deprecated: This is no longer used and will be removed in 0.5.
|
|
||||||
SpecialOverlayOption = ","
|
SpecialOverlayOption = ","
|
||||||
// SpecialOverlayPath is the separator string between overlay paths.
|
// SpecialOverlayPath is the separator string between overlay paths.
|
||||||
//
|
|
||||||
// Deprecated: This is no longer used and will be removed in 0.5.
|
|
||||||
SpecialOverlayPath = ":"
|
SpecialOverlayPath = ":"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EscapeOverlayDataSegment escapes a string for formatting into the data
|
// EscapeOverlayDataSegment escapes a string for formatting into the data
|
||||||
// argument of an overlay mount system call.
|
// argument of an overlay mount system call.
|
||||||
//
|
|
||||||
// Deprecated: This is no longer used and will be removed in 0.5.
|
|
||||||
func EscapeOverlayDataSegment(s string) string {
|
func EscapeOverlayDataSegment(s string) string {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
27
cmd/dist/main.go
vendored
27
cmd/dist/main.go
vendored
@@ -42,18 +42,14 @@ func mustRun(ctx context.Context, name string, arg ...string) {
|
|||||||
var comp []byte
|
var comp []byte
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
fmt.Println()
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("")
|
log.SetPrefix("# ")
|
||||||
|
|
||||||
verbose := os.Getenv("VERBOSE") != ""
|
|
||||||
version := getenv("HAKUREI_VERSION", "untagged")
|
version := getenv("HAKUREI_VERSION", "untagged")
|
||||||
prefix := getenv("PREFIX", "/usr")
|
prefix := getenv("PREFIX", "/usr")
|
||||||
destdir := getenv("DESTDIR", "dist")
|
destdir := getenv("DESTDIR", "dist")
|
||||||
|
|
||||||
if verbose {
|
|
||||||
log.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.MkdirAll(destdir, 0755); err != nil {
|
if err := os.MkdirAll(destdir, 0755); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -80,17 +76,12 @@ func main() {
|
|||||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
verboseFlag := "-v"
|
log.Println("Building hakurei.")
|
||||||
if !verbose {
|
|
||||||
verboseFlag = "-buildvcs=false"
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Building hakurei for %s/%s.", runtime.GOOS, runtime.GOARCH)
|
|
||||||
mustRun(ctx, "go", "generate", "./...")
|
mustRun(ctx, "go", "generate", "./...")
|
||||||
mustRun(
|
mustRun(
|
||||||
ctx, "go", "build",
|
ctx, "go", "build",
|
||||||
"-trimpath",
|
"-trimpath",
|
||||||
verboseFlag, "-o", s,
|
"-v", "-o", s,
|
||||||
"-ldflags=-s -w "+
|
"-ldflags=-s -w "+
|
||||||
"-buildid= -linkmode external -extldflags=-static "+
|
"-buildid= -linkmode external -extldflags=-static "+
|
||||||
"-X hakurei.app/internal/info.buildVersion="+version+" "+
|
"-X hakurei.app/internal/info.buildVersion="+version+" "+
|
||||||
@@ -99,17 +90,17 @@ func main() {
|
|||||||
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
"-X main.hakureiPath="+prefix+"/bin/hakurei",
|
||||||
"./...",
|
"./...",
|
||||||
)
|
)
|
||||||
log.Println()
|
fmt.Println()
|
||||||
|
|
||||||
log.Println("##### Testing Hakurei.")
|
log.Println("Testing Hakurei.")
|
||||||
mustRun(
|
mustRun(
|
||||||
ctx, "go", "test",
|
ctx, "go", "test",
|
||||||
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
"-ldflags=-buildid= -linkmode external -extldflags=-static",
|
||||||
"./...",
|
"./...",
|
||||||
)
|
)
|
||||||
log.Println()
|
fmt.Println()
|
||||||
|
|
||||||
log.Println("##### Creating distribution.")
|
log.Println("Creating distribution.")
|
||||||
const suffix = ".tar.gz"
|
const suffix = ".tar.gz"
|
||||||
distName := "hakurei-" + version + "-" + runtime.GOARCH
|
distName := "hakurei-" + version + "-" + runtime.GOARCH
|
||||||
var f *os.File
|
var f *os.File
|
||||||
@@ -130,7 +121,7 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
h := sha512.New()
|
h := sha512.New()
|
||||||
gw, _ := gzip.NewWriterLevel(io.MultiWriter(f, h), gzip.BestCompression)
|
gw := gzip.NewWriter(io.MultiWriter(f, h))
|
||||||
tw := tar.NewWriter(gw)
|
tw := tar.NewWriter(gw)
|
||||||
|
|
||||||
mustWriteHeader := func(name string, size int64, mode os.FileMode) {
|
mustWriteHeader := func(name string, size int64, mode os.FileMode) {
|
||||||
|
|||||||
@@ -38,9 +38,8 @@ var errSuccess = errors.New("success")
|
|||||||
|
|
||||||
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErrs, out io.Writer) command.Command {
|
||||||
var (
|
var (
|
||||||
flagVerbose bool
|
flagVerbose bool
|
||||||
flagInsecure bool
|
flagJSON bool
|
||||||
flagJSON bool
|
|
||||||
)
|
)
|
||||||
c := command.New(out, log.Printf, "hakurei", func([]string) error {
|
c := command.New(out, log.Printf, "hakurei", func([]string) error {
|
||||||
msg.SwapVerbose(flagVerbose)
|
msg.SwapVerbose(flagVerbose)
|
||||||
@@ -58,7 +57,6 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
return nil
|
return nil
|
||||||
}).
|
}).
|
||||||
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
Flag(&flagVerbose, "v", command.BoolFlag(false), "Increase log verbosity").
|
||||||
Flag(&flagInsecure, "insecure", command.BoolFlag(false), "Allow use of insecure compatibility options").
|
|
||||||
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output in JSON when applicable")
|
||||||
|
|
||||||
c.Command("shim", command.UsageInternal, func([]string) error { outcome.Shim(msg); return errSuccess })
|
c.Command("shim", command.UsageInternal, func([]string) error { outcome.Shim(msg); return errSuccess })
|
||||||
@@ -77,12 +75,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
config.Container.Args = append(config.Container.Args, args[1:]...)
|
config.Container.Args = append(config.Container.Args, args[1:]...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags int
|
outcome.Main(ctx, msg, config, flagIdentifierFile)
|
||||||
if flagInsecure {
|
|
||||||
flags |= hst.VAllowInsecure
|
|
||||||
}
|
|
||||||
|
|
||||||
outcome.Main(ctx, msg, config, flags, flagIdentifierFile)
|
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}).
|
}).
|
||||||
Flag(&flagIdentifierFile, "identifier-fd", command.IntFlag(-1),
|
Flag(&flagIdentifierFile, "identifier-fd", command.IntFlag(-1),
|
||||||
@@ -152,7 +145,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var et hst.Enablements
|
var et hst.Enablement
|
||||||
if flagWayland {
|
if flagWayland {
|
||||||
et |= hst.EWayland
|
et |= hst.EWayland
|
||||||
}
|
}
|
||||||
@@ -170,7 +163,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
ID: flagID,
|
ID: flagID,
|
||||||
Identity: flagIdentity,
|
Identity: flagIdentity,
|
||||||
Groups: flagGroups,
|
Groups: flagGroups,
|
||||||
Enablements: &et,
|
Enablements: hst.NewEnablements(et),
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
@@ -289,7 +282,7 @@ func buildCommand(ctx context.Context, msg message.Msg, early *earlyHardeningErr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outcome.Main(ctx, msg, &config, 0, -1)
|
outcome.Main(ctx, msg, &config, -1)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}).
|
}).
|
||||||
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
Flag(&flagDBusConfigSession, "dbus-config", command.StringFlag("builtin"),
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func TestHelp(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"main", []string{}, `
|
"main", []string{}, `
|
||||||
Usage: hakurei [-h | --help] [-v] [--insecure] [--json] COMMAND [OPTIONS]
|
Usage: hakurei [-h | --help] [-v] [--json] COMMAND [OPTIONS]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
run Load and start container from configuration file
|
run Load and start container from configuration file
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func printShowInstance(
|
|||||||
t := newPrinter(output)
|
t := newPrinter(output)
|
||||||
defer t.MustFlush()
|
defer t.MustFlush()
|
||||||
|
|
||||||
if err := config.Validate(hst.VAllowInsecure); err != nil {
|
if err := config.Validate(); err != nil {
|
||||||
valid = false
|
valid = false
|
||||||
if m, ok := message.GetMessage(err); ok {
|
if m, ok := message.GetMessage(err); ok {
|
||||||
mustPrint(output, "Error: "+m+"!\n\n")
|
mustPrint(output, "Error: "+m+"!\n\n")
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ var (
|
|||||||
PID: 0xbeef,
|
PID: 0xbeef,
|
||||||
ShimPID: 0xcafe,
|
ShimPID: 0xcafe,
|
||||||
Config: &hst.Config{
|
Config: &hst.Config{
|
||||||
Enablements: new(hst.EWayland | hst.EPipeWire),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EPipeWire),
|
||||||
Identity: 1,
|
Identity: 1,
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Shell: check.MustAbs("/bin/sh"),
|
Shell: check.MustAbs("/bin/sh"),
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
// cache refers to an instance of [pkg.Cache] that might be open.
|
|
||||||
type cache struct {
|
|
||||||
ctx context.Context
|
|
||||||
msg message.Msg
|
|
||||||
|
|
||||||
// Should generally not be used directly.
|
|
||||||
c *pkg.Cache
|
|
||||||
|
|
||||||
cures, jobs int
|
|
||||||
hostAbstract, idle bool
|
|
||||||
verboseInit bool
|
|
||||||
|
|
||||||
base string
|
|
||||||
}
|
|
||||||
|
|
||||||
// open opens the underlying [pkg.Cache].
|
|
||||||
func (cache *cache) open() (err error) {
|
|
||||||
if cache.c != nil {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
var base *check.Absolute
|
|
||||||
if cache.base, err = filepath.Abs(cache.base); err != nil {
|
|
||||||
return
|
|
||||||
} else if base, err = check.NewAbs(cache.base); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags int
|
|
||||||
if cache.idle {
|
|
||||||
flags |= pkg.CSchedIdle
|
|
||||||
}
|
|
||||||
if cache.hostAbstract {
|
|
||||||
flags |= pkg.CHostAbstract
|
|
||||||
}
|
|
||||||
if !cache.verboseInit {
|
|
||||||
flags |= pkg.CSuppressInit
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-cache.ctx.Done():
|
|
||||||
if testing.Testing() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
os.Exit(2)
|
|
||||||
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
cache.msg.Verbosef("opening cache at %s", base)
|
|
||||||
cache.c, err = pkg.Open(
|
|
||||||
cache.ctx,
|
|
||||||
cache.msg,
|
|
||||||
flags,
|
|
||||||
cache.cures,
|
|
||||||
cache.jobs,
|
|
||||||
base,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the underlying [pkg.Cache] if it is open.
|
|
||||||
func (cache *cache) Close() {
|
|
||||||
if cache.c != nil {
|
|
||||||
cache.c.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do calls f on the underlying cache and returns its error value.
|
|
||||||
func (cache *cache) Do(f func(cache *pkg.Cache) error) error {
|
|
||||||
if cache.c == nil {
|
|
||||||
if err := cache.open(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f(cache.c)
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCache(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
cm := cache{
|
|
||||||
ctx: t.Context(),
|
|
||||||
msg: message.New(log.New(os.Stderr, "check: ", 0)),
|
|
||||||
base: t.TempDir(),
|
|
||||||
|
|
||||||
hostAbstract: true, idle: true,
|
|
||||||
}
|
|
||||||
defer cm.Close()
|
|
||||||
cm.Close()
|
|
||||||
|
|
||||||
if err := cm.open(); err != nil {
|
|
||||||
t.Fatalf("open: error = %v", err)
|
|
||||||
}
|
|
||||||
if err := cm.open(); err != os.ErrInvalid {
|
|
||||||
t.Errorf("(duplicate) open: error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
|
||||||
return cache.Scrub(0)
|
|
||||||
}); err != nil {
|
|
||||||
t.Errorf("Scrub: error = %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,354 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
"unique"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
)
|
|
||||||
|
|
||||||
// daemonTimeout is the maximum amount of time cureFromIR will wait on I/O.
|
|
||||||
const daemonTimeout = 30 * time.Second
|
|
||||||
|
|
||||||
// daemonDeadline returns the deadline corresponding to daemonTimeout, or the
|
|
||||||
// zero value when running in a test.
|
|
||||||
func daemonDeadline() time.Time {
|
|
||||||
if testing.Testing() {
|
|
||||||
return time.Time{}
|
|
||||||
}
|
|
||||||
return time.Now().Add(daemonTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// remoteNoReply notifies that the client will not receive a cure reply.
|
|
||||||
remoteNoReply = 1 << iota
|
|
||||||
)
|
|
||||||
|
|
||||||
// cureFromIR services an IR curing request.
|
|
||||||
func cureFromIR(
|
|
||||||
cache *pkg.Cache,
|
|
||||||
conn net.Conn,
|
|
||||||
flags uint64,
|
|
||||||
) (pkg.Artifact, error) {
|
|
||||||
a, decodeErr := cache.NewDecoder(conn).Decode()
|
|
||||||
if decodeErr != nil {
|
|
||||||
_, err := conn.Write([]byte("\x00" + decodeErr.Error()))
|
|
||||||
return nil, errors.Join(decodeErr, err, conn.Close())
|
|
||||||
}
|
|
||||||
|
|
||||||
pathname, _, cureErr := cache.Cure(a)
|
|
||||||
if flags&remoteNoReply != 0 {
|
|
||||||
return a, errors.Join(cureErr, conn.Close())
|
|
||||||
}
|
|
||||||
if err := conn.SetWriteDeadline(daemonDeadline()); err != nil {
|
|
||||||
return a, errors.Join(cureErr, err, conn.Close())
|
|
||||||
}
|
|
||||||
if cureErr != nil {
|
|
||||||
_, err := conn.Write([]byte("\x00" + cureErr.Error()))
|
|
||||||
return a, errors.Join(cureErr, err, conn.Close())
|
|
||||||
}
|
|
||||||
_, err := conn.Write([]byte(pathname.String()))
|
|
||||||
if testing.Testing() && errors.Is(err, io.ErrClosedPipe) {
|
|
||||||
return a, nil
|
|
||||||
}
|
|
||||||
return a, errors.Join(err, conn.Close())
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
// specialCancel is a message consisting of a single identifier referring
|
|
||||||
// to a curing artifact to be cancelled.
|
|
||||||
specialCancel = iota
|
|
||||||
// specialAbort requests for all pending cures to be aborted. It has no
|
|
||||||
// message body.
|
|
||||||
specialAbort
|
|
||||||
|
|
||||||
// remoteSpecial denotes a special message with custom layout.
|
|
||||||
remoteSpecial = math.MaxUint64
|
|
||||||
)
|
|
||||||
|
|
||||||
// writeSpecialHeader writes the header of a remoteSpecial message.
|
|
||||||
func writeSpecialHeader(conn net.Conn, kind uint64) error {
|
|
||||||
var sh [16]byte
|
|
||||||
binary.LittleEndian.PutUint64(sh[:], remoteSpecial)
|
|
||||||
binary.LittleEndian.PutUint64(sh[8:], kind)
|
|
||||||
if n, err := conn.Write(sh[:]); err != nil {
|
|
||||||
return err
|
|
||||||
} else if n != len(sh) {
|
|
||||||
return io.ErrShortWrite
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancelIdent reads an identifier from conn and cancels the corresponding cure.
|
|
||||||
func cancelIdent(
|
|
||||||
cache *pkg.Cache,
|
|
||||||
conn net.Conn,
|
|
||||||
) (*pkg.ID, bool, error) {
|
|
||||||
var ident pkg.ID
|
|
||||||
if _, err := io.ReadFull(conn, ident[:]); err != nil {
|
|
||||||
return nil, false, errors.Join(err, conn.Close())
|
|
||||||
}
|
|
||||||
ok := cache.Cancel(unique.Make(ident))
|
|
||||||
return &ident, ok, conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// serve services connections from a [net.UnixListener].
|
|
||||||
func serve(
|
|
||||||
ctx context.Context,
|
|
||||||
log *log.Logger,
|
|
||||||
cm *cache,
|
|
||||||
ul *net.UnixListener,
|
|
||||||
) error {
|
|
||||||
ul.SetUnlinkOnClose(true)
|
|
||||||
if cm.c == nil {
|
|
||||||
if err := cm.open(); err != nil {
|
|
||||||
return errors.Join(err, ul.Close())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
defer wg.Wait()
|
|
||||||
|
|
||||||
wg.Go(func() {
|
|
||||||
for {
|
|
||||||
if ctx.Err() != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := ul.AcceptUnix()
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, os.ErrDeadlineExceeded) {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
wg.Go(func() {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
_ = conn.SetDeadline(time.Now())
|
|
||||||
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _err := conn.SetReadDeadline(daemonDeadline()); _err != nil {
|
|
||||||
log.Println(_err)
|
|
||||||
if _err = conn.Close(); _err != nil {
|
|
||||||
log.Println(_err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var word [8]byte
|
|
||||||
if _, _err := io.ReadFull(conn, word[:]); _err != nil {
|
|
||||||
log.Println(_err)
|
|
||||||
if _err = conn.Close(); _err != nil {
|
|
||||||
log.Println(_err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
flags := binary.LittleEndian.Uint64(word[:])
|
|
||||||
|
|
||||||
if flags == remoteSpecial {
|
|
||||||
if _, _err := io.ReadFull(conn, word[:]); _err != nil {
|
|
||||||
log.Println(_err)
|
|
||||||
if _err = conn.Close(); _err != nil {
|
|
||||||
log.Println(_err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch special := binary.LittleEndian.Uint64(word[:]); special {
|
|
||||||
default:
|
|
||||||
log.Printf("invalid special %d", special)
|
|
||||||
|
|
||||||
case specialCancel:
|
|
||||||
if id, ok, _err := cancelIdent(cm.c, conn); _err != nil {
|
|
||||||
log.Println(_err)
|
|
||||||
} else if !ok {
|
|
||||||
log.Println(
|
|
||||||
"attempting to cancel invalid artifact",
|
|
||||||
pkg.Encode(*id),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
log.Println(
|
|
||||||
"cancelled artifact",
|
|
||||||
pkg.Encode(*id),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
case specialAbort:
|
|
||||||
log.Println("aborting all pending cures")
|
|
||||||
cm.c.Abort()
|
|
||||||
if _err := conn.Close(); _err != nil {
|
|
||||||
log.Println(_err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if a, _err := cureFromIR(cm.c, conn, flags); _err != nil {
|
|
||||||
log.Println(_err)
|
|
||||||
} else {
|
|
||||||
log.Printf(
|
|
||||||
"fulfilled artifact %s",
|
|
||||||
pkg.Encode(cm.c.Ident(a).Value()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
<-ctx.Done()
|
|
||||||
if err := ul.SetDeadline(time.Now()); err != nil {
|
|
||||||
return errors.Join(err, ul.Close())
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
return ul.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// dial wraps [net.DialUnix] with a context.
|
|
||||||
func dial(ctx context.Context, addr *net.UnixAddr) (
|
|
||||||
done chan<- struct{},
|
|
||||||
conn *net.UnixConn,
|
|
||||||
err error,
|
|
||||||
) {
|
|
||||||
conn, err = net.DialUnix("unix", nil, addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
d := make(chan struct{})
|
|
||||||
done = d
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
_ = conn.SetDeadline(time.Now())
|
|
||||||
|
|
||||||
case <-d:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// cureRemote cures a [pkg.Artifact] on a daemon.
|
|
||||||
func cureRemote(
|
|
||||||
ctx context.Context,
|
|
||||||
addr *net.UnixAddr,
|
|
||||||
a pkg.Artifact,
|
|
||||||
flags uint64,
|
|
||||||
) (*check.Absolute, error) {
|
|
||||||
if flags == remoteSpecial {
|
|
||||||
return nil, syscall.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
done, conn, err := dial(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
if n, flagErr := conn.Write(binary.LittleEndian.AppendUint64(nil, flags)); flagErr != nil {
|
|
||||||
return nil, errors.Join(flagErr, conn.Close())
|
|
||||||
} else if n != 8 {
|
|
||||||
return nil, errors.Join(io.ErrShortWrite, conn.Close())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = pkg.NewIR().EncodeAll(conn, a); err != nil {
|
|
||||||
return nil, errors.Join(err, conn.Close())
|
|
||||||
} else if err = conn.CloseWrite(); err != nil {
|
|
||||||
return nil, errors.Join(err, conn.Close())
|
|
||||||
}
|
|
||||||
|
|
||||||
if flags&remoteNoReply != 0 {
|
|
||||||
return nil, conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
payload, recvErr := io.ReadAll(conn)
|
|
||||||
if err = errors.Join(recvErr, conn.Close()); err != nil {
|
|
||||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
|
||||||
if cancelErr := ctx.Err(); cancelErr != nil {
|
|
||||||
err = cancelErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(payload) > 0 && payload[0] == 0 {
|
|
||||||
return nil, errors.New(string(payload[1:]))
|
|
||||||
}
|
|
||||||
|
|
||||||
var p *check.Absolute
|
|
||||||
p, err = check.NewAbs(string(payload))
|
|
||||||
return p, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancelRemote cancels a [pkg.Artifact] curing on a daemon.
|
|
||||||
func cancelRemote(
|
|
||||||
ctx context.Context,
|
|
||||||
addr *net.UnixAddr,
|
|
||||||
a pkg.Artifact,
|
|
||||||
wait bool,
|
|
||||||
) error {
|
|
||||||
done, conn, err := dial(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
if err = writeSpecialHeader(conn, specialCancel); err != nil {
|
|
||||||
return errors.Join(err, conn.Close())
|
|
||||||
}
|
|
||||||
|
|
||||||
var n int
|
|
||||||
id := pkg.NewIR().Ident(a).Value()
|
|
||||||
if n, err = conn.Write(id[:]); err != nil {
|
|
||||||
return errors.Join(err, conn.Close())
|
|
||||||
} else if n != len(id) {
|
|
||||||
return errors.Join(io.ErrShortWrite, conn.Close())
|
|
||||||
}
|
|
||||||
if wait {
|
|
||||||
if _, err = conn.Read(make([]byte, 1)); err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Join(err, conn.Close())
|
|
||||||
}
|
|
||||||
|
|
||||||
// abortRemote aborts all [pkg.Artifact] curing on a daemon.
|
|
||||||
func abortRemote(
|
|
||||||
ctx context.Context,
|
|
||||||
addr *net.UnixAddr,
|
|
||||||
wait bool,
|
|
||||||
) error {
|
|
||||||
done, conn, err := dial(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer close(done)
|
|
||||||
|
|
||||||
err = writeSpecialHeader(conn, specialAbort)
|
|
||||||
if wait && err == nil {
|
|
||||||
if _, err = conn.Read(make([]byte, 1)); err == io.EOF {
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errors.Join(err, conn.Close())
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNoReply(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
if !daemonDeadline().IsZero() {
|
|
||||||
t.Fatal("daemonDeadline did not return the zero value")
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := pkg.Open(
|
|
||||||
t.Context(),
|
|
||||||
message.New(log.New(os.Stderr, "cir: ", 0)),
|
|
||||||
0, 0, 0,
|
|
||||||
check.MustAbs(t.TempDir()),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Open: error = %v", err)
|
|
||||||
}
|
|
||||||
defer c.Close()
|
|
||||||
|
|
||||||
client, server := net.Pipe()
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
defer close(done)
|
|
||||||
go func() {
|
|
||||||
<-t.Context().Done()
|
|
||||||
if _err := client.SetDeadline(time.Now()); _err != nil && !errors.Is(_err, io.ErrClosedPipe) {
|
|
||||||
panic(_err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _err := c.EncodeAll(
|
|
||||||
client,
|
|
||||||
pkg.NewFile("check", []byte{0}),
|
|
||||||
); _err != nil {
|
|
||||||
panic(_err)
|
|
||||||
} else if _err = client.Close(); _err != nil {
|
|
||||||
panic(_err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
a, cureErr := cureFromIR(c, server, remoteNoReply)
|
|
||||||
if cureErr != nil {
|
|
||||||
t.Fatalf("cureFromIR: error = %v", cureErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
<-done
|
|
||||||
wantIdent := pkg.MustDecode("fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG")
|
|
||||||
if gotIdent := c.Ident(a).Value(); gotIdent != wantIdent {
|
|
||||||
t.Errorf(
|
|
||||||
"cureFromIR: %s, want %s",
|
|
||||||
pkg.Encode(gotIdent), pkg.Encode(wantIdent),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDaemon(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
logger := log.New(&buf, "daemon: ", 0)
|
|
||||||
|
|
||||||
addr := net.UnixAddr{
|
|
||||||
Name: filepath.Join(t.TempDir(), "daemon"),
|
|
||||||
Net: "unix",
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(t.Context())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
cm := cache{
|
|
||||||
ctx: ctx,
|
|
||||||
msg: message.New(logger),
|
|
||||||
base: t.TempDir(),
|
|
||||||
}
|
|
||||||
defer cm.Close()
|
|
||||||
|
|
||||||
ul, err := net.ListenUnix("unix", &addr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ListenUnix: error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
defer close(done)
|
|
||||||
if _err := serve(ctx, logger, &cm, ul); _err != nil {
|
|
||||||
panic(_err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = cancelRemote(ctx, &addr, pkg.NewFile("nonexistent", nil), true); err != nil {
|
|
||||||
t.Fatalf("cancelRemote: error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = abortRemote(ctx, &addr, true); err != nil {
|
|
||||||
t.Fatalf("abortRemote: error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep this last for synchronisation
|
|
||||||
var p *check.Absolute
|
|
||||||
p, err = cureRemote(ctx, &addr, pkg.NewFile("check", []byte{0}), 0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("cureRemote: error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cancel()
|
|
||||||
<-done
|
|
||||||
|
|
||||||
const want = "fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG"
|
|
||||||
if got := filepath.Base(p.String()); got != want {
|
|
||||||
t.Errorf("cureRemote: %s, want %s", got, want)
|
|
||||||
}
|
|
||||||
|
|
||||||
wantLog := []string{
|
|
||||||
"",
|
|
||||||
"daemon: aborting all pending cures",
|
|
||||||
"daemon: attempting to cancel invalid artifact kQm9fmnCmXST1-MMmxzcau2oKZCXXrlZydo4PkeV5hO_2PKfeC8t98hrbV_ZZx_j",
|
|
||||||
"daemon: fulfilled artifact fiZf-ZY_Yq6qxJNrHbMiIPYCsGkUiKCRsZrcSELXTqZWtCnESlHmzV5ThhWWGGYG",
|
|
||||||
}
|
|
||||||
gotLog := strings.Split(buf.String(), "\n")
|
|
||||||
slices.Sort(gotLog)
|
|
||||||
if !slices.Equal(gotLog, wantLog) {
|
|
||||||
t.Errorf(
|
|
||||||
"serve: logged\n%s\nwant\n%s",
|
|
||||||
strings.Join(gotLog, "\n"), strings.Join(wantLog, "\n"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
114
cmd/mbf/info.go
114
cmd/mbf/info.go
@@ -1,114 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
"hakurei.app/internal/rosa"
|
|
||||||
)
|
|
||||||
|
|
||||||
// commandInfo implements the info subcommand.
|
|
||||||
func commandInfo(
|
|
||||||
cm *cache,
|
|
||||||
args []string,
|
|
||||||
w io.Writer,
|
|
||||||
writeStatus bool,
|
|
||||||
r *rosa.Report,
|
|
||||||
) (err error) {
|
|
||||||
if len(args) == 0 {
|
|
||||||
return errors.New("info requires at least 1 argument")
|
|
||||||
}
|
|
||||||
|
|
||||||
// recovered by HandleAccess
|
|
||||||
mustPrintln := func(a ...any) {
|
|
||||||
if _, _err := fmt.Fprintln(w, a...); _err != nil {
|
|
||||||
panic(_err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mustPrint := func(a ...any) {
|
|
||||||
if _, _err := fmt.Fprint(w, a...); _err != nil {
|
|
||||||
panic(_err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, name := range args {
|
|
||||||
if p, ok := rosa.ResolveName(name); !ok {
|
|
||||||
return fmt.Errorf("unknown artifact %q", name)
|
|
||||||
} else {
|
|
||||||
var suffix string
|
|
||||||
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 : " +
|
|
||||||
strings.TrimSuffix(meta.Website, "/"))
|
|
||||||
}
|
|
||||||
if len(meta.Dependencies) > 0 {
|
|
||||||
mustPrint("depends on :")
|
|
||||||
for _, d := range meta.Dependencies {
|
|
||||||
s := rosa.GetMetadata(d).Name
|
|
||||||
if version := rosa.Std.Version(d); version != rosa.Unversioned {
|
|
||||||
s += "-" + version
|
|
||||||
}
|
|
||||||
mustPrint(" " + s)
|
|
||||||
}
|
|
||||||
mustPrintln()
|
|
||||||
}
|
|
||||||
|
|
||||||
const statusPrefix = "status : "
|
|
||||||
if writeStatus {
|
|
||||||
if r == nil {
|
|
||||||
var f io.ReadSeekCloser
|
|
||||||
err = cm.Do(func(cache *pkg.Cache) (err error) {
|
|
||||||
f, err = cache.OpenStatus(rosa.Std.Load(p))
|
|
||||||
return
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
mustPrintln(
|
|
||||||
statusPrefix + "not yet cured",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mustPrint(statusPrefix)
|
|
||||||
_, err = io.Copy(w, f)
|
|
||||||
if err = errors.Join(err, f.Close()); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
|
||||||
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
|
|
||||||
if status == nil {
|
|
||||||
mustPrintln(
|
|
||||||
statusPrefix + "not in report",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
mustPrintln("size :", n)
|
|
||||||
mustPrint(statusPrefix)
|
|
||||||
if _, err = w.Write(status); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i != len(args)-1 {
|
|
||||||
mustPrintln()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"hakurei.app/internal/pkg"
|
|
||||||
"hakurei.app/internal/rosa"
|
|
||||||
"hakurei.app/message"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInfo(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
args []string
|
|
||||||
status map[string]string
|
|
||||||
report string
|
|
||||||
want string
|
|
||||||
wantErr any
|
|
||||||
}{
|
|
||||||
{"qemu", []string{"qemu"}, nil, "", `
|
|
||||||
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-` + rosa.Std.Version(rosa.GLib) + ` zstd-` + rosa.Std.Version(rosa.Zstd) + `
|
|
||||||
`, nil},
|
|
||||||
|
|
||||||
{"multi", []string{"hakurei", "hakurei-dist"}, nil, "", `
|
|
||||||
name : hakurei-` + rosa.Std.Version(rosa.Hakurei) + `
|
|
||||||
description : low-level userspace tooling for Rosa OS
|
|
||||||
website : https://hakurei.app
|
|
||||||
|
|
||||||
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-` + rosa.Std.Version(rosa.Zlib) + `
|
|
||||||
description : lossless data-compression library
|
|
||||||
website : https://zlib.net
|
|
||||||
|
|
||||||
`, fmt.Errorf("unknown artifact %q", "\x00")},
|
|
||||||
|
|
||||||
{"status cache", []string{"zlib", "zstd"}, map[string]string{
|
|
||||||
"zstd": "internal/pkg (amd64) on satori\n",
|
|
||||||
"hakurei": "internal/pkg (amd64) on satori\n\n",
|
|
||||||
}, "", `
|
|
||||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
|
||||||
description : lossless data-compression library
|
|
||||||
website : https://zlib.net
|
|
||||||
status : not yet cured
|
|
||||||
|
|
||||||
name : zstd-` + rosa.Std.Version(rosa.Zstd) + `
|
|
||||||
description : a fast compression algorithm
|
|
||||||
website : https://facebook.github.io/zstd
|
|
||||||
status : internal/pkg (amd64) on satori
|
|
||||||
`, nil},
|
|
||||||
|
|
||||||
{"status cache perm", []string{"zlib"}, map[string]string{
|
|
||||||
"zlib": "\x00",
|
|
||||||
}, "", `
|
|
||||||
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(rosa.Std.Load(rosa.Zlib)).Value())),
|
|
||||||
Err: syscall.EACCES,
|
|
||||||
}
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"status report", []string{"zlib"}, nil, strings.Repeat("\x00", len(pkg.Checksum{})+8), `
|
|
||||||
name : zlib-` + rosa.Std.Version(rosa.Zlib) + `
|
|
||||||
description : lossless data-compression library
|
|
||||||
website : https://zlib.net
|
|
||||||
status : not in report
|
|
||||||
`, nil},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
cm *cache
|
|
||||||
buf strings.Builder
|
|
||||||
r *rosa.Report
|
|
||||||
)
|
|
||||||
|
|
||||||
if tc.status != nil || tc.report != "" {
|
|
||||||
cm = &cache{
|
|
||||||
ctx: context.Background(),
|
|
||||||
msg: message.New(log.New(os.Stderr, "info: ", 0)),
|
|
||||||
base: t.TempDir(),
|
|
||||||
}
|
|
||||||
defer cm.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.report != "" {
|
|
||||||
pathname := filepath.Join(t.TempDir(), "report")
|
|
||||||
err := os.WriteFile(
|
|
||||||
pathname,
|
|
||||||
unsafe.Slice(unsafe.StringData(tc.report), len(tc.report)),
|
|
||||||
0400,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err = rosa.OpenReport(pathname)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err = r.Close(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.status != nil {
|
|
||||||
for name, status := range tc.status {
|
|
||||||
p, ok := rosa.ResolveName(name)
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("invalid name %q", name)
|
|
||||||
}
|
|
||||||
perm := os.FileMode(0400)
|
|
||||||
if status == "\x00" {
|
|
||||||
perm = 0
|
|
||||||
}
|
|
||||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
|
||||||
return os.WriteFile(filepath.Join(
|
|
||||||
cm.base,
|
|
||||||
"status",
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var wantErr error
|
|
||||||
switch c := tc.wantErr.(type) {
|
|
||||||
case error:
|
|
||||||
wantErr = c
|
|
||||||
case func(cm *cache) error:
|
|
||||||
wantErr = c(cm)
|
|
||||||
default:
|
|
||||||
if tc.wantErr != nil {
|
|
||||||
t.Fatalf("invalid wantErr %#v", tc.wantErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := commandInfo(
|
|
||||||
cm,
|
|
||||||
tc.args,
|
|
||||||
&buf,
|
|
||||||
cm != nil,
|
|
||||||
r,
|
|
||||||
); !reflect.DeepEqual(err, wantErr) {
|
|
||||||
t.Fatalf("commandInfo: error = %v, want %v", err, wantErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := buf.String(); got != strings.TrimPrefix(tc.want, "\n") {
|
|
||||||
t.Errorf("commandInfo:\n%s\nwant\n%s", got, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Package ui holds the static web UI.
|
|
||||||
package ui
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// Register arranges for mux to serve the embedded frontend.
|
|
||||||
func Register(mux *http.ServeMux) {
|
|
||||||
mux.Handle("GET /", http.FileServer(http.FS(static)))
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
//go:build frontend
|
|
||||||
|
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"io/fs"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:generate tsc
|
|
||||||
//go:generate cp index.html style.css static
|
|
||||||
//go:embed static
|
|
||||||
var _static embed.FS
|
|
||||||
|
|
||||||
var static = func() fs.FS {
|
|
||||||
if f, err := fs.Sub(_static, "static"); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
524
cmd/mbf/main.go
524
cmd/mbf/main.go
@@ -14,18 +14,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/sha512"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -42,9 +40,6 @@ import (
|
|||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
"hakurei.app/internal/rosa"
|
"hakurei.app/internal/rosa"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
|
|
||||||
"hakurei.app/cmd/mbf/internal/pkgserver"
|
|
||||||
"hakurei.app/cmd/mbf/internal/pkgserver/ui"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -58,110 +53,77 @@ func main() {
|
|||||||
log.Fatal("this program must not run as root")
|
log.Fatal("this program must not run as root")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cache *pkg.Cache
|
||||||
ctx, stop := signal.NotifyContext(context.Background(),
|
ctx, stop := signal.NotifyContext(context.Background(),
|
||||||
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
defer func() {
|
||||||
|
if cache != nil {
|
||||||
|
cache.Close()
|
||||||
|
}
|
||||||
|
|
||||||
var cm cache
|
if r := recover(); r != nil {
|
||||||
defer func() { cm.Close() }()
|
fmt.Println(r)
|
||||||
|
log.Fatal("consider scrubbing the on-disk cache")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
flagQuiet bool
|
flagQuiet bool
|
||||||
flagCheck bool
|
flagCures int
|
||||||
flagLTO bool
|
flagBase string
|
||||||
|
flagIdle bool
|
||||||
|
|
||||||
addr net.UnixAddr
|
flagHostAbstract bool
|
||||||
)
|
)
|
||||||
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) error {
|
c := command.New(os.Stderr, log.Printf, "mbf", func([]string) (err error) {
|
||||||
msg.SwapVerbose(!flagQuiet)
|
msg.SwapVerbose(!flagQuiet)
|
||||||
cm.ctx, cm.msg = ctx, msg
|
|
||||||
cm.base = os.ExpandEnv(cm.base)
|
flagBase = os.ExpandEnv(flagBase)
|
||||||
if cm.base == "" {
|
if flagBase == "" {
|
||||||
cm.base = "cache"
|
flagBase = "cache"
|
||||||
}
|
}
|
||||||
|
|
||||||
addr.Net = "unix"
|
var base *check.Absolute
|
||||||
addr.Name = os.ExpandEnv(addr.Name)
|
if flagBase, err = filepath.Abs(flagBase); err != nil {
|
||||||
if addr.Name == "" {
|
return
|
||||||
addr.Name = filepath.Join(cm.base, "daemon")
|
} else if base, err = check.NewAbs(flagBase); err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags int
|
var flags int
|
||||||
if !flagCheck {
|
if flagIdle {
|
||||||
flags |= rosa.OptSkipCheck
|
flags |= pkg.CSchedIdle
|
||||||
}
|
}
|
||||||
if !flagLTO {
|
if flagHostAbstract {
|
||||||
flags |= rosa.OptLLVMNoLTO
|
flags |= pkg.CHostAbstract
|
||||||
}
|
}
|
||||||
rosa.DropCaches(flags)
|
cache, err = pkg.Open(ctx, msg, flags, flagCures, base)
|
||||||
|
|
||||||
return nil
|
return
|
||||||
}).Flag(
|
}).Flag(
|
||||||
&flagQuiet,
|
&flagQuiet,
|
||||||
"q", command.BoolFlag(false),
|
"q", command.BoolFlag(false),
|
||||||
"Do not print cure messages",
|
"Do not print cure messages",
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagLTO,
|
&flagCures,
|
||||||
"lto", command.BoolFlag(false),
|
|
||||||
"Enable LTO in stage2 and stage3 LLVM toolchains",
|
|
||||||
).Flag(
|
|
||||||
&flagCheck,
|
|
||||||
"check", command.BoolFlag(true),
|
|
||||||
"Run test suites",
|
|
||||||
).Flag(
|
|
||||||
&cm.verboseInit,
|
|
||||||
"v", command.BoolFlag(false),
|
|
||||||
"Do not suppress verbose output from init",
|
|
||||||
).Flag(
|
|
||||||
&cm.cures,
|
|
||||||
"cures", command.IntFlag(0),
|
"cures", command.IntFlag(0),
|
||||||
"Maximum number of dependencies to cure at any given time",
|
"Maximum number of dependencies to cure at any given time",
|
||||||
).Flag(
|
).Flag(
|
||||||
&cm.jobs,
|
&flagBase,
|
||||||
"jobs", command.IntFlag(0),
|
|
||||||
"Preferred number of jobs to run, when applicable",
|
|
||||||
).Flag(
|
|
||||||
&cm.base,
|
|
||||||
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
"d", command.StringFlag("$MBF_CACHE_DIR"),
|
||||||
"Directory to store cured artifacts",
|
"Directory to store cured artifacts",
|
||||||
).Flag(
|
).Flag(
|
||||||
&cm.idle,
|
&flagIdle,
|
||||||
"sched-idle", command.BoolFlag(false),
|
"sched-idle", command.BoolFlag(false),
|
||||||
"Set SCHED_IDLE scheduling policy",
|
"Set SCHED_IDLE scheduling policy",
|
||||||
).Flag(
|
).Flag(
|
||||||
&cm.hostAbstract,
|
&flagHostAbstract,
|
||||||
"host-abstract", command.BoolFlag(
|
"host-abstract", command.BoolFlag(
|
||||||
os.Getenv("MBF_HOST_ABSTRACT") != "",
|
os.Getenv("MBF_HOST_ABSTRACT") != "",
|
||||||
),
|
),
|
||||||
"Do not restrict networked cure containers from connecting to host "+
|
"Do not restrict networked cure containers from connecting to host "+
|
||||||
"abstract UNIX sockets",
|
"abstract UNIX sockets",
|
||||||
).Flag(
|
|
||||||
&addr.Name,
|
|
||||||
"socket", command.StringFlag("$MBF_DAEMON_SOCKET"),
|
|
||||||
"Pathname of socket to bind to",
|
|
||||||
)
|
|
||||||
|
|
||||||
c.NewCommand(
|
|
||||||
"checksum", "Compute checksum of data read from standard input",
|
|
||||||
func([]string) error {
|
|
||||||
done := make(chan struct{})
|
|
||||||
defer close(done)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
os.Exit(1)
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
h := sha512.New384()
|
|
||||||
if _, err := io.Copy(h, os.Stdin); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Println(pkg.Encode(pkg.Checksum(h.Sum(nil))))
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -175,9 +137,7 @@ func main() {
|
|||||||
if flagShifts < 0 || flagShifts > 31 {
|
if flagShifts < 0 || flagShifts > 31 {
|
||||||
flagShifts = 12
|
flagShifts = 12
|
||||||
}
|
}
|
||||||
return cm.Do(func(cache *pkg.Cache) error {
|
return cache.Scrub(runtime.NumCPU() << flagShifts)
|
||||||
return cache.Scrub(runtime.NumCPU() << flagShifts)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
).Flag(
|
).Flag(
|
||||||
&flagShifts,
|
&flagShifts,
|
||||||
@@ -188,7 +148,6 @@ func main() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagBind string
|
|
||||||
flagStatus bool
|
flagStatus bool
|
||||||
flagReport string
|
flagReport string
|
||||||
)
|
)
|
||||||
@@ -196,7 +155,9 @@ func main() {
|
|||||||
"info",
|
"info",
|
||||||
"Display out-of-band metadata of an artifact",
|
"Display out-of-band metadata of an artifact",
|
||||||
func(args []string) (err error) {
|
func(args []string) (err error) {
|
||||||
const shutdownTimeout = 15 * time.Second
|
if len(args) == 0 {
|
||||||
|
return errors.New("info requires at least 1 argument")
|
||||||
|
}
|
||||||
|
|
||||||
var r *rosa.Report
|
var r *rosa.Report
|
||||||
if flagReport != "" {
|
if flagReport != "" {
|
||||||
@@ -211,46 +172,88 @@ func main() {
|
|||||||
defer r.HandleAccess(&err)()
|
defer r.HandleAccess(&err)()
|
||||||
}
|
}
|
||||||
|
|
||||||
if flagBind == "" {
|
for i, name := range args {
|
||||||
return commandInfo(&cm, args, os.Stdout, flagStatus, r)
|
if p, ok := rosa.ResolveName(name); !ok {
|
||||||
}
|
return fmt.Errorf("unknown artifact %q", name)
|
||||||
|
} else {
|
||||||
|
var suffix string
|
||||||
|
if version := rosa.Std.Version(p); version != rosa.Unversioned {
|
||||||
|
suffix += "-" + version
|
||||||
|
}
|
||||||
|
fmt.Println("name : " + name + suffix)
|
||||||
|
|
||||||
var mux http.ServeMux
|
meta := rosa.GetMetadata(p)
|
||||||
ui.Register(&mux)
|
fmt.Println("description : " + meta.Description)
|
||||||
if err = pkgserver.Register(ctx, &mux, r); err != nil {
|
if meta.Website != "" {
|
||||||
return
|
fmt.Println("website : " +
|
||||||
}
|
strings.TrimSuffix(meta.Website, "/"))
|
||||||
|
}
|
||||||
|
if len(meta.Dependencies) > 0 {
|
||||||
|
fmt.Print("depends on :")
|
||||||
|
for _, d := range meta.Dependencies {
|
||||||
|
s := rosa.GetMetadata(d).Name
|
||||||
|
if version := rosa.Std.Version(d); version != rosa.Unversioned {
|
||||||
|
s += "-" + version
|
||||||
|
}
|
||||||
|
fmt.Print(" " + s)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
server := http.Server{Addr: flagBind, Handler: &mux}
|
const statusPrefix = "status : "
|
||||||
go func() {
|
if flagStatus {
|
||||||
<-ctx.Done()
|
if r == nil {
|
||||||
cc, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
var f io.ReadSeekCloser
|
||||||
defer cancel()
|
f, err = cache.OpenStatus(rosa.Std.Load(p))
|
||||||
if _err := server.Shutdown(cc); _err != nil {
|
if err != nil {
|
||||||
log.Fatal(_err)
|
if errors.Is(err, os.ErrNotExist) {
|
||||||
|
fmt.Println(
|
||||||
|
statusPrefix + "not yet cured",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Print(statusPrefix)
|
||||||
|
_, err = io.Copy(os.Stdout, f)
|
||||||
|
if err = errors.Join(err, f.Close()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
status, n := r.ArtifactOf(cache.Ident(rosa.Std.Load(p)))
|
||||||
|
if status == nil {
|
||||||
|
fmt.Println(
|
||||||
|
statusPrefix + "not in report",
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fmt.Println("size :", n)
|
||||||
|
fmt.Print(statusPrefix)
|
||||||
|
if _, err = os.Stdout.Write(status); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != len(args)-1 {
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
msg.Verbosef("listening on %q", flagBind)
|
|
||||||
err = server.ListenAndServe()
|
|
||||||
if errors.Is(err, http.ErrServerClosed) {
|
|
||||||
err = nil
|
|
||||||
}
|
}
|
||||||
return
|
return nil
|
||||||
},
|
},
|
||||||
).Flag(
|
).
|
||||||
&flagBind,
|
Flag(
|
||||||
"bind", command.StringFlag(""),
|
&flagStatus,
|
||||||
"TCP address for the server to listen on",
|
"status", command.BoolFlag(false),
|
||||||
).Flag(
|
"Display cure status if available",
|
||||||
&flagStatus,
|
).
|
||||||
"status", command.BoolFlag(false),
|
Flag(
|
||||||
"Display cure status if available",
|
&flagReport,
|
||||||
).Flag(
|
"report", command.StringFlag(""),
|
||||||
&flagReport,
|
"Load cure status from this report file instead of cache",
|
||||||
"report", command.StringFlag(""),
|
)
|
||||||
"Load cure status from this report file instead of cache",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
@@ -284,9 +287,7 @@ func main() {
|
|||||||
if ext.Isatty(int(w.Fd())) {
|
if ext.Isatty(int(w.Fd())) {
|
||||||
return errors.New("output appears to be a terminal")
|
return errors.New("output appears to be a terminal")
|
||||||
}
|
}
|
||||||
return cm.Do(func(cache *pkg.Cache) error {
|
return rosa.WriteReport(msg, w, cache)
|
||||||
return rosa.WriteReport(msg, w, cache)
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -349,26 +350,14 @@ func main() {
|
|||||||
" package(s) are out of date"))
|
" package(s) are out of date"))
|
||||||
}
|
}
|
||||||
return errors.Join(errs...)
|
return errors.Join(errs...)
|
||||||
}).Flag(
|
}).
|
||||||
&flagJobs,
|
Flag(
|
||||||
"j", command.IntFlag(32),
|
&flagJobs,
|
||||||
"Maximum number of simultaneous connections",
|
"j", command.IntFlag(32),
|
||||||
)
|
"Maximum number of simultaneous connections",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.NewCommand(
|
|
||||||
"daemon",
|
|
||||||
"Service artifact IR with Rosa OS extensions",
|
|
||||||
func(args []string) error {
|
|
||||||
ul, err := net.ListenUnix("unix", &addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("listening on pathname socket at %s", addr.Name)
|
|
||||||
return serve(ctx, log.Default(), &cm, ul)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagGentoo string
|
flagGentoo string
|
||||||
@@ -393,37 +382,25 @@ func main() {
|
|||||||
rosa.SetGentooStage3(flagGentoo, checksum)
|
rosa.SetGentooStage3(flagGentoo, checksum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _, _, stage1 := (t - 2).NewLLVM()
|
||||||
|
_, _, _, stage2 := (t - 1).NewLLVM()
|
||||||
|
_, _, _, stage3 := t.NewLLVM()
|
||||||
var (
|
var (
|
||||||
pathname *check.Absolute
|
pathname *check.Absolute
|
||||||
checksum [2]unique.Handle[pkg.Checksum]
|
checksum [2]unique.Handle[pkg.Checksum]
|
||||||
)
|
)
|
||||||
|
|
||||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
if pathname, _, err = cache.Cure(stage1); err != nil {
|
||||||
pathname, _, err = cache.Cure(
|
return err
|
||||||
(t - 2).Load(rosa.LLVM),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Println("stage1:", pathname)
|
log.Println("stage1:", pathname)
|
||||||
|
|
||||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
if pathname, checksum[0], err = cache.Cure(stage2); err != nil {
|
||||||
pathname, checksum[0], err = cache.Cure(
|
return err
|
||||||
(t - 1).Load(rosa.LLVM),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Println("stage2:", pathname)
|
log.Println("stage2:", pathname)
|
||||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
if pathname, checksum[1], err = cache.Cure(stage3); err != nil {
|
||||||
pathname, checksum[1], err = cache.Cure(
|
return err
|
||||||
t.Load(rosa.LLVM),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Println("stage3:", pathname)
|
log.Println("stage3:", pathname)
|
||||||
|
|
||||||
@@ -440,44 +417,39 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if flagStage0 {
|
if flagStage0 {
|
||||||
if err = cm.Do(func(cache *pkg.Cache) (err error) {
|
if pathname, _, err = cache.Cure(
|
||||||
pathname, _, err = cache.Cure(
|
t.Load(rosa.Stage0),
|
||||||
t.Load(rosa.Stage0),
|
); err != nil {
|
||||||
)
|
return err
|
||||||
return
|
|
||||||
}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
log.Println(pathname)
|
log.Println(pathname)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
).Flag(
|
).
|
||||||
&flagGentoo,
|
Flag(
|
||||||
"gentoo", command.StringFlag(""),
|
&flagGentoo,
|
||||||
"Bootstrap from a Gentoo stage3 tarball",
|
"gentoo", command.StringFlag(""),
|
||||||
).Flag(
|
"Bootstrap from a Gentoo stage3 tarball",
|
||||||
&flagChecksum,
|
).
|
||||||
"checksum", command.StringFlag(""),
|
Flag(
|
||||||
"Checksum of Gentoo stage3 tarball",
|
&flagChecksum,
|
||||||
).Flag(
|
"checksum", command.StringFlag(""),
|
||||||
&flagStage0,
|
"Checksum of Gentoo stage3 tarball",
|
||||||
"stage0", command.BoolFlag(false),
|
).
|
||||||
"Create bootstrap stage0 tarball",
|
Flag(
|
||||||
)
|
&flagStage0,
|
||||||
|
"stage0", command.BoolFlag(false),
|
||||||
|
"Create bootstrap stage0 tarball",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagDump string
|
flagDump string
|
||||||
flagEnter bool
|
flagEnter bool
|
||||||
flagExport string
|
flagExport string
|
||||||
flagRemote bool
|
|
||||||
flagNoReply bool
|
|
||||||
|
|
||||||
flagBoot bool
|
|
||||||
flagStd bool
|
|
||||||
)
|
)
|
||||||
c.NewCommand(
|
c.NewCommand(
|
||||||
"cure",
|
"cure",
|
||||||
@@ -491,20 +463,9 @@ func main() {
|
|||||||
return fmt.Errorf("unknown artifact %q", args[0])
|
return fmt.Errorf("unknown artifact %q", args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
t := rosa.Std
|
|
||||||
if flagBoot {
|
|
||||||
t -= 2
|
|
||||||
} else if flagStd {
|
|
||||||
t -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
default:
|
default:
|
||||||
var pathname *check.Absolute
|
pathname, _, err := cache.Cure(rosa.Std.Load(p))
|
||||||
err := cm.Do(func(cache *pkg.Cache) (err error) {
|
|
||||||
pathname, _, err = cache.Cure(t.Load(p))
|
|
||||||
return
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -544,7 +505,7 @@ func main() {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = pkg.NewIR().EncodeAll(f, rosa.Std.Load(p)); err != nil {
|
if err = cache.EncodeAll(f, rosa.Std.Load(p)); err != nil {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -552,76 +513,33 @@ func main() {
|
|||||||
return f.Close()
|
return f.Close()
|
||||||
|
|
||||||
case flagEnter:
|
case flagEnter:
|
||||||
return cm.Do(func(cache *pkg.Cache) error {
|
return cache.EnterExec(
|
||||||
return cache.EnterExec(
|
ctx,
|
||||||
ctx,
|
rosa.Std.Load(p),
|
||||||
t.Load(p),
|
true, os.Stdin, os.Stdout, os.Stderr,
|
||||||
true, os.Stdin, os.Stdout, os.Stderr,
|
rosa.AbsSystem.Append("bin", "mksh"),
|
||||||
rosa.AbsSystem.Append("bin", "mksh"),
|
"sh",
|
||||||
"sh",
|
)
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
case flagRemote:
|
|
||||||
var flags uint64
|
|
||||||
if flagNoReply {
|
|
||||||
flags |= remoteNoReply
|
|
||||||
}
|
|
||||||
a := t.Load(p)
|
|
||||||
pathname, err := cureRemote(ctx, &addr, a, flags)
|
|
||||||
if !flagNoReply && err == nil {
|
|
||||||
log.Println(pathname)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, context.Canceled) {
|
|
||||||
cc, cancel := context.WithDeadline(context.Background(), daemonDeadline())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
if _err := cancelRemote(cc, &addr, a, false); _err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
).Flag(
|
).
|
||||||
&flagDump,
|
Flag(
|
||||||
"dump", command.StringFlag(""),
|
&flagDump,
|
||||||
"Write IR to specified pathname and terminate",
|
"dump", command.StringFlag(""),
|
||||||
).Flag(
|
"Write IR to specified pathname and terminate",
|
||||||
&flagExport,
|
).
|
||||||
"export", command.StringFlag(""),
|
Flag(
|
||||||
"Export cured artifact to specified pathname",
|
&flagExport,
|
||||||
).Flag(
|
"export", command.StringFlag(""),
|
||||||
&flagEnter,
|
"Export cured artifact to specified pathname",
|
||||||
"enter", command.BoolFlag(false),
|
).
|
||||||
"Enter cure container with an interactive shell",
|
Flag(
|
||||||
).Flag(
|
&flagEnter,
|
||||||
&flagRemote,
|
"enter", command.BoolFlag(false),
|
||||||
"daemon", command.BoolFlag(false),
|
"Enter cure container with an interactive shell",
|
||||||
"Cure artifact on the daemon",
|
)
|
||||||
).Flag(
|
|
||||||
&flagNoReply,
|
|
||||||
"no-reply", command.BoolFlag(false),
|
|
||||||
"Do not receive a reply from the daemon",
|
|
||||||
).Flag(
|
|
||||||
&flagBoot,
|
|
||||||
"boot", command.BoolFlag(false),
|
|
||||||
"Build on the stage0 toolchain",
|
|
||||||
).Flag(
|
|
||||||
&flagStd,
|
|
||||||
"std", command.BoolFlag(false),
|
|
||||||
"Build on the intermediate toolchain",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.NewCommand(
|
|
||||||
"abort",
|
|
||||||
"Abort all pending cures on the daemon",
|
|
||||||
func([]string) error { return abortRemote(ctx, &addr, false) },
|
|
||||||
)
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var (
|
var (
|
||||||
flagNet bool
|
flagNet bool
|
||||||
@@ -633,7 +551,7 @@ func main() {
|
|||||||
"shell",
|
"shell",
|
||||||
"Interactive shell in the specified Rosa OS environment",
|
"Interactive shell in the specified Rosa OS environment",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
presets := make([]rosa.PArtifact, len(args)+3)
|
presets := make([]rosa.PArtifact, len(args))
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
p, ok := rosa.ResolveName(arg)
|
p, ok := rosa.ResolveName(arg)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -641,24 +559,21 @@ func main() {
|
|||||||
}
|
}
|
||||||
presets[i] = p
|
presets[i] = p
|
||||||
}
|
}
|
||||||
|
|
||||||
base := rosa.LLVM
|
|
||||||
if !flagWithToolchain {
|
|
||||||
base = rosa.Musl
|
|
||||||
}
|
|
||||||
presets = append(presets,
|
|
||||||
base,
|
|
||||||
rosa.Mksh,
|
|
||||||
rosa.Toybox,
|
|
||||||
)
|
|
||||||
|
|
||||||
root := make(pkg.Collect, 0, 6+len(args))
|
root := make(pkg.Collect, 0, 6+len(args))
|
||||||
root = rosa.Std.AppendPresets(root, presets...)
|
root = rosa.Std.AppendPresets(root, presets...)
|
||||||
|
|
||||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
if flagWithToolchain {
|
||||||
_, _, err := cache.Cure(&root)
|
musl, compilerRT, runtimes, clang := (rosa.Std - 1).NewLLVM()
|
||||||
return err
|
root = append(root, musl, compilerRT, runtimes, clang)
|
||||||
}); err == nil {
|
} else {
|
||||||
|
root = append(root, rosa.Std.Load(rosa.Musl))
|
||||||
|
}
|
||||||
|
root = append(root,
|
||||||
|
rosa.Std.Load(rosa.Mksh),
|
||||||
|
rosa.Std.Load(rosa.Toybox),
|
||||||
|
)
|
||||||
|
|
||||||
|
if _, _, err := cache.Cure(&root); err == nil {
|
||||||
return errors.New("unreachable")
|
return errors.New("unreachable")
|
||||||
} else if !pkg.IsCollected(err) {
|
} else if !pkg.IsCollected(err) {
|
||||||
return err
|
return err
|
||||||
@@ -670,22 +585,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
cured := make(map[pkg.Artifact]cureRes)
|
cured := make(map[pkg.Artifact]cureRes)
|
||||||
for _, a := range root {
|
for _, a := range root {
|
||||||
if err := cm.Do(func(cache *pkg.Cache) error {
|
pathname, checksum, err := cache.Cure(a)
|
||||||
pathname, checksum, err := cache.Cure(a)
|
if err != nil {
|
||||||
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
|
return err
|
||||||
}
|
}
|
||||||
|
cured[a] = cureRes{pathname, checksum}
|
||||||
}
|
}
|
||||||
|
|
||||||
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
|
layers := pkg.PromoteLayers(root, func(a pkg.Artifact) (
|
||||||
@@ -695,7 +599,7 @@ func main() {
|
|||||||
res := cured[a]
|
res := cured[a]
|
||||||
return res.pathname, res.checksum
|
return res.pathname, res.checksum
|
||||||
}, func(i int, d pkg.Artifact) {
|
}, func(i int, d pkg.Artifact) {
|
||||||
r := pkg.Encode(cm.c.Ident(d).Value())
|
r := pkg.Encode(cache.Ident(d).Value())
|
||||||
if s, ok := d.(fmt.Stringer); ok {
|
if s, ok := d.(fmt.Stringer); ok {
|
||||||
if name := s.String(); name != "" {
|
if name := s.String(); name != "" {
|
||||||
r += "-" + name
|
r += "-" + name
|
||||||
@@ -714,7 +618,6 @@ func main() {
|
|||||||
z.Hostname = "localhost"
|
z.Hostname = "localhost"
|
||||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||||
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
z.Stdin, z.Stdout, z.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
z.Quiet = !cm.verboseInit
|
|
||||||
if s, ok := os.LookupEnv("TERM"); ok {
|
if s, ok := os.LookupEnv("TERM"); ok {
|
||||||
z.Env = append(z.Env, "TERM="+s)
|
z.Env = append(z.Env, "TERM="+s)
|
||||||
}
|
}
|
||||||
@@ -760,19 +663,22 @@ func main() {
|
|||||||
}
|
}
|
||||||
return z.Wait()
|
return z.Wait()
|
||||||
},
|
},
|
||||||
).Flag(
|
).
|
||||||
&flagNet,
|
Flag(
|
||||||
"net", command.BoolFlag(false),
|
&flagNet,
|
||||||
"Share host net namespace",
|
"net", command.BoolFlag(false),
|
||||||
).Flag(
|
"Share host net namespace",
|
||||||
&flagSession,
|
).
|
||||||
"session", command.BoolFlag(true),
|
Flag(
|
||||||
"Retain session",
|
&flagSession,
|
||||||
).Flag(
|
"session", command.BoolFlag(true),
|
||||||
&flagWithToolchain,
|
"Retain session",
|
||||||
"with-toolchain", command.BoolFlag(false),
|
).
|
||||||
"Include the stage2 LLVM toolchain",
|
Flag(
|
||||||
)
|
&flagWithToolchain,
|
||||||
|
"with-toolchain", command.BoolFlag(false),
|
||||||
|
"Include the stage2 LLVM toolchain",
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,7 +689,9 @@ func main() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
c.MustParse(os.Args[1:], func(err error) {
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
cm.Close()
|
if cache != nil {
|
||||||
|
cache.Close()
|
||||||
|
}
|
||||||
if w, ok := err.(interface{ Unwrap() []error }); !ok {
|
if w, ok := err.(interface{ Unwrap() []error }); !ok {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"hakurei.app/internal/rosa"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
rosa.DropCaches(rosa.OptLLVMNoLTO)
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCureAll(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
const env = "ROSA_TEST_DAEMON"
|
|
||||||
|
|
||||||
if !testing.Verbose() {
|
|
||||||
t.Skip("verbose flag not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
pathname, ok := os.LookupEnv(env)
|
|
||||||
if !ok {
|
|
||||||
t.Skip(env + " not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := net.UnixAddr{Net: "unix", Name: pathname}
|
|
||||||
t.Cleanup(func() {
|
|
||||||
if t.Failed() {
|
|
||||||
if err := abortRemote(t.Context(), &addr, false); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
// Package pkgserver implements the package metadata service backend.
|
package main
|
||||||
package pkgserver
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -10,7 +8,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"hakurei.app/internal/info"
|
"hakurei.app/internal/info"
|
||||||
"hakurei.app/internal/rosa"
|
"hakurei.app/internal/rosa"
|
||||||
@@ -161,29 +158,6 @@ func (index *packageIndex) registerAPI(mux *http.ServeMux) {
|
|||||||
mux.HandleFunc("GET /status/", index.newStatusHandler(true))
|
mux.HandleFunc("GET /status/", index.newStatusHandler(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register arranges for mux to service API requests.
|
|
||||||
func Register(ctx context.Context, mux *http.ServeMux, report *rosa.Report) error {
|
|
||||||
var index packageIndex
|
|
||||||
index.search = make(searchCache)
|
|
||||||
if err := index.populate(report); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
ticker.Stop()
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
index.search.clean()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
index.registerAPI(mux)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// writeAPIPayload sets headers common to API responses and encodes payload as
|
// writeAPIPayload sets headers common to API responses and encodes payload as
|
||||||
// JSON for the response body.
|
// JSON for the response body.
|
||||||
func writeAPIPayload(w http.ResponseWriter, payload any) {
|
func writeAPIPayload(w http.ResponseWriter, payload any) {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package pkgserver
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -118,31 +118,33 @@ func TestAPIGet(t *testing.T) {
|
|||||||
checkStatus(t, resp, http.StatusOK)
|
checkStatus(t, resp, http.StatusOK)
|
||||||
checkAPIHeader(t, w.Header())
|
checkAPIHeader(t, w.Header())
|
||||||
checkPayloadFunc(t, resp, func(got *struct {
|
checkPayloadFunc(t, resp, func(got *struct {
|
||||||
|
Count int `json:"count"`
|
||||||
Values []*metadata `json:"values"`
|
Values []*metadata `json:"values"`
|
||||||
}) bool {
|
}) bool {
|
||||||
return slices.EqualFunc(got.Values, want, func(a, b *metadata) bool {
|
return got.Count == len(want) &&
|
||||||
return (a.Version == b.Version ||
|
slices.EqualFunc(got.Values, want, func(a, b *metadata) bool {
|
||||||
a.Version == rosa.Unversioned ||
|
return (a.Version == b.Version ||
|
||||||
b.Version == rosa.Unversioned) &&
|
a.Version == rosa.Unversioned ||
|
||||||
a.HasReport == b.HasReport &&
|
b.Version == rosa.Unversioned) &&
|
||||||
a.Name == b.Name &&
|
a.HasReport == b.HasReport &&
|
||||||
a.Description == b.Description &&
|
a.Name == b.Name &&
|
||||||
a.Website == b.Website
|
a.Description == b.Description &&
|
||||||
})
|
a.Website == b.Website
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
checkWithSuffix("declarationAscending", "?limit=2&index=1&sort=0", []*metadata{
|
checkWithSuffix("declarationAscending", "?limit=2&index=0&sort=0", []*metadata{
|
||||||
|
{
|
||||||
|
Metadata: rosa.GetMetadata(0),
|
||||||
|
Version: rosa.Std.Version(0),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Metadata: rosa.GetMetadata(1),
|
Metadata: rosa.GetMetadata(1),
|
||||||
Version: rosa.Std.Version(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{
|
checkWithSuffix("declarationAscending offset", "?limit=3&index=5&sort=0", []*metadata{
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package pkgserver
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
@@ -50,7 +50,7 @@ type metadata struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// populate deterministically populates packageIndex, optionally with a report.
|
// populate deterministically populates packageIndex, optionally with a report.
|
||||||
func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
func (index *packageIndex) populate(cache *pkg.Cache, report *rosa.Report) (err error) {
|
||||||
if report != nil {
|
if report != nil {
|
||||||
defer report.HandleAccess(&err)()
|
defer report.HandleAccess(&err)()
|
||||||
index.handleAccess = report.HandleAccess
|
index.handleAccess = report.HandleAccess
|
||||||
@@ -58,7 +58,6 @@ func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
|||||||
|
|
||||||
var work [rosa.PresetUnexportedStart]*metadata
|
var work [rosa.PresetUnexportedStart]*metadata
|
||||||
index.names = make(map[string]*metadata)
|
index.names = make(map[string]*metadata)
|
||||||
ir := pkg.NewIR()
|
|
||||||
for p := range rosa.PresetUnexportedStart {
|
for p := range rosa.PresetUnexportedStart {
|
||||||
m := metadata{
|
m := metadata{
|
||||||
p: p,
|
p: p,
|
||||||
@@ -73,8 +72,8 @@ func (index *packageIndex) populate(report *rosa.Report) (err error) {
|
|||||||
m.Version = ""
|
m.Version = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if report != nil {
|
if cache != nil && report != nil {
|
||||||
id := ir.Ident(rosa.Std.Load(p))
|
id := cache.Ident(rosa.Std.Load(p))
|
||||||
m.ids = pkg.Encode(id.Value())
|
m.ids = pkg.Encode(id.Value())
|
||||||
m.status, m.Size = report.ArtifactOf(id)
|
m.status, m.Size = report.ArtifactOf(id)
|
||||||
m.HasReport = m.Size >= 0
|
m.HasReport = m.Size >= 0
|
||||||
115
cmd/pkgserver/main.go
Normal file
115
cmd/pkgserver/main.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
|
"hakurei.app/command"
|
||||||
|
"hakurei.app/internal/pkg"
|
||||||
|
"hakurei.app/internal/rosa"
|
||||||
|
"hakurei.app/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shutdownTimeout = 15 * time.Second
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
log.SetPrefix("pkgserver: ")
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagBaseDir string
|
||||||
|
flagAddr string
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||||
|
defer stop()
|
||||||
|
msg := message.New(log.Default())
|
||||||
|
|
||||||
|
c := command.New(os.Stderr, log.Printf, "pkgserver", func(args []string) error {
|
||||||
|
var (
|
||||||
|
cache *pkg.Cache
|
||||||
|
report *rosa.Report
|
||||||
|
)
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
baseDir, err := check.NewAbs(flagBaseDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cache, err = pkg.Open(ctx, msg, 0, 0, baseDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cache.Close()
|
||||||
|
|
||||||
|
report, err = rosa.OpenReport(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New("pkgserver requires 1 argument")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var index packageIndex
|
||||||
|
index.search = make(searchCache)
|
||||||
|
if err := index.populate(cache, report); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
ticker.Stop()
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
index.search.clean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var mux http.ServeMux
|
||||||
|
uiRoutes(&mux)
|
||||||
|
testUIRoutes(&mux)
|
||||||
|
index.registerAPI(&mux)
|
||||||
|
server := http.Server{
|
||||||
|
Addr: flagAddr,
|
||||||
|
Handler: &mux,
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
c, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := server.Shutdown(c); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return server.ListenAndServe()
|
||||||
|
}).Flag(
|
||||||
|
&flagBaseDir,
|
||||||
|
"b", command.StringFlag(""),
|
||||||
|
"base directory for cache",
|
||||||
|
).Flag(
|
||||||
|
&flagAddr,
|
||||||
|
"addr", command.StringFlag(":8067"),
|
||||||
|
"TCP network address to listen on",
|
||||||
|
)
|
||||||
|
c.MustParse(os.Args[1:], func(err error) {
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
log.Fatal(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package pkgserver
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -15,7 +15,7 @@ func newIndex(t *testing.T) *packageIndex {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var index packageIndex
|
var index packageIndex
|
||||||
if err := index.populate(nil); err != nil {
|
if err := index.populate(nil, nil); err != nil {
|
||||||
t.Fatalf("populate: error = %v", err)
|
t.Fatalf("populate: error = %v", err)
|
||||||
}
|
}
|
||||||
return &index
|
return &index
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package pkgserver
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"cmp"
|
||||||
35
cmd/pkgserver/test_ui.go
Normal file
35
cmd/pkgserver/test_ui.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
//go:build frontend && frontend_test
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Always remove ui_test/ui; if the previous tsc run failed, the rm never
|
||||||
|
// executes.
|
||||||
|
|
||||||
|
//go:generate sh -c "rm -r ui_test/ui/ 2>/dev/null || true"
|
||||||
|
//go:generate mkdir ui_test/ui
|
||||||
|
//go:generate sh -c "cp ui/static/*.ts ui_test/ui/"
|
||||||
|
//go:generate tsc -p ui_test
|
||||||
|
//go:generate rm -r ui_test/ui/
|
||||||
|
//go:generate cp ui_test/lib/ui.css ui_test/static/style.css
|
||||||
|
//go:generate cp ui_test/lib/ui.html ui_test/static/index.html
|
||||||
|
//go:generate sh -c "cd ui_test/lib && cp *.svg ../static/"
|
||||||
|
//go:embed ui_test/static
|
||||||
|
var _staticTest embed.FS
|
||||||
|
|
||||||
|
var staticTest = func() fs.FS {
|
||||||
|
if f, err := fs.Sub(_staticTest, "ui_test/static"); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
func testUIRoutes(mux *http.ServeMux) {
|
||||||
|
mux.Handle("GET /test/", http.StripPrefix("/test", http.FileServer(http.FS(staticTest))))
|
||||||
|
}
|
||||||
7
cmd/pkgserver/test_ui_stub.go
Normal file
7
cmd/pkgserver/test_ui_stub.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !(frontend && frontend_test)
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func testUIRoutes(mux *http.ServeMux) {}
|
||||||
33
cmd/pkgserver/ui.go
Normal file
33
cmd/pkgserver/ui.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func serveWebUI(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||||
|
w.Header().Set("Pragma", "no-cache")
|
||||||
|
w.Header().Set("Expires", "0")
|
||||||
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||||
|
w.Header().Set("X-XSS-Protection", "1")
|
||||||
|
w.Header().Set("X-Frame-Options", "DENY")
|
||||||
|
|
||||||
|
http.ServeFileFS(w, r, content, "ui/index.html")
|
||||||
|
}
|
||||||
|
func serveStaticContent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/static/style.css":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/style.css")
|
||||||
|
case "/favicon.ico":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/favicon.ico")
|
||||||
|
case "/static/index.js":
|
||||||
|
http.ServeFileFS(w, r, content, "ui/static/index.js")
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uiRoutes(mux *http.ServeMux) {
|
||||||
|
mux.HandleFunc("GET /{$}", serveWebUI)
|
||||||
|
mux.HandleFunc("GET /favicon.ico", serveStaticContent)
|
||||||
|
mux.HandleFunc("GET /static/", serveStaticContent)
|
||||||
|
}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="style.css">
|
<link rel="stylesheet" href="static/style.css">
|
||||||
<title>Hakurei PkgServer</title>
|
<title>Hakurei PkgServer</title>
|
||||||
<script src="index.js"></script>
|
<script src="static/index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Hakurei PkgServer</h1>
|
<h1>Hakurei PkgServer</h1>
|
||||||
@@ -124,8 +124,8 @@ interface SearchPayload {
|
|||||||
async function searchRequest(limit: number, index: number, search: string, desc: boolean): Promise<SearchPayload> {
|
async function searchRequest(limit: number, index: number, search: string, desc: boolean): Promise<SearchPayload> {
|
||||||
const res = await fetch(`${ENDPOINT}/search?limit=${limit}&index=${index}&search=${encodeURIComponent(search)}&desc=${desc}`)
|
const res = await fetch(`${ENDPOINT}/search?limit=${limit}&index=${index}&search=${encodeURIComponent(search)}&desc=${desc}`)
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
exitSearch()
|
|
||||||
alert("invalid search query!")
|
alert("invalid search query!")
|
||||||
|
exitSearch()
|
||||||
return Promise.reject(res.statusText)
|
return Promise.reject(res.statusText)
|
||||||
}
|
}
|
||||||
const payload = await res.json()
|
const payload = await res.json()
|
||||||
@@ -214,10 +214,6 @@ class State {
|
|||||||
}
|
}
|
||||||
STATE.maxTotal = res.count!
|
STATE.maxTotal = res.count!
|
||||||
STATE.updateRange()
|
STATE.updateRange()
|
||||||
if(res.count! < 1) {
|
|
||||||
exitSearch()
|
|
||||||
alert("no results found!")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
getRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSortOrder())
|
getRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSortOrder())
|
||||||
BIN
cmd/pkgserver/ui/static/favicon.ico
Normal file
BIN
cmd/pkgserver/ui/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
9
cmd/pkgserver/ui_full.go
Normal file
9
cmd/pkgserver/ui_full.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build frontend
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:generate tsc -p ui
|
||||||
|
//go:embed ui/*
|
||||||
|
var content embed.FS
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//go:build !frontend
|
//go:build !frontend
|
||||||
|
|
||||||
package ui
|
package main
|
||||||
|
|
||||||
import "testing/fstest"
|
import "testing/fstest"
|
||||||
|
|
||||||
var static fstest.MapFS
|
var content fstest.MapFS
|
||||||
2
cmd/pkgserver/ui_test/all_tests.ts
Normal file
2
cmd/pkgserver/ui_test/all_tests.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Import all test files to register their test suites.
|
||||||
|
import "./index_test.js";
|
||||||
2
cmd/pkgserver/ui_test/index_test.ts
Normal file
2
cmd/pkgserver/ui_test/index_test.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import { suite, test } from "./lib/test.js";
|
||||||
|
import "./ui/index.js";
|
||||||
48
cmd/pkgserver/ui_test/lib/cli.ts
Normal file
48
cmd/pkgserver/ui_test/lib/cli.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Many editors have terminal emulators built in, so running tests with NodeJS
|
||||||
|
// provides faster iteration, especially for those acclimated to test-driven
|
||||||
|
// development.
|
||||||
|
|
||||||
|
import "../all_tests.js";
|
||||||
|
import { StreamReporter, GLOBAL_REGISTRAR } from "./test.js";
|
||||||
|
|
||||||
|
// TypeScript doesn't like process and Deno as their type definitions aren't
|
||||||
|
// installed, but doesn't seem to complain if they're accessed through
|
||||||
|
// globalThis.
|
||||||
|
const process: any = (globalThis as any).process;
|
||||||
|
const Deno: any = (globalThis as any).Deno;
|
||||||
|
|
||||||
|
function getArgs(): string[] {
|
||||||
|
if (process) {
|
||||||
|
const [runtime, program, ...args] = process.argv;
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
if (Deno) return Deno.args;
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function exit(code?: number): never {
|
||||||
|
if (Deno) Deno.exit(code);
|
||||||
|
if (process) process.exit(code);
|
||||||
|
throw `exited with code ${code ?? 0}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = getArgs();
|
||||||
|
let verbose = false;
|
||||||
|
if (args.length > 1) {
|
||||||
|
console.error("Too many arguments");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
if (args.length === 1) {
|
||||||
|
if (args[0] === "-v" || args[0] === "--verbose" || args[0] === "-verbose") {
|
||||||
|
verbose = true;
|
||||||
|
} else if (args[0] !== "--") {
|
||||||
|
console.error(`Unknown argument '${args[0]}'`);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let reporter = new StreamReporter({ writeln: console.log }, verbose);
|
||||||
|
GLOBAL_REGISTRAR.run(reporter);
|
||||||
|
exit(reporter.succeeded() ? 0 : 1);
|
||||||
13
cmd/pkgserver/ui_test/lib/failure-closed.svg
Normal file
13
cmd/pkgserver/ui_test/lib/failure-closed.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
||||||
|
<!-- This triangle should match success-closed.svg, fill and stroke color notwithstanding. -->
|
||||||
|
<polygon points="0,0 100,50 0,100" fill="red" stroke="red" stroke-width="15" stroke-linejoin="round"/>
|
||||||
|
<!--
|
||||||
|
! y-coordinates go before x-coordinates here to highlight the difference
|
||||||
|
! (or, lack thereof) between these numbers and the ones in failure-open.svg;
|
||||||
|
! try a textual diff. Make sure to keep the numbers in sync!
|
||||||
|
-->
|
||||||
|
<line y1="30" x1="10" y2="70" x2="50" stroke="white" stroke-width="16"/>
|
||||||
|
<line y1="30" x1="50" y2="70" x2="10" stroke="white" stroke-width="16"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 788 B |
35
cmd/pkgserver/ui_test/lib/failure-open.svg
Normal file
35
cmd/pkgserver/ui_test/lib/failure-open.svg
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!--
|
||||||
|
! This view box is a bit weird: the strokes assume they're working in a view
|
||||||
|
! box that spans from the (0,0) to (100,100), and indeed that is convenient
|
||||||
|
! conceptualizing the strokes, but the stroke itself has a considerable width
|
||||||
|
! that gets clipped by restrictive view box dimensions. Hence, the view is
|
||||||
|
! shifted from (0,0)–(100,100) to (-20,-20)–(120,120), to make room for the
|
||||||
|
! clipped stroke, while leaving behind an illusion of working in a view box
|
||||||
|
! spanning from (0,0) to (100,100).
|
||||||
|
!
|
||||||
|
! However, the resulting SVG is too close to the summary text, and CSS
|
||||||
|
! properties to add padding do not seem to work with `content:` (likely because
|
||||||
|
! they're anonymous replaced elements); thus, the width of the view is
|
||||||
|
! increased considerably to provide padding in the SVG itself, while leaving
|
||||||
|
! the strokes oblivious.
|
||||||
|
!
|
||||||
|
! It gets worse: the summary text isn't vertically aligned with the icon! As
|
||||||
|
! a flexbox cannot be used in a summary to align the marker with the text, the
|
||||||
|
! simplest and most effective solution is to reduce the height of the view box
|
||||||
|
! from 140 to 130, thereby removing some of the bottom padding present.
|
||||||
|
!
|
||||||
|
! All six SVGs use the same view box (and indeed, they refer to this comment)
|
||||||
|
! so that they all appear to be the same size and position relative to each
|
||||||
|
! other on the DOM—indeed, the view box dimensions, alongside the width,
|
||||||
|
! directly control their placement on the DOM.
|
||||||
|
!
|
||||||
|
! TL;DR: CSS is janky, overflow is weird, and SVG is awesome!
|
||||||
|
-->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
||||||
|
<!-- This triangle should match success-open.svg, fill and stroke color notwithstanding. -->
|
||||||
|
<polygon points="0,0 100,0 50,100" fill="red" stroke="red" stroke-width="15" stroke-linejoin="round"/>
|
||||||
|
<!-- See the comment in failure-closed.svg before modifying this. -->
|
||||||
|
<line x1="30" y1="10" x2="70" y2="50" stroke="white" stroke-width="16"/>
|
||||||
|
<line x1="30" y1="50" x2="70" y2="10" stroke="white" stroke-width="16"/>
|
||||||
|
</svg>
|
||||||
3
cmd/pkgserver/ui_test/lib/go_test_entrypoint.ts
Normal file
3
cmd/pkgserver/ui_test/lib/go_test_entrypoint.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import "../all_tests.js";
|
||||||
|
import { GoTestReporter, GLOBAL_REGISTRAR } from "./test.js";
|
||||||
|
GLOBAL_REGISTRAR.run(new GoTestReporter());
|
||||||
21
cmd/pkgserver/ui_test/lib/skip-closed.svg
Normal file
21
cmd/pkgserver/ui_test/lib/skip-closed.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
||||||
|
<!-- This triangle should match success-closed.svg, fill and stroke color notwithstanding. -->
|
||||||
|
<polygon points="0,0 100,50 0,100" fill="blue" stroke="blue" stroke-width="15" stroke-linejoin="round"/>
|
||||||
|
<!--
|
||||||
|
! This path is extremely similar to the one in skip-open.svg; before
|
||||||
|
! making minor modifications, diff the two to understand how they should
|
||||||
|
! remain in sync.
|
||||||
|
-->
|
||||||
|
<path
|
||||||
|
d="M 50,50
|
||||||
|
A 23,23 270,1,1 30,30
|
||||||
|
l -10,20
|
||||||
|
m 10,-20
|
||||||
|
l -20,-10"
|
||||||
|
fill="none"
|
||||||
|
stroke="white"
|
||||||
|
stroke-width="12"
|
||||||
|
stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 812 B |
21
cmd/pkgserver/ui_test/lib/skip-open.svg
Normal file
21
cmd/pkgserver/ui_test/lib/skip-open.svg
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
||||||
|
<!-- This triangle should match success-open.svg, fill and stroke color notwithstanding. -->
|
||||||
|
<polygon points="0,0 100,0 50,100" fill="blue" stroke="blue" stroke-width="15" stroke-linejoin="round"/>
|
||||||
|
<!--
|
||||||
|
! This path is extremely similar to the one in skip-closed.svg; before
|
||||||
|
! making minor modifications, diff the two to understand how they should
|
||||||
|
! remain in sync.
|
||||||
|
-->
|
||||||
|
<path
|
||||||
|
d="M 50,50
|
||||||
|
A 23,23 270,1,1 70,30
|
||||||
|
l 10,-20
|
||||||
|
m -10,20
|
||||||
|
l -20,-10"
|
||||||
|
fill="none"
|
||||||
|
stroke="white"
|
||||||
|
stroke-width="12"
|
||||||
|
stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 812 B |
16
cmd/pkgserver/ui_test/lib/success-closed.svg
Normal file
16
cmd/pkgserver/ui_test/lib/success-closed.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
||||||
|
<style>
|
||||||
|
.adaptive-stroke {
|
||||||
|
stroke: black;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.adaptive-stroke {
|
||||||
|
stroke: ghostwhite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- When updating this triangle, also update the other five SVGs. -->
|
||||||
|
<polygon points="0,0 100,50 0,100" fill="none" class="adaptive-stroke" stroke-width="15" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 572 B |
16
cmd/pkgserver/ui_test/lib/success-open.svg
Normal file
16
cmd/pkgserver/ui_test/lib/success-open.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- See failure-open.svg for an explanation of the view box dimensions. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="-20,-20 160 130">
|
||||||
|
<style>
|
||||||
|
.adaptive-stroke {
|
||||||
|
stroke: black;
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.adaptive-stroke {
|
||||||
|
stroke: ghostwhite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!-- When updating this triangle, also update the other five SVGs. -->
|
||||||
|
<polygon points="0,0 100,0 50,100" fill="none" class="adaptive-stroke" stroke-width="15" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 572 B |
403
cmd/pkgserver/ui_test/lib/test.ts
Normal file
403
cmd/pkgserver/ui_test/lib/test.ts
Normal file
@@ -0,0 +1,403 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// DSL
|
||||||
|
|
||||||
|
type TestTree = TestGroup | Test;
|
||||||
|
type TestGroup = { name: string; children: TestTree[] };
|
||||||
|
type Test = { name: string; test: (t: TestController) => void };
|
||||||
|
|
||||||
|
export class TestRegistrar {
|
||||||
|
#suites: TestGroup[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#suites = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
suite(name: string, children: TestTree[]) {
|
||||||
|
checkDuplicates(name, children);
|
||||||
|
this.#suites.push({ name, children });
|
||||||
|
}
|
||||||
|
|
||||||
|
run(reporter: Reporter) {
|
||||||
|
reporter.register(this.#suites);
|
||||||
|
for (const suite of this.#suites) {
|
||||||
|
for (const c of suite.children) runTests(reporter, [suite.name], c);
|
||||||
|
}
|
||||||
|
reporter.finalize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let GLOBAL_REGISTRAR = new TestRegistrar();
|
||||||
|
|
||||||
|
// Register a suite in the global registrar.
|
||||||
|
export function suite(name: string, children: TestTree[]) {
|
||||||
|
GLOBAL_REGISTRAR.suite(name, children);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function group(name: string, children: TestTree[]): TestTree {
|
||||||
|
checkDuplicates(name, children);
|
||||||
|
return { name, children };
|
||||||
|
}
|
||||||
|
export const context = group;
|
||||||
|
export const describe = group;
|
||||||
|
|
||||||
|
export function test(name: string, test: (t: TestController) => void): TestTree {
|
||||||
|
return { name, test };
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDuplicates(parent: string, names: { name: string }[]) {
|
||||||
|
let seen = new Set<string>();
|
||||||
|
for (const { name } of names) {
|
||||||
|
if (seen.has(name)) {
|
||||||
|
throw new RangeError(`duplicate name '${name}' in '${parent}'`);
|
||||||
|
}
|
||||||
|
seen.add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TestState = "success" | "failure" | "skip";
|
||||||
|
|
||||||
|
class AbortSentinel {}
|
||||||
|
|
||||||
|
export class TestController {
|
||||||
|
#state: TestState;
|
||||||
|
logs: string[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#state = "success";
|
||||||
|
this.logs = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getState(): TestState {
|
||||||
|
return this.#state;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
this.#state = "failure";
|
||||||
|
}
|
||||||
|
|
||||||
|
failed(): boolean {
|
||||||
|
return this.#state === "failure";
|
||||||
|
}
|
||||||
|
|
||||||
|
failNow(): never {
|
||||||
|
this.fail();
|
||||||
|
throw new AbortSentinel();
|
||||||
|
}
|
||||||
|
|
||||||
|
log(message: string) {
|
||||||
|
this.logs.push(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(message: string) {
|
||||||
|
this.log(message);
|
||||||
|
this.fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
fatal(message: string): never {
|
||||||
|
this.log(message);
|
||||||
|
this.failNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
skip(message?: string): never {
|
||||||
|
if (message != null) this.log(message);
|
||||||
|
if (this.#state !== "failure") this.#state = "skip";
|
||||||
|
throw new AbortSentinel();
|
||||||
|
}
|
||||||
|
|
||||||
|
skipped(): boolean {
|
||||||
|
return this.#state === "skip";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Execution
|
||||||
|
|
||||||
|
export interface TestResult {
|
||||||
|
state: TestState;
|
||||||
|
logs: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function runTests(reporter: Reporter, parents: string[], node: TestTree) {
|
||||||
|
const path = [...parents, node.name];
|
||||||
|
if ("children" in node) {
|
||||||
|
for (const c of node.children) runTests(reporter, path, c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let controller = new TestController();
|
||||||
|
try {
|
||||||
|
node.test(controller);
|
||||||
|
} catch (e) {
|
||||||
|
if (!(e instanceof AbortSentinel)) {
|
||||||
|
controller.error(extractExceptionString(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reporter.update(path, { state: controller.getState(), logs: controller.logs });
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractExceptionString(e: any): string {
|
||||||
|
// String() instead of .toString() as null and undefined don't have
|
||||||
|
// properties.
|
||||||
|
const s = String(e);
|
||||||
|
if (!(e instanceof Error && e.stack)) return s;
|
||||||
|
// v8 (Chromium, NodeJS) includes the error message, while Firefox and
|
||||||
|
// WebKit do not.
|
||||||
|
if (e.stack.startsWith(s)) return e.stack;
|
||||||
|
return `${s}\n${e.stack}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Reporting
|
||||||
|
|
||||||
|
export interface Reporter {
|
||||||
|
register(suites: TestGroup[]): void;
|
||||||
|
update(path: string[], result: TestResult): void;
|
||||||
|
finalize(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NoOpReporter implements Reporter {
|
||||||
|
suites: TestGroup[];
|
||||||
|
results: ({ path: string[] } & TestResult)[];
|
||||||
|
finalized: boolean;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.suites = [];
|
||||||
|
this.results = [];
|
||||||
|
this.finalized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
register(suites: TestGroup[]) {
|
||||||
|
this.suites = suites;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(path: string[], result: TestResult) {
|
||||||
|
this.results.push({ path, ...result });
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
this.finalized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Stream {
|
||||||
|
writeln(s: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SEP = " ❯ ";
|
||||||
|
|
||||||
|
export class StreamReporter implements Reporter {
|
||||||
|
stream: Stream;
|
||||||
|
verbose: boolean;
|
||||||
|
#successes: ({ path: string[] } & TestResult)[];
|
||||||
|
#failures: ({ path: string[] } & TestResult)[];
|
||||||
|
#skips: ({ path: string[] } & TestResult)[];
|
||||||
|
|
||||||
|
constructor(stream: Stream, verbose: boolean = false) {
|
||||||
|
this.stream = stream;
|
||||||
|
this.verbose = verbose;
|
||||||
|
this.#successes = [];
|
||||||
|
this.#failures = [];
|
||||||
|
this.#skips = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
succeeded(): boolean {
|
||||||
|
return this.#successes.length > 0 && this.#failures.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
register(suites: TestGroup[]) {}
|
||||||
|
|
||||||
|
update(path: string[], result: TestResult) {
|
||||||
|
if (path.length === 0) throw new RangeError("path is empty");
|
||||||
|
const pathStr = path.join(SEP);
|
||||||
|
switch (result.state) {
|
||||||
|
case "success":
|
||||||
|
this.#successes.push({ path, ...result });
|
||||||
|
if (this.verbose) this.stream.writeln(`✅️ ${pathStr}`);
|
||||||
|
break;
|
||||||
|
case "failure":
|
||||||
|
this.#failures.push({ path, ...result });
|
||||||
|
this.stream.writeln(`⚠️ ${pathStr}`);
|
||||||
|
break;
|
||||||
|
case "skip":
|
||||||
|
this.#skips.push({ path, ...result });
|
||||||
|
this.stream.writeln(`⏭️ ${pathStr}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
if (this.verbose) this.#displaySection("successes", this.#successes, true);
|
||||||
|
this.#displaySection("failures", this.#failures);
|
||||||
|
this.#displaySection("skips", this.#skips);
|
||||||
|
this.stream.writeln("");
|
||||||
|
this.stream.writeln(
|
||||||
|
`${this.#successes.length} succeeded, ${this.#failures.length} failed` +
|
||||||
|
(this.#skips.length ? `, ${this.#skips.length} skipped` : ""),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#displaySection(name: string, data: ({ path: string[] } & TestResult)[], ignoreEmpty: boolean = false) {
|
||||||
|
if (!data.length) return;
|
||||||
|
|
||||||
|
// Transform [{ path: ["a", "b", "c"] }, { path: ["a", "b", "d"] }]
|
||||||
|
// into { "a ❯ b": ["c", "d"] }.
|
||||||
|
let pathMap = new Map<string, ({ name: string } & TestResult)[]>();
|
||||||
|
for (const t of data) {
|
||||||
|
if (t.path.length === 0) throw new RangeError("path is empty");
|
||||||
|
const key = t.path.slice(0, -1).join(SEP);
|
||||||
|
if (!pathMap.has(key)) pathMap.set(key, []);
|
||||||
|
pathMap.get(key)!.push({ name: t.path.at(-1)!, ...t });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stream.writeln("");
|
||||||
|
this.stream.writeln(name.toUpperCase());
|
||||||
|
this.stream.writeln("=".repeat(name.length));
|
||||||
|
|
||||||
|
for (let [path, tests] of pathMap) {
|
||||||
|
if (ignoreEmpty) tests = tests.filter((t) => t.logs.length);
|
||||||
|
if (tests.length === 0) continue;
|
||||||
|
if (tests.length === 1) {
|
||||||
|
this.#writeOutput(tests[0], path ? `${path}${SEP}` : "", false);
|
||||||
|
} else {
|
||||||
|
this.stream.writeln(path);
|
||||||
|
for (const t of tests) this.#writeOutput(t, " - ", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#writeOutput(test: { name: string } & TestResult, prefix: string, nested: boolean) {
|
||||||
|
let output = "";
|
||||||
|
if (test.logs.length) {
|
||||||
|
// Individual logs might span multiple lines, so join them together
|
||||||
|
// then split it again.
|
||||||
|
const logStr = test.logs.join("\n");
|
||||||
|
const lines = logStr.split("\n");
|
||||||
|
if (lines.length <= 1) {
|
||||||
|
output = `: ${logStr}`;
|
||||||
|
} else {
|
||||||
|
const padding = nested ? " " : " ";
|
||||||
|
output = ":\n" + lines.map((line) => padding + line).join("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.stream.writeln(`${prefix}${test.name}${output}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertGetElementById(id: string): HTMLElement {
|
||||||
|
let elem = document.getElementById(id);
|
||||||
|
if (elem == null) throw new ReferenceError(`element with ID '${id}' missing from DOM`);
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DOMReporter implements Reporter {
|
||||||
|
register(suites: TestGroup[]) {}
|
||||||
|
|
||||||
|
update(path: string[], result: TestResult) {
|
||||||
|
if (path.length === 0) throw new RangeError("path is empty");
|
||||||
|
if (result.state === "skip") {
|
||||||
|
assertGetElementById("skip-counter-text").hidden = false;
|
||||||
|
}
|
||||||
|
const counter = assertGetElementById(`${result.state}-counter`);
|
||||||
|
counter.innerText = (Number(counter.innerText) + 1).toString();
|
||||||
|
|
||||||
|
let parent = assertGetElementById("root");
|
||||||
|
for (const node of path) {
|
||||||
|
let child: HTMLDetailsElement | null = null;
|
||||||
|
let summary: HTMLElement | null = null;
|
||||||
|
let d: Element;
|
||||||
|
outer: for (d of parent.children) {
|
||||||
|
if (!(d instanceof HTMLDetailsElement)) continue;
|
||||||
|
for (const s of d.children) {
|
||||||
|
if (!(s instanceof HTMLElement)) continue;
|
||||||
|
if (!(s.tagName === "SUMMARY" && s.innerText === node)) continue;
|
||||||
|
child = d;
|
||||||
|
summary = s;
|
||||||
|
break outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!child) {
|
||||||
|
child = document.createElement("details");
|
||||||
|
child.className = "test-node";
|
||||||
|
child.ariaRoleDescription = "test";
|
||||||
|
summary = document.createElement("summary");
|
||||||
|
summary.appendChild(document.createTextNode(node));
|
||||||
|
summary.ariaRoleDescription = "test name";
|
||||||
|
child.appendChild(summary);
|
||||||
|
parent.appendChild(child);
|
||||||
|
}
|
||||||
|
if (!summary) throw new Error("unreachable as assigned above");
|
||||||
|
|
||||||
|
switch (result.state) {
|
||||||
|
case "failure":
|
||||||
|
child.open = true;
|
||||||
|
child.classList.add("failure");
|
||||||
|
child.classList.remove("skip");
|
||||||
|
child.classList.remove("success");
|
||||||
|
// The summary marker does not appear in the AOM, so setting its
|
||||||
|
// alt text is fruitless; label the summary itself instead.
|
||||||
|
summary.setAttribute("aria-labelledby", "failure-description");
|
||||||
|
break;
|
||||||
|
case "skip":
|
||||||
|
if (child.classList.contains("failure")) break;
|
||||||
|
child.classList.add("skip");
|
||||||
|
child.classList.remove("success");
|
||||||
|
summary.setAttribute("aria-labelledby", "skip-description");
|
||||||
|
break;
|
||||||
|
case "success":
|
||||||
|
if (child.classList.contains("failure") || child.classList.contains("skip")) break;
|
||||||
|
child.classList.add("success");
|
||||||
|
summary.setAttribute("aria-labelledby", "success-description");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
const p = document.createElement("p");
|
||||||
|
p.classList.add("test-desc");
|
||||||
|
if (result.logs.length) {
|
||||||
|
const pre = document.createElement("pre");
|
||||||
|
pre.appendChild(document.createTextNode(result.logs.join("\n")));
|
||||||
|
p.appendChild(pre);
|
||||||
|
} else {
|
||||||
|
p.classList.add("italic");
|
||||||
|
p.appendChild(document.createTextNode("No output."));
|
||||||
|
}
|
||||||
|
parent.appendChild(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GoNode {
|
||||||
|
name: string;
|
||||||
|
subtests?: GoNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to display results via `go test`, via some glue code from the Go side.
|
||||||
|
export class GoTestReporter implements Reporter {
|
||||||
|
register(suites: TestGroup[]) {
|
||||||
|
console.log(JSON.stringify(suites.map(GoTestReporter.serialize)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a test tree into the one expected by the Go code.
|
||||||
|
static serialize(node: TestTree): GoNode {
|
||||||
|
return {
|
||||||
|
name: node.name,
|
||||||
|
subtests: "children" in node ? node.children.map(GoTestReporter.serialize) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
update(path: string[], result: TestResult) {
|
||||||
|
let state: number;
|
||||||
|
switch (result.state) {
|
||||||
|
case "success": state = 0; break;
|
||||||
|
case "failure": state = 1; break;
|
||||||
|
case "skip": state = 2; break;
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify({ path, state, logs: result.logs }));
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize() {
|
||||||
|
console.log("null");
|
||||||
|
}
|
||||||
|
}
|
||||||
87
cmd/pkgserver/ui_test/lib/ui.css
Normal file
87
cmd/pkgserver/ui_test/lib/ui.css
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* When updating the theme colors, also update them in success-closed.svg and
|
||||||
|
* success-open.svg!
|
||||||
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #d3d3d3;
|
||||||
|
--fg: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg: #2c2c2c;
|
||||||
|
--fg: ghostwhite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: var(--bg);
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, p, summary, noscript {
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
noscript {
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
details.test-node {
|
||||||
|
margin-left: 1rem;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-left: 2px dashed var(--fg);
|
||||||
|
> summary {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&.success > summary::marker {
|
||||||
|
/*
|
||||||
|
* WebKit only supports color and font-size properties in ::marker [1], and
|
||||||
|
* its ::-webkit-details-marker only supports hiding the marker entirely
|
||||||
|
* [2], contrary to mdn's example [3]; thus, set a color as a fallback:
|
||||||
|
* while it may not be accessible for colorblind individuals, it's better
|
||||||
|
* than no indication of a test's state for anyone, as that there's no other
|
||||||
|
* way to include an indication in the marker on WebKit.
|
||||||
|
*
|
||||||
|
* [1]: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/::marker#browser_compatibility
|
||||||
|
* [2]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/summary#default_style
|
||||||
|
* [3]: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/summary#changing_the_summarys_icon
|
||||||
|
*/
|
||||||
|
color: var(--fg);
|
||||||
|
content: url("/test/success-closed.svg") / "success";
|
||||||
|
}
|
||||||
|
&.success[open] > summary::marker {
|
||||||
|
content: url("/test/success-open.svg") / "success";
|
||||||
|
}
|
||||||
|
&.failure > summary::marker {
|
||||||
|
color: red;
|
||||||
|
content: url("/test/failure-closed.svg") / "failure";
|
||||||
|
}
|
||||||
|
&.failure[open] > summary::marker {
|
||||||
|
content: url("/test/failure-open.svg") / "failure";
|
||||||
|
}
|
||||||
|
&.skip > summary::marker {
|
||||||
|
color: blue;
|
||||||
|
content: url("/test/skip-closed.svg") / "skip";
|
||||||
|
}
|
||||||
|
&.skip[open] > summary::marker {
|
||||||
|
content: url("/test/skip-open.svg") / "skip";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.test-desc {
|
||||||
|
margin: 0 0 0 1rem;
|
||||||
|
padding: 2px 0;
|
||||||
|
> pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
39
cmd/pkgserver/ui_test/lib/ui.html
Normal file
39
cmd/pkgserver/ui_test/lib/ui.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="/test/style.css">
|
||||||
|
<title>PkgServer Tests</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
I hate JavaScript as much as you, but this page runs tests written in
|
||||||
|
JavaScript to test the functionality of code written in JavaScript, so it
|
||||||
|
wouldn't make sense for it to work without JavaScript. <strong>Please turn
|
||||||
|
JavaScript on!</strong>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<h1>PkgServer Tests</h1>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<p id="counters">
|
||||||
|
<span id="success-counter">0</span> succeeded, <span id="failure-counter">0</span>
|
||||||
|
failed<span id="skip-counter-text" hidden>, <span id="skip-counter">0</span> skipped</span>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p hidden id="success-description">Successful test</p>
|
||||||
|
<p hidden id="failure-description">Failed test</p>
|
||||||
|
<p hidden id="skip-description">Partially or fully skipped test</p>
|
||||||
|
|
||||||
|
<div id="root">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import "/test/all_tests.js";
|
||||||
|
import { DOMReporter, GLOBAL_REGISTRAR } from "/test/lib/test.js";
|
||||||
|
GLOBAL_REGISTRAR.run(new DOMReporter());
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
cmd/pkgserver/ui_test/tsconfig.json
Normal file
8
cmd/pkgserver/ui_test/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2024",
|
||||||
|
"strict": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"outDir": "static"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"hakurei.app/container/std"
|
"hakurei.app/container/std"
|
||||||
"hakurei.app/ext"
|
"hakurei.app/ext"
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
"hakurei.app/internal/landlock"
|
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,9 +66,6 @@ type (
|
|||||||
// Copied to the underlying [exec.Cmd].
|
// Copied to the underlying [exec.Cmd].
|
||||||
WaitDelay time.Duration
|
WaitDelay time.Duration
|
||||||
|
|
||||||
// Suppress verbose output of init.
|
|
||||||
Quiet bool
|
|
||||||
|
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
msg message.Msg
|
msg message.Msg
|
||||||
@@ -311,7 +307,7 @@ func (p *Container) Start() error {
|
|||||||
done <- func() error {
|
done <- func() error {
|
||||||
// PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
|
// PR_SET_NO_NEW_PRIVS: thread-directed but acts on all processes
|
||||||
// created from the calling thread
|
// created from the calling thread
|
||||||
if err := setNoNewPrivs(); err != nil {
|
if err := SetNoNewPrivs(); err != nil {
|
||||||
return &StartError{
|
return &StartError{
|
||||||
Fatal: true,
|
Fatal: true,
|
||||||
Step: "prctl(PR_SET_NO_NEW_PRIVS)",
|
Step: "prctl(PR_SET_NO_NEW_PRIVS)",
|
||||||
@@ -321,14 +317,12 @@ func (p *Container) Start() error {
|
|||||||
|
|
||||||
// landlock: depends on per-thread state but acts on a process group
|
// landlock: depends on per-thread state but acts on a process group
|
||||||
{
|
{
|
||||||
rulesetAttr := &landlock.RulesetAttr{
|
rulesetAttr := &RulesetAttr{Scoped: LANDLOCK_SCOPE_SIGNAL}
|
||||||
Scoped: landlock.LANDLOCK_SCOPE_SIGNAL,
|
|
||||||
}
|
|
||||||
if !p.HostAbstract {
|
if !p.HostAbstract {
|
||||||
rulesetAttr.Scoped |= landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
|
rulesetAttr.Scoped |= LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET
|
||||||
}
|
}
|
||||||
|
|
||||||
if abi, err := landlock.GetABI(); err != nil {
|
if abi, err := LandlockGetABI(); err != nil {
|
||||||
if p.HostAbstract || !p.HostNet {
|
if p.HostAbstract || !p.HostNet {
|
||||||
// landlock can be skipped here as it restricts access
|
// landlock can be skipped here as it restricts access
|
||||||
// to resources already covered by namespaces (pid, net)
|
// to resources already covered by namespaces (pid, net)
|
||||||
@@ -345,6 +339,8 @@ func (p *Container) Start() error {
|
|||||||
Err: ENOSYS,
|
Err: ENOSYS,
|
||||||
Origin: true,
|
Origin: true,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
p.msg.Verbosef("landlock abi version %d", abi)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
if rulesetFd, err := rulesetAttr.Create(0); err != nil {
|
||||||
@@ -354,7 +350,8 @@ func (p *Container) Start() error {
|
|||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err = landlock.RestrictSelf(rulesetFd, 0); err != nil {
|
p.msg.Verbosef("enforcing landlock ruleset %s", rulesetAttr)
|
||||||
|
if err = LandlockRestrictSelf(rulesetFd, 0); err != nil {
|
||||||
_ = Close(rulesetFd)
|
_ = Close(rulesetFd)
|
||||||
return &StartError{
|
return &StartError{
|
||||||
Fatal: true,
|
Fatal: true,
|
||||||
@@ -410,6 +407,7 @@ func (p *Container) Start() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.msg.Verbose("starting container init")
|
||||||
if err := p.cmd.Start(); err != nil {
|
if err := p.cmd.Start(); err != nil {
|
||||||
return &StartError{
|
return &StartError{
|
||||||
Step: "start container init",
|
Step: "start container init",
|
||||||
@@ -480,6 +478,7 @@ func (p *Container) Serve() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case <-done:
|
case <-done:
|
||||||
|
p.msg.Verbose("setup payload took", time.Since(t))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}(p.setup[1])
|
}(p.setup[1])
|
||||||
@@ -489,7 +488,7 @@ func (p *Container) Serve() (err error) {
|
|||||||
Getuid(),
|
Getuid(),
|
||||||
Getgid(),
|
Getgid(),
|
||||||
len(p.ExtraFiles),
|
len(p.ExtraFiles),
|
||||||
p.msg.IsVerbose() && !p.Quiet,
|
p.msg.IsVerbose(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/command"
|
"hakurei.app/command"
|
||||||
@@ -27,7 +26,6 @@ import (
|
|||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/info"
|
"hakurei.app/internal/info"
|
||||||
"hakurei.app/internal/landlock"
|
|
||||||
"hakurei.app/internal/params"
|
"hakurei.app/internal/params"
|
||||||
"hakurei.app/ldd"
|
"hakurei.app/ldd"
|
||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
@@ -234,9 +232,6 @@ func earlyMnt(mnt ...*vfs.MountInfoEntry) func(*testing.T, context.Context) []*v
|
|||||||
return func(*testing.T, context.Context) []*vfs.MountInfoEntry { return mnt }
|
return func(*testing.T, context.Context) []*vfs.MountInfoEntry { return mnt }
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:linkname toHost hakurei.app/container.toHost
|
|
||||||
func toHost(name string) string
|
|
||||||
|
|
||||||
var containerTestCases = []struct {
|
var containerTestCases = []struct {
|
||||||
name string
|
name string
|
||||||
filter bool
|
filter bool
|
||||||
@@ -336,15 +331,13 @@ var containerTestCases = []struct {
|
|||||||
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
||||||
return []*vfs.MountInfoEntry{
|
return []*vfs.MountInfoEntry{
|
||||||
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
||||||
"rw"+
|
"rw,lowerdir="+
|
||||||
",lowerdir+="+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
||||||
toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||||
",lowerdir+="+
|
|
||||||
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
|
||||||
",upperdir="+
|
",upperdir="+
|
||||||
toHost(ctx.Value(testVal("upper")).(*check.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("upper")).(*check.Absolute).String())+
|
||||||
",workdir="+
|
",workdir="+
|
||||||
toHost(ctx.Value(testVal("work")).(*check.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("work")).(*check.Absolute).String())+
|
||||||
",redirect_dir=nofollow,uuid=on,userxattr"),
|
",redirect_dir=nofollow,uuid=on,userxattr"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -394,11 +387,9 @@ var containerTestCases = []struct {
|
|||||||
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
func(t *testing.T, ctx context.Context) []*vfs.MountInfoEntry {
|
||||||
return []*vfs.MountInfoEntry{
|
return []*vfs.MountInfoEntry{
|
||||||
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
ent("/", hst.PrivateTmp, "rw", "overlay", "overlay",
|
||||||
"ro"+
|
"ro,lowerdir="+
|
||||||
",lowerdir+="+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower0")).(*check.Absolute).String())+":"+
|
||||||
toHost(ctx.Value(testVal("lower0")).(*check.Absolute).String())+
|
container.InternalToHostOvlEscape(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
||||||
",lowerdir+="+
|
|
||||||
toHost(ctx.Value(testVal("lower1")).(*check.Absolute).String())+
|
|
||||||
",redirect_dir=nofollow,userxattr"),
|
",redirect_dir=nofollow,userxattr"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -465,7 +456,7 @@ func TestContainer(t *testing.T) {
|
|||||||
c.RetainSession = tc.session
|
c.RetainSession = tc.session
|
||||||
c.HostNet = tc.net
|
c.HostNet = tc.net
|
||||||
if info.CanDegrade {
|
if info.CanDegrade {
|
||||||
if _, err := landlock.GetABI(); err != nil {
|
if _, err := container.LandlockGetABI(); err != nil {
|
||||||
if !errors.Is(err, syscall.ENOSYS) {
|
if !errors.Is(err, syscall.ENOSYS) {
|
||||||
t.Fatalf("LandlockGetABI: error = %v", err)
|
t.Fatalf("LandlockGetABI: error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -658,58 +649,6 @@ func init() {
|
|||||||
return fmt.Errorf("gid: %d, want %d", gid, tc.gid)
|
return fmt.Errorf("gid: %d, want %d", gid, tc.gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
PR_CAP_AMBIENT = 0x2f
|
|
||||||
PR_CAP_AMBIENT_IS_SET = 0x1
|
|
||||||
)
|
|
||||||
for i := range container.LastCap(nil) {
|
|
||||||
r, _, errno := syscall.Syscall(
|
|
||||||
syscall.SYS_PRCTL,
|
|
||||||
PR_CAP_AMBIENT,
|
|
||||||
PR_CAP_AMBIENT_IS_SET,
|
|
||||||
i,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
return os.NewSyscallError("prctl", errno)
|
|
||||||
}
|
|
||||||
if r != 0 {
|
|
||||||
return fmt.Errorf("capability %d is set", i)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, errno = syscall.Syscall(
|
|
||||||
syscall.SYS_PRCTL,
|
|
||||||
syscall.PR_CAPBSET_READ,
|
|
||||||
i,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
return os.NewSyscallError("prctl", errno)
|
|
||||||
}
|
|
||||||
if r != 0 {
|
|
||||||
return fmt.Errorf("capability %d in set", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const _LINUX_CAPABILITY_VERSION_3 = 0x20080522
|
|
||||||
var capData struct {
|
|
||||||
effective uint32
|
|
||||||
permitted uint32
|
|
||||||
inheritable uint32
|
|
||||||
}
|
|
||||||
if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&struct {
|
|
||||||
version uint32
|
|
||||||
pid int32
|
|
||||||
}{_LINUX_CAPABILITY_VERSION_3, 0})), uintptr(unsafe.Pointer(&capData)), 0); errno != 0 {
|
|
||||||
return os.NewSyscallError("capget", errno)
|
|
||||||
}
|
|
||||||
|
|
||||||
if max(capData.effective, capData.permitted, capData.inheritable) != 0 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"effective = %d, permitted = %d, inheritable = %d",
|
|
||||||
capData.effective, capData.permitted, capData.inheritable,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
wantHost := hostnameFromTestCase(tc.name)
|
wantHost := hostnameFromTestCase(tc.name)
|
||||||
if host, err := os.Hostname(); err != nil {
|
if host, err := os.Hostname(); err != nil {
|
||||||
return fmt.Errorf("cannot get hostname: %v", err)
|
return fmt.Errorf("cannot get hostname: %v", err)
|
||||||
|
|||||||
@@ -65,8 +65,6 @@ type syscallDispatcher interface {
|
|||||||
remount(msg message.Msg, target string, flags uintptr) error
|
remount(msg message.Msg, target string, flags uintptr) error
|
||||||
// mountTmpfs provides mountTmpfs.
|
// mountTmpfs provides mountTmpfs.
|
||||||
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error
|
||||||
// mountOverlay provides mountOverlay.
|
|
||||||
mountOverlay(target string, options [][2]string) error
|
|
||||||
// ensureFile provides ensureFile.
|
// ensureFile provides ensureFile.
|
||||||
ensureFile(name string, perm, pperm os.FileMode) error
|
ensureFile(name string, perm, pperm os.FileMode) error
|
||||||
// mustLoopback provides mustLoopback.
|
// mustLoopback provides mustLoopback.
|
||||||
@@ -150,7 +148,7 @@ func (direct) lockOSThread() { runtime.LockOSThread() }
|
|||||||
|
|
||||||
func (direct) setPtracer(pid uintptr) error { return ext.SetPtracer(pid) }
|
func (direct) setPtracer(pid uintptr) error { return ext.SetPtracer(pid) }
|
||||||
func (direct) setDumpable(dumpable uintptr) error { return ext.SetDumpable(dumpable) }
|
func (direct) setDumpable(dumpable uintptr) error { return ext.SetDumpable(dumpable) }
|
||||||
func (direct) setNoNewPrivs() error { return setNoNewPrivs() }
|
func (direct) setNoNewPrivs() error { return SetNoNewPrivs() }
|
||||||
|
|
||||||
func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) }
|
func (direct) lastcap(msg message.Msg) uintptr { return LastCap(msg) }
|
||||||
func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
|
func (direct) capset(hdrp *capHeader, datap *[2]capData) error { return capset(hdrp, datap) }
|
||||||
@@ -171,9 +169,6 @@ func (direct) remount(msg message.Msg, target string, flags uintptr) error {
|
|||||||
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
func (k direct) mountTmpfs(fsname, target string, flags uintptr, size int, perm os.FileMode) error {
|
||||||
return mountTmpfs(k, fsname, target, flags, size, perm)
|
return mountTmpfs(k, fsname, target, flags, size, perm)
|
||||||
}
|
}
|
||||||
func (k direct) mountOverlay(target string, options [][2]string) error {
|
|
||||||
return mountOverlay(target, options)
|
|
||||||
}
|
|
||||||
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
func (direct) ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
return ensureFile(name, perm, pperm)
|
return ensureFile(name, perm, pperm)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,14 +468,6 @@ func (k *kstub) mountTmpfs(fsname, target string, flags uintptr, size int, perm
|
|||||||
stub.CheckArg(k.Stub, "perm", perm, 4))
|
stub.CheckArg(k.Stub, "perm", perm, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *kstub) mountOverlay(target string, options [][2]string) error {
|
|
||||||
k.Helper()
|
|
||||||
return k.Expects("mountOverlay").Error(
|
|
||||||
stub.CheckArg(k.Stub, "target", target, 0),
|
|
||||||
stub.CheckArgReflect(k.Stub, "options", options, 1),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
func (k *kstub) ensureFile(name string, perm, pperm os.FileMode) error {
|
||||||
k.Helper()
|
k.Helper()
|
||||||
return k.Expects("ensureFile").Error(
|
return k.Expects("ensureFile").Error(
|
||||||
|
|||||||
@@ -118,10 +118,6 @@ func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
|
|||||||
|
|
||||||
// mount wraps syscall.Mount for error handling.
|
// mount wraps syscall.Mount for error handling.
|
||||||
func mount(source, target, fstype string, flags uintptr, data string) error {
|
func mount(source, target, fstype string, flags uintptr, data string) error {
|
||||||
if max(len(source), len(target), len(data))+1 > os.Getpagesize() {
|
|
||||||
return &MountError{source, target, fstype, flags, data, syscall.ENOMEM}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := syscall.Mount(source, target, fstype, flags, data)
|
err := syscall.Mount(source, target, fstype, flags, data)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -223,13 +223,6 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
state := &setupState{process: make(map[int]WaitStatus), Params: ¶m.Params, Msg: msg, Context: ctx}
|
state := &setupState{process: make(map[int]WaitStatus), Params: ¶m.Params, Msg: msg, Context: ctx}
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
|
|
||||||
k.fatalf(msg, "cannot mount intermediate root: %v", optionalErrorUnwrap(err))
|
|
||||||
}
|
|
||||||
if err := k.chdir(intermediateHostPath); err != nil {
|
|
||||||
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* early is called right before pivot_root into intermediate root;
|
/* early is called right before pivot_root into intermediate root;
|
||||||
this step is mostly for gathering information that would otherwise be
|
this step is mostly for gathering information that would otherwise be
|
||||||
difficult to obtain via library functions after pivot_root, and
|
difficult to obtain via library functions after pivot_root, and
|
||||||
@@ -249,6 +242,13 @@ func initEntrypoint(k syscallDispatcher, msg message.Msg) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := k.mount(SourceTmpfsRootfs, intermediateHostPath, FstypeTmpfs, MS_NODEV|MS_NOSUID, zeroString); err != nil {
|
||||||
|
k.fatalf(msg, "cannot mount intermediate root: %v", optionalErrorUnwrap(err))
|
||||||
|
}
|
||||||
|
if err := k.chdir(intermediateHostPath); err != nil {
|
||||||
|
k.fatalf(msg, "cannot enter intermediate host path: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err := k.mkdir(sysrootDir, 0755); err != nil {
|
if err := k.mkdir(sysrootDir, 0755); err != nil {
|
||||||
k.fatalf(msg, "%v", err)
|
k.fatalf(msg, "%v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -332,8 +332,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
@@ -372,8 +370,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"invalid op at index %d", []any{0}}, nil, nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
@@ -412,8 +408,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", stub.UniqueError(61)),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", stub.UniqueError(61)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot prepare op at index %d: %v", []any{0, stub.UniqueError(61)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot prepare op at index %d: %v", []any{0, stub.UniqueError(61)}}, nil, nil),
|
||||||
@@ -453,8 +447,6 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", &os.PathError{Op: "readlink", Path: "/", Err: stub.UniqueError(60)}),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", &os.PathError{Op: "readlink", Path: "/", Err: stub.UniqueError(60)}),
|
||||||
call("fatal", stub.ExpectArgs{[]any{"cannot readlink /: unique error 60 injected by the test suite"}}, nil, nil),
|
call("fatal", stub.ExpectArgs{[]any{"cannot readlink /: unique error 60 injected by the test suite"}}, nil, nil),
|
||||||
@@ -494,6 +486,9 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
/* begin early */
|
||||||
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, stub.UniqueError(58)),
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, stub.UniqueError(58)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot mount intermediate root: %v", []any{stub.UniqueError(58)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot mount intermediate root: %v", []any{stub.UniqueError(58)}}, nil, nil),
|
||||||
},
|
},
|
||||||
@@ -531,6 +526,9 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
|
/* begin early */
|
||||||
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
|
/* end early */
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, stub.UniqueError(56)),
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, stub.UniqueError(56)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot enter intermediate host path: %v", []any{stub.UniqueError(56)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot enter intermediate host path: %v", []any{stub.UniqueError(56)}}, nil, nil),
|
||||||
@@ -569,11 +567,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, stub.UniqueError(54)),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, stub.UniqueError(54)),
|
||||||
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(54)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"%v", []any{stub.UniqueError(54)}}, nil, nil),
|
||||||
},
|
},
|
||||||
@@ -611,11 +609,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, stub.UniqueError(52)),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, stub.UniqueError(52)),
|
||||||
call("fatalf", stub.ExpectArgs{"cannot bind sysroot: %v", []any{stub.UniqueError(52)}}, nil, nil),
|
call("fatalf", stub.ExpectArgs{"cannot bind sysroot: %v", []any{stub.UniqueError(52)}}, nil, nil),
|
||||||
@@ -654,11 +652,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, stub.UniqueError(50)),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, stub.UniqueError(50)),
|
||||||
@@ -698,11 +696,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -743,11 +741,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -789,11 +787,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -844,11 +842,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -899,11 +897,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -955,11 +953,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1012,11 +1010,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1071,11 +1069,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1131,11 +1129,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1192,11 +1190,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1254,11 +1252,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1317,11 +1315,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1381,11 +1379,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1446,11 +1444,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1512,11 +1510,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1586,11 +1584,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1693,11 +1691,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1801,11 +1799,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -1910,11 +1908,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2034,11 +2032,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2134,11 +2132,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2234,11 +2232,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2325,11 +2323,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2420,11 +2418,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(4), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2522,11 +2520,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
@@ -2661,11 +2659,11 @@ func TestInitEntrypoint(t *testing.T) {
|
|||||||
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
call("sethostname", stub.ExpectArgs{[]byte("hakurei-check")}, nil, nil),
|
||||||
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
call("lastcap", stub.ExpectArgs{}, uintptr(40), nil),
|
||||||
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"", "/", "", uintptr(0x8c000), ""}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
|
||||||
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
|
||||||
/* begin early */
|
/* begin early */
|
||||||
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/"}, "/", nil),
|
||||||
/* end early */
|
/* end early */
|
||||||
|
call("mount", stub.ExpectArgs{"rootfs", "/proc/self/fd", "tmpfs", uintptr(6), ""}, nil, nil),
|
||||||
|
call("chdir", stub.ExpectArgs{"/proc/self/fd"}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"sysroot", os.FileMode(0755)}, nil, nil),
|
||||||
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
call("mount", stub.ExpectArgs{"sysroot", "sysroot", "", uintptr(0xd000), ""}, nil, nil),
|
||||||
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
call("mkdir", stub.ExpectArgs{"host", os.FileMode(0755)}, nil, nil),
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/ext"
|
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Upper.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.upper = toHost(v)
|
o.upper = check.EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
if v, err := k.evalSymlinks(o.Work.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.work = toHost(v)
|
o.work = check.EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,39 +168,12 @@ func (o *MountOverlayOp) early(_ *setupState, k syscallDispatcher) error {
|
|||||||
if v, err := k.evalSymlinks(a.String()); err != nil {
|
if v, err := k.evalSymlinks(a.String()); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
o.lower[i] = toHost(v)
|
o.lower[i] = check.EscapeOverlayDataSegment(toHost(v))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// mountOverlay sets up an overlay mount via [ext.FS].
|
|
||||||
func mountOverlay(target string, options [][2]string) error {
|
|
||||||
fs, err := ext.OpenFS(SourceOverlay, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = fs.SetString("source", SourceOverlay); err != nil {
|
|
||||||
_ = fs.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, option := range options {
|
|
||||||
if err = fs.SetString(option[0], option[1]); err != nil {
|
|
||||||
_ = fs.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = fs.SetFlag(OptionOverlayUserxattr); err != nil {
|
|
||||||
_ = fs.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = fs.Mount(target, 0); err != nil {
|
|
||||||
_ = fs.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fs.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
||||||
target := o.Target.String()
|
target := o.Target.String()
|
||||||
if !o.noPrefix {
|
if !o.noPrefix {
|
||||||
@@ -221,7 +194,7 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
options := make([][2]string, 0, 2+len(o.lower))
|
options := make([]string, 0, 4)
|
||||||
|
|
||||||
if o.upper == zeroString && o.work == zeroString { // readonly
|
if o.upper == zeroString && o.work == zeroString { // readonly
|
||||||
if len(o.Lower) < 2 {
|
if len(o.Lower) < 2 {
|
||||||
@@ -232,16 +205,15 @@ func (o *MountOverlayOp) apply(state *setupState, k syscallDispatcher) error {
|
|||||||
if len(o.Lower) == 0 {
|
if len(o.Lower) == 0 {
|
||||||
return &OverlayArgumentError{OverlayEmptyLower, zeroString}
|
return &OverlayArgumentError{OverlayEmptyLower, zeroString}
|
||||||
}
|
}
|
||||||
options = append(options, [][2]string{
|
options = append(options,
|
||||||
{OptionOverlayUpperdir, o.upper},
|
OptionOverlayUpperdir+"="+o.upper,
|
||||||
{OptionOverlayWorkdir, o.work},
|
OptionOverlayWorkdir+"="+o.work)
|
||||||
}...)
|
|
||||||
}
|
|
||||||
for _, lower := range o.lower {
|
|
||||||
options = append(options, [2]string{OptionOverlayLowerdir + "+", lower})
|
|
||||||
}
|
}
|
||||||
|
options = append(options,
|
||||||
|
OptionOverlayLowerdir+"="+strings.Join(o.lower, check.SpecialOverlayPath),
|
||||||
|
OptionOverlayUserxattr)
|
||||||
|
|
||||||
return k.mountOverlay(target, options)
|
return k.mount(SourceOverlay, target, FstypeOverlay, 0, strings.Join(options, check.SpecialOverlayOption))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil }
|
func (o *MountOverlayOp) late(*setupState, syscallDispatcher) error { return nil }
|
||||||
|
|||||||
@@ -97,12 +97,13 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot", os.FileMode(0705)}, nil, nil),
|
||||||
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil),
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.upper.*"}, "overlay.upper.32768", nil),
|
||||||
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil),
|
call("mkdirTemp", stub.ExpectArgs{"/", "overlay.work.*"}, "overlay.work.32768", nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot", "overlay", uintptr(0), "" +
|
||||||
{"upperdir", "overlay.upper.32768"},
|
"upperdir=overlay.upper.32768," +
|
||||||
{"workdir", "overlay.work.32768"},
|
"workdir=overlay.work.32768," +
|
||||||
{"lowerdir+", `/host/var/lib/planterette/base/debian:f92c9052`},
|
"lowerdir=" +
|
||||||
{"lowerdir+", `/host/var/lib/planterette/app/org.chromium.Chromium@debian:f92c9052`},
|
`/host/var/lib/planterette/base/debian\:f92c9052:` +
|
||||||
}}, nil, nil),
|
`/host/var/lib/planterette/app/org.chromium.Chromium@debian\:f92c9052,` +
|
||||||
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"short lower ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
@@ -128,10 +129,11 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/nix/store", os.FileMode(0755)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/nix/store", "overlay", uintptr(0), "" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/.ro-store"},
|
"lowerdir=" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
|
"/host/mnt-root/nix/.ro-store:" +
|
||||||
}}, nil, nil),
|
"/host/mnt-root/nix/.ro-store0," +
|
||||||
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
{"success ro", &Params{ParentPerm: 0755}, &MountOverlayOp{
|
||||||
@@ -145,10 +147,11 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store0"}, "/mnt-root/nix/.ro-store0", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0755)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/.ro-store"},
|
"lowerdir=" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/.ro-store0"},
|
"/host/mnt-root/nix/.ro-store:" +
|
||||||
}}, nil, nil),
|
"/host/mnt-root/nix/.ro-store0," +
|
||||||
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"nil lower", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -216,11 +219,7 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "upperdir=/host/mnt-root/nix/.rw-store/.upper,workdir=/host/mnt-root/nix/.rw-store/.work,lowerdir=/host/mnt-root/nix/ro-store,userxattr"}, nil, stub.UniqueError(0)),
|
||||||
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
|
|
||||||
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
|
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
|
|
||||||
}}, nil, stub.UniqueError(0)),
|
|
||||||
}, stub.UniqueError(0)},
|
}, stub.UniqueError(0)},
|
||||||
|
|
||||||
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success single layer", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -234,11 +233,11 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store"}, "/mnt-root/nix/ro-store", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
||||||
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
|
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
||||||
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
|
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
|
"lowerdir=/host/mnt-root/nix/ro-store," +
|
||||||
}}, nil, nil),
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
|
|
||||||
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
{"success", &Params{ParentPerm: 0700}, &MountOverlayOp{
|
||||||
@@ -262,15 +261,16 @@ func TestMountOverlayOp(t *testing.T) {
|
|||||||
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil),
|
call("evalSymlinks", stub.ExpectArgs{"/mnt-root/nix/.ro-store3"}, "/mnt-root/nix/ro-store3", nil),
|
||||||
}, nil, []stub.Call{
|
}, nil, []stub.Call{
|
||||||
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
call("mkdirAll", stub.ExpectArgs{"/sysroot/nix/store", os.FileMode(0700)}, nil, nil),
|
||||||
call("mountOverlay", stub.ExpectArgs{"/sysroot/nix/store", [][2]string{
|
call("mount", stub.ExpectArgs{"overlay", "/sysroot/nix/store", "overlay", uintptr(0), "" +
|
||||||
{"upperdir", "/host/mnt-root/nix/.rw-store/.upper"},
|
"upperdir=/host/mnt-root/nix/.rw-store/.upper," +
|
||||||
{"workdir", "/host/mnt-root/nix/.rw-store/.work"},
|
"workdir=/host/mnt-root/nix/.rw-store/.work," +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store"},
|
"lowerdir=" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store0"},
|
"/host/mnt-root/nix/ro-store:" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store1"},
|
"/host/mnt-root/nix/ro-store0:" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store2"},
|
"/host/mnt-root/nix/ro-store1:" +
|
||||||
{"lowerdir+", "/host/mnt-root/nix/ro-store3"},
|
"/host/mnt-root/nix/ro-store2:" +
|
||||||
}}, nil, nil),
|
"/host/mnt-root/nix/ro-store3," +
|
||||||
|
"userxattr"}, nil, nil),
|
||||||
}, nil},
|
}, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package landlock
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
@@ -14,11 +14,11 @@ const (
|
|||||||
LANDLOCK_CREATE_RULESET_VERSION = 1 << iota
|
LANDLOCK_CREATE_RULESET_VERSION = 1 << iota
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccessFS is bitmask of handled filesystem actions.
|
// LandlockAccessFS is bitmask of handled filesystem actions.
|
||||||
type AccessFS uint64
|
type LandlockAccessFS uint64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LANDLOCK_ACCESS_FS_EXECUTE AccessFS = 1 << iota
|
LANDLOCK_ACCESS_FS_EXECUTE LandlockAccessFS = 1 << iota
|
||||||
LANDLOCK_ACCESS_FS_WRITE_FILE
|
LANDLOCK_ACCESS_FS_WRITE_FILE
|
||||||
LANDLOCK_ACCESS_FS_READ_FILE
|
LANDLOCK_ACCESS_FS_READ_FILE
|
||||||
LANDLOCK_ACCESS_FS_READ_DIR
|
LANDLOCK_ACCESS_FS_READ_DIR
|
||||||
@@ -38,8 +38,8 @@ const (
|
|||||||
_LANDLOCK_ACCESS_FS_DELIM
|
_LANDLOCK_ACCESS_FS_DELIM
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a space-separated string of [AccessFS] flags.
|
// String returns a space-separated string of [LandlockAccessFS] flags.
|
||||||
func (f AccessFS) String() string {
|
func (f LandlockAccessFS) String() string {
|
||||||
switch f {
|
switch f {
|
||||||
case LANDLOCK_ACCESS_FS_EXECUTE:
|
case LANDLOCK_ACCESS_FS_EXECUTE:
|
||||||
return "execute"
|
return "execute"
|
||||||
@@ -90,8 +90,8 @@ func (f AccessFS) String() string {
|
|||||||
return "fs_ioctl_dev"
|
return "fs_ioctl_dev"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var c []AccessFS
|
var c []LandlockAccessFS
|
||||||
for i := AccessFS(1); i < _LANDLOCK_ACCESS_FS_DELIM; i <<= 1 {
|
for i := LandlockAccessFS(1); i < _LANDLOCK_ACCESS_FS_DELIM; i <<= 1 {
|
||||||
if f&i != 0 {
|
if f&i != 0 {
|
||||||
c = append(c, i)
|
c = append(c, i)
|
||||||
}
|
}
|
||||||
@@ -107,18 +107,18 @@ func (f AccessFS) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccessNet is bitmask of handled network actions.
|
// LandlockAccessNet is bitmask of handled network actions.
|
||||||
type AccessNet uint64
|
type LandlockAccessNet uint64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LANDLOCK_ACCESS_NET_BIND_TCP AccessNet = 1 << iota
|
LANDLOCK_ACCESS_NET_BIND_TCP LandlockAccessNet = 1 << iota
|
||||||
LANDLOCK_ACCESS_NET_CONNECT_TCP
|
LANDLOCK_ACCESS_NET_CONNECT_TCP
|
||||||
|
|
||||||
_LANDLOCK_ACCESS_NET_DELIM
|
_LANDLOCK_ACCESS_NET_DELIM
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a space-separated string of [AccessNet] flags.
|
// String returns a space-separated string of [LandlockAccessNet] flags.
|
||||||
func (f AccessNet) String() string {
|
func (f LandlockAccessNet) String() string {
|
||||||
switch f {
|
switch f {
|
||||||
case LANDLOCK_ACCESS_NET_BIND_TCP:
|
case LANDLOCK_ACCESS_NET_BIND_TCP:
|
||||||
return "bind_tcp"
|
return "bind_tcp"
|
||||||
@@ -127,8 +127,8 @@ func (f AccessNet) String() string {
|
|||||||
return "connect_tcp"
|
return "connect_tcp"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var c []AccessNet
|
var c []LandlockAccessNet
|
||||||
for i := AccessNet(1); i < _LANDLOCK_ACCESS_NET_DELIM; i <<= 1 {
|
for i := LandlockAccessNet(1); i < _LANDLOCK_ACCESS_NET_DELIM; i <<= 1 {
|
||||||
if f&i != 0 {
|
if f&i != 0 {
|
||||||
c = append(c, i)
|
c = append(c, i)
|
||||||
}
|
}
|
||||||
@@ -144,18 +144,18 @@ func (f AccessNet) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scope is bitmask of scopes restricting a Landlock domain from accessing outside resources.
|
// LandlockScope is bitmask of scopes restricting a Landlock domain from accessing outside resources.
|
||||||
type Scope uint64
|
type LandlockScope uint64
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET Scope = 1 << iota
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET LandlockScope = 1 << iota
|
||||||
LANDLOCK_SCOPE_SIGNAL
|
LANDLOCK_SCOPE_SIGNAL
|
||||||
|
|
||||||
_LANDLOCK_SCOPE_DELIM
|
_LANDLOCK_SCOPE_DELIM
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a space-separated string of [Scope] flags.
|
// String returns a space-separated string of [LandlockScope] flags.
|
||||||
func (f Scope) String() string {
|
func (f LandlockScope) String() string {
|
||||||
switch f {
|
switch f {
|
||||||
case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:
|
case LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET:
|
||||||
return "abstract_unix_socket"
|
return "abstract_unix_socket"
|
||||||
@@ -164,8 +164,8 @@ func (f Scope) String() string {
|
|||||||
return "signal"
|
return "signal"
|
||||||
|
|
||||||
default:
|
default:
|
||||||
var c []Scope
|
var c []LandlockScope
|
||||||
for i := Scope(1); i < _LANDLOCK_SCOPE_DELIM; i <<= 1 {
|
for i := LandlockScope(1); i < _LANDLOCK_SCOPE_DELIM; i <<= 1 {
|
||||||
if f&i != 0 {
|
if f&i != 0 {
|
||||||
c = append(c, i)
|
c = append(c, i)
|
||||||
}
|
}
|
||||||
@@ -184,12 +184,12 @@ func (f Scope) String() string {
|
|||||||
// RulesetAttr is equivalent to struct landlock_ruleset_attr.
|
// RulesetAttr is equivalent to struct landlock_ruleset_attr.
|
||||||
type RulesetAttr struct {
|
type RulesetAttr struct {
|
||||||
// Bitmask of handled filesystem actions.
|
// Bitmask of handled filesystem actions.
|
||||||
HandledAccessFS AccessFS
|
HandledAccessFS LandlockAccessFS
|
||||||
// Bitmask of handled network actions.
|
// Bitmask of handled network actions.
|
||||||
HandledAccessNet AccessNet
|
HandledAccessNet LandlockAccessNet
|
||||||
// Bitmask of scopes restricting a Landlock domain from accessing outside
|
// Bitmask of scopes restricting a Landlock domain from accessing outside
|
||||||
// resources (e.g. IPCs).
|
// resources (e.g. IPCs).
|
||||||
Scoped Scope
|
Scoped LandlockScope
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a user-facing description of [RulesetAttr].
|
// String returns a user-facing description of [RulesetAttr].
|
||||||
@@ -239,13 +239,13 @@ func (rulesetAttr *RulesetAttr) Create(flags uintptr) (fd int, err error) {
|
|||||||
return fd, nil
|
return fd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetABI returns the ABI version supported by the kernel.
|
// LandlockGetABI returns the ABI version supported by the kernel.
|
||||||
func GetABI() (int, error) {
|
func LandlockGetABI() (int, error) {
|
||||||
return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION)
|
return (*RulesetAttr)(nil).Create(LANDLOCK_CREATE_RULESET_VERSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestrictSelf applies a loaded ruleset to the calling thread.
|
// LandlockRestrictSelf applies a loaded ruleset to the calling thread.
|
||||||
func RestrictSelf(rulesetFd int, flags uintptr) error {
|
func LandlockRestrictSelf(rulesetFd int, flags uintptr) error {
|
||||||
r, _, errno := syscall.Syscall(
|
r, _, errno := syscall.Syscall(
|
||||||
ext.SYS_LANDLOCK_RESTRICT_SELF,
|
ext.SYS_LANDLOCK_RESTRICT_SELF,
|
||||||
uintptr(rulesetFd),
|
uintptr(rulesetFd),
|
||||||
65
container/landlock_test.go
Normal file
65
container/landlock_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package container_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/container"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLandlockString(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
rulesetAttr *container.RulesetAttr
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"nil", nil, "NULL"},
|
||||||
|
{"zero", new(container.RulesetAttr), "0"},
|
||||||
|
{"some", &container.RulesetAttr{Scoped: container.LANDLOCK_SCOPE_SIGNAL}, "scoped: signal"},
|
||||||
|
{"set", &container.RulesetAttr{
|
||||||
|
HandledAccessFS: container.LANDLOCK_ACCESS_FS_MAKE_SYM | container.LANDLOCK_ACCESS_FS_IOCTL_DEV | container.LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||||
|
HandledAccessNet: container.LANDLOCK_ACCESS_NET_BIND_TCP,
|
||||||
|
Scoped: container.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | container.LANDLOCK_SCOPE_SIGNAL,
|
||||||
|
}, "fs: write_file make_sym fs_ioctl_dev, net: bind_tcp, scoped: abstract_unix_socket signal"},
|
||||||
|
{"all", &container.RulesetAttr{
|
||||||
|
HandledAccessFS: container.LANDLOCK_ACCESS_FS_EXECUTE |
|
||||||
|
container.LANDLOCK_ACCESS_FS_WRITE_FILE |
|
||||||
|
container.LANDLOCK_ACCESS_FS_READ_FILE |
|
||||||
|
container.LANDLOCK_ACCESS_FS_READ_DIR |
|
||||||
|
container.LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
||||||
|
container.LANDLOCK_ACCESS_FS_REMOVE_FILE |
|
||||||
|
container.LANDLOCK_ACCESS_FS_MAKE_CHAR |
|
||||||
|
container.LANDLOCK_ACCESS_FS_MAKE_DIR |
|
||||||
|
container.LANDLOCK_ACCESS_FS_MAKE_REG |
|
||||||
|
container.LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
||||||
|
container.LANDLOCK_ACCESS_FS_MAKE_FIFO |
|
||||||
|
container.LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
||||||
|
container.LANDLOCK_ACCESS_FS_MAKE_SYM |
|
||||||
|
container.LANDLOCK_ACCESS_FS_REFER |
|
||||||
|
container.LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||||
|
container.LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||||
|
HandledAccessNet: container.LANDLOCK_ACCESS_NET_BIND_TCP |
|
||||||
|
container.LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
||||||
|
Scoped: container.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
|
||||||
|
container.LANDLOCK_SCOPE_SIGNAL,
|
||||||
|
}, "fs: execute write_file read_file read_dir remove_dir remove_file make_char make_dir make_reg make_sock make_fifo make_block make_sym fs_refer fs_truncate fs_ioctl_dev, net: bind_tcp connect_tcp, scoped: abstract_unix_socket signal"},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if got := tc.rulesetAttr.String(); got != tc.want {
|
||||||
|
t.Errorf("String: %s, want %s", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLandlockAttrSize(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
want := 24
|
||||||
|
if got := unsafe.Sizeof(container.RulesetAttr{}); got != uintptr(want) {
|
||||||
|
t.Errorf("Sizeof: %d, want %d", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
"hakurei.app/vfs"
|
"hakurei.app/vfs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +50,9 @@ func TestToHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InternalToHostOvlEscape exports toHost passed to [check.EscapeOverlayDataSegment].
|
||||||
|
func InternalToHostOvlEscape(s string) string { return check.EscapeOverlayDataSegment(toHost(s)) }
|
||||||
|
|
||||||
func TestCreateFile(t *testing.T) {
|
func TestCreateFile(t *testing.T) {
|
||||||
t.Run("nonexistent", func(t *testing.T) {
|
t.Run("nonexistent", func(t *testing.T) {
|
||||||
t.Run("mkdir", func(t *testing.T) {
|
t.Run("mkdir", func(t *testing.T) {
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"hakurei.app/ext"
|
"hakurei.app/ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
// setNoNewPrivs sets the calling thread's no_new_privs attribute.
|
// SetNoNewPrivs sets the calling thread's no_new_privs attribute.
|
||||||
func setNoNewPrivs() error {
|
func SetNoNewPrivs() error {
|
||||||
return ext.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0)
|
return ext.Prctl(PR_SET_NO_NEW_PRIVS, 1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
267
ext/fs.go
267
ext/fs.go
@@ -1,267 +0,0 @@
|
|||||||
package ext
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// include/uapi/linux/mount.h
|
|
||||||
|
|
||||||
/*
|
|
||||||
* move_mount() flags.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
MOVE_MOUNT_F_SYMLINKS = 1 << iota /* Follow symlinks on from path */
|
|
||||||
MOVE_MOUNT_F_AUTOMOUNTS /* Follow automounts on from path */
|
|
||||||
MOVE_MOUNT_F_EMPTY_PATH /* Empty from path permitted */
|
|
||||||
_
|
|
||||||
MOVE_MOUNT_T_SYMLINKS /* Follow symlinks on to path */
|
|
||||||
MOVE_MOUNT_T_AUTOMOUNTS /* Follow automounts on to path */
|
|
||||||
MOVE_MOUNT_T_EMPTY_PATH /* Empty to path permitted */
|
|
||||||
_
|
|
||||||
MOVE_MOUNT_SET_GROUP /* Set sharing group instead */
|
|
||||||
MOVE_MOUNT_BENEATH /* Mount beneath top mount */
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fsopen() flags.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
FSOPEN_CLOEXEC = 1 << iota
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fspick() flags.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
FSPICK_CLOEXEC = 1 << iota
|
|
||||||
FSPICK_SYMLINK_NOFOLLOW
|
|
||||||
FSPICK_NO_AUTOMOUNT
|
|
||||||
FSPICK_EMPTY_PATH
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The type of fsconfig() call made.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
FSCONFIG_SET_FLAG = iota /* Set parameter, supplying no value */
|
|
||||||
FSCONFIG_SET_STRING /* Set parameter, supplying a string value */
|
|
||||||
FSCONFIG_SET_BINARY /* Set parameter, supplying a binary blob value */
|
|
||||||
FSCONFIG_SET_PATH /* Set parameter, supplying an object by path */
|
|
||||||
FSCONFIG_SET_PATH_EMPTY /* Set parameter, supplying an object by (empty) path */
|
|
||||||
FSCONFIG_SET_FD /* Set parameter, supplying an object by fd */
|
|
||||||
FSCONFIG_CMD_CREATE /* Create new or reuse existing superblock */
|
|
||||||
FSCONFIG_CMD_RECONFIGURE /* Invoke superblock reconfiguration */
|
|
||||||
FSCONFIG_CMD_CREATE_EXCL /* Create new superblock, fail if reusing existing superblock */
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* fsmount() flags.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
FSMOUNT_CLOEXEC = 1 << iota
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Mount attributes.
|
|
||||||
*/
|
|
||||||
const (
|
|
||||||
MOUNT_ATTR_RDONLY = 0x00000001 /* Mount read-only */
|
|
||||||
MOUNT_ATTR_NOSUID = 0x00000002 /* Ignore suid and sgid bits */
|
|
||||||
MOUNT_ATTR_NODEV = 0x00000004 /* Disallow access to device special files */
|
|
||||||
MOUNT_ATTR_NOEXEC = 0x00000008 /* Disallow program execution */
|
|
||||||
MOUNT_ATTR__ATIME = 0x00000070 /* Setting on how atime should be updated */
|
|
||||||
MOUNT_ATTR_RELATIME = 0x00000000 /* - Update atime relative to mtime/ctime. */
|
|
||||||
MOUNT_ATTR_NOATIME = 0x00000010 /* - Do not update access times. */
|
|
||||||
MOUNT_ATTR_STRICTATIME = 0x00000020 /* - Always perform atime updates */
|
|
||||||
MOUNT_ATTR_NODIRATIME = 0x00000080 /* Do not update directory access times */
|
|
||||||
MOUNT_ATTR_IDMAP = 0x00100000 /* Idmap mount to @userns_fd in struct mount_attr. */
|
|
||||||
MOUNT_ATTR_NOSYMFOLLOW = 0x00200000 /* Do not follow symlinks */
|
|
||||||
)
|
|
||||||
|
|
||||||
// FS provides low-level wrappers around the suite of file-descriptor-based
|
|
||||||
// mount facilities in Linux.
|
|
||||||
type FS struct {
|
|
||||||
fd uintptr
|
|
||||||
c runtime.Cleanup
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFS allocates a new [FS] for the specified fd.
|
|
||||||
func newFS(fd uintptr) *FS {
|
|
||||||
fs := FS{fd: fd}
|
|
||||||
fs.c = runtime.AddCleanup(&fs, func(fd uintptr) {
|
|
||||||
_ = syscall.Close(int(fd))
|
|
||||||
}, fd)
|
|
||||||
return &fs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the underlying filesystem context.
|
|
||||||
func (fs *FS) Close() error {
|
|
||||||
if fs == nil {
|
|
||||||
return syscall.EINVAL
|
|
||||||
}
|
|
||||||
err := syscall.Close(int(fs.fd))
|
|
||||||
fs.c.Stop()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenFS creates a new filesystem context.
|
|
||||||
func OpenFS(fsname string, flags int) (fs *FS, err error) {
|
|
||||||
var s *byte
|
|
||||||
s, err = syscall.BytePtrFromString(fsname)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fd, _, errno := syscall.Syscall(
|
|
||||||
SYS_FSOPEN,
|
|
||||||
uintptr(unsafe.Pointer(s)),
|
|
||||||
uintptr(flags|FSOPEN_CLOEXEC),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("fsopen", errno)
|
|
||||||
} else {
|
|
||||||
fs = newFS(fd)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// PickFS selects filesystem for reconfiguration.
|
|
||||||
func PickFS(dirfd int, pathname string, flags int) (fs *FS, err error) {
|
|
||||||
var s *byte
|
|
||||||
s, err = syscall.BytePtrFromString(pathname)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fd, _, errno := syscall.Syscall(
|
|
||||||
SYS_FSPICK,
|
|
||||||
uintptr(dirfd),
|
|
||||||
uintptr(unsafe.Pointer(s)),
|
|
||||||
uintptr(flags|FSPICK_CLOEXEC),
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("fspick", errno)
|
|
||||||
} else {
|
|
||||||
fs = newFS(fd)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// config configures new or existing filesystem context.
|
|
||||||
func (fs *FS) config(cmd uint, key *byte, value unsafe.Pointer, aux int) (err error) {
|
|
||||||
_, _, errno := syscall.Syscall6(
|
|
||||||
SYS_FSCONFIG,
|
|
||||||
fs.fd,
|
|
||||||
uintptr(cmd),
|
|
||||||
uintptr(unsafe.Pointer(key)),
|
|
||||||
uintptr(value),
|
|
||||||
uintptr(aux),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("fsconfig", errno)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFlag sets the flag parameter named by key. ([FSCONFIG_SET_FLAG])
|
|
||||||
func (fs *FS) SetFlag(key string) (err error) {
|
|
||||||
var s *byte
|
|
||||||
s, err = syscall.BytePtrFromString(key)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.config(FSCONFIG_SET_FLAG, s, nil, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetString sets the string parameter named by key to the value specified by
|
|
||||||
// value. ([FSCONFIG_SET_STRING])
|
|
||||||
func (fs *FS) SetString(key, value string) (err error) {
|
|
||||||
var s0 *byte
|
|
||||||
s0, err = syscall.BytePtrFromString(key)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var s1 *byte
|
|
||||||
s1, err = syscall.BytePtrFromString(value)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.config(FSCONFIG_SET_STRING, s0, unsafe.Pointer(s1), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mount instantiates mount object from filesystem context.
|
|
||||||
func (fs *FS) mount(flags, attrFlags int) (fsfd int, err error) {
|
|
||||||
r, _, errno := syscall.Syscall(
|
|
||||||
SYS_FSMOUNT,
|
|
||||||
fs.fd,
|
|
||||||
uintptr(flags|FSMOUNT_CLOEXEC),
|
|
||||||
uintptr(attrFlags),
|
|
||||||
)
|
|
||||||
fsfd = int(r)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("fsmount", errno)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveMount moves or attaches mount object to filesystem.
|
|
||||||
func MoveMount(
|
|
||||||
fromDirfd int,
|
|
||||||
fromPathname string,
|
|
||||||
toDirfd int,
|
|
||||||
toPathname string,
|
|
||||||
flags int,
|
|
||||||
) (err error) {
|
|
||||||
var s0 *byte
|
|
||||||
s0, err = syscall.BytePtrFromString(fromPathname)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var s1 *byte
|
|
||||||
s1, err = syscall.BytePtrFromString(toPathname)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall6(
|
|
||||||
SYS_MOVE_MOUNT,
|
|
||||||
uintptr(fromDirfd),
|
|
||||||
uintptr(unsafe.Pointer(s0)),
|
|
||||||
uintptr(toDirfd),
|
|
||||||
uintptr(unsafe.Pointer(s1)),
|
|
||||||
uintptr(flags),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
if errno != 0 {
|
|
||||||
err = os.NewSyscallError("move_mount", errno)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount attaches the underlying filesystem context to the specified pathname.
|
|
||||||
func (fs *FS) Mount(pathname string, attrFlags int) error {
|
|
||||||
if err := fs.config(FSCONFIG_CMD_CREATE_EXCL, nil, nil, 0); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fd, err := fs.mount(0, attrFlags)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = MoveMount(
|
|
||||||
fd, "",
|
|
||||||
-1, pathname,
|
|
||||||
MOVE_MOUNT_F_EMPTY_PATH,
|
|
||||||
)
|
|
||||||
closeErr := syscall.Close(fd)
|
|
||||||
if err == nil {
|
|
||||||
err = closeErr
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -140,29 +140,21 @@ var (
|
|||||||
ErrInsecure = errors.New("configuration is insecure")
|
ErrInsecure = errors.New("configuration is insecure")
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// VAllowInsecure allows use of compatibility options considered insecure
|
|
||||||
// under any configuration, to work around ecosystem-wide flaws.
|
|
||||||
VAllowInsecure = 1 << iota
|
|
||||||
)
|
|
||||||
|
|
||||||
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
// Validate checks [Config] and returns [AppError] if an invalid value is encountered.
|
||||||
func (config *Config) Validate(flags int) error {
|
func (config *Config) Validate() error {
|
||||||
const step = "validate configuration"
|
|
||||||
|
|
||||||
if config == nil {
|
if config == nil {
|
||||||
return &AppError{Step: step, Err: ErrConfigNull,
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
Msg: "invalid configuration"}
|
Msg: "invalid configuration"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is checked again in hsu
|
// this is checked again in hsu
|
||||||
if config.Identity < IdentityStart || config.Identity > IdentityEnd {
|
if config.Identity < IdentityStart || config.Identity > IdentityEnd {
|
||||||
return &AppError{Step: step, Err: ErrIdentityBounds,
|
return &AppError{Step: "validate configuration", Err: ErrIdentityBounds,
|
||||||
Msg: "identity " + strconv.Itoa(config.Identity) + " out of range"}
|
Msg: "identity " + strconv.Itoa(config.Identity) + " out of range"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.SchedPolicy < 0 || config.SchedPolicy > ext.SCHED_LAST {
|
if config.SchedPolicy < 0 || config.SchedPolicy > ext.SCHED_LAST {
|
||||||
return &AppError{Step: step, Err: ErrSchedPolicyBounds,
|
return &AppError{Step: "validate configuration", Err: ErrSchedPolicyBounds,
|
||||||
Msg: "scheduling policy " +
|
Msg: "scheduling policy " +
|
||||||
strconv.Itoa(int(config.SchedPolicy)) +
|
strconv.Itoa(int(config.SchedPolicy)) +
|
||||||
" out of range"}
|
" out of range"}
|
||||||
@@ -176,51 +168,34 @@ func (config *Config) Validate(flags int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if config.Container == nil {
|
if config.Container == nil {
|
||||||
return &AppError{Step: step, Err: ErrConfigNull,
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
Msg: "configuration missing container state"}
|
Msg: "configuration missing container state"}
|
||||||
}
|
}
|
||||||
if config.Container.Home == nil {
|
if config.Container.Home == nil {
|
||||||
return &AppError{Step: step, Err: ErrConfigNull,
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
Msg: "container configuration missing path to home directory"}
|
Msg: "container configuration missing path to home directory"}
|
||||||
}
|
}
|
||||||
if config.Container.Shell == nil {
|
if config.Container.Shell == nil {
|
||||||
return &AppError{Step: step, Err: ErrConfigNull,
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
Msg: "container configuration missing path to shell"}
|
Msg: "container configuration missing path to shell"}
|
||||||
}
|
}
|
||||||
if config.Container.Path == nil {
|
if config.Container.Path == nil {
|
||||||
return &AppError{Step: step, Err: ErrConfigNull,
|
return &AppError{Step: "validate configuration", Err: ErrConfigNull,
|
||||||
Msg: "container configuration missing path to initial program"}
|
Msg: "container configuration missing path to initial program"}
|
||||||
}
|
}
|
||||||
|
|
||||||
for key := range config.Container.Env {
|
for key := range config.Container.Env {
|
||||||
if strings.IndexByte(key, '=') != -1 || strings.IndexByte(key, 0) != -1 {
|
if strings.IndexByte(key, '=') != -1 || strings.IndexByte(key, 0) != -1 {
|
||||||
return &AppError{Step: step, Err: ErrEnviron,
|
return &AppError{Step: "validate configuration", Err: ErrEnviron,
|
||||||
Msg: "invalid environment variable " + strconv.Quote(key)}
|
Msg: "invalid environment variable " + strconv.Quote(key)}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
et := config.Enablements.Unwrap()
|
if et := config.Enablements.Unwrap(); !config.DirectPulse && et&EPulse != 0 {
|
||||||
if !config.DirectPulse && et&EPulse != 0 {
|
return &AppError{Step: "validate configuration", Err: ErrInsecure,
|
||||||
return &AppError{Step: step, Err: ErrInsecure,
|
|
||||||
Msg: "enablement PulseAudio is insecure and no longer supported"}
|
Msg: "enablement PulseAudio is insecure and no longer supported"}
|
||||||
}
|
}
|
||||||
|
|
||||||
if flags&VAllowInsecure == 0 {
|
|
||||||
switch {
|
|
||||||
case et&EWayland != 0 && config.DirectWayland:
|
|
||||||
return &AppError{Step: step, Err: ErrInsecure,
|
|
||||||
Msg: "direct_wayland is insecure and no longer supported"}
|
|
||||||
|
|
||||||
case et&EPipeWire != 0 && config.DirectPipeWire:
|
|
||||||
return &AppError{Step: step, Err: ErrInsecure,
|
|
||||||
Msg: "direct_pipewire is insecure and no longer supported"}
|
|
||||||
|
|
||||||
case et&EPulse != 0 && config.DirectPulse:
|
|
||||||
return &AppError{Step: step, Err: ErrInsecure,
|
|
||||||
Msg: "direct_pulse is insecure and no longer supported"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,109 +14,65 @@ func TestConfigValidate(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
config *hst.Config
|
config *hst.Config
|
||||||
flags int
|
|
||||||
wantErr error
|
wantErr error
|
||||||
}{
|
}{
|
||||||
{"nil", nil, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
{"nil", nil, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "invalid configuration"}},
|
Msg: "invalid configuration"}},
|
||||||
|
{"identity lower", &hst.Config{Identity: -1}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||||
{"identity lower", &hst.Config{Identity: -1}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
|
||||||
Msg: "identity -1 out of range"}},
|
Msg: "identity -1 out of range"}},
|
||||||
{"identity upper", &hst.Config{Identity: 10000}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
{"identity upper", &hst.Config{Identity: 10000}, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
|
||||||
Msg: "identity 10000 out of range"}},
|
Msg: "identity 10000 out of range"}},
|
||||||
|
{"sched lower", &hst.Config{SchedPolicy: -1}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
||||||
{"sched lower", &hst.Config{SchedPolicy: -1}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
|
||||||
Msg: "scheduling policy -1 out of range"}},
|
Msg: "scheduling policy -1 out of range"}},
|
||||||
{"sched upper", &hst.Config{SchedPolicy: 0xcafe}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
{"sched upper", &hst.Config{SchedPolicy: 0xcafe}, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
|
||||||
Msg: "scheduling policy 51966 out of range"}},
|
Msg: "scheduling policy 51966 out of range"}},
|
||||||
|
{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}},
|
||||||
{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}}, 0,
|
|
||||||
&hst.BadInterfaceError{Interface: "", Segment: "session"}},
|
&hst.BadInterfaceError{Interface: "", Segment: "session"}},
|
||||||
{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}}, 0,
|
{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}},
|
||||||
&hst.BadInterfaceError{Interface: "", Segment: "system"}},
|
&hst.BadInterfaceError{Interface: "", Segment: "system"}},
|
||||||
|
{"container", &hst.Config{}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
{"container", &hst.Config{}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
|
||||||
Msg: "configuration missing container state"}},
|
Msg: "configuration missing container state"}},
|
||||||
{"home", &hst.Config{Container: &hst.ContainerConfig{}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
{"home", &hst.Config{Container: &hst.ContainerConfig{}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "container configuration missing path to home directory"}},
|
Msg: "container configuration missing path to home directory"}},
|
||||||
{"shell", &hst.Config{Container: &hst.ContainerConfig{
|
{"shell", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "container configuration missing path to shell"}},
|
Msg: "container configuration missing path to shell"}},
|
||||||
{"path", &hst.Config{Container: &hst.ContainerConfig{
|
{"path", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
|
||||||
Msg: "container configuration missing path to initial program"}},
|
Msg: "container configuration missing path to initial program"}},
|
||||||
|
|
||||||
{"env equals", &hst.Config{Container: &hst.ContainerConfig{
|
{"env equals", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
Path: fhs.AbsTmp,
|
Path: fhs.AbsTmp,
|
||||||
Env: map[string]string{"TERM=": ""},
|
Env: map[string]string{"TERM=": ""},
|
||||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||||
Msg: `invalid environment variable "TERM="`}},
|
Msg: `invalid environment variable "TERM="`}},
|
||||||
{"env NUL", &hst.Config{Container: &hst.ContainerConfig{
|
{"env NUL", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
Path: fhs.AbsTmp,
|
Path: fhs.AbsTmp,
|
||||||
Env: map[string]string{"TERM\x00": ""},
|
Env: map[string]string{"TERM\x00": ""},
|
||||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
|
||||||
Msg: `invalid environment variable "TERM\x00"`}},
|
Msg: `invalid environment variable "TERM\x00"`}},
|
||||||
|
{"insecure pulse", &hst.Config{Enablements: hst.NewEnablements(hst.EPulse), Container: &hst.ContainerConfig{
|
||||||
{"insecure pulse", &hst.Config{Enablements: new(hst.EPulse), Container: &hst.ContainerConfig{
|
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
Path: fhs.AbsTmp,
|
Path: fhs.AbsTmp,
|
||||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
}}, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
||||||
Msg: "enablement PulseAudio is insecure and no longer supported"}},
|
Msg: "enablement PulseAudio is insecure and no longer supported"}},
|
||||||
|
|
||||||
{"direct wayland", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{
|
|
||||||
Home: fhs.AbsTmp,
|
|
||||||
Shell: fhs.AbsTmp,
|
|
||||||
Path: fhs.AbsTmp,
|
|
||||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
|
||||||
Msg: "direct_wayland is insecure and no longer supported"}},
|
|
||||||
{"direct wayland allow", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{
|
|
||||||
Home: fhs.AbsTmp,
|
|
||||||
Shell: fhs.AbsTmp,
|
|
||||||
Path: fhs.AbsTmp,
|
|
||||||
}}, hst.VAllowInsecure, nil},
|
|
||||||
|
|
||||||
{"direct pipewire", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{
|
|
||||||
Home: fhs.AbsTmp,
|
|
||||||
Shell: fhs.AbsTmp,
|
|
||||||
Path: fhs.AbsTmp,
|
|
||||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
|
||||||
Msg: "direct_pipewire is insecure and no longer supported"}},
|
|
||||||
{"direct pipewire allow", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{
|
|
||||||
Home: fhs.AbsTmp,
|
|
||||||
Shell: fhs.AbsTmp,
|
|
||||||
Path: fhs.AbsTmp,
|
|
||||||
}}, hst.VAllowInsecure, nil},
|
|
||||||
|
|
||||||
{"direct pulse", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{
|
|
||||||
Home: fhs.AbsTmp,
|
|
||||||
Shell: fhs.AbsTmp,
|
|
||||||
Path: fhs.AbsTmp,
|
|
||||||
}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
|
|
||||||
Msg: "direct_pulse is insecure and no longer supported"}},
|
|
||||||
{"direct pulse allow", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{
|
|
||||||
Home: fhs.AbsTmp,
|
|
||||||
Shell: fhs.AbsTmp,
|
|
||||||
Path: fhs.AbsTmp,
|
|
||||||
}}, hst.VAllowInsecure, nil},
|
|
||||||
|
|
||||||
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
{"valid", &hst.Config{Container: &hst.ContainerConfig{
|
||||||
Home: fhs.AbsTmp,
|
Home: fhs.AbsTmp,
|
||||||
Shell: fhs.AbsTmp,
|
Shell: fhs.AbsTmp,
|
||||||
Path: fhs.AbsTmp,
|
Path: fhs.AbsTmp,
|
||||||
}}, 0, nil},
|
}}, nil},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
if err := tc.config.Validate(tc.flags); !reflect.DeepEqual(err, tc.wantErr) {
|
if err := tc.config.Validate(); !reflect.DeepEqual(err, tc.wantErr) {
|
||||||
t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr)
|
t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enablements denotes optional host service to export to the target user.
|
// Enablement represents an optional host service to export to the target user.
|
||||||
type Enablements byte
|
type Enablement byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// EWayland exposes a Wayland pathname socket via security-context-v1.
|
// EWayland exposes a Wayland pathname socket via security-context-v1.
|
||||||
EWayland Enablements = 1 << iota
|
EWayland Enablement = 1 << iota
|
||||||
// EX11 adds the target user via X11 ChangeHosts and exposes the X11
|
// EX11 adds the target user via X11 ChangeHosts and exposes the X11
|
||||||
// pathname socket.
|
// pathname socket.
|
||||||
EX11
|
EX11
|
||||||
@@ -28,8 +28,8 @@ const (
|
|||||||
EM
|
EM
|
||||||
)
|
)
|
||||||
|
|
||||||
// String returns a string representation of the flags set on [Enablements].
|
// String returns a string representation of the flags set on [Enablement].
|
||||||
func (e Enablements) String() string {
|
func (e Enablement) String() string {
|
||||||
switch e {
|
switch e {
|
||||||
case 0:
|
case 0:
|
||||||
return "(no enablements)"
|
return "(no enablements)"
|
||||||
@@ -47,7 +47,7 @@ func (e Enablements) String() string {
|
|||||||
buf := new(strings.Builder)
|
buf := new(strings.Builder)
|
||||||
buf.Grow(32)
|
buf.Grow(32)
|
||||||
|
|
||||||
for i := Enablements(1); i < EM; i <<= 1 {
|
for i := Enablement(1); i < EM; i <<= 1 {
|
||||||
if e&i != 0 {
|
if e&i != 0 {
|
||||||
buf.WriteString(", " + i.String())
|
buf.WriteString(", " + i.String())
|
||||||
}
|
}
|
||||||
@@ -60,6 +60,12 @@ func (e Enablements) String() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewEnablements returns the address of [Enablement] as [Enablements].
|
||||||
|
func NewEnablements(e Enablement) *Enablements { return (*Enablements)(&e) }
|
||||||
|
|
||||||
|
// Enablements is the [json] adapter for [Enablement].
|
||||||
|
type Enablements Enablement
|
||||||
|
|
||||||
// enablementsJSON is the [json] representation of [Enablements].
|
// enablementsJSON is the [json] representation of [Enablements].
|
||||||
type enablementsJSON = struct {
|
type enablementsJSON = struct {
|
||||||
Wayland bool `json:"wayland,omitempty"`
|
Wayland bool `json:"wayland,omitempty"`
|
||||||
@@ -69,21 +75,24 @@ type enablementsJSON = struct {
|
|||||||
Pulse bool `json:"pulse,omitempty"`
|
Pulse bool `json:"pulse,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the value pointed to by e.
|
// Unwrap returns the underlying [Enablement].
|
||||||
func (e *Enablements) Unwrap() Enablements {
|
func (e *Enablements) Unwrap() Enablement {
|
||||||
if e == nil {
|
if e == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return *e
|
return Enablement(*e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Enablements) MarshalJSON() ([]byte, error) {
|
func (e *Enablements) MarshalJSON() ([]byte, error) {
|
||||||
|
if e == nil {
|
||||||
|
return nil, syscall.EINVAL
|
||||||
|
}
|
||||||
return json.Marshal(&enablementsJSON{
|
return json.Marshal(&enablementsJSON{
|
||||||
Wayland: e&EWayland != 0,
|
Wayland: Enablement(*e)&EWayland != 0,
|
||||||
X11: e&EX11 != 0,
|
X11: Enablement(*e)&EX11 != 0,
|
||||||
DBus: e&EDBus != 0,
|
DBus: Enablement(*e)&EDBus != 0,
|
||||||
PipeWire: e&EPipeWire != 0,
|
PipeWire: Enablement(*e)&EPipeWire != 0,
|
||||||
Pulse: e&EPulse != 0,
|
Pulse: Enablement(*e)&EPulse != 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,21 +106,22 @@ func (e *Enablements) UnmarshalJSON(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
*e = 0
|
var ve Enablement
|
||||||
if v.Wayland {
|
if v.Wayland {
|
||||||
*e |= EWayland
|
ve |= EWayland
|
||||||
}
|
}
|
||||||
if v.X11 {
|
if v.X11 {
|
||||||
*e |= EX11
|
ve |= EX11
|
||||||
}
|
}
|
||||||
if v.DBus {
|
if v.DBus {
|
||||||
*e |= EDBus
|
ve |= EDBus
|
||||||
}
|
}
|
||||||
if v.PipeWire {
|
if v.PipeWire {
|
||||||
*e |= EPipeWire
|
ve |= EPipeWire
|
||||||
}
|
}
|
||||||
if v.Pulse {
|
if v.Pulse {
|
||||||
*e |= EPulse
|
ve |= EPulse
|
||||||
}
|
}
|
||||||
|
*e = Enablements(ve)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func TestEnablementString(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
flags hst.Enablements
|
flags hst.Enablement
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{0, "(no enablements)"},
|
{0, "(no enablements)"},
|
||||||
@@ -59,13 +59,13 @@ func TestEnablements(t *testing.T) {
|
|||||||
sData string
|
sData string
|
||||||
}{
|
}{
|
||||||
{"nil", nil, "null", `{"value":null,"magic":3236757504}`},
|
{"nil", nil, "null", `{"value":null,"magic":3236757504}`},
|
||||||
{"zero", new(hst.Enablements(0)), `{}`, `{"value":{},"magic":3236757504}`},
|
{"zero", hst.NewEnablements(0), `{}`, `{"value":{},"magic":3236757504}`},
|
||||||
{"wayland", new(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
{"wayland", hst.NewEnablements(hst.EWayland), `{"wayland":true}`, `{"value":{"wayland":true},"magic":3236757504}`},
|
||||||
{"x11", new(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
{"x11", hst.NewEnablements(hst.EX11), `{"x11":true}`, `{"value":{"x11":true},"magic":3236757504}`},
|
||||||
{"dbus", new(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
{"dbus", hst.NewEnablements(hst.EDBus), `{"dbus":true}`, `{"value":{"dbus":true},"magic":3236757504}`},
|
||||||
{"pipewire", new(hst.EPipeWire), `{"pipewire":true}`, `{"value":{"pipewire":true},"magic":3236757504}`},
|
{"pipewire", hst.NewEnablements(hst.EPipeWire), `{"pipewire":true}`, `{"value":{"pipewire":true},"magic":3236757504}`},
|
||||||
{"pulse", new(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
{"pulse", hst.NewEnablements(hst.EPulse), `{"pulse":true}`, `{"value":{"pulse":true},"magic":3236757504}`},
|
||||||
{"all", new(hst.EM - 1), `{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true},"magic":3236757504}`},
|
{"all", hst.NewEnablements(hst.EM - 1), `{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true}`, `{"value":{"wayland":true,"x11":true,"dbus":true,"pipewire":true,"pulse":true},"magic":3236757504}`},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -137,7 +137,7 @@ func TestEnablements(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("val", func(t *testing.T) {
|
t.Run("val", func(t *testing.T) {
|
||||||
if got := new(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse {
|
if got := hst.NewEnablements(hst.EWayland | hst.EPulse).Unwrap(); got != hst.EWayland|hst.EPulse {
|
||||||
t.Errorf("Unwrap: %v", got)
|
t.Errorf("Unwrap: %v", got)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -146,6 +146,9 @@ func TestEnablements(t *testing.T) {
|
|||||||
t.Run("passthrough", func(t *testing.T) {
|
t.Run("passthrough", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
if _, err := (*hst.Enablements)(nil).MarshalJSON(); !errors.Is(err, syscall.EINVAL) {
|
||||||
|
t.Errorf("MarshalJSON: error = %v", err)
|
||||||
|
}
|
||||||
if err := (*hst.Enablements)(nil).UnmarshalJSON(nil); !errors.Is(err, syscall.EINVAL) {
|
if err := (*hst.Enablements)(nil).UnmarshalJSON(nil); !errors.Is(err, syscall.EINVAL) {
|
||||||
t.Errorf("UnmarshalJSON: error = %v", err)
|
t.Errorf("UnmarshalJSON: error = %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func Template() *Config {
|
|||||||
return &Config{
|
return &Config{
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
|
|
||||||
Enablements: new(EWayland | EDBus | EPipeWire),
|
Enablements: NewEnablements(EWayland | EDBus | EPipeWire),
|
||||||
|
|
||||||
SessionBus: &BusConfig{
|
SessionBus: &BusConfig{
|
||||||
See: nil,
|
See: nil,
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
package landlock_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"hakurei.app/internal/landlock"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLandlockString(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
rulesetAttr *landlock.RulesetAttr
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{"nil", nil, "NULL"},
|
|
||||||
{"zero", new(landlock.RulesetAttr), "0"},
|
|
||||||
{"some", &landlock.RulesetAttr{Scoped: landlock.LANDLOCK_SCOPE_SIGNAL}, "scoped: signal"},
|
|
||||||
{"set", &landlock.RulesetAttr{
|
|
||||||
HandledAccessFS: landlock.LANDLOCK_ACCESS_FS_MAKE_SYM | landlock.LANDLOCK_ACCESS_FS_IOCTL_DEV | landlock.LANDLOCK_ACCESS_FS_WRITE_FILE,
|
|
||||||
HandledAccessNet: landlock.LANDLOCK_ACCESS_NET_BIND_TCP,
|
|
||||||
Scoped: landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET | landlock.LANDLOCK_SCOPE_SIGNAL,
|
|
||||||
}, "fs: write_file make_sym fs_ioctl_dev, net: bind_tcp, scoped: abstract_unix_socket signal"},
|
|
||||||
{"all", &landlock.RulesetAttr{
|
|
||||||
HandledAccessFS: landlock.LANDLOCK_ACCESS_FS_EXECUTE |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_WRITE_FILE |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_READ_FILE |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_READ_DIR |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_REMOVE_FILE |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_MAKE_CHAR |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_MAKE_DIR |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_MAKE_REG |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_MAKE_FIFO |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_MAKE_BLOCK |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_MAKE_SYM |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_REFER |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_TRUNCATE |
|
|
||||||
landlock.LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
|
||||||
HandledAccessNet: landlock.LANDLOCK_ACCESS_NET_BIND_TCP |
|
|
||||||
landlock.LANDLOCK_ACCESS_NET_CONNECT_TCP,
|
|
||||||
Scoped: landlock.LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET |
|
|
||||||
landlock.LANDLOCK_SCOPE_SIGNAL,
|
|
||||||
}, "fs: execute write_file read_file read_dir remove_dir remove_file make_char make_dir make_reg make_sock make_fifo make_block make_sym fs_refer fs_truncate fs_ioctl_dev, net: bind_tcp connect_tcp, scoped: abstract_unix_socket signal"},
|
|
||||||
}
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
if got := tc.rulesetAttr.String(); got != tc.want {
|
|
||||||
t.Errorf("String: %s, want %s", got, tc.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLandlockAttrSize(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
want := 24
|
|
||||||
if got := unsafe.Sizeof(landlock.RulesetAttr{}); got != uintptr(want) {
|
|
||||||
t.Errorf("Sizeof: %d, want %d", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,14 +32,7 @@ type outcome struct {
|
|||||||
syscallDispatcher
|
syscallDispatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// finalise prepares an outcome for main.
|
func (k *outcome) finalise(ctx context.Context, msg message.Msg, id *hst.ID, config *hst.Config) error {
|
||||||
func (k *outcome) finalise(
|
|
||||||
ctx context.Context,
|
|
||||||
msg message.Msg,
|
|
||||||
id *hst.ID,
|
|
||||||
config *hst.Config,
|
|
||||||
flags int,
|
|
||||||
) error {
|
|
||||||
if ctx == nil || id == nil {
|
if ctx == nil || id == nil {
|
||||||
// unreachable
|
// unreachable
|
||||||
panic("invalid call to finalise")
|
panic("invalid call to finalise")
|
||||||
@@ -50,7 +43,7 @@ func (k *outcome) finalise(
|
|||||||
}
|
}
|
||||||
k.ctx = ctx
|
k.ctx = ctx
|
||||||
|
|
||||||
if err := config.Validate(flags); err != nil {
|
if err := config.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ type outcomeStateSys struct {
|
|||||||
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
||||||
appId string
|
appId string
|
||||||
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
// Copied from [hst.Config]. Safe for read by outcomeOp.toSystem.
|
||||||
et hst.Enablements
|
et hst.Enablement
|
||||||
|
|
||||||
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
// Copied from [hst.Config]. Safe for read by spWaylandOp.toSystem only.
|
||||||
directWayland bool
|
directWayland bool
|
||||||
|
|||||||
@@ -297,12 +297,12 @@ func (k *outcome) main(msg message.Msg, identifierFd int) {
|
|||||||
// accumulate enablements of remaining instances
|
// accumulate enablements of remaining instances
|
||||||
var (
|
var (
|
||||||
// alive enablement bits
|
// alive enablement bits
|
||||||
rt hst.Enablements
|
rt hst.Enablement
|
||||||
// alive instance count
|
// alive instance count
|
||||||
n int
|
n int
|
||||||
)
|
)
|
||||||
for eh := range entries {
|
for eh := range entries {
|
||||||
var et hst.Enablements
|
var et hst.Enablement
|
||||||
if et, err = eh.Load(nil); err != nil {
|
if et, err = eh.Load(nil); err != nil {
|
||||||
perror(err, "read state header of instance "+eh.ID.String())
|
perror(err, "read state header of instance "+eh.ID.String())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -18,13 +18,7 @@ import (
|
|||||||
func IsPollDescriptor(fd uintptr) bool
|
func IsPollDescriptor(fd uintptr) bool
|
||||||
|
|
||||||
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
// Main runs an app according to [hst.Config] and terminates. Main does not return.
|
||||||
func Main(
|
func Main(ctx context.Context, msg message.Msg, config *hst.Config, fd int) {
|
||||||
ctx context.Context,
|
|
||||||
msg message.Msg,
|
|
||||||
config *hst.Config,
|
|
||||||
flags int,
|
|
||||||
fd int,
|
|
||||||
) {
|
|
||||||
// avoids runtime internals or standard streams
|
// avoids runtime internals or standard streams
|
||||||
if fd >= 0 {
|
if fd >= 0 {
|
||||||
if IsPollDescriptor(uintptr(fd)) || fd < 3 {
|
if IsPollDescriptor(uintptr(fd)) || fd < 3 {
|
||||||
@@ -40,7 +34,7 @@ func Main(
|
|||||||
k := outcome{syscallDispatcher: direct{msg}}
|
k := outcome{syscallDispatcher: direct{msg}}
|
||||||
|
|
||||||
finaliseTime := time.Now()
|
finaliseTime := time.Now()
|
||||||
if err := k.finalise(ctx, msg, &id, config, flags); err != nil {
|
if err := k.finalise(ctx, msg, &id, config); err != nil {
|
||||||
printMessageError(msg.GetLogger().Fatalln, "cannot seal app:", err)
|
printMessageError(msg.GetLogger().Fatalln, "cannot seal app:", err)
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Filter: true,
|
Filter: true,
|
||||||
},
|
},
|
||||||
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||||
|
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
@@ -427,7 +427,7 @@ func TestOutcomeRun(t *testing.T) {
|
|||||||
DirectPipeWire: true,
|
DirectPipeWire: true,
|
||||||
|
|
||||||
ID: "org.chromium.Chromium",
|
ID: "org.chromium.Chromium",
|
||||||
Enablements: new(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
Enablements: hst.NewEnablements(hst.EWayland | hst.EDBus | hst.EPipeWire | hst.EPulse),
|
||||||
Container: &hst.ContainerConfig{
|
Container: &hst.ContainerConfig{
|
||||||
Env: nil,
|
Env: nil,
|
||||||
Filesystem: []hst.FilesystemConfigJSON{
|
Filesystem: []hst.FilesystemConfigJSON{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func TestSpPulseOp(t *testing.T) {
|
|||||||
newConfig := func() *hst.Config {
|
newConfig := func() *hst.Config {
|
||||||
config := hst.Template()
|
config := hst.Template()
|
||||||
config.DirectPulse = true
|
config.DirectPulse = true
|
||||||
config.Enablements = new(hst.EPulse)
|
config.Enablements = hst.NewEnablements(hst.EPulse)
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,78 @@ func TestFlatten(t *testing.T) {
|
|||||||
{Mode: fs.ModeDir | 0700, Path: "work"},
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
|
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C"), nil},
|
||||||
|
|
||||||
|
{"sample cache file", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq": {Mode: 0400, Data: []byte{0, 0, 0, 0, 0xad, 0xb, 0, 4, 0xfe, 0xfe, 0, 0, 0xfe, 0xca, 0, 0}},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
||||||
|
"identifier/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
||||||
|
"identifier/cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
||||||
|
"identifier/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
||||||
|
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
||||||
|
{Mode: 0400, Path: "checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq", Data: []byte{0, 0, 0, 0, 0xad, 0xb, 0, 4, 0xfe, 0xfe, 0, 0, 0xfe, 0xca, 0, 0}},
|
||||||
|
{Mode: 0400, Path: "checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX", Data: []byte{0}},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq", Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe", Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", Data: []byte("../checksum/0bSFPu5Tnd-2Jj0Mv6co23PW2t3BmHc7eLFj9TgY3eIBg8zislo7xZYNBqovVLcq")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX", Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("St9rlE-mGZ5gXwiv_hzQ_B8bZP-UUvSNmf4nHUZzCMOumb6hKnheZSe0dmnuc4Q2"), nil},
|
||||||
|
|
||||||
|
{"sample http get cure", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU": {Mode: 0400, Data: []byte("\x7f\xe1\x69\xa2\xdd\x63\x96\x26\x83\x79\x61\x8b\xf0\x3f\xd5\x16\x9a\x39\x3a\xdb\xcf\xb1\xbc\x8d\x33\xff\x75\xee\x62\x56\xa9\xf0\x27\xac\x13\x94\x69")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
|
||||||
|
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
||||||
|
{Mode: 0400, Path: "checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU", Data: []byte("\x7f\xe1\x69\xa2\xdd\x63\x96\x26\x83\x79\x61\x8b\xf0\x3f\xd5\x16\x9a\x39\x3a\xdb\xcf\xb1\xbc\x8d\x33\xff\x75\xee\x62\x56\xa9\xf0\x27\xac\x13\x94\x69")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_", Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("L_0RFHpr9JUS4Zp14rz2dESSRvfLzpvqsLhR1-YjQt8hYlmEdVl7vI3_-v8UNPKs"), nil},
|
||||||
|
|
||||||
|
{"sample directory step simple", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"check": {Mode: 0400, Data: []byte{0, 0}},
|
||||||
|
|
||||||
|
"lib": {Mode: fs.ModeDir | 0700},
|
||||||
|
"lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
|
||||||
|
"lib/pkgconfig": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "."},
|
||||||
|
|
||||||
|
{Mode: 0400, Path: "check", Data: []byte{0, 0}},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "lib"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "lib/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "lib/pkgconfig"},
|
||||||
|
}, pkg.MustDecode("qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b"), nil},
|
||||||
|
|
||||||
{"sample directory step garbage", fstest.MapFS{
|
{"sample directory step garbage", fstest.MapFS{
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
@@ -79,6 +151,421 @@ func TestFlatten(t *testing.T) {
|
|||||||
|
|
||||||
{Mode: fs.ModeDir | 0500, Path: "lib/pkgconfig"},
|
{Mode: fs.ModeDir | 0500, Path: "lib/pkgconfig"},
|
||||||
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
|
}, pkg.MustDecode("CUx-3hSbTWPsbMfDhgalG4Ni_GmR9TnVX8F99tY_P5GtkYvczg9RrF5zO0jX9XYT"), nil},
|
||||||
|
|
||||||
|
{"sample directory", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/check": {Mode: 0400, Data: []byte{0, 0}},
|
||||||
|
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/pkgconfig": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
|
||||||
|
"identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
|
||||||
|
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b"},
|
||||||
|
{Mode: 0400, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/check", Data: []byte{0, 0}},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b/lib/pkgconfig"},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY", Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa", Data: []byte("../checksum/qRN6in76LndiiOZJheHkwyW8UT1N5-f-bXvHfDvwrMw2fSkOoZdh8pWE1qhLk65b")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("WVpvsVqVKg9Nsh744x57h51AuWUoUR2nnh8Md-EYBQpk6ziyTuUn6PLtF2e0Eu_d"), nil},
|
||||||
|
|
||||||
|
{"sample no assume checksum", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M/check": {Mode: 0400, Data: []byte{}},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/_wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
||||||
|
"identifier/_wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
||||||
|
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M"},
|
||||||
|
{Mode: 0400, Path: "checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M/check", Data: []byte{}},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/Aubi5EG4_Y8DhL9bQ3Q4HFBhLRF7X5gt9D3CNCQfT-TeBtlRXc7Zi_JYZEMoCC7M")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("OC290t23aimNo2Rp2pPwan5GI2KRLRdOwYxXQMD9jw0QROgHnNXWodoWdV0hwu2w"), nil},
|
||||||
|
|
||||||
|
{"sample tar step unpack", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check": {Mode: 0400, Data: []byte{0, 0}},
|
||||||
|
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0500},
|
||||||
|
"identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
||||||
|
"identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
||||||
|
|
||||||
|
"work": {Mode: fs.ModeDir | 0500},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "."},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum"},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP"},
|
||||||
|
{Mode: 0400, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check", Data: []byte{0, 0}},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig"},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY", Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa", Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "work"},
|
||||||
|
}, pkg.MustDecode("cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM"), nil},
|
||||||
|
|
||||||
|
{"sample tar", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check": {Mode: 0400, Data: []byte{0, 0}},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
||||||
|
"checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/work": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
|
||||||
|
"identifier/rg7F1D5hwv6o4xctjD5zDq4i5MD0mArTsUIWfhUbik8xC6Bsyt3mjXXOm3goojTz": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
|
||||||
|
|
||||||
|
"temp": {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 | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM"},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum"},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP"},
|
||||||
|
{Mode: 0400, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check", Data: []byte{0, 0}},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig"},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY", Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa", Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM/work"},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw", Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/rg7F1D5hwv6o4xctjD5zDq4i5MD0mArTsUIWfhUbik8xC6Bsyt3mjXXOm3goojTz", Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "temp"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("NQTlc466JmSVLIyWklm_u8_g95jEEb98PxJU-kjwxLpfdjwMWJq0G8ze9R4Vo1Vu"), nil},
|
||||||
|
|
||||||
|
{"sample tar expand step unpack", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "."},
|
||||||
|
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
}, pkg.MustDecode("CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN"), nil},
|
||||||
|
|
||||||
|
{"sample tar expand", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
|
||||||
|
"identifier/_v1blm2h-_KA-dVaawdpLas6MjHc6rbhhFS8JWwx8iJxZGUu8EBbRrhr5AaZ9PJL": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
|
||||||
|
|
||||||
|
"temp": {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 | 0500, Path: "checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN/libedac.so", Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw", Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_v1blm2h-_KA-dVaawdpLas6MjHc6rbhhFS8JWwx8iJxZGUu8EBbRrhr5AaZ9PJL", Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "temp"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("hSoSSgCYTNonX3Q8FjvjD1fBl-E-BQyA6OTXro2OadXqbST4tZ-akGXszdeqphRe"), nil},
|
||||||
|
|
||||||
|
{"testtool", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "."},
|
||||||
|
|
||||||
|
{Mode: 0400, Path: "check", Data: []byte{0}},
|
||||||
|
}, pkg.MustDecode("GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"), nil},
|
||||||
|
|
||||||
|
{"sample exec container", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
"identifier/dztPS6jRjiZtCF4_p8AzfnxGp6obkhrgFVsxdodbKWUoAEVtDz3MykepJB4kI_ks": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"temp": {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 | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
||||||
|
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
||||||
|
{Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/dztPS6jRjiZtCF4_p8AzfnxGp6obkhrgFVsxdodbKWUoAEVtDz3MykepJB4kI_ks", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "temp"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("Q5DluWQCAeohLoiGRImurwFp3vdz9IfQCoj7Fuhh73s4KQPRHpEQEnHTdNHmB8Fx"), nil},
|
||||||
|
|
||||||
|
{"testtool net", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"check": {Mode: 0400, Data: []byte("net")},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "."},
|
||||||
|
|
||||||
|
{Mode: 0400, Path: "check", Data: []byte("net")},
|
||||||
|
}, pkg.MustDecode("a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W"), nil},
|
||||||
|
|
||||||
|
{"sample exec net container", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
||||||
|
"checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check": {Mode: 0400, Data: []byte("net")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/G8qPxD9puvvoOVV7lrT80eyDeIl3G_CCFoKw12c8mCjMdG1zF7NEPkwYpNubClK3": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
|
||||||
|
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"temp": {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 | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
||||||
|
{Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W"},
|
||||||
|
{Mode: 0400, Path: "checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check", Data: []byte("net")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/G8qPxD9puvvoOVV7lrT80eyDeIl3G_CCFoKw12c8mCjMdG1zF7NEPkwYpNubClK3", Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "temp"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("bPYvvqxpfV7xcC1EptqyKNK1klLJgYHMDkzBcoOyK6j_Aj5hb0mXNPwTwPSK5F6Z"), nil},
|
||||||
|
|
||||||
|
{"sample exec container overlay root", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/RdMA-mubnrHuu3Ky1wWyxauSYCO0ZH_zCPUj3uDHqkfwv5sGcByoF_g5PjlGiClb": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"temp": {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 | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
||||||
|
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/RdMA-mubnrHuu3Ky1wWyxauSYCO0ZH_zCPUj3uDHqkfwv5sGcByoF_g5PjlGiClb", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "temp"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("PO2DSSCa4yoSgEYRcCSZfQfwow1yRigL3Ry-hI0RDI4aGuFBha-EfXeSJnG_5_Rl"), nil},
|
||||||
|
|
||||||
|
{"sample exec container overlay work", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/5hlaukCirnXE4W_RSLJFOZN47Z5RiHnacXzdFp_70cLgiJUGR6cSb_HaFftkzi0-": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"temp": {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 | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
||||||
|
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/5hlaukCirnXE4W_RSLJFOZN47Z5RiHnacXzdFp_70cLgiJUGR6cSb_HaFftkzi0-", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "temp"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("iaRt6l_Wm2n-h5UsDewZxQkCmjZjyL8r7wv32QT2kyV55-Lx09Dq4gfg9BiwPnKs"), nil},
|
||||||
|
|
||||||
|
{"sample exec container multiple layers", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
||||||
|
"checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK/check": {Mode: 0400, Data: []byte("layers")},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
"identifier/B-kc5iJMx8GtlCua4dz6BiJHnDAOUfPjgpbKq4e-QEn0_CZkSYs3fOA1ve06qMs2": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
|
||||||
|
"identifier/p1t_drXr34i-jZNuxDMLaMOdL6tZvQqhavNafGynGqxOZoXAUTSn7kqNh3Ovv3DT": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
"temp": {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 | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
||||||
|
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
||||||
|
{Mode: 0400, Path: "checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb", Data: []byte{}},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK"},
|
||||||
|
{Mode: 0400, Path: "checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK/check", Data: []byte("layers")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/B-kc5iJMx8GtlCua4dz6BiJHnDAOUfPjgpbKq4e-QEn0_CZkSYs3fOA1ve06qMs2", Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/p1t_drXr34i-jZNuxDMLaMOdL6tZvQqhavNafGynGqxOZoXAUTSn7kqNh3Ovv3DT", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "temp"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("O2YzyR7IUGU5J2CADy0hUZ3A5NkP_Vwzs4UadEdn2oMZZVWRtH0xZGJ3HXiimTnZ"), nil},
|
||||||
|
|
||||||
|
{"sample exec container layer promotion", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9": {Mode: fs.ModeDir | 0500},
|
||||||
|
"checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check": {Mode: 0400, Data: []byte{0}},
|
||||||
|
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/kvJIqZo5DKFOxC2ZQ-8_nPaQzEAz9cIm3p6guO-uLqm-xaiPu7oRkSnsu411jd_U": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
"identifier/xXTIYcXmgJWNLC91c417RRrNM9cjELwEZHpGvf8Fk_GNP5agRJp_SicD0w9aMeLJ": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
|
||||||
|
"temp": {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 | 0500, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9"},
|
||||||
|
{Mode: 0400, Path: "checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9/check", Data: []byte{0}},
|
||||||
|
{Mode: fs.ModeDir | 0500, Path: "checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/kvJIqZo5DKFOxC2ZQ-8_nPaQzEAz9cIm3p6guO-uLqm-xaiPu7oRkSnsu411jd_U", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK", Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/xXTIYcXmgJWNLC91c417RRrNM9cjELwEZHpGvf8Fk_GNP5agRJp_SicD0w9aMeLJ", Data: []byte("../checksum/GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "temp"},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("3EaW6WibLi9gl03_UieiFPaFcPy5p4x3JPxrnLJxGaTI-bh3HU9DK9IMx7c3rrNm"), nil},
|
||||||
|
|
||||||
|
{"sample file short", fstest.MapFS{
|
||||||
|
".": {Mode: fs.ModeDir | 0700},
|
||||||
|
|
||||||
|
"checksum": {Mode: fs.ModeDir | 0700},
|
||||||
|
"checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX": {Mode: 0400, Data: []byte{0}},
|
||||||
|
|
||||||
|
"identifier": {Mode: fs.ModeDir | 0700},
|
||||||
|
"identifier/3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
||||||
|
|
||||||
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
|
}, []pkg.FlatEntry{
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "."},
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "checksum"},
|
||||||
|
{Mode: 0400, Path: "checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX", Data: []byte{0}},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "identifier"},
|
||||||
|
{Mode: fs.ModeSymlink | 0777, Path: "identifier/3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi", Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
||||||
|
|
||||||
|
{Mode: fs.ModeDir | 0700, Path: "work"},
|
||||||
|
}, pkg.MustDecode("iR6H5OIsyOW4EwEgtm9rGzGF6DVtyHLySEtwnFE8bnus9VJcoCbR4JIek7Lw-vwT"), nil},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|||||||
@@ -27,11 +27,6 @@ import (
|
|||||||
// AbsWork is the container pathname [TContext.GetWorkDir] is mounted on.
|
// AbsWork is the container pathname [TContext.GetWorkDir] is mounted on.
|
||||||
var AbsWork = fhs.AbsRoot.Append("work/")
|
var AbsWork = fhs.AbsRoot.Append("work/")
|
||||||
|
|
||||||
// EnvJobs is the name of the environment variable holding a decimal
|
|
||||||
// representation of the preferred job count. Its value must not affect cure
|
|
||||||
// outcome.
|
|
||||||
const EnvJobs = "CURE_JOBS"
|
|
||||||
|
|
||||||
// ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make
|
// ExecPath is a slice of [Artifact] and the [check.Absolute] pathname to make
|
||||||
// it available at under in the container.
|
// it available at under in the container.
|
||||||
type ExecPath struct {
|
type ExecPath struct {
|
||||||
@@ -402,7 +397,7 @@ const SeccompPresets = std.PresetStrict &
|
|||||||
func (a *execArtifact) makeContainer(
|
func (a *execArtifact) makeContainer(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
msg message.Msg,
|
msg message.Msg,
|
||||||
flags, jobs int,
|
flags int,
|
||||||
hostNet bool,
|
hostNet bool,
|
||||||
temp, work *check.Absolute,
|
temp, work *check.Absolute,
|
||||||
getArtifact GetArtifactFunc,
|
getArtifact GetArtifactFunc,
|
||||||
@@ -436,10 +431,9 @@ func (a *execArtifact) makeContainer(
|
|||||||
if z.HostNet {
|
if z.HostNet {
|
||||||
z.Hostname = "cure-net"
|
z.Hostname = "cure-net"
|
||||||
}
|
}
|
||||||
z.Quiet = flags&CSuppressInit != 0
|
|
||||||
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
z.Uid, z.Gid = (1<<10)-1, (1<<10)-1
|
||||||
z.Dir, z.Path, z.Args = a.dir, a.path, a.args
|
|
||||||
z.Env = slices.Concat(a.env, []string{EnvJobs + "=" + strconv.Itoa(jobs)})
|
z.Dir, z.Env, z.Path, z.Args = a.dir, a.env, a.path, a.args
|
||||||
z.Grow(len(a.paths) + 4)
|
z.Grow(len(a.paths) + 4)
|
||||||
|
|
||||||
for i, b := range a.paths {
|
for i, b := range a.paths {
|
||||||
@@ -569,7 +563,6 @@ func (c *Cache) EnterExec(
|
|||||||
z, err = e.makeContainer(
|
z, err = e.makeContainer(
|
||||||
ctx, c.msg,
|
ctx, c.msg,
|
||||||
c.flags,
|
c.flags,
|
||||||
c.jobs,
|
|
||||||
hostNet,
|
hostNet,
|
||||||
temp, work,
|
temp, work,
|
||||||
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
func(a Artifact) (*check.Absolute, unique.Handle[Checksum]) {
|
||||||
@@ -609,7 +602,7 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
msg := f.GetMessage()
|
msg := f.GetMessage()
|
||||||
var z *container.Container
|
var z *container.Container
|
||||||
if z, err = a.makeContainer(
|
if z, err = a.makeContainer(
|
||||||
ctx, msg, f.cache.flags, f.GetJobs(), hostNet,
|
ctx, msg, f.cache.flags, hostNet,
|
||||||
f.GetTempDir(), f.GetWorkDir(),
|
f.GetTempDir(), f.GetWorkDir(),
|
||||||
f.GetArtifact,
|
f.GetArtifact,
|
||||||
f.cache.Ident,
|
f.cache.Ident,
|
||||||
@@ -631,6 +624,12 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
_ = stdout.Close()
|
_ = stdout.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil && !errors.As(err, new(*exec.ExitError)) {
|
||||||
|
_ = stdout.Close()
|
||||||
|
_ = stderr.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr)
|
brStdout, brStderr := f.cache.getReader(stdout), f.cache.getReader(stderr)
|
||||||
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
|
stdoutDone, stderrDone := make(chan struct{}), make(chan struct{})
|
||||||
@@ -645,11 +644,6 @@ func (a *execArtifact) cure(f *FContext, hostNet bool) (err error) {
|
|||||||
io.TeeReader(brStderr, status),
|
io.TeeReader(brStderr, status),
|
||||||
)
|
)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err != nil && !errors.As(err, new(*exec.ExitError)) {
|
|
||||||
_ = stdout.Close()
|
|
||||||
_ = stderr.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
<-stdoutDone
|
<-stdoutDone
|
||||||
<-stderrDone
|
<-stderrDone
|
||||||
f.cache.putReader(brStdout)
|
f.cache.putReader(brStdout)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package pkg_test
|
package pkg_test
|
||||||
|
|
||||||
|
//go:generate env CGO_ENABLED=0 go build -tags testtool -o testdata/testtool ./testdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
"io/fs"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -16,26 +17,20 @@ import (
|
|||||||
"hakurei.app/hst"
|
"hakurei.app/hst"
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
"hakurei.app/internal/stub"
|
"hakurei.app/internal/stub"
|
||||||
|
|
||||||
"hakurei.app/internal/pkg/internal/testtool/expected"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// testtoolBin is the container test tool binary made available to the
|
// testtoolBin is the container test tool binary made available to the
|
||||||
// execArtifact for testing its curing environment.
|
// execArtifact for testing its curing environment.
|
||||||
//
|
//
|
||||||
//go:generate env CGO_ENABLED=0 go build -tags testtool -o internal/testtool ./internal/testtool
|
//go:embed testdata/testtool
|
||||||
//go:embed internal/testtool/testtool
|
|
||||||
var testtoolBin []byte
|
var testtoolBin []byte
|
||||||
|
|
||||||
func TestExec(t *testing.T) {
|
func TestExec(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
wantOffline := expectsFS{
|
wantChecksumOffline := pkg.MustDecode(
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
"GPa4aBakdSJd7Tz7LYj_VJFoojzyZinmVcG3k6M5xI6CZ821J5sXLhLDDuS47gi9",
|
||||||
|
)
|
||||||
"check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
}
|
|
||||||
wantOfflineEncode := pkg.Encode(wantOffline.hash())
|
|
||||||
|
|
||||||
checkWithCache(t, []cacheTestCase{
|
checkWithCache(t, []cacheTestCase{
|
||||||
{"offline", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"offline", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
@@ -63,7 +58,7 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantChecksumOffline, nil},
|
||||||
|
|
||||||
{"error passthrough", pkg.NewExec(
|
{"error passthrough", pkg.NewExec(
|
||||||
"", nil, 0, true,
|
"", nil, 0, true,
|
||||||
@@ -79,7 +74,7 @@ func TestExec(t *testing.T) {
|
|||||||
return stub.UniqueError(0xcafe)
|
return stub.UniqueError(0xcafe)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
), nil, nil, &pkg.DependencyCureError{
|
), nil, pkg.Checksum{}, &pkg.DependencyCureError{
|
||||||
{
|
{
|
||||||
Ident: unique.Make(pkg.ID(pkg.MustDecode(
|
Ident: unique.Make(pkg.ID(pkg.MustDecode(
|
||||||
"Sowo6oZRmG6xVtUaxB6bDWZhVsqAJsIJWUp0OPKlE103cY0lodx7dem8J-qQF0Z1",
|
"Sowo6oZRmG6xVtUaxB6bDWZhVsqAJsIJWUp0OPKlE103cY0lodx7dem8J-qQF0Z1",
|
||||||
@@ -96,7 +91,7 @@ func TestExec(t *testing.T) {
|
|||||||
[]string{"testtool"},
|
[]string{"testtool"},
|
||||||
|
|
||||||
pkg.ExecPath{},
|
pkg.ExecPath{},
|
||||||
), nil, nil, pkg.ErrInvalidPaths},
|
), nil, pkg.Checksum{}, pkg.ErrInvalidPaths},
|
||||||
})
|
})
|
||||||
|
|
||||||
// check init failure passthrough
|
// check init failure passthrough
|
||||||
@@ -113,35 +108,17 @@ func TestExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, expectsFS{
|
}, pkg.MustDecode("Q5DluWQCAeohLoiGRImurwFp3vdz9IfQCoj7Fuhh73s4KQPRHpEQEnHTdNHmB8Fx")},
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"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/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
||||||
|
|
||||||
"temp": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"net", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"net", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
|
|
||||||
wantNet := expectsFS{
|
wantChecksum := pkg.MustDecode(
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
"a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W",
|
||||||
|
)
|
||||||
"check": {Mode: 0400, Data: []byte("net")},
|
|
||||||
}
|
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"container", pkg.NewExec(
|
{"container", pkg.NewExec(
|
||||||
"exec-net", new(wantNet.hash()), 0, false,
|
"exec-net", &wantChecksum, 0, false,
|
||||||
pkg.AbsWork,
|
pkg.AbsWork,
|
||||||
[]string{"HAKUREI_TEST=1"},
|
[]string{"HAKUREI_TEST=1"},
|
||||||
check.MustAbs("/opt/bin/testtool"),
|
check.MustAbs("/opt/bin/testtool"),
|
||||||
@@ -161,27 +138,11 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantNet, nil},
|
), ignorePathname, wantChecksum, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, expectsFS{
|
}, pkg.MustDecode("bPYvvqxpfV7xcC1EptqyKNK1klLJgYHMDkzBcoOyK6j_Aj5hb0mXNPwTwPSK5F6Z")},
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
|
||||||
"checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W/check": {Mode: 0400, Data: []byte("net")},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/" + expected.Net: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/a1F_i9PVQI4qMcoHgTQkORuyWLkC1GLIxOhDt2JpU1NGAxWc5VJzdlfRK-PYBh3W")},
|
|
||||||
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
||||||
|
|
||||||
"temp": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"overlay root", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"overlay root", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
@@ -202,25 +163,11 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantChecksumOffline, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, expectsFS{
|
}, pkg.MustDecode("PO2DSSCa4yoSgEYRcCSZfQfwow1yRigL3Ry-hI0RDI4aGuFBha-EfXeSJnG_5_Rl")},
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/" + expected.OvlRoot: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
||||||
|
|
||||||
"temp": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"overlay work", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"overlay work", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
@@ -246,25 +193,11 @@ func TestExec(t *testing.T) {
|
|||||||
return os.MkdirAll(t.GetWorkDir().String(), 0700)
|
return os.MkdirAll(t.GetWorkDir().String(), 0700)
|
||||||
},
|
},
|
||||||
}), pkg.Path(pkg.AbsWork, false /* ignored */, testtool),
|
}), pkg.Path(pkg.AbsWork, false /* ignored */, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantChecksumOffline, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, expectsFS{
|
}, pkg.MustDecode("iaRt6l_Wm2n-h5UsDewZxQkCmjZjyL8r7wv32QT2kyV55-Lx09Dq4gfg9BiwPnKs")},
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/" + expected.Work: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
||||||
|
|
||||||
"temp": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"multiple layers", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"multiple layers", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
@@ -312,30 +245,11 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantChecksumOffline, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, expectsFS{
|
}, pkg.MustDecode("O2YzyR7IUGU5J2CADy0hUZ3A5NkP_Vwzs4UadEdn2oMZZVWRtH0xZGJ3HXiimTnZ")},
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb": {Mode: 0400, Data: []byte{}},
|
|
||||||
"checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK/check": {Mode: 0400, Data: []byte("layers")},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/_gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/OLBgp1GsljhM2TJ-sbHjaiH9txEUvgdDTAzHv2P24donTt6_529l-9Ua0vFImLlb")},
|
|
||||||
"identifier/B-kc5iJMx8GtlCua4dz6BiJHnDAOUfPjgpbKq4e-QEn0_CZkSYs3fOA1ve06qMs2": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK")},
|
|
||||||
"identifier/" + expected.Layers: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
||||||
|
|
||||||
"temp": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"overlay layer promotion", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"overlay layer promotion", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
testtool, testtoolDestroy := newTesttool()
|
testtool, testtoolDestroy := newTesttool()
|
||||||
@@ -362,26 +276,11 @@ func TestExec(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
pkg.MustPath("/opt", false, testtool),
|
pkg.MustPath("/opt", false, testtool),
|
||||||
), ignorePathname, wantOffline, nil},
|
), ignorePathname, wantChecksumOffline, nil},
|
||||||
})
|
})
|
||||||
|
|
||||||
testtoolDestroy(t, base, c)
|
testtoolDestroy(t, base, c)
|
||||||
}, expectsFS{
|
}, pkg.MustDecode("3EaW6WibLi9gl03_UieiFPaFcPy5p4x3JPxrnLJxGaTI-bh3HU9DK9IMx7c3rrNm")},
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/" + wantOfflineEncode: {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantOfflineEncode + "/check": {Mode: 0400, Data: []byte{0}},
|
|
||||||
"checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/kvJIqZo5DKFOxC2ZQ-8_nPaQzEAz9cIm3p6guO-uLqm-xaiPu7oRkSnsu411jd_U": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
||||||
"identifier/vjz1MHPcGBKV7sjcs8jQP3cqxJ1hgPTiQBMCEHP9BGXjGxd-tJmEmXKaStObo5gK": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU")},
|
|
||||||
"identifier/" + expected.Promote: {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantOfflineEncode)},
|
|
||||||
|
|
||||||
"temp": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package pkg_test
|
package pkg_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
@@ -11,25 +10,18 @@ import (
|
|||||||
func TestFile(t *testing.T) {
|
func TestFile(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
want := expectsFile{0}
|
|
||||||
checkWithCache(t, []cacheTestCase{
|
checkWithCache(t, []cacheTestCase{
|
||||||
{"file", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"file", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
cureMany(t, c, []cureStep{
|
cureMany(t, c, []cureStep{
|
||||||
{"short", pkg.NewFile("null", []byte{0}), base.Append(
|
{"short", pkg.NewFile("null", []byte{0}), base.Append(
|
||||||
"identifier",
|
"identifier",
|
||||||
"3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi",
|
"3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi",
|
||||||
), want, nil},
|
), pkg.MustDecode(
|
||||||
|
"vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX",
|
||||||
|
), nil},
|
||||||
})
|
})
|
||||||
}, expectsFS{
|
}, pkg.MustDecode(
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
"iR6H5OIsyOW4EwEgtm9rGzGF6DVtyHLySEtwnFE8bnus9VJcoCbR4JIek7Lw-vwT",
|
||||||
|
)},
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/" + pkg.Encode(want.hash()): {Mode: 0400, Data: []byte{0}},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/3376ALA7hIUm2LbzH2fDvRezgzod1eTK_G6XjyOgbM2u-6swvkFaF0BOwSl_juBi": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/vsAhtPNo4waRNOASwrQwcIPTqb3SBuJOXw2G4T1mNmVZM-wrQTRllmgXqcIIoRcX")},
|
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
// Package expected contains expected identifiers for exec artifact tests.
|
|
||||||
package expected
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package expected
|
|
||||||
|
|
||||||
const (
|
|
||||||
Offline = "dztPS6jRjiZtCF4_p8AzfnxGp6obkhrgFVsxdodbKWUoAEVtDz3MykepJB4kI_ks"
|
|
||||||
OvlRoot = "RdMA-mubnrHuu3Ky1wWyxauSYCO0ZH_zCPUj3uDHqkfwv5sGcByoF_g5PjlGiClb"
|
|
||||||
Layers = "p1t_drXr34i-jZNuxDMLaMOdL6tZvQqhavNafGynGqxOZoXAUTSn7kqNh3Ovv3DT"
|
|
||||||
Net = "G8qPxD9puvvoOVV7lrT80eyDeIl3G_CCFoKw12c8mCjMdG1zF7NEPkwYpNubClK3"
|
|
||||||
Promote = "xXTIYcXmgJWNLC91c417RRrNM9cjELwEZHpGvf8Fk_GNP5agRJp_SicD0w9aMeLJ"
|
|
||||||
Work = "5hlaukCirnXE4W_RSLJFOZN47Z5RiHnacXzdFp_70cLgiJUGR6cSb_HaFftkzi0-"
|
|
||||||
)
|
|
||||||
@@ -3,6 +3,7 @@ package pkg
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -10,7 +11,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"unique"
|
"unique"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -39,45 +39,22 @@ func panicToError(errP *error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// irCache implements [IRCache].
|
|
||||||
type irCache struct {
|
|
||||||
// Artifact to [unique.Handle] of identifier cache.
|
|
||||||
artifact sync.Map
|
|
||||||
// Identifier free list, must not be accessed directly.
|
|
||||||
identPool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// zeroIRCache returns the initialised value of irCache.
|
|
||||||
func zeroIRCache() irCache {
|
|
||||||
return irCache{
|
|
||||||
identPool: sync.Pool{New: func() any { return new(extIdent) }},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IRCache provides memory management and caching primitives for IR and
|
|
||||||
// identifier operations against [Artifact] implementations.
|
|
||||||
//
|
|
||||||
// The zero value is not safe for use.
|
|
||||||
type IRCache struct{ irCache }
|
|
||||||
|
|
||||||
// NewIR returns the address of a new [IRCache].
|
|
||||||
func NewIR() *IRCache {
|
|
||||||
return &IRCache{zeroIRCache()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IContext is passed to [Artifact.Params] and provides methods for writing
|
// IContext is passed to [Artifact.Params] and provides methods for writing
|
||||||
// values to the IR writer. It does not expose the underlying [io.Writer].
|
// values to the IR writer. It does not expose the underlying [io.Writer].
|
||||||
//
|
//
|
||||||
// IContext is valid until [Artifact.Params] returns.
|
// IContext is valid until [Artifact.Params] returns.
|
||||||
type IContext struct {
|
type IContext struct {
|
||||||
// Address of underlying irCache, should be zeroed or made unusable after
|
// Address of underlying [Cache], should be zeroed or made unusable after
|
||||||
// [Artifact.Params] returns and must not be exposed directly.
|
// [Artifact.Params] returns and must not be exposed directly.
|
||||||
ic *irCache
|
cache *Cache
|
||||||
// Written to by various methods, should be zeroed after [Artifact.Params]
|
// Written to by various methods, should be zeroed after [Artifact.Params]
|
||||||
// returns and must not be exposed directly.
|
// returns and must not be exposed directly.
|
||||||
w io.Writer
|
w io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the underlying [context.Context].
|
||||||
|
func (i *IContext) Unwrap() context.Context { return i.cache.ctx }
|
||||||
|
|
||||||
// irZero is a zero IR word.
|
// irZero is a zero IR word.
|
||||||
var irZero [wordSize]byte
|
var irZero [wordSize]byte
|
||||||
|
|
||||||
@@ -159,11 +136,11 @@ func (i *IContext) mustWrite(p []byte) {
|
|||||||
// WriteIdent is not defined for an [Artifact] not part of the slice returned by
|
// WriteIdent is not defined for an [Artifact] not part of the slice returned by
|
||||||
// [Artifact.Dependencies].
|
// [Artifact.Dependencies].
|
||||||
func (i *IContext) WriteIdent(a Artifact) {
|
func (i *IContext) WriteIdent(a Artifact) {
|
||||||
buf := i.ic.getIdentBuf()
|
buf := i.cache.getIdentBuf()
|
||||||
defer i.ic.putIdentBuf(buf)
|
defer i.cache.putIdentBuf(buf)
|
||||||
|
|
||||||
IRKindIdent.encodeHeader(0).put(buf[:])
|
IRKindIdent.encodeHeader(0).put(buf[:])
|
||||||
*(*ID)(buf[wordSize:]) = i.ic.Ident(a).Value()
|
*(*ID)(buf[wordSize:]) = i.cache.Ident(a).Value()
|
||||||
i.mustWrite(buf[:])
|
i.mustWrite(buf[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,19 +183,19 @@ func (i *IContext) WriteString(s string) {
|
|||||||
|
|
||||||
// Encode writes a deterministic, efficient representation of a to w and returns
|
// Encode writes a deterministic, efficient representation of a to w and returns
|
||||||
// the first non-nil error encountered while writing to w.
|
// the first non-nil error encountered while writing to w.
|
||||||
func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
func (c *Cache) Encode(w io.Writer, a Artifact) (err error) {
|
||||||
deps := a.Dependencies()
|
deps := a.Dependencies()
|
||||||
idents := make([]*extIdent, len(deps))
|
idents := make([]*extIdent, len(deps))
|
||||||
for i, d := range deps {
|
for i, d := range deps {
|
||||||
dbuf, did := ic.unsafeIdent(d, true)
|
dbuf, did := c.unsafeIdent(d, true)
|
||||||
if dbuf == nil {
|
if dbuf == nil {
|
||||||
dbuf = ic.getIdentBuf()
|
dbuf = c.getIdentBuf()
|
||||||
binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
|
binary.LittleEndian.PutUint64(dbuf[:], uint64(d.Kind()))
|
||||||
*(*ID)(dbuf[wordSize:]) = did.Value()
|
*(*ID)(dbuf[wordSize:]) = did.Value()
|
||||||
} else {
|
} else {
|
||||||
ic.storeIdent(d, dbuf)
|
c.storeIdent(d, dbuf)
|
||||||
}
|
}
|
||||||
defer ic.putIdentBuf(dbuf)
|
defer c.putIdentBuf(dbuf)
|
||||||
idents[i] = dbuf
|
idents[i] = dbuf
|
||||||
}
|
}
|
||||||
slices.SortFunc(idents, func(a, b *extIdent) int {
|
slices.SortFunc(idents, func(a, b *extIdent) int {
|
||||||
@@ -244,10 +221,10 @@ func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func() {
|
func() {
|
||||||
i := IContext{ic, w}
|
i := IContext{c, w}
|
||||||
|
|
||||||
defer panicToError(&err)
|
defer panicToError(&err)
|
||||||
defer func() { i.ic, i.w = nil, nil }()
|
defer func() { i.cache, i.w = nil, nil }()
|
||||||
|
|
||||||
a.Params(&i)
|
a.Params(&i)
|
||||||
}()
|
}()
|
||||||
@@ -256,7 +233,7 @@ func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var f IREndFlag
|
var f IREndFlag
|
||||||
kcBuf := ic.getIdentBuf()
|
kcBuf := c.getIdentBuf()
|
||||||
sz := wordSize
|
sz := wordSize
|
||||||
if kc, ok := a.(KnownChecksum); ok {
|
if kc, ok := a.(KnownChecksum); ok {
|
||||||
f |= IREndKnownChecksum
|
f |= IREndKnownChecksum
|
||||||
@@ -266,13 +243,13 @@ func (ic *irCache) Encode(w io.Writer, a Artifact) (err error) {
|
|||||||
IRKindEnd.encodeHeader(uint32(f)).put(kcBuf[:])
|
IRKindEnd.encodeHeader(uint32(f)).put(kcBuf[:])
|
||||||
|
|
||||||
_, err = w.Write(kcBuf[:sz])
|
_, err = w.Write(kcBuf[:sz])
|
||||||
ic.putIdentBuf(kcBuf)
|
c.putIdentBuf(kcBuf)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeAll implements EncodeAll by recursively encoding dependencies and
|
// encodeAll implements EncodeAll by recursively encoding dependencies and
|
||||||
// performs deduplication by value via the encoded map.
|
// performs deduplication by value via the encoded map.
|
||||||
func (ic *irCache) encodeAll(
|
func (c *Cache) encodeAll(
|
||||||
w io.Writer,
|
w io.Writer,
|
||||||
a Artifact,
|
a Artifact,
|
||||||
encoded map[Artifact]struct{},
|
encoded map[Artifact]struct{},
|
||||||
@@ -282,13 +259,13 @@ func (ic *irCache) encodeAll(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range a.Dependencies() {
|
for _, d := range a.Dependencies() {
|
||||||
if err = ic.encodeAll(w, d, encoded); err != nil {
|
if err = c.encodeAll(w, d, encoded); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
encoded[a] = struct{}{}
|
encoded[a] = struct{}{}
|
||||||
return ic.Encode(w, a)
|
return c.Encode(w, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeAll writes a self-describing IR stream of a to w and returns the first
|
// EncodeAll writes a self-describing IR stream of a to w and returns the first
|
||||||
@@ -306,8 +283,8 @@ func (ic *irCache) encodeAll(
|
|||||||
// the ident cache, nor does it contribute identifiers it computes back to the
|
// the ident cache, nor does it contribute identifiers it computes back to the
|
||||||
// ident cache. Because of this, multiple invocations of EncodeAll will have
|
// ident cache. Because of this, multiple invocations of EncodeAll will have
|
||||||
// similar cost and does not amortise when combined with a call to Cure.
|
// similar cost and does not amortise when combined with a call to Cure.
|
||||||
func (ic *irCache) EncodeAll(w io.Writer, a Artifact) error {
|
func (c *Cache) EncodeAll(w io.Writer, a Artifact) error {
|
||||||
return ic.encodeAll(w, a, make(map[Artifact]struct{}))
|
return c.encodeAll(w, a, make(map[Artifact]struct{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrRemainingIR is returned for a [IRReadFunc] that failed to call
|
// ErrRemainingIR is returned for a [IRReadFunc] that failed to call
|
||||||
@@ -432,12 +409,6 @@ func (e InvalidKindError) Error() string {
|
|||||||
// register is not safe for concurrent use. register must not be called after
|
// register is not safe for concurrent use. register must not be called after
|
||||||
// the first instance of [Cache] has been opened.
|
// the first instance of [Cache] has been opened.
|
||||||
func register(k Kind, f IRReadFunc) {
|
func register(k Kind, f IRReadFunc) {
|
||||||
openMu.Lock()
|
|
||||||
defer openMu.Unlock()
|
|
||||||
|
|
||||||
if opened {
|
|
||||||
panic("attempting to register after open")
|
|
||||||
}
|
|
||||||
if _, ok := irArtifact[k]; ok {
|
if _, ok := irArtifact[k]; ok {
|
||||||
panic("attempting to register " + strconv.Itoa(int(k)) + " twice")
|
panic("attempting to register " + strconv.Itoa(int(k)) + " twice")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package pkg_test
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -106,12 +105,9 @@ func TestIRRoundtrip(t *testing.T) {
|
|||||||
if err := <-done; err != nil {
|
if err := <-done; err != nil {
|
||||||
t.Fatalf("EncodeAll: error = %v", err)
|
t.Fatalf("EncodeAll: error = %v", err)
|
||||||
}
|
}
|
||||||
}, expectsFS{
|
}, pkg.MustDecode(
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
"E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C",
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
),
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkWithCache(t, testCasesCache)
|
checkWithCache(t, testCasesCache)
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package pkg_test
|
|||||||
import (
|
import (
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"testing/fstest"
|
"testing/fstest"
|
||||||
"unique"
|
"unique"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"hakurei.app/check"
|
"hakurei.app/check"
|
||||||
"hakurei.app/internal/pkg"
|
"hakurei.app/internal/pkg"
|
||||||
@@ -33,14 +33,20 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
|
|
||||||
checkWithCache(t, []cacheTestCase{
|
checkWithCache(t, []cacheTestCase{
|
||||||
{"direct", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"direct", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
r := newRContext(t, c)
|
var r pkg.RContext
|
||||||
|
rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
|
||||||
|
reflect.NewAt(
|
||||||
|
rCacheVal.Type(),
|
||||||
|
unsafe.Pointer(rCacheVal.UnsafeAddr()),
|
||||||
|
).Elem().Set(reflect.ValueOf(c))
|
||||||
|
|
||||||
f := pkg.NewHTTPGet(
|
f := pkg.NewHTTPGet(
|
||||||
&client,
|
&client,
|
||||||
"file:///testdata",
|
"file:///testdata",
|
||||||
testdataChecksum.Value(),
|
testdataChecksum.Value(),
|
||||||
)
|
)
|
||||||
var got []byte
|
var got []byte
|
||||||
if rc, err := f.Cure(r); err != nil {
|
if rc, err := f.Cure(&r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if got, err = io.ReadAll(rc); err != nil {
|
} else if got, err = io.ReadAll(rc); err != nil {
|
||||||
t.Fatalf("ReadAll: error = %v", err)
|
t.Fatalf("ReadAll: error = %v", err)
|
||||||
@@ -59,7 +65,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
wantErrMismatch := &pkg.ChecksumMismatchError{
|
wantErrMismatch := &pkg.ChecksumMismatchError{
|
||||||
Got: testdataChecksum.Value(),
|
Got: testdataChecksum.Value(),
|
||||||
}
|
}
|
||||||
if rc, err := f.Cure(r); err != nil {
|
if rc, err := f.Cure(&r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if got, err = io.ReadAll(rc); err != nil {
|
} else if got, err = io.ReadAll(rc); err != nil {
|
||||||
t.Fatalf("ReadAll: error = %v", err)
|
t.Fatalf("ReadAll: error = %v", err)
|
||||||
@@ -70,7 +76,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check fallback validation
|
// check fallback validation
|
||||||
if rc, err := f.Cure(r); err != nil {
|
if rc, err := f.Cure(&r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if err = rc.Close(); !reflect.DeepEqual(err, wantErrMismatch) {
|
} else if err = rc.Close(); !reflect.DeepEqual(err, wantErrMismatch) {
|
||||||
t.Fatalf("Close: error = %#v, want %#v", err, wantErrMismatch)
|
t.Fatalf("Close: error = %#v, want %#v", err, wantErrMismatch)
|
||||||
@@ -83,18 +89,18 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
pkg.Checksum{},
|
pkg.Checksum{},
|
||||||
)
|
)
|
||||||
wantErrNotFound := pkg.ResponseStatusError(http.StatusNotFound)
|
wantErrNotFound := pkg.ResponseStatusError(http.StatusNotFound)
|
||||||
if _, err := f.Cure(r); !reflect.DeepEqual(err, wantErrNotFound) {
|
if _, err := f.Cure(&r); !reflect.DeepEqual(err, wantErrNotFound) {
|
||||||
t.Fatalf("Cure: error = %#v, want %#v", err, wantErrNotFound)
|
t.Fatalf("Cure: error = %#v, want %#v", err, wantErrNotFound)
|
||||||
}
|
}
|
||||||
}, expectsFS{
|
}, pkg.MustDecode("E4vEZKhCcL2gPZ2Tt59FS3lDng-d_2SKa2i5G_RbDfwGn6EemptFaGLPUDiOa94C")},
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"cure", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"cure", pkg.CValidateKnown, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
r := newRContext(t, c)
|
var r pkg.RContext
|
||||||
|
rCacheVal := reflect.ValueOf(&r).Elem().FieldByName("cache")
|
||||||
|
reflect.NewAt(
|
||||||
|
rCacheVal.Type(),
|
||||||
|
unsafe.Pointer(rCacheVal.UnsafeAddr()),
|
||||||
|
).Elem().Set(reflect.ValueOf(c))
|
||||||
|
|
||||||
f := pkg.NewHTTPGet(
|
f := pkg.NewHTTPGet(
|
||||||
&client,
|
&client,
|
||||||
@@ -114,7 +120,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var got []byte
|
var got []byte
|
||||||
if rc, err := f.Cure(r); err != nil {
|
if rc, err := f.Cure(&r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if got, err = io.ReadAll(rc); err != nil {
|
} else if got, err = io.ReadAll(rc); err != nil {
|
||||||
t.Fatalf("ReadAll: error = %v", err)
|
t.Fatalf("ReadAll: error = %v", err)
|
||||||
@@ -130,7 +136,7 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
"file:///testdata",
|
"file:///testdata",
|
||||||
testdataChecksum.Value(),
|
testdataChecksum.Value(),
|
||||||
)
|
)
|
||||||
if rc, err := f.Cure(r); err != nil {
|
if rc, err := f.Cure(&r); err != nil {
|
||||||
t.Fatalf("Cure: error = %v", err)
|
t.Fatalf("Cure: error = %v", err)
|
||||||
} else if got, err = io.ReadAll(rc); err != nil {
|
} else if got, err = io.ReadAll(rc); err != nil {
|
||||||
t.Fatalf("ReadAll: error = %v", err)
|
t.Fatalf("ReadAll: error = %v", err)
|
||||||
@@ -150,16 +156,6 @@ func TestHTTPGet(t *testing.T) {
|
|||||||
if _, _, err := c.Cure(f); !reflect.DeepEqual(err, wantErrNotFound) {
|
if _, _, err := c.Cure(f); !reflect.DeepEqual(err, wantErrNotFound) {
|
||||||
t.Fatalf("Pathname: error = %#v, want %#v", err, wantErrNotFound)
|
t.Fatalf("Pathname: error = %#v, want %#v", err, wantErrNotFound)
|
||||||
}
|
}
|
||||||
}, expectsFS{
|
}, pkg.MustDecode("L_0RFHpr9JUS4Zp14rz2dESSRvfLzpvqsLhR1-YjQt8hYlmEdVl7vI3_-v8UNPKs")},
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU": {Mode: 0400, Data: []byte("\x7f\xe1\x69\xa2\xdd\x63\x96\x26\x83\x79\x61\x8b\xf0\x3f\xd5\x16\x9a\x39\x3a\xdb\xcf\xb1\xbc\x8d\x33\xff\x75\xee\x62\x56\xa9\xf0\x27\xac\x13\x94\x69")},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/oM-2pUlk-mOxK1t3aMWZer69UdOQlAXiAgMrpZ1476VoOqpYVP1aGFS9_HYy-D8_": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/fLYGIMHgN1louE-JzITJZJo2SDniPu-IHBXubtvQWFO-hXnDVKNuscV7-zlyr5fU")},
|
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -71,70 +70,8 @@ func MustDecode(s string) (checksum Checksum) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
// extension is a string uniquely identifying a set of custom [Artifact]
|
|
||||||
// implementations registered by calling [Register].
|
|
||||||
extension string
|
|
||||||
|
|
||||||
// openMu synchronises access to global state for initialisation.
|
|
||||||
openMu sync.Mutex
|
|
||||||
// opened is false if [Open] was never called.
|
|
||||||
opened bool
|
|
||||||
)
|
|
||||||
|
|
||||||
// Extension returns a string uniquely identifying the currently registered set
|
|
||||||
// of custom [Artifact], or the zero value if none was registered.
|
|
||||||
func Extension() string { return extension }
|
|
||||||
|
|
||||||
// ValidExtension returns whether s is valid for use in a call to SetExtension.
|
|
||||||
func ValidExtension(s string) bool {
|
|
||||||
if l := len(s); l == 0 || l > 128 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, v := range s {
|
|
||||||
if v < 'a' || v > 'z' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrInvalidExtension is returned for a variant identification string for which
|
|
||||||
// [ValidExtension] returns false.
|
|
||||||
var ErrInvalidExtension = errors.New("invalid extension variant identification string")
|
|
||||||
|
|
||||||
// SetExtension sets the extension variant identification string. SetExtension
|
|
||||||
// must be called before [Open] if custom [Artifact] implementations had been
|
|
||||||
// recorded by calling [Register].
|
|
||||||
//
|
|
||||||
// The variant identification string must be between 1 and 128 bytes long and
|
|
||||||
// consists of only bytes between 'a' and 'z'.
|
|
||||||
//
|
|
||||||
// SetExtension is not safe for concurrent use. SetExtension is called at most
|
|
||||||
// once and must not be called after the first instance of Cache has been opened.
|
|
||||||
func SetExtension(s string) {
|
|
||||||
openMu.Lock()
|
|
||||||
defer openMu.Unlock()
|
|
||||||
|
|
||||||
if opened {
|
|
||||||
panic("attempting to set extension after open")
|
|
||||||
}
|
|
||||||
if extension != "" {
|
|
||||||
panic("attempting to set extension twice")
|
|
||||||
}
|
|
||||||
if !ValidExtension(s) {
|
|
||||||
panic(ErrInvalidExtension)
|
|
||||||
}
|
|
||||||
extension = s
|
|
||||||
statusHeader = makeStatusHeader(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// common holds elements and receives methods shared between different contexts.
|
// common holds elements and receives methods shared between different contexts.
|
||||||
type common struct {
|
type common struct {
|
||||||
// Context specific to this [Artifact]. The toplevel context in [Cache] must
|
|
||||||
// not be exposed directly.
|
|
||||||
ctx context.Context
|
|
||||||
|
|
||||||
// Address of underlying [Cache], should be zeroed or made unusable after
|
// Address of underlying [Cache], should be zeroed or made unusable after
|
||||||
// Cure returns and must not be exposed directly.
|
// Cure returns and must not be exposed directly.
|
||||||
cache *Cache
|
cache *Cache
|
||||||
@@ -161,27 +98,19 @@ type TContext struct {
|
|||||||
common
|
common
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeStatusHeader creates the header written to every status file. This should
|
// statusHeader is the header written to all status files in dirStatus.
|
||||||
// not be called directly, its result is stored in statusHeader and will not
|
var statusHeader = func() string {
|
||||||
// change after the first [Cache] is opened.
|
|
||||||
func makeStatusHeader(extension string) string {
|
|
||||||
s := programName
|
s := programName
|
||||||
if v := info.Version(); v != info.FallbackVersion {
|
if v := info.Version(); v != info.FallbackVersion {
|
||||||
s += " " + v
|
s += " " + v
|
||||||
}
|
}
|
||||||
if extension != "" {
|
|
||||||
s += " with " + extension + " extensions"
|
|
||||||
}
|
|
||||||
s += " (" + runtime.GOARCH + ")"
|
s += " (" + runtime.GOARCH + ")"
|
||||||
if name, err := os.Hostname(); err == nil {
|
if name, err := os.Hostname(); err == nil {
|
||||||
s += " on " + name
|
s += " on " + name
|
||||||
}
|
}
|
||||||
s += "\n\n"
|
s += "\n\n"
|
||||||
return s
|
return s
|
||||||
}
|
}()
|
||||||
|
|
||||||
// statusHeader is the header written to all status files in dirStatus.
|
|
||||||
var statusHeader = makeStatusHeader("")
|
|
||||||
|
|
||||||
// prepareStatus initialises the status file once.
|
// prepareStatus initialises the status file once.
|
||||||
func (t *TContext) prepareStatus() error {
|
func (t *TContext) prepareStatus() error {
|
||||||
@@ -254,15 +183,11 @@ func (t *TContext) destroy(errP *error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unwrap returns the underlying [context.Context].
|
// Unwrap returns the underlying [context.Context].
|
||||||
func (c *common) Unwrap() context.Context { return c.ctx }
|
func (c *common) Unwrap() context.Context { return c.cache.ctx }
|
||||||
|
|
||||||
// GetMessage returns [message.Msg] held by the underlying [Cache].
|
// GetMessage returns [message.Msg] held by the underlying [Cache].
|
||||||
func (c *common) GetMessage() message.Msg { return c.cache.msg }
|
func (c *common) GetMessage() message.Msg { return c.cache.msg }
|
||||||
|
|
||||||
// GetJobs returns the preferred number of jobs to run, when applicable. Its
|
|
||||||
// value must not affect cure outcome.
|
|
||||||
func (c *common) GetJobs() int { return c.cache.jobs }
|
|
||||||
|
|
||||||
// GetWorkDir returns a pathname to a directory which [Artifact] is expected to
|
// GetWorkDir returns a pathname to a directory which [Artifact] is expected to
|
||||||
// write its output to. This is not the final resting place of the [Artifact]
|
// write its output to. This is not the final resting place of the [Artifact]
|
||||||
// and this pathname should not be directly referred to in the final contents.
|
// and this pathname should not be directly referred to in the final contents.
|
||||||
@@ -282,11 +207,11 @@ func (t *TContext) GetTempDir() *check.Absolute { return t.temp }
|
|||||||
// [ChecksumMismatchError], or the underlying implementation may block on Close.
|
// [ChecksumMismatchError], or the underlying implementation may block on Close.
|
||||||
func (c *common) Open(a Artifact) (r io.ReadCloser, err error) {
|
func (c *common) Open(a Artifact) (r io.ReadCloser, err error) {
|
||||||
if f, ok := a.(FileArtifact); ok {
|
if f, ok := a.(FileArtifact); ok {
|
||||||
return c.cache.openFile(c.ctx, f)
|
return c.cache.openFile(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
var pathname *check.Absolute
|
var pathname *check.Absolute
|
||||||
if pathname, _, err = c.cache.cure(a, true); err != nil {
|
if pathname, _, err = c.cache.Cure(a); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,9 +372,6 @@ type KnownChecksum interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FileArtifact refers to an [Artifact] backed by a single file.
|
// FileArtifact refers to an [Artifact] backed by a single file.
|
||||||
//
|
|
||||||
// FileArtifact does not support fine-grained cancellation. Its context is
|
|
||||||
// inherited from the first [TrivialArtifact] or [FloodArtifact] that opens it.
|
|
||||||
type FileArtifact interface {
|
type FileArtifact interface {
|
||||||
// Cure returns [io.ReadCloser] of the full contents of [FileArtifact]. If
|
// Cure returns [io.ReadCloser] of the full contents of [FileArtifact]. If
|
||||||
// [FileArtifact] implements [KnownChecksum], Cure is responsible for
|
// [FileArtifact] implements [KnownChecksum], Cure is responsible for
|
||||||
@@ -494,9 +416,6 @@ const (
|
|||||||
// KindFile is the kind of [Artifact] returned by [NewFile].
|
// KindFile is the kind of [Artifact] returned by [NewFile].
|
||||||
KindFile
|
KindFile
|
||||||
|
|
||||||
// _kindEnd is the total number of kinds and does not denote a kind.
|
|
||||||
_kindEnd
|
|
||||||
|
|
||||||
// KindCustomOffset is the first [Kind] value reserved for implementations
|
// KindCustomOffset is the first [Kind] value reserved for implementations
|
||||||
// not from this package.
|
// not from this package.
|
||||||
KindCustomOffset = 1 << 31
|
KindCustomOffset = 1 << 31
|
||||||
@@ -511,9 +430,6 @@ const (
|
|||||||
// fileLock is the file name appended to Cache.base for guaranteeing
|
// fileLock is the file name appended to Cache.base for guaranteeing
|
||||||
// exclusive access to the cache directory.
|
// exclusive access to the cache directory.
|
||||||
fileLock = "lock"
|
fileLock = "lock"
|
||||||
// fileVariant is the file name appended to Cache.base holding the variant
|
|
||||||
// identification string set by a prior call to [SetExtension].
|
|
||||||
fileVariant = "variant"
|
|
||||||
|
|
||||||
// dirIdentifier is the directory name appended to Cache.base for storing
|
// dirIdentifier is the directory name appended to Cache.base for storing
|
||||||
// artifacts named after their [ID].
|
// artifacts named after their [ID].
|
||||||
@@ -613,40 +529,8 @@ const (
|
|||||||
// impurity due to [KindExecNet] being [KnownChecksum]. This flag exists
|
// impurity due to [KindExecNet] being [KnownChecksum]. This flag exists
|
||||||
// to support kernels without Landlock LSM enabled.
|
// to support kernels without Landlock LSM enabled.
|
||||||
CHostAbstract
|
CHostAbstract
|
||||||
|
|
||||||
// CPromoteVariant allows [pkg.Open] to promote an unextended on-disk cache
|
|
||||||
// to the current extension variant. This is a one-way operation.
|
|
||||||
CPromoteVariant
|
|
||||||
|
|
||||||
// CSuppressInit arranges for verbose output of the container init to be
|
|
||||||
// suppressed regardless of [message.Msg] state.
|
|
||||||
CSuppressInit
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// toplevel holds [context.WithCancel] over caller-supplied context, where all
|
|
||||||
// [Artifact] context are derived from.
|
|
||||||
type toplevel struct {
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// newToplevel returns the address of a new toplevel via ctx.
|
|
||||||
func newToplevel(ctx context.Context) *toplevel {
|
|
||||||
var t toplevel
|
|
||||||
t.ctx, t.cancel = context.WithCancel(ctx)
|
|
||||||
return &t
|
|
||||||
}
|
|
||||||
|
|
||||||
// pendingCure provides synchronisation and cancellation for pending cures.
|
|
||||||
type pendingCure struct {
|
|
||||||
// Closed on cure completion.
|
|
||||||
done <-chan struct{}
|
|
||||||
// Error outcome, safe to access after done is closed.
|
|
||||||
err error
|
|
||||||
// Cancels the corresponding cure.
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache is a support layer that implementations of [Artifact] can use to store
|
// Cache is a support layer that implementations of [Artifact] can use to store
|
||||||
// cured [Artifact] data in a content addressed fashion.
|
// cured [Artifact] data in a content addressed fashion.
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
@@ -654,10 +538,11 @@ type Cache struct {
|
|||||||
// implementation and receives an equal amount of elements after.
|
// implementation and receives an equal amount of elements after.
|
||||||
cures chan struct{}
|
cures chan struct{}
|
||||||
|
|
||||||
// Parent context which toplevel was derived from.
|
// [context.WithCancel] over caller-supplied context, used by [Artifact] and
|
||||||
parent context.Context
|
// all dependency curing goroutines.
|
||||||
// For deriving curing context, must not be accessed directly.
|
ctx context.Context
|
||||||
toplevel atomic.Pointer[toplevel]
|
// Cancels ctx.
|
||||||
|
cancel context.CancelFunc
|
||||||
// For waiting on dependency curing goroutines.
|
// For waiting on dependency curing goroutines.
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
// Reports new cures and passed to [Artifact].
|
// Reports new cures and passed to [Artifact].
|
||||||
@@ -667,11 +552,11 @@ type Cache struct {
|
|||||||
base *check.Absolute
|
base *check.Absolute
|
||||||
// Immutable cure options set by [Open].
|
// Immutable cure options set by [Open].
|
||||||
flags int
|
flags int
|
||||||
// Immutable job count, when applicable.
|
|
||||||
jobs int
|
|
||||||
|
|
||||||
// Must not be exposed directly.
|
// Artifact to [unique.Handle] of identifier cache.
|
||||||
irCache
|
artifact sync.Map
|
||||||
|
// Identifier free list, must not be accessed directly.
|
||||||
|
identPool sync.Pool
|
||||||
|
|
||||||
// Synchronises access to dirChecksum.
|
// Synchronises access to dirChecksum.
|
||||||
checksumMu sync.RWMutex
|
checksumMu sync.RWMutex
|
||||||
@@ -681,11 +566,9 @@ type Cache struct {
|
|||||||
// Identifier to error pair for unrecoverably faulted [Artifact].
|
// Identifier to error pair for unrecoverably faulted [Artifact].
|
||||||
identErr map[unique.Handle[ID]]error
|
identErr map[unique.Handle[ID]]error
|
||||||
// Pending identifiers, accessed through Cure for entries not in ident.
|
// Pending identifiers, accessed through Cure for entries not in ident.
|
||||||
identPending map[unique.Handle[ID]]*pendingCure
|
identPending map[unique.Handle[ID]]<-chan struct{}
|
||||||
// Synchronises access to ident and corresponding filesystem entries.
|
// Synchronises access to ident and corresponding filesystem entries.
|
||||||
identMu sync.RWMutex
|
identMu sync.RWMutex
|
||||||
// Synchronises entry into Abort and Cure.
|
|
||||||
abortMu sync.RWMutex
|
|
||||||
|
|
||||||
// Synchronises entry into exclusive artifacts for the cure method.
|
// Synchronises entry into exclusive artifacts for the cure method.
|
||||||
exclMu sync.Mutex
|
exclMu sync.Mutex
|
||||||
@@ -694,10 +577,8 @@ type Cache struct {
|
|||||||
|
|
||||||
// Unlocks the on-filesystem cache. Must only be called from Close.
|
// Unlocks the on-filesystem cache. Must only be called from Close.
|
||||||
unlock func()
|
unlock func()
|
||||||
// Whether [Cache] is considered closed.
|
// Synchronises calls to Close.
|
||||||
closed bool
|
closeOnce sync.Once
|
||||||
// Synchronises calls to Abort and Close.
|
|
||||||
closeMu sync.Mutex
|
|
||||||
|
|
||||||
// Whether EnterExec has not yet returned.
|
// Whether EnterExec has not yet returned.
|
||||||
inExec atomic.Bool
|
inExec atomic.Bool
|
||||||
@@ -707,24 +588,24 @@ type Cache struct {
|
|||||||
type extIdent [wordSize + len(ID{})]byte
|
type extIdent [wordSize + len(ID{})]byte
|
||||||
|
|
||||||
// getIdentBuf returns the address of an extIdent for Ident.
|
// getIdentBuf returns the address of an extIdent for Ident.
|
||||||
func (ic *irCache) getIdentBuf() *extIdent { return ic.identPool.Get().(*extIdent) }
|
func (c *Cache) getIdentBuf() *extIdent { return c.identPool.Get().(*extIdent) }
|
||||||
|
|
||||||
// putIdentBuf adds buf to identPool.
|
// putIdentBuf adds buf to identPool.
|
||||||
func (ic *irCache) putIdentBuf(buf *extIdent) { ic.identPool.Put(buf) }
|
func (c *Cache) putIdentBuf(buf *extIdent) { c.identPool.Put(buf) }
|
||||||
|
|
||||||
// storeIdent adds an [Artifact] to the artifact cache.
|
// storeIdent adds an [Artifact] to the artifact cache.
|
||||||
func (ic *irCache) storeIdent(a Artifact, buf *extIdent) unique.Handle[ID] {
|
func (c *Cache) storeIdent(a Artifact, buf *extIdent) unique.Handle[ID] {
|
||||||
idu := unique.Make(ID(buf[wordSize:]))
|
idu := unique.Make(ID(buf[wordSize:]))
|
||||||
ic.artifact.Store(a, idu)
|
c.artifact.Store(a, idu)
|
||||||
return idu
|
return idu
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ident returns the identifier of an [Artifact].
|
// Ident returns the identifier of an [Artifact].
|
||||||
func (ic *irCache) Ident(a Artifact) unique.Handle[ID] {
|
func (c *Cache) Ident(a Artifact) unique.Handle[ID] {
|
||||||
buf, idu := ic.unsafeIdent(a, false)
|
buf, idu := c.unsafeIdent(a, false)
|
||||||
if buf != nil {
|
if buf != nil {
|
||||||
idu = ic.storeIdent(a, buf)
|
idu = c.storeIdent(a, buf)
|
||||||
ic.putIdentBuf(buf)
|
c.putIdentBuf(buf)
|
||||||
}
|
}
|
||||||
return idu
|
return idu
|
||||||
}
|
}
|
||||||
@@ -732,17 +613,17 @@ func (ic *irCache) Ident(a Artifact) unique.Handle[ID] {
|
|||||||
// unsafeIdent implements Ident but returns the underlying buffer for a newly
|
// unsafeIdent implements Ident but returns the underlying buffer for a newly
|
||||||
// computed identifier. Callers must return this buffer to identPool. encodeKind
|
// computed identifier. Callers must return this buffer to identPool. encodeKind
|
||||||
// is only a hint, kind may still be encoded in the buffer.
|
// is only a hint, kind may still be encoded in the buffer.
|
||||||
func (ic *irCache) unsafeIdent(a Artifact, encodeKind bool) (
|
func (c *Cache) unsafeIdent(a Artifact, encodeKind bool) (
|
||||||
buf *extIdent,
|
buf *extIdent,
|
||||||
idu unique.Handle[ID],
|
idu unique.Handle[ID],
|
||||||
) {
|
) {
|
||||||
if id, ok := ic.artifact.Load(a); ok {
|
if id, ok := c.artifact.Load(a); ok {
|
||||||
idu = id.(unique.Handle[ID])
|
idu = id.(unique.Handle[ID])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ki, ok := a.(KnownIdent); ok {
|
if ki, ok := a.(KnownIdent); ok {
|
||||||
buf = ic.getIdentBuf()
|
buf = c.getIdentBuf()
|
||||||
if encodeKind {
|
if encodeKind {
|
||||||
binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
|
binary.LittleEndian.PutUint64(buf[:], uint64(a.Kind()))
|
||||||
}
|
}
|
||||||
@@ -750,9 +631,9 @@ func (ic *irCache) unsafeIdent(a Artifact, encodeKind bool) (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
buf = ic.getIdentBuf()
|
buf = c.getIdentBuf()
|
||||||
h := sha512.New384()
|
h := sha512.New384()
|
||||||
if err := ic.Encode(h, a); err != nil {
|
if err := c.Encode(h, a); err != nil {
|
||||||
// unreachable
|
// unreachable
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@@ -1121,11 +1002,7 @@ func (c *Cache) Scrub(checks int) error {
|
|||||||
// loadOrStoreIdent attempts to load a cached [Artifact] by its identifier or
|
// loadOrStoreIdent attempts to load a cached [Artifact] by its identifier or
|
||||||
// wait for a pending [Artifact] to cure. If neither is possible, the current
|
// wait for a pending [Artifact] to cure. If neither is possible, the current
|
||||||
// identifier is stored in identPending and a non-nil channel is returned.
|
// identifier is stored in identPending and a non-nil channel is returned.
|
||||||
//
|
|
||||||
// Since identErr is treated as grow-only, loadOrStoreIdent must not be entered
|
|
||||||
// without holding a read lock on abortMu.
|
|
||||||
func (c *Cache) loadOrStoreIdent(id unique.Handle[ID]) (
|
func (c *Cache) loadOrStoreIdent(id unique.Handle[ID]) (
|
||||||
ctx context.Context,
|
|
||||||
done chan<- struct{},
|
done chan<- struct{},
|
||||||
checksum unique.Handle[Checksum],
|
checksum unique.Handle[Checksum],
|
||||||
err error,
|
err error,
|
||||||
@@ -1142,23 +1019,20 @@ func (c *Cache) loadOrStoreIdent(id unique.Handle[ID]) (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var pending *pendingCure
|
var notify <-chan struct{}
|
||||||
if pending, ok = c.identPending[id]; ok {
|
if notify, ok = c.identPending[id]; ok {
|
||||||
c.identMu.Unlock()
|
c.identMu.Unlock()
|
||||||
<-pending.done
|
<-notify
|
||||||
c.identMu.RLock()
|
c.identMu.RLock()
|
||||||
if checksum, ok = c.ident[id]; !ok {
|
if checksum, ok = c.ident[id]; !ok {
|
||||||
err = pending.err
|
err = c.identErr[id]
|
||||||
}
|
}
|
||||||
c.identMu.RUnlock()
|
c.identMu.RUnlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d := make(chan struct{})
|
d := make(chan struct{})
|
||||||
pending = &pendingCure{done: d}
|
c.identPending[id] = d
|
||||||
ctx, pending.cancel = context.WithCancel(c.toplevel.Load().ctx)
|
|
||||||
c.wg.Add(1)
|
|
||||||
c.identPending[id] = pending
|
|
||||||
c.identMu.Unlock()
|
c.identMu.Unlock()
|
||||||
done = d
|
done = d
|
||||||
return
|
return
|
||||||
@@ -1174,62 +1048,21 @@ func (c *Cache) finaliseIdent(
|
|||||||
) {
|
) {
|
||||||
c.identMu.Lock()
|
c.identMu.Lock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.identPending[id].err = err
|
|
||||||
c.identErr[id] = err
|
c.identErr[id] = err
|
||||||
} else {
|
} else {
|
||||||
c.ident[id] = checksum
|
c.ident[id] = checksum
|
||||||
}
|
}
|
||||||
delete(c.identPending, id)
|
delete(c.identPending, id)
|
||||||
c.identMu.Unlock()
|
c.identMu.Unlock()
|
||||||
c.wg.Done()
|
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Done returns a channel that is closed when the ongoing cure of an [Artifact]
|
|
||||||
// referred to by the specified identifier completes. Done may return nil if
|
|
||||||
// no ongoing cure of the specified identifier exists.
|
|
||||||
func (c *Cache) Done(id unique.Handle[ID]) <-chan struct{} {
|
|
||||||
c.identMu.RLock()
|
|
||||||
pending, ok := c.identPending[id]
|
|
||||||
c.identMu.RUnlock()
|
|
||||||
if !ok || pending == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return pending.done
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cancel cancels the ongoing cure of an [Artifact] referred to by the specified
|
|
||||||
// identifier. Cancel returns whether the [context.CancelFunc] has been killed.
|
|
||||||
// Cancel returns after the cure is complete.
|
|
||||||
func (c *Cache) Cancel(id unique.Handle[ID]) bool {
|
|
||||||
c.identMu.RLock()
|
|
||||||
pending, ok := c.identPending[id]
|
|
||||||
c.identMu.RUnlock()
|
|
||||||
if !ok || pending == nil || pending.cancel == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
pending.cancel()
|
|
||||||
<-pending.done
|
|
||||||
|
|
||||||
c.abortMu.Lock()
|
|
||||||
c.identMu.Lock()
|
|
||||||
delete(c.identErr, id)
|
|
||||||
c.identMu.Unlock()
|
|
||||||
c.abortMu.Unlock()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// openFile tries to load [FileArtifact] from [Cache], and if that fails,
|
// openFile tries to load [FileArtifact] from [Cache], and if that fails,
|
||||||
// obtains it via [FileArtifact.Cure] instead. Notably, it does not cure
|
// obtains it via [FileArtifact.Cure] instead. Notably, it does not cure
|
||||||
// [FileArtifact] to the filesystem. If err is nil, the caller is responsible
|
// [FileArtifact] to the filesystem. If err is nil, the caller is responsible
|
||||||
// for closing the resulting [io.ReadCloser].
|
// for closing the resulting [io.ReadCloser].
|
||||||
//
|
func (c *Cache) openFile(f FileArtifact) (r io.ReadCloser, err error) {
|
||||||
// The context must originate from loadOrStoreIdent to enable cancellation.
|
|
||||||
func (c *Cache) openFile(
|
|
||||||
ctx context.Context,
|
|
||||||
f FileArtifact,
|
|
||||||
) (r io.ReadCloser, err error) {
|
|
||||||
if kc, ok := f.(KnownChecksum); c.flags&CAssumeChecksum != 0 && ok {
|
if kc, ok := f.(KnownChecksum); c.flags&CAssumeChecksum != 0 && ok {
|
||||||
c.checksumMu.RLock()
|
c.checksumMu.RLock()
|
||||||
r, err = os.Open(c.base.Append(
|
r, err = os.Open(c.base.Append(
|
||||||
@@ -1260,7 +1093,7 @@ func (c *Cache) openFile(
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
return f.Cure(&RContext{common{ctx, c}})
|
return f.Cure(&RContext{common{c}})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1408,11 +1241,12 @@ func (c *Cache) Cure(a Artifact) (
|
|||||||
checksum unique.Handle[Checksum],
|
checksum unique.Handle[Checksum],
|
||||||
err error,
|
err error,
|
||||||
) {
|
) {
|
||||||
c.abortMu.RLock()
|
select {
|
||||||
defer c.abortMu.RUnlock()
|
case <-c.ctx.Done():
|
||||||
|
err = c.ctx.Err()
|
||||||
if err = c.toplevel.Load().ctx.Err(); err != nil {
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.cure(a, true)
|
return c.cure(a, true)
|
||||||
@@ -1498,16 +1332,15 @@ func (c *Cache) enterCure(a Artifact, curesExempt bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := c.toplevel.Load().ctx
|
|
||||||
select {
|
select {
|
||||||
case c.cures <- struct{}{}:
|
case c.cures <- struct{}{}:
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-c.ctx.Done():
|
||||||
if a.IsExclusive() {
|
if a.IsExclusive() {
|
||||||
c.exclMu.Unlock()
|
c.exclMu.Unlock()
|
||||||
}
|
}
|
||||||
return ctx.Err()
|
return c.ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1605,8 +1438,7 @@ func (r *RContext) NewMeasuredReader(
|
|||||||
return r.cache.newMeasuredReader(rc, checksum)
|
return r.cache.newMeasuredReader(rc, checksum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cure implements Cure without acquiring a read lock on abortMu. cure must not
|
// cure implements Cure without checking the full dependency graph.
|
||||||
// be entered during Abort.
|
|
||||||
func (c *Cache) cure(a Artifact, curesExempt bool) (
|
func (c *Cache) cure(a Artifact, curesExempt bool) (
|
||||||
pathname *check.Absolute,
|
pathname *check.Absolute,
|
||||||
checksum unique.Handle[Checksum],
|
checksum unique.Handle[Checksum],
|
||||||
@@ -1625,11 +1457,8 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var (
|
var done chan<- struct{}
|
||||||
ctx context.Context
|
done, checksum, err = c.loadOrStoreIdent(id)
|
||||||
done chan<- struct{}
|
|
||||||
)
|
|
||||||
ctx, done, checksum, err = c.loadOrStoreIdent(id)
|
|
||||||
if done == nil {
|
if done == nil {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@@ -1742,7 +1571,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
|||||||
if err = c.enterCure(a, curesExempt); err != nil {
|
if err = c.enterCure(a, curesExempt); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r, err = f.Cure(&RContext{common{ctx, c}})
|
r, err = f.Cure(&RContext{common{c}})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if checksumPathname == nil || c.flags&CValidateKnown != 0 {
|
if checksumPathname == nil || c.flags&CValidateKnown != 0 {
|
||||||
h := sha512.New384()
|
h := sha512.New384()
|
||||||
@@ -1822,7 +1651,7 @@ func (c *Cache) cure(a Artifact, curesExempt bool) (
|
|||||||
c.base.Append(dirWork, ids),
|
c.base.Append(dirWork, ids),
|
||||||
c.base.Append(dirTemp, ids),
|
c.base.Append(dirTemp, ids),
|
||||||
ids, nil, nil, nil,
|
ids, nil, nil, nil,
|
||||||
common{ctx, c},
|
common{c},
|
||||||
}
|
}
|
||||||
switch ca := a.(type) {
|
switch ca := a.(type) {
|
||||||
case TrivialArtifact:
|
case TrivialArtifact:
|
||||||
@@ -1973,65 +1802,23 @@ func (c *Cache) OpenStatus(a Artifact) (r io.ReadSeekCloser, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abort cancels all pending cures and waits for them to clean up, but does not
|
|
||||||
// close the cache.
|
|
||||||
func (c *Cache) Abort() {
|
|
||||||
c.closeMu.Lock()
|
|
||||||
defer c.closeMu.Unlock()
|
|
||||||
|
|
||||||
if c.closed {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.toplevel.Load().cancel()
|
|
||||||
c.abortMu.Lock()
|
|
||||||
defer c.abortMu.Unlock()
|
|
||||||
|
|
||||||
// holding abortMu, identPending stays empty
|
|
||||||
c.wg.Wait()
|
|
||||||
c.identMu.Lock()
|
|
||||||
c.toplevel.Store(newToplevel(c.parent))
|
|
||||||
clear(c.identErr)
|
|
||||||
c.identMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close cancels all pending cures and waits for them to clean up.
|
// Close cancels all pending cures and waits for them to clean up.
|
||||||
func (c *Cache) Close() {
|
func (c *Cache) Close() {
|
||||||
c.closeMu.Lock()
|
c.closeOnce.Do(func() {
|
||||||
defer c.closeMu.Unlock()
|
c.cancel()
|
||||||
|
c.wg.Wait()
|
||||||
if c.closed {
|
close(c.cures)
|
||||||
return
|
c.unlock()
|
||||||
}
|
})
|
||||||
|
|
||||||
c.closed = true
|
|
||||||
c.toplevel.Load().cancel()
|
|
||||||
c.wg.Wait()
|
|
||||||
close(c.cures)
|
|
||||||
c.unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnsupportedVariantError describes an on-disk cache with an extension variant
|
|
||||||
// identification string that differs from the value returned by [Extension].
|
|
||||||
type UnsupportedVariantError string
|
|
||||||
|
|
||||||
func (e UnsupportedVariantError) Error() string {
|
|
||||||
return "unsupported variant " + strconv.Quote(string(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrWouldPromote is returned by [Open] if the [CPromoteVariant] bit is not
|
|
||||||
// set and the on-disk cache requires variant promotion.
|
|
||||||
ErrWouldPromote = errors.New("operation would promote unextended cache")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Open returns the address of a newly opened instance of [Cache].
|
// Open returns the address of a newly opened instance of [Cache].
|
||||||
//
|
//
|
||||||
// Concurrent cures of a [FloodArtifact] dependency graph is limited to the
|
// Concurrent cures of a [FloodArtifact] dependency graph is limited to the
|
||||||
// caller-supplied value, however direct calls to [Cache.Cure] is not subject
|
// caller-supplied value, however direct calls to [Cache.Cure] is not subject
|
||||||
// to this limitation.
|
// to this limitation.
|
||||||
//
|
//
|
||||||
// A cures or jobs value of 0 or lower is equivalent to the value returned by
|
// A cures value of 0 or lower is equivalent to the value returned by
|
||||||
// [runtime.NumCPU].
|
// [runtime.NumCPU].
|
||||||
//
|
//
|
||||||
// A successful call to Open guarantees exclusive access to the on-filesystem
|
// A successful call to Open guarantees exclusive access to the on-filesystem
|
||||||
@@ -2041,10 +1828,10 @@ var (
|
|||||||
func Open(
|
func Open(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
msg message.Msg,
|
msg message.Msg,
|
||||||
flags, cures, jobs int,
|
flags, cures int,
|
||||||
base *check.Absolute,
|
base *check.Absolute,
|
||||||
) (*Cache, error) {
|
) (*Cache, error) {
|
||||||
return open(ctx, msg, flags, cures, jobs, base, true)
|
return open(ctx, msg, flags, cures, base, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// open implements Open but allows omitting the [lockedfile] lock when called
|
// open implements Open but allows omitting the [lockedfile] lock when called
|
||||||
@@ -2052,24 +1839,13 @@ func Open(
|
|||||||
func open(
|
func open(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
msg message.Msg,
|
msg message.Msg,
|
||||||
flags, cures, jobs int,
|
flags, cures int,
|
||||||
base *check.Absolute,
|
base *check.Absolute,
|
||||||
lock bool,
|
lock bool,
|
||||||
) (*Cache, error) {
|
) (*Cache, error) {
|
||||||
openMu.Lock()
|
|
||||||
defer openMu.Unlock()
|
|
||||||
opened = true
|
|
||||||
|
|
||||||
if extension == "" && len(irArtifact) != int(_kindEnd) {
|
|
||||||
panic("attempting to open cache with incomplete variant setup")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cures < 1 {
|
if cures < 1 {
|
||||||
cures = runtime.NumCPU()
|
cures = runtime.NumCPU()
|
||||||
}
|
}
|
||||||
if jobs < 1 {
|
|
||||||
jobs = runtime.NumCPU()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name := range []string{
|
for _, name := range []string{
|
||||||
dirIdentifier,
|
dirIdentifier,
|
||||||
@@ -2077,34 +1853,29 @@ func open(
|
|||||||
dirStatus,
|
dirStatus,
|
||||||
dirWork,
|
dirWork,
|
||||||
} {
|
} {
|
||||||
if err := os.MkdirAll(
|
if err := os.MkdirAll(base.Append(name).String(), 0700); err != nil &&
|
||||||
base.Append(name).String(),
|
!errors.Is(err, os.ErrExist) {
|
||||||
0700,
|
|
||||||
); err != nil && !errors.Is(err, os.ErrExist) {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c := Cache{
|
c := Cache{
|
||||||
parent: ctx,
|
|
||||||
|
|
||||||
cures: make(chan struct{}, cures),
|
cures: make(chan struct{}, cures),
|
||||||
flags: flags,
|
flags: flags,
|
||||||
jobs: jobs,
|
|
||||||
|
|
||||||
msg: msg,
|
msg: msg,
|
||||||
base: base,
|
base: base,
|
||||||
|
|
||||||
irCache: zeroIRCache(),
|
identPool: sync.Pool{New: func() any { return new(extIdent) }},
|
||||||
|
|
||||||
ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]),
|
ident: make(map[unique.Handle[ID]]unique.Handle[Checksum]),
|
||||||
identErr: make(map[unique.Handle[ID]]error),
|
identErr: make(map[unique.Handle[ID]]error),
|
||||||
identPending: make(map[unique.Handle[ID]]*pendingCure),
|
identPending: make(map[unique.Handle[ID]]<-chan struct{}),
|
||||||
|
|
||||||
brPool: sync.Pool{New: func() any { return new(bufio.Reader) }},
|
brPool: sync.Pool{New: func() any { return new(bufio.Reader) }},
|
||||||
bwPool: sync.Pool{New: func() any { return new(bufio.Writer) }},
|
bwPool: sync.Pool{New: func() any { return new(bufio.Writer) }},
|
||||||
}
|
}
|
||||||
c.toplevel.Store(newToplevel(ctx))
|
c.ctx, c.cancel = context.WithCancel(ctx)
|
||||||
|
|
||||||
if lock || !testing.Testing() {
|
if lock || !testing.Testing() {
|
||||||
if unlock, err := lockedfile.MutexAt(
|
if unlock, err := lockedfile.MutexAt(
|
||||||
@@ -2118,45 +1889,6 @@ func open(
|
|||||||
c.unlock = func() {}
|
c.unlock = func() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
variantPath := base.Append(fileVariant).String()
|
|
||||||
if p, err := os.ReadFile(variantPath); err != nil {
|
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
|
||||||
c.unlock()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// nonexistence implies newly created cache, or a cache predating
|
|
||||||
// variant identification strings, in which case it is silently promoted
|
|
||||||
if err = os.WriteFile(
|
|
||||||
variantPath,
|
|
||||||
[]byte(extension),
|
|
||||||
0400,
|
|
||||||
); err != nil {
|
|
||||||
c.unlock()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else if s := string(p); s == "" {
|
|
||||||
if extension != "" {
|
|
||||||
if flags&CPromoteVariant == 0 {
|
|
||||||
c.unlock()
|
|
||||||
return nil, ErrWouldPromote
|
|
||||||
}
|
|
||||||
if err = os.WriteFile(
|
|
||||||
variantPath,
|
|
||||||
[]byte(extension),
|
|
||||||
0400,
|
|
||||||
); err != nil {
|
|
||||||
c.unlock()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !ValidExtension(s) {
|
|
||||||
c.unlock()
|
|
||||||
return nil, ErrInvalidExtension
|
|
||||||
} else if s != extension {
|
|
||||||
c.unlock()
|
|
||||||
return nil, UnsupportedVariantError(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,8 @@ var _ fmt.Stringer = new(tarArtifactNamed)
|
|||||||
func (a *tarArtifactNamed) String() string { return a.name + "-unpack" }
|
func (a *tarArtifactNamed) String() string { return a.name + "-unpack" }
|
||||||
|
|
||||||
// NewTar returns a new [Artifact] backed by the supplied [Artifact] and
|
// NewTar returns a new [Artifact] backed by the supplied [Artifact] and
|
||||||
// compression method. The source [Artifact] must be a [FileArtifact].
|
// compression method. The source [Artifact] must be compatible with
|
||||||
|
// [TContext.Open].
|
||||||
func NewTar(a Artifact, compression uint32) Artifact {
|
func NewTar(a Artifact, compression uint32) Artifact {
|
||||||
ta := tarArtifact{a, compression}
|
ta := tarArtifact{a, compression}
|
||||||
if s, ok := a.(fmt.Stringer); ok {
|
if s, ok := a.(fmt.Stringer); ok {
|
||||||
|
|||||||
@@ -20,31 +20,6 @@ import (
|
|||||||
func TestTar(t *testing.T) {
|
func TestTar(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
want := expectsFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"checksum": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check": {Mode: 0400, Data: []byte{0, 0}},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0500},
|
|
||||||
"identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
"identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0500},
|
|
||||||
}
|
|
||||||
wantEncode := pkg.Encode(want.hash())
|
|
||||||
|
|
||||||
wantExpand := expectsFS{
|
|
||||||
".": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
}
|
|
||||||
wantExpandEncode := pkg.Encode(wantExpand.hash())
|
|
||||||
|
|
||||||
checkWithCache(t, []cacheTestCase{
|
checkWithCache(t, []cacheTestCase{
|
||||||
{"http", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"http", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
checkTarHTTP(t, base, c, fstest.MapFS{
|
checkTarHTTP(t, base, c, fstest.MapFS{
|
||||||
@@ -62,30 +37,10 @@ func TestTar(t *testing.T) {
|
|||||||
"identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
"identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
||||||
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
"work": {Mode: fs.ModeDir | 0700},
|
||||||
}, want)
|
}, pkg.MustDecode(
|
||||||
}, expectsFS{
|
"cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM",
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
))
|
||||||
|
}, pkg.MustDecode("NQTlc466JmSVLIyWklm_u8_g95jEEb98PxJU-kjwxLpfdjwMWJq0G8ze9R4Vo1Vu")},
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/" + wantEncode: {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantEncode + "/checksum": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantEncode + "/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantEncode + "/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/check": {Mode: 0400, Data: []byte{0, 0}},
|
|
||||||
"checksum/" + wantEncode + "/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantEncode + "/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
"checksum/" + wantEncode + "/checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP/lib/pkgconfig": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantEncode + "/identifier": {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantEncode + "/identifier/HnySzeLQvSBZuTUcvfmLEX_OmH4yJWWH788NxuLuv7kVn8_uPM6Ks4rqFWM2NZJY": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
"checksum/" + wantEncode + "/identifier/Zx5ZG9BAwegNT3zQwCySuI2ktCXxNgxirkGLFjW4FW06PtojYVaCdtEw8yuntPLa": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/1TL00Qb8dcqayX7wTO8WNaraHvY6b-KCsctLDTrb64QBCmxj_-byK1HdIUwMaFEP")},
|
|
||||||
"checksum/" + wantEncode + "/work": {Mode: fs.ModeDir | 0500},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)},
|
|
||||||
"identifier/rg7F1D5hwv6o4xctjD5zDq4i5MD0mArTsUIWfhUbik8xC6Bsyt3mjXXOm3goojTz": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantEncode)},
|
|
||||||
|
|
||||||
"temp": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
|
|
||||||
{"http expand", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
{"http expand", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
|
||||||
checkTarHTTP(t, base, c, fstest.MapFS{
|
checkTarHTTP(t, base, c, fstest.MapFS{
|
||||||
@@ -93,21 +48,10 @@ func TestTar(t *testing.T) {
|
|||||||
|
|
||||||
"lib": {Mode: fs.ModeDir | 0700},
|
"lib": {Mode: fs.ModeDir | 0700},
|
||||||
"lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
"lib/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
||||||
}, wantExpand)
|
}, pkg.MustDecode(
|
||||||
}, expectsFS{
|
"CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN",
|
||||||
".": {Mode: fs.ModeDir | 0700},
|
))
|
||||||
|
}, pkg.MustDecode("hSoSSgCYTNonX3Q8FjvjD1fBl-E-BQyA6OTXro2OadXqbST4tZ-akGXszdeqphRe")},
|
||||||
"checksum": {Mode: fs.ModeDir | 0700},
|
|
||||||
"checksum/" + wantExpandEncode: {Mode: fs.ModeDir | 0500},
|
|
||||||
"checksum/" + wantExpandEncode + "/libedac.so": {Mode: fs.ModeSymlink | 0777, Data: []byte("/proc/nonexistent/libedac.so")},
|
|
||||||
|
|
||||||
"identifier": {Mode: fs.ModeDir | 0700},
|
|
||||||
"identifier/W5S65DEhawz_WKaok5NjUKLmnD9dNl5RPauNJjcOVcB3VM4eGhSaLGmXbL8vZpiw": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)},
|
|
||||||
"identifier/_v1blm2h-_KA-dVaawdpLas6MjHc6rbhhFS8JWwx8iJxZGUu8EBbRrhr5AaZ9PJL": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantExpandEncode)},
|
|
||||||
|
|
||||||
"temp": {Mode: fs.ModeDir | 0700},
|
|
||||||
"work": {Mode: fs.ModeDir | 0700},
|
|
||||||
}},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +60,7 @@ func checkTarHTTP(
|
|||||||
base *check.Absolute,
|
base *check.Absolute,
|
||||||
c *pkg.Cache,
|
c *pkg.Cache,
|
||||||
testdataFsys fs.FS,
|
testdataFsys fs.FS,
|
||||||
want expectsKnown,
|
wantChecksum pkg.Checksum,
|
||||||
) {
|
) {
|
||||||
var testdata string
|
var testdata string
|
||||||
{
|
{
|
||||||
@@ -250,24 +194,24 @@ func checkTarHTTP(
|
|||||||
{"file", a, base.Append(
|
{"file", a, base.Append(
|
||||||
"identifier",
|
"identifier",
|
||||||
pkg.Encode(wantIdent),
|
pkg.Encode(wantIdent),
|
||||||
), want, nil},
|
), wantChecksum, nil},
|
||||||
|
|
||||||
{"directory", pkg.NewTar(
|
{"directory", pkg.NewTar(
|
||||||
&tarDir,
|
&tarDir,
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), ignorePathname, want, nil},
|
), ignorePathname, wantChecksum, nil},
|
||||||
|
|
||||||
{"multiple entries", pkg.NewTar(
|
{"multiple entries", pkg.NewTar(
|
||||||
&tarDirMulti,
|
&tarDirMulti,
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, nil, errors.New(
|
), nil, pkg.Checksum{}, errors.New(
|
||||||
"input directory does not contain a single regular file",
|
"input directory does not contain a single regular file",
|
||||||
)},
|
)},
|
||||||
|
|
||||||
{"bad type", pkg.NewTar(
|
{"bad type", pkg.NewTar(
|
||||||
&tarDirType,
|
&tarDirType,
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, nil, errors.New(
|
), nil, pkg.Checksum{}, errors.New(
|
||||||
"input directory does not contain a single regular file",
|
"input directory does not contain a single regular file",
|
||||||
)},
|
)},
|
||||||
|
|
||||||
@@ -277,6 +221,6 @@ func checkTarHTTP(
|
|||||||
cure: func(t *pkg.TContext) error {
|
cure: func(t *pkg.TContext) error {
|
||||||
return stub.UniqueError(0xcafe)
|
return stub.UniqueError(0xcafe)
|
||||||
},
|
},
|
||||||
}, pkg.TarGzip), nil, nil, stub.UniqueError(0xcafe)},
|
}, pkg.TarGzip), nil, pkg.Checksum{}, stub.UniqueError(0xcafe)},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,25 +9,18 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"hakurei.app/check"
|
||||||
"hakurei.app/fhs"
|
"hakurei.app/fhs"
|
||||||
"hakurei.app/vfs"
|
"hakurei.app/vfs"
|
||||||
|
|
||||||
"hakurei.app/internal/pkg/internal/testtool/expected"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
log.SetFlags(0)
|
log.SetFlags(0)
|
||||||
log.SetPrefix("testtool: ")
|
log.SetPrefix("testtool: ")
|
||||||
|
|
||||||
environ := slices.DeleteFunc(slices.Clone(os.Environ()), func(s string) bool {
|
|
||||||
return s == "CURE_JOBS="+strconv.Itoa(runtime.NumCPU())
|
|
||||||
})
|
|
||||||
|
|
||||||
var hostNet, layers, promote bool
|
var hostNet, layers, promote bool
|
||||||
if len(os.Args) == 2 && os.Args[0] == "testtool" {
|
if len(os.Args) == 2 && os.Args[0] == "testtool" {
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
@@ -55,15 +48,15 @@ func main() {
|
|||||||
|
|
||||||
var overlayRoot bool
|
var overlayRoot bool
|
||||||
wantEnv := []string{"HAKUREI_TEST=1"}
|
wantEnv := []string{"HAKUREI_TEST=1"}
|
||||||
if len(environ) == 2 {
|
if len(os.Environ()) == 2 {
|
||||||
overlayRoot = true
|
overlayRoot = true
|
||||||
if !layers && !promote {
|
if !layers && !promote {
|
||||||
log.SetPrefix("testtool(overlay root): ")
|
log.SetPrefix("testtool(overlay root): ")
|
||||||
}
|
}
|
||||||
wantEnv = []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}
|
wantEnv = []string{"HAKUREI_TEST=1", "HAKUREI_ROOT=1"}
|
||||||
}
|
}
|
||||||
if !slices.Equal(wantEnv, environ) {
|
if !slices.Equal(wantEnv, os.Environ()) {
|
||||||
log.Fatalf("Environ: %q, want %q", environ, wantEnv)
|
log.Fatalf("Environ: %q, want %q", os.Environ(), wantEnv)
|
||||||
}
|
}
|
||||||
|
|
||||||
var overlayWork bool
|
var overlayWork bool
|
||||||
@@ -149,40 +142,59 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const checksumEmptyDir = "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"
|
const checksumEmptyDir = "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU"
|
||||||
ident := expected.Offline
|
ident := "dztPS6jRjiZtCF4_p8AzfnxGp6obkhrgFVsxdodbKWUoAEVtDz3MykepJB4kI_ks"
|
||||||
log.Println(m)
|
log.Println(m)
|
||||||
next := func() { m = m.Next; log.Println(m) }
|
next := func() { m = m.Next; log.Println(m) }
|
||||||
|
|
||||||
if overlayRoot {
|
if overlayRoot {
|
||||||
ident = expected.OvlRoot
|
ident = "RdMA-mubnrHuu3Ky1wWyxauSYCO0ZH_zCPUj3uDHqkfwv5sGcByoF_g5PjlGiClb"
|
||||||
|
|
||||||
if m.Root != "/" || m.Target != "/" ||
|
if m.Root != "/" || m.Target != "/" ||
|
||||||
m.Source != "overlay" || m.FsType != "overlay" {
|
m.Source != "overlay" || m.FsType != "overlay" {
|
||||||
log.Fatal("unexpected root mount entry")
|
log.Fatal("unexpected root mount entry")
|
||||||
}
|
}
|
||||||
var lowerdir []string
|
var lowerdir string
|
||||||
for _, o := range strings.Split(m.FsOptstr, ",") {
|
for _, o := range strings.Split(m.FsOptstr, ",") {
|
||||||
const lowerdirKey = "lowerdir+="
|
const lowerdirKey = "lowerdir="
|
||||||
if strings.HasPrefix(o, lowerdirKey) {
|
if strings.HasPrefix(o, lowerdirKey) {
|
||||||
lowerdir = append(lowerdir, o[len(lowerdirKey):])
|
lowerdir = o[len(lowerdirKey):]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !layers {
|
if !layers {
|
||||||
if len(lowerdir) != 1 || filepath.Base(lowerdir[0]) != checksumEmptyDir {
|
if filepath.Base(lowerdir) != checksumEmptyDir {
|
||||||
log.Fatal("unexpected artifact checksum")
|
log.Fatal("unexpected artifact checksum")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ident = expected.Layers
|
ident = "p1t_drXr34i-jZNuxDMLaMOdL6tZvQqhavNafGynGqxOZoXAUTSn7kqNh3Ovv3DT"
|
||||||
|
|
||||||
if len(lowerdir) != 2 ||
|
lowerdirsEscaped := strings.Split(lowerdir, ":")
|
||||||
filepath.Base(lowerdir[0]) != "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU" ||
|
lowerdirs := lowerdirsEscaped[:0]
|
||||||
filepath.Base(lowerdir[1]) != "nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK" {
|
// ignore the option separator since it does not appear in ident
|
||||||
log.Fatalf("unexpected lowerdirs %s", strings.Join(lowerdir, ", "))
|
for i, e := range lowerdirsEscaped {
|
||||||
|
if len(e) > 0 &&
|
||||||
|
e[len(e)-1] == check.SpecialOverlayEscape[0] &&
|
||||||
|
(len(e) == 1 || e[len(e)-2] != check.SpecialOverlayEscape[0]) {
|
||||||
|
// ignore escaped pathname separator since it does not
|
||||||
|
// appear in ident
|
||||||
|
|
||||||
|
e = e[:len(e)-1]
|
||||||
|
if len(lowerdirsEscaped) != i {
|
||||||
|
lowerdirsEscaped[i+1] = e + lowerdirsEscaped[i+1]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lowerdirs = append(lowerdirs, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(lowerdirs) != 2 ||
|
||||||
|
filepath.Base(lowerdirs[0]) != "MGWmEfjut2QE2xPJwTsmUzpff4BN_FEnQ7T0j7gvUCCiugJQNwqt9m151fm9D1yU" ||
|
||||||
|
filepath.Base(lowerdirs[1]) != "nY_CUdiaUM1OL4cPr5TS92FCJ3rCRV7Hm5oVTzAvMXwC03_QnTRfQ5PPs7mOU9fK" {
|
||||||
|
log.Fatalf("unexpected lowerdirs %s", strings.Join(lowerdirs, ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if hostNet {
|
if hostNet {
|
||||||
ident = expected.Net
|
ident = "G8qPxD9puvvoOVV7lrT80eyDeIl3G_CCFoKw12c8mCjMdG1zF7NEPkwYpNubClK3"
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.Root != "/sysroot" || m.Target != "/" {
|
if m.Root != "/sysroot" || m.Target != "/" {
|
||||||
@@ -201,14 +213,14 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if promote {
|
if promote {
|
||||||
ident = expected.Promote
|
ident = "xXTIYcXmgJWNLC91c417RRrNM9cjELwEZHpGvf8Fk_GNP5agRJp_SicD0w9aMeLJ"
|
||||||
}
|
}
|
||||||
|
|
||||||
next() // testtool artifact
|
next() // testtool artifact
|
||||||
|
|
||||||
next()
|
next()
|
||||||
if overlayWork {
|
if overlayWork {
|
||||||
ident = expected.Work
|
ident = "5hlaukCirnXE4W_RSLJFOZN47Z5RiHnacXzdFp_70cLgiJUGR6cSb_HaFftkzi0-"
|
||||||
if m.Root != "/" || m.Target != "/work" ||
|
if m.Root != "/" || m.Target != "/work" ||
|
||||||
m.Source != "overlay" || m.FsType != "overlay" {
|
m.Source != "overlay" || m.FsType != "overlay" {
|
||||||
log.Fatal("unexpected work mount entry")
|
log.Fatal("unexpected work mount entry")
|
||||||
@@ -7,10 +7,10 @@ func (t Toolchain) newAttr() (pkg.Artifact, string) {
|
|||||||
version = "2.5.2"
|
version = "2.5.2"
|
||||||
checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l"
|
checksum = "YWEphrz6vg1sUMmHHVr1CRo53pFXRhq_pjN-AlG8UgwZK1y6m7zuDhxqJhD0SV0l"
|
||||||
)
|
)
|
||||||
return t.NewPackage("attr", version, newTar(
|
return t.NewPackage("attr", version, pkg.NewHTTPGetTar(
|
||||||
"https://download.savannah.nongnu.org/releases/attr/"+
|
nil, "https://download.savannah.nongnu.org/releases/attr/"+
|
||||||
"attr-"+version+".tar.gz",
|
"attr-"+version+".tar.gz",
|
||||||
checksum,
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Patches: []KV{
|
Patches: []KV{
|
||||||
@@ -81,10 +81,10 @@ func (t Toolchain) newACL() (pkg.Artifact, string) {
|
|||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P"
|
checksum = "-fY5nwH4K8ZHBCRXrzLdguPkqjKI6WIiGu4dBtrZ1o0t6AIU73w8wwJz_UyjIS0P"
|
||||||
)
|
)
|
||||||
return t.NewPackage("acl", version, newTar(
|
return t.NewPackage("acl", version, pkg.NewHTTPGetTar(
|
||||||
"https://download.savannah.nongnu.org/releases/acl/"+
|
nil, "https://download.savannah.nongnu.org/releases/acl/"+
|
||||||
"acl-"+version+".tar.gz",
|
"acl-"+version+".tar.gz",
|
||||||
checksum,
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), nil, &MakeHelper{
|
), nil, &MakeHelper{
|
||||||
// makes assumptions about uid_map/gid_map
|
// makes assumptions about uid_map/gid_map
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ import (
|
|||||||
type PArtifact int
|
type PArtifact int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LLVM PArtifact = iota
|
LLVMCompilerRT PArtifact = iota
|
||||||
|
LLVMRuntimes
|
||||||
|
LLVMClang
|
||||||
|
|
||||||
// EarlyInit is the Rosa OS init program.
|
// EarlyInit is the Rosa OS init program.
|
||||||
EarlyInit
|
EarlyInit
|
||||||
@@ -62,7 +64,6 @@ const (
|
|||||||
GenInitCPIO
|
GenInitCPIO
|
||||||
Gettext
|
Gettext
|
||||||
Git
|
Git
|
||||||
Glslang
|
|
||||||
GnuTLS
|
GnuTLS
|
||||||
Go
|
Go
|
||||||
Gperf
|
Gperf
|
||||||
@@ -72,37 +73,25 @@ const (
|
|||||||
HakureiDist
|
HakureiDist
|
||||||
IPTables
|
IPTables
|
||||||
Kmod
|
Kmod
|
||||||
LIT
|
|
||||||
LibX11
|
|
||||||
LibXau
|
LibXau
|
||||||
LibXext
|
|
||||||
LibXrandr
|
|
||||||
LibXrender
|
|
||||||
LibXxf86vm
|
|
||||||
Libbsd
|
Libbsd
|
||||||
Libcap
|
Libcap
|
||||||
Libdrm
|
|
||||||
Libev
|
Libev
|
||||||
Libexpat
|
Libexpat
|
||||||
Libffi
|
Libffi
|
||||||
Libgd
|
Libgd
|
||||||
Libglvnd
|
|
||||||
Libiconv
|
Libiconv
|
||||||
Libmd
|
Libmd
|
||||||
Libmnl
|
Libmnl
|
||||||
Libnftnl
|
Libnftnl
|
||||||
Libpciaccess
|
|
||||||
Libpng
|
|
||||||
Libpsl
|
Libpsl
|
||||||
Libseccomp
|
Libseccomp
|
||||||
Libtasn1
|
Libtasn1
|
||||||
Libtool
|
Libtool
|
||||||
Libucontext
|
Libucontext
|
||||||
Libunistring
|
Libunistring
|
||||||
Libxshmfence
|
|
||||||
Libxml2
|
Libxml2
|
||||||
Libxslt
|
Libxslt
|
||||||
Libxtrans
|
|
||||||
M4
|
M4
|
||||||
MPC
|
MPC
|
||||||
MPFR
|
MPFR
|
||||||
@@ -130,36 +119,22 @@ const (
|
|||||||
PerlTermReadKey
|
PerlTermReadKey
|
||||||
PerlTextCharWidth
|
PerlTextCharWidth
|
||||||
PerlTextWrapI18N
|
PerlTextWrapI18N
|
||||||
PerlUnicodeLineBreak
|
PerlUnicodeGCString
|
||||||
PerlYAMLTiny
|
PerlYAMLTiny
|
||||||
PkgConfig
|
PkgConfig
|
||||||
Procps
|
Procps
|
||||||
Python
|
Python
|
||||||
PythonFlitCore
|
|
||||||
PythonHatchling
|
|
||||||
PythonIniConfig
|
PythonIniConfig
|
||||||
PythonMako
|
|
||||||
PythonMarkupSafe
|
|
||||||
PythonPackaging
|
PythonPackaging
|
||||||
PythonPathspec
|
|
||||||
PythonPluggy
|
PythonPluggy
|
||||||
PythonPyTest
|
PythonPyTest
|
||||||
PythonPyYAML
|
|
||||||
PythonPycparser
|
|
||||||
PythonPygments
|
PythonPygments
|
||||||
PythonSetuptools
|
|
||||||
PythonSetuptoolsSCM
|
|
||||||
PythonTroveClassifiers
|
|
||||||
PythonVCSVersioning
|
|
||||||
PythonWheel
|
|
||||||
QEMU
|
QEMU
|
||||||
Rdfind
|
Rdfind
|
||||||
Readline
|
Readline
|
||||||
Rsync
|
Rsync
|
||||||
Sed
|
Sed
|
||||||
SPIRVHeaders
|
Setuptools
|
||||||
SPIRVLLVMTranslator
|
|
||||||
SPIRVTools
|
|
||||||
SquashfsTools
|
SquashfsTools
|
||||||
Strace
|
Strace
|
||||||
TamaGo
|
TamaGo
|
||||||
@@ -173,35 +148,19 @@ const (
|
|||||||
WaylandProtocols
|
WaylandProtocols
|
||||||
XCB
|
XCB
|
||||||
XCBProto
|
XCBProto
|
||||||
XDGDBusProxy
|
Xproto
|
||||||
XZ
|
XZ
|
||||||
XorgProto
|
|
||||||
Zlib
|
Zlib
|
||||||
Zstd
|
Zstd
|
||||||
|
|
||||||
// PresetUnexportedStart is the first unexported preset.
|
// PresetUnexportedStart is the first unexported preset.
|
||||||
PresetUnexportedStart
|
PresetUnexportedStart
|
||||||
|
|
||||||
llvmSource = iota - 1
|
buildcatrust = iota - 1
|
||||||
// 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
|
utilMacros
|
||||||
|
|
||||||
// Musl is a standalone libc that does not depend on the toolchain.
|
// Musl is a standalone libc that does not depend on the toolchain.
|
||||||
Musl
|
Musl
|
||||||
// muslHeaders is a system installation of [Musl] headers.
|
|
||||||
muslHeaders
|
|
||||||
|
|
||||||
// gcc is a hacked-to-pieces GCC toolchain meant for use in intermediate
|
// 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.
|
// stages only. This preset and its direct output must never be exposed.
|
||||||
@@ -346,29 +305,15 @@ var (
|
|||||||
}
|
}
|
||||||
// artifactsOnce is for lazy initialisation of artifacts.
|
// artifactsOnce is for lazy initialisation of artifacts.
|
||||||
artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once
|
artifactsOnce [_toolchainEnd][len(artifactsM)]sync.Once
|
||||||
|
|
||||||
// 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
|
|
||||||
)
|
|
||||||
|
|
||||||
// Flags returns the current preset flags
|
|
||||||
func Flags() int { return presetOpts }
|
|
||||||
|
|
||||||
// zero zeros the value pointed to by p.
|
// zero zeros the value pointed to by p.
|
||||||
func zero[T any](p *T) { var v T; *p = v }
|
func zero[T any](p *T) { var v T; *p = v }
|
||||||
|
|
||||||
// DropCaches arranges for all cached [pkg.Artifact] to be freed some time after
|
// 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
|
// it returns. Must not be used concurrently with any other function from this
|
||||||
// package.
|
// package.
|
||||||
func DropCaches(flags int) {
|
func DropCaches() {
|
||||||
presetOpts = flags
|
|
||||||
zero(&artifacts)
|
zero(&artifacts)
|
||||||
zero(&artifactsOnce)
|
zero(&artifactsOnce)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,16 +20,13 @@ func TestLoad(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkAll(b *testing.B) {
|
func BenchmarkAll(b *testing.B) {
|
||||||
flags := rosa.Flags()
|
|
||||||
b.Cleanup(func() { rosa.DropCaches(flags) })
|
|
||||||
|
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
for i := range rosa.PresetEnd {
|
for i := range rosa.PresetEnd {
|
||||||
rosa.Std.Load(rosa.PArtifact(i))
|
rosa.Std.Load(rosa.PArtifact(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
rosa.DropCaches(0)
|
rosa.DropCaches()
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ func (t Toolchain) newArgpStandalone() (pkg.Artifact, string) {
|
|||||||
version = "1.3"
|
version = "1.3"
|
||||||
checksum = "vtW0VyO2pJ-hPyYmDI2zwSLS8QL0sPAUKC1t3zNYbwN2TmsaE-fADhaVtNd3eNFl"
|
checksum = "vtW0VyO2pJ-hPyYmDI2zwSLS8QL0sPAUKC1t3zNYbwN2TmsaE-fADhaVtNd3eNFl"
|
||||||
)
|
)
|
||||||
return t.NewPackage("argp-standalone", version, newTar(
|
return t.NewPackage("argp-standalone", version, pkg.NewHTTPGetTar(
|
||||||
"http://www.lysator.liu.se/~nisse/misc/"+
|
nil, "http://www.lysator.liu.se/~nisse/misc/"+
|
||||||
"argp-standalone-"+version+".tar.gz",
|
"argp-standalone-"+version+".tar.gz",
|
||||||
checksum,
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Env: []string{
|
Env: []string{
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ func (t Toolchain) newBzip2() (pkg.Artifact, string) {
|
|||||||
version = "1.0.8"
|
version = "1.0.8"
|
||||||
checksum = "cTLykcco7boom-s05H1JVsQi1AtChYL84nXkg_92Dm1Xt94Ob_qlMg_-NSguIK-c"
|
checksum = "cTLykcco7boom-s05H1JVsQi1AtChYL84nXkg_92Dm1Xt94Ob_qlMg_-NSguIK-c"
|
||||||
)
|
)
|
||||||
return t.NewPackage("bzip2", version, newTar(
|
return t.NewPackage("bzip2", version, pkg.NewHTTPGetTar(
|
||||||
"https://sourceware.org/pub/bzip2/bzip2-"+version+".tar.gz",
|
nil, "https://sourceware.org/pub/bzip2/bzip2-"+version+".tar.gz",
|
||||||
checksum,
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Writable: true,
|
Writable: true,
|
||||||
|
|||||||
@@ -10,14 +10,13 @@ import (
|
|||||||
|
|
||||||
func (t Toolchain) newCMake() (pkg.Artifact, string) {
|
func (t Toolchain) newCMake() (pkg.Artifact, string) {
|
||||||
const (
|
const (
|
||||||
version = "4.3.2"
|
version = "4.3.1"
|
||||||
checksum = "6QylwRVKletndTSkZTV2YBRwgd_9rUVgav_QW23HpjUgV21AVYZOUOal8tdBDmO7"
|
checksum = "RHpzZiM1kJ5bwLjo9CpXSeHJJg3hTtV9QxBYpQoYwKFtRh5YhGWpShrqZCSOzQN6"
|
||||||
)
|
)
|
||||||
return t.NewPackage("cmake", version, newFromGitHubRelease(
|
return t.NewPackage("cmake", version, pkg.NewHTTPGetTar(
|
||||||
"Kitware/CMake",
|
nil, "https://github.com/Kitware/CMake/releases/download/"+
|
||||||
"v"+version,
|
"v"+version+"/cmake-"+version+".tar.gz",
|
||||||
"cmake-"+version+".tar.gz",
|
mustDecode(checksum),
|
||||||
checksum,
|
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
// test suite expects writable source tree
|
// test suite expects writable source tree
|
||||||
@@ -91,7 +90,7 @@ index 2ead810437..f85cbb8b1c 100644
|
|||||||
ConfigureName: "/usr/src/cmake/bootstrap",
|
ConfigureName: "/usr/src/cmake/bootstrap",
|
||||||
Configure: []KV{
|
Configure: []KV{
|
||||||
{"prefix", "/system"},
|
{"prefix", "/system"},
|
||||||
{"parallel", jobsE},
|
{"parallel", `"$(nproc)"`},
|
||||||
{"--"},
|
{"--"},
|
||||||
{"-DCMAKE_USE_OPENSSL", "OFF"},
|
{"-DCMAKE_USE_OPENSSL", "OFF"},
|
||||||
{"-DCMake_TEST_NO_NETWORK", "ON"},
|
{"-DCMake_TEST_NO_NETWORK", "ON"},
|
||||||
@@ -119,27 +118,31 @@ func init() {
|
|||||||
|
|
||||||
// CMakeHelper is the [CMake] build system helper.
|
// CMakeHelper is the [CMake] build system helper.
|
||||||
type CMakeHelper struct {
|
type CMakeHelper struct {
|
||||||
|
// Joined with name with a dash if non-empty.
|
||||||
|
Variant string
|
||||||
|
|
||||||
// Path elements joined with source.
|
// Path elements joined with source.
|
||||||
Append []string
|
Append []string
|
||||||
|
|
||||||
// Value of CMAKE_BUILD_TYPE. The zero value is equivalent to "Release".
|
|
||||||
BuildType string
|
|
||||||
// CMake CACHE entries.
|
// CMake CACHE entries.
|
||||||
Cache []KV
|
Cache []KV
|
||||||
// Runs after install.
|
// Runs after install.
|
||||||
Script string
|
Script string
|
||||||
|
|
||||||
// Replaces the default test command.
|
|
||||||
Test string
|
|
||||||
// Whether to skip running tests.
|
|
||||||
SkipTest bool
|
|
||||||
|
|
||||||
// Whether to generate Makefile instead.
|
// Whether to generate Makefile instead.
|
||||||
Make bool
|
Make bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Helper = new(CMakeHelper)
|
var _ Helper = new(CMakeHelper)
|
||||||
|
|
||||||
|
// name returns its arguments and an optional variant string joined with '-'.
|
||||||
|
func (attr *CMakeHelper) name(name, version string) string {
|
||||||
|
if attr != nil && attr.Variant != "" {
|
||||||
|
name += "-" + attr.Variant
|
||||||
|
}
|
||||||
|
return name + "-" + version
|
||||||
|
}
|
||||||
|
|
||||||
// extra returns a hardcoded slice of [CMake] and [Ninja].
|
// extra returns a hardcoded slice of [CMake] and [Ninja].
|
||||||
func (attr *CMakeHelper) extra(int) P {
|
func (attr *CMakeHelper) extra(int) P {
|
||||||
if attr != nil && attr.Make {
|
if attr != nil && attr.Make {
|
||||||
@@ -166,30 +169,22 @@ func (*CMakeHelper) wantsDir() string { return "/cure/" }
|
|||||||
// script generates the cure script.
|
// script generates the cure script.
|
||||||
func (attr *CMakeHelper) script(name string) string {
|
func (attr *CMakeHelper) script(name string) string {
|
||||||
if attr == nil {
|
if attr == nil {
|
||||||
attr = new(CMakeHelper)
|
attr = &CMakeHelper{
|
||||||
|
Cache: []KV{
|
||||||
|
{"CMAKE_BUILD_TYPE", "Release"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(attr.Cache) == 0 {
|
||||||
|
panic("CACHE must be non-empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
generate := "Ninja"
|
generate := "Ninja"
|
||||||
test := "ninja " + jobsFlagE + " test"
|
jobs := ""
|
||||||
if attr.Make {
|
if attr.Make {
|
||||||
generate = "'Unix Makefiles'"
|
generate = "'Unix Makefiles'"
|
||||||
test = "make " + jobsFlagE + " test"
|
jobs += ` "--parallel=$(nproc)"`
|
||||||
}
|
}
|
||||||
if attr.Test != "" {
|
|
||||||
test = attr.Test
|
|
||||||
}
|
|
||||||
|
|
||||||
script := attr.Script
|
|
||||||
if !attr.SkipTest && presetOpts&OptSkipCheck == 0 {
|
|
||||||
script += "\n" + test
|
|
||||||
}
|
|
||||||
|
|
||||||
cache := make([]KV, 1, 1+len(attr.Cache))
|
|
||||||
cache[0] = KV{"CMAKE_BUILD_TYPE", "Release"}
|
|
||||||
if attr.BuildType != "" {
|
|
||||||
cache[0][1] = attr.BuildType
|
|
||||||
}
|
|
||||||
cache = append(cache, attr.Cache...)
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
cmake -G ` + generate + ` \
|
cmake -G ` + generate + ` \
|
||||||
@@ -198,7 +193,7 @@ cmake -G ` + generate + ` \
|
|||||||
-DCMAKE_ASM_COMPILER_TARGET="${ROSA_TRIPLE}" \
|
-DCMAKE_ASM_COMPILER_TARGET="${ROSA_TRIPLE}" \
|
||||||
-DCMAKE_INSTALL_LIBDIR=lib \
|
-DCMAKE_INSTALL_LIBDIR=lib \
|
||||||
` + strings.Join(slices.Collect(func(yield func(string) bool) {
|
` + strings.Join(slices.Collect(func(yield func(string) bool) {
|
||||||
for _, v := range cache {
|
for _, v := range attr.Cache {
|
||||||
if !yield("-D" + v[0] + "=" + v[1]) {
|
if !yield("-D" + v[0] + "=" + v[1]) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -206,7 +201,7 @@ cmake -G ` + generate + ` \
|
|||||||
}), " \\\n\t") + ` \
|
}), " \\\n\t") + ` \
|
||||||
-DCMAKE_INSTALL_PREFIX=/system \
|
-DCMAKE_INSTALL_PREFIX=/system \
|
||||||
'/usr/src/` + name + `/` + filepath.Join(attr.Append...) + `'
|
'/usr/src/` + name + `/` + filepath.Join(attr.Append...) + `'
|
||||||
cmake --build . --parallel=` + jobsE + `
|
cmake --build .` + jobs + `
|
||||||
cmake --install . --prefix=/work/system
|
cmake --install . --prefix=/work/system
|
||||||
` + script
|
` + attr.Script
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ func (t Toolchain) newConnman() (pkg.Artifact, string) {
|
|||||||
version = "2.0"
|
version = "2.0"
|
||||||
checksum = "MhVTdJOhndnZn2SWd8URKo_Pj7Zvc14tntEbrVOf9L3yVWJvpb3v3Q6104tWJgtW"
|
checksum = "MhVTdJOhndnZn2SWd8URKo_Pj7Zvc14tntEbrVOf9L3yVWJvpb3v3Q6104tWJgtW"
|
||||||
)
|
)
|
||||||
return t.NewPackage("connman", version, newTar(
|
return t.NewPackage("connman", version, pkg.NewHTTPGetTar(
|
||||||
"https://git.kernel.org/pub/scm/network/connman/connman.git/"+
|
nil, "https://git.kernel.org/pub/scm/network/connman/connman.git/"+
|
||||||
"snapshot/connman-"+version+".tar.gz",
|
"snapshot/connman-"+version+".tar.gz",
|
||||||
checksum,
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
Patches: []KV{
|
Patches: []KV{
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ func (t Toolchain) newCurl() (pkg.Artifact, string) {
|
|||||||
version = "8.19.0"
|
version = "8.19.0"
|
||||||
checksum = "YHuVLVVp8q_Y7-JWpID5ReNjq2Zk6t7ArHB6ngQXilp_R5l3cubdxu3UKo-xDByv"
|
checksum = "YHuVLVVp8q_Y7-JWpID5ReNjq2Zk6t7ArHB6ngQXilp_R5l3cubdxu3UKo-xDByv"
|
||||||
)
|
)
|
||||||
return t.NewPackage("curl", version, newTar(
|
return t.NewPackage("curl", version, pkg.NewHTTPGetTar(
|
||||||
"https://curl.se/download/curl-"+version+".tar.bz2",
|
nil, "https://curl.se/download/curl-"+version+".tar.bz2",
|
||||||
checksum,
|
mustDecode(checksum),
|
||||||
pkg.TarBzip2,
|
pkg.TarBzip2,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
// remove broken test
|
// remove broken test
|
||||||
Writable: true,
|
Writable: true,
|
||||||
ScriptEarly: `
|
ScriptEarly: `
|
||||||
chmod +w tests/data && rm -f tests/data/test459
|
chmod +w tests/data && rm tests/data/test459
|
||||||
`,
|
`,
|
||||||
}, &MakeHelper{
|
}, &MakeHelper{
|
||||||
Configure: []KV{
|
Configure: []KV{
|
||||||
@@ -25,7 +25,7 @@ chmod +w tests/data && rm -f tests/data/test459
|
|||||||
{"disable-smb"},
|
{"disable-smb"},
|
||||||
},
|
},
|
||||||
Check: []string{
|
Check: []string{
|
||||||
"TFLAGS=" + jobsLFlagE,
|
`TFLAGS="-j$(expr "$(nproc)" '*' 2)"`,
|
||||||
"test-nonflaky",
|
"test-nonflaky",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ func (t Toolchain) newDBus() (pkg.Artifact, string) {
|
|||||||
version = "1.16.2"
|
version = "1.16.2"
|
||||||
checksum = "INwOuNdrDG7XW5ilW_vn8JSxEa444rRNc5ho97i84I1CNF09OmcFcV-gzbF4uCyg"
|
checksum = "INwOuNdrDG7XW5ilW_vn8JSxEa444rRNc5ho97i84I1CNF09OmcFcV-gzbF4uCyg"
|
||||||
)
|
)
|
||||||
return t.NewPackage("dbus", version, newFromGitLab(
|
return t.NewPackage("dbus", version, pkg.NewHTTPGetTar(
|
||||||
"gitlab.freedesktop.org",
|
nil, "https://gitlab.freedesktop.org/dbus/dbus/-/archive/"+
|
||||||
"dbus/dbus",
|
"dbus-"+version+"/dbus-dbus-"+version+".tar.bz2",
|
||||||
"dbus-"+version,
|
mustDecode(checksum),
|
||||||
checksum,
|
pkg.TarBzip2,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
// OSError: [Errno 30] Read-only file system: '/usr/src/dbus/subprojects/packagecache'
|
// OSError: [Errno 30] Read-only file system: '/usr/src/dbus/subprojects/packagecache'
|
||||||
Writable: true,
|
Writable: true,
|
||||||
@@ -44,38 +44,3 @@ func init() {
|
|||||||
ID: 5356,
|
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ func (t Toolchain) newDTC() (pkg.Artifact, string) {
|
|||||||
version = "1.7.2"
|
version = "1.7.2"
|
||||||
checksum = "vUoiRynPyYRexTpS6USweT5p4SVHvvVJs8uqFkkVD-YnFjwf6v3elQ0-Etrh00Dt"
|
checksum = "vUoiRynPyYRexTpS6USweT5p4SVHvvVJs8uqFkkVD-YnFjwf6v3elQ0-Etrh00Dt"
|
||||||
)
|
)
|
||||||
return t.NewPackage("dtc", version, newTar(
|
return t.NewPackage("dtc", version, pkg.NewHTTPGetTar(
|
||||||
"https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/"+
|
nil, "https://git.kernel.org/pub/scm/utils/dtc/dtc.git/snapshot/"+
|
||||||
"dtc-v"+version+".tar.gz",
|
"dtc-v"+version+".tar.gz",
|
||||||
checksum,
|
mustDecode(checksum),
|
||||||
pkg.TarGzip,
|
pkg.TarGzip,
|
||||||
), &PackageAttr{
|
), &PackageAttr{
|
||||||
// works around buggy test:
|
// works around buggy test:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user