ldd: decode from reader
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m27s
Test / Hakurei (push) Successful in 3m20s
Test / Hpkg (push) Successful in 4m10s
Test / Sandbox (race detector) (push) Successful in 4m18s
Test / Hakurei (race detector) (push) Successful in 5m5s
Test / Flake checks (push) Successful in 1m31s
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 2m27s
Test / Hakurei (push) Successful in 3m20s
Test / Hpkg (push) Successful in 4m10s
Test / Sandbox (race detector) (push) Successful in 4m18s
Test / Hakurei (race detector) (push) Successful in 5m5s
Test / Flake checks (push) Successful in 1m31s
This should reduce memory footprint of the parsing process and allow decoding part of the stream. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
a9d72a5eb1
commit
690a0ed0d6
27
ldd/error.go
27
ldd/error.go
@ -1,27 +0,0 @@
|
|||||||
package ldd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrUnexpectedSeparator = errors.New("unexpected separator")
|
|
||||||
ErrPathNotAbsolute = errors.New("path not absolute")
|
|
||||||
ErrBadLocationFormat = errors.New("bad location format")
|
|
||||||
ErrUnexpectedNewline = errors.New("unexpected newline")
|
|
||||||
)
|
|
||||||
|
|
||||||
type EntryUnexpectedSegmentsError string
|
|
||||||
|
|
||||||
func (e EntryUnexpectedSegmentsError) Is(err error) bool {
|
|
||||||
var eq EntryUnexpectedSegmentsError
|
|
||||||
if !errors.As(err, &eq) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return e == eq
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e EntryUnexpectedSegmentsError) Error() string {
|
|
||||||
return fmt.Sprintf("unexpected segments in entry %q", string(e))
|
|
||||||
}
|
|
||||||
33
ldd/exec.go
33
ldd/exec.go
@ -3,6 +3,7 @@ package ldd
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -16,11 +17,14 @@ import (
|
|||||||
"hakurei.app/message"
|
"hakurei.app/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
msgStatic = []byte("Not a valid dynamic program")
|
// msgStaticSuffix is the suffix of message printed to stderr by musl on a statically linked program.
|
||||||
msgStaticGlibc = []byte("not a dynamic executable")
|
msgStaticSuffix = ": Not a valid dynamic program"
|
||||||
|
// msgStaticGlibc is a substring of the message printed to stderr by glibc on a statically linked program.
|
||||||
|
msgStaticGlibc = "not a dynamic executable"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Exec runs ldd(1) in a restrictive [container] and connects it to a [Decoder], returning resulting entries.
|
||||||
func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
|
func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
|
||||||
const (
|
const (
|
||||||
lddName = "ldd"
|
lddName = "ldd"
|
||||||
@ -41,14 +45,20 @@ func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
|
|||||||
z.Hostname = "hakurei-" + lddName
|
z.Hostname = "hakurei-" + lddName
|
||||||
z.SeccompFlags |= seccomp.AllowMultiarch
|
z.SeccompFlags |= seccomp.AllowMultiarch
|
||||||
z.SeccompPresets |= std.PresetStrict
|
z.SeccompPresets |= std.PresetStrict
|
||||||
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
|
stderr := new(bytes.Buffer)
|
||||||
z.Stdout = stdout
|
|
||||||
z.Stderr = stderr
|
z.Stderr = stderr
|
||||||
z.
|
z.
|
||||||
Bind(fhs.AbsRoot, fhs.AbsRoot, 0).
|
Bind(fhs.AbsRoot, fhs.AbsRoot, 0).
|
||||||
Proc(fhs.AbsProc).
|
Proc(fhs.AbsProc).
|
||||||
Dev(fhs.AbsDev, false)
|
Dev(fhs.AbsDev, false)
|
||||||
|
|
||||||
|
var d *Decoder
|
||||||
|
if r, err := z.StdoutPipe(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
d = NewDecoder(r)
|
||||||
|
}
|
||||||
|
|
||||||
if err := z.Start(); err != nil {
|
if err := z.Start(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -56,15 +66,18 @@ func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
|
|||||||
if err := z.Serve(); err != nil {
|
if err := z.Serve(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entries, decodeErr := d.Decode()
|
||||||
if err := z.Wait(); err != nil {
|
if err := z.Wait(); err != nil {
|
||||||
m := stderr.Bytes()
|
m := stderr.Bytes()
|
||||||
if bytes.Contains(m, append([]byte(p+": "), msgStatic...)) ||
|
if bytes.Contains(m, []byte(msgStaticSuffix)) || bytes.Contains(m, []byte(msgStaticGlibc)) {
|
||||||
bytes.Contains(m, msgStaticGlibc) {
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if decodeErr != nil {
|
||||||
|
return nil, errors.Join(decodeErr, err)
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return entries, decodeErr
|
||||||
v := stdout.Bytes()
|
|
||||||
return Parse(v)
|
|
||||||
}
|
}
|
||||||
|
|||||||
228
ldd/ldd.go
228
ldd/ldd.go
@ -2,65 +2,203 @@
|
|||||||
package ldd
|
package ldd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"bufio"
|
||||||
"path"
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrUnexpectedNewline is returned when encountering an unexpected empty line.
|
||||||
|
ErrUnexpectedNewline = errors.New("unexpected newline")
|
||||||
|
// ErrUnexpectedSeparator is returned when encountering an unexpected separator segment.
|
||||||
|
ErrUnexpectedSeparator = errors.New("unexpected separator")
|
||||||
|
// ErrBadLocationFormat is returned for an incorrectly formatted [Entry.Location] segment.
|
||||||
|
ErrBadLocationFormat = errors.New("bad location format")
|
||||||
|
)
|
||||||
|
|
||||||
|
// EntryUnexpectedSegmentsError is returned when encountering
|
||||||
|
// a line containing unexpected number of segments.
|
||||||
|
type EntryUnexpectedSegmentsError string
|
||||||
|
|
||||||
|
func (e EntryUnexpectedSegmentsError) Error() string {
|
||||||
|
return fmt.Sprintf("unexpected segments in entry %q", string(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// An Entry represents one line of ldd(1) output.
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
Name string `json:"name,omitempty"`
|
// File name of required object.
|
||||||
Path string `json:"path,omitempty"`
|
Name string `json:"name"`
|
||||||
|
// Absolute pathname of matched object. Only populated for the long variant.
|
||||||
|
Path *check.Absolute `json:"path,omitempty"`
|
||||||
|
// Address at which the object is loaded.
|
||||||
Location uint64 `json:"location"`
|
Location uint64 `json:"location"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func Parse(p []byte) ([]*Entry, error) {
|
// Path returns a deduplicated slice of absolute directory paths in entries.
|
||||||
payload := strings.Split(strings.TrimSpace(string(p)), "\n")
|
func Path(entries []*Entry) []*check.Absolute {
|
||||||
result := make([]*Entry, len(payload))
|
p := make([]*check.Absolute, 0, len(entries)*2)
|
||||||
|
for _, entry := range entries {
|
||||||
for i, ent := range payload {
|
if entry.Path != nil {
|
||||||
if len(ent) == 0 {
|
p = append(p, entry.Path.Dir())
|
||||||
return nil, ErrUnexpectedNewline
|
|
||||||
}
|
}
|
||||||
|
if a, err := check.NewAbs(entry.Name); err == nil {
|
||||||
segment := strings.SplitN(ent, " ", 5)
|
p = append(p, a.Dir())
|
||||||
|
|
||||||
// location index
|
|
||||||
var iL int
|
|
||||||
|
|
||||||
switch len(segment) {
|
|
||||||
case 2: // /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
|
|
||||||
iL = 1
|
|
||||||
result[i] = &Entry{Name: strings.TrimSpace(segment[0])}
|
|
||||||
case 4: // libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
|
|
||||||
iL = 3
|
|
||||||
if segment[1] != "=>" {
|
|
||||||
return nil, ErrUnexpectedSeparator
|
|
||||||
}
|
|
||||||
if !path.IsAbs(segment[2]) {
|
|
||||||
return nil, ErrPathNotAbsolute
|
|
||||||
}
|
|
||||||
result[i] = &Entry{
|
|
||||||
Name: strings.TrimSpace(segment[0]),
|
|
||||||
Path: segment[2],
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, EntryUnexpectedSegmentsError(ent)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
check.SortAbs(p)
|
||||||
|
return check.CompactAbs(p)
|
||||||
|
}
|
||||||
|
|
||||||
if loc, err := parseLocation(segment[iL]); err != nil {
|
const (
|
||||||
return nil, err
|
// entrySegmentIndexName is the index of the segment holding [Entry.Name].
|
||||||
|
entrySegmentIndexName = 0
|
||||||
|
// entrySegmentIndexPath is the index of the segment holding [Entry.Path],
|
||||||
|
// present only for a line describing a fully populated [Entry].
|
||||||
|
entrySegmentIndexPath = 2
|
||||||
|
// entrySegmentIndexSeparator is the index of the segment containing the magic bytes entrySegmentFullSeparator,
|
||||||
|
// present only for a line describing a fully populated [Entry].
|
||||||
|
entrySegmentIndexSeparator = 1
|
||||||
|
// entrySegmentIndexLocation is the index of the segment holding [Entry.Location]
|
||||||
|
// for a line describing a fully populated [Entry].
|
||||||
|
entrySegmentIndexLocation = 3
|
||||||
|
// entrySegmentIndexLocationShort is the index of the segment holding [Entry.Location]
|
||||||
|
// for a line describing only [Entry.Name].
|
||||||
|
entrySegmentIndexLocationShort = 1
|
||||||
|
|
||||||
|
// entrySegmentSep is the byte separating segments in an [Entry] line.
|
||||||
|
entrySegmentSep = ' '
|
||||||
|
// entrySegmentFullSeparator is the exact contents of the segment at index entrySegmentIndexSeparator.
|
||||||
|
entrySegmentFullSeparator = "=>"
|
||||||
|
|
||||||
|
// entrySegmentLocationLengthMin is the minimum possible length of a segment corresponding to [Entry.Location].
|
||||||
|
entrySegmentLocationLengthMin = 4
|
||||||
|
// entrySegmentLocationPrefix are magic bytes prefixing a segment corresponding to [Entry.Location].
|
||||||
|
entrySegmentLocationPrefix = "(0x"
|
||||||
|
// entrySegmentLocationSuffix is the magic byte suffixing a segment corresponding to [Entry.Location].
|
||||||
|
entrySegmentLocationSuffix = ')'
|
||||||
|
)
|
||||||
|
|
||||||
|
// decodeLocationSegment decodes and saves the segment corresponding to [Entry.Location].
|
||||||
|
func (e *Entry) decodeLocationSegment(segment []byte) (err error) {
|
||||||
|
if len(segment) < entrySegmentLocationLengthMin ||
|
||||||
|
segment[len(segment)-1] != entrySegmentLocationSuffix ||
|
||||||
|
string(segment[:len(entrySegmentLocationPrefix)]) != entrySegmentLocationPrefix {
|
||||||
|
return ErrBadLocationFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Location, err = strconv.ParseUint(string(segment[3:len(segment)-1]), 16, 64)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText parses a line of ldd(1) output and saves it to [Entry].
|
||||||
|
func (e *Entry) UnmarshalText(data []byte) error {
|
||||||
|
var (
|
||||||
|
segments = bytes.SplitN(data, []byte{entrySegmentSep}, 5)
|
||||||
|
// segment to pass to decodeLocationSegment
|
||||||
|
iL int
|
||||||
|
)
|
||||||
|
|
||||||
|
switch len(segments) {
|
||||||
|
case 2: // /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
|
||||||
|
iL = entrySegmentIndexLocationShort
|
||||||
|
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
|
||||||
|
|
||||||
|
case 4: // libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f04d14ef000)
|
||||||
|
iL = entrySegmentIndexLocation
|
||||||
|
if string(segments[entrySegmentIndexSeparator]) != entrySegmentFullSeparator {
|
||||||
|
return ErrUnexpectedSeparator
|
||||||
|
}
|
||||||
|
if a, err := check.NewAbs(string(segments[entrySegmentIndexPath])); err != nil {
|
||||||
|
return err
|
||||||
} else {
|
} else {
|
||||||
result[i].Location = loc
|
e.Path = a
|
||||||
}
|
}
|
||||||
|
e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
|
||||||
|
|
||||||
|
default:
|
||||||
|
return EntryUnexpectedSegmentsError(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return e.decodeLocationSegment(segments[iL])
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLocation(s string) (uint64, error) {
|
// A Decoder reads and decodes [Entry] values from an input stream.
|
||||||
if len(s) < 4 || s[len(s)-1] != ')' || s[:3] != "(0x" {
|
//
|
||||||
return math.MaxUint64, ErrBadLocationFormat
|
// The zero value is not safe for use.
|
||||||
}
|
type Decoder struct {
|
||||||
return strconv.ParseUint(s[3:len(s)-1], 16, 64)
|
s *bufio.Scanner
|
||||||
|
|
||||||
|
// Whether the current line is not the first line.
|
||||||
|
notFirst bool
|
||||||
|
// Whether s has no more tokens.
|
||||||
|
depleted bool
|
||||||
|
// Holds onto the first error encountered while parsing.
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDecoder returns a new decoder that reads from r.
|
||||||
|
//
|
||||||
|
// The decoder introduces its own buffering and may read
|
||||||
|
// data from r beyond the [Entry] values requested.
|
||||||
|
func NewDecoder(r io.Reader) *Decoder { return &Decoder{s: bufio.NewScanner(r)} }
|
||||||
|
|
||||||
|
// Scan advances the [Decoder] to the next [Entry] and
|
||||||
|
// stores the result in the value pointed to by v.
|
||||||
|
func (d *Decoder) Scan(v *Entry) bool {
|
||||||
|
if d.s == nil || d.err != nil || d.depleted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !d.s.Scan() {
|
||||||
|
d.depleted = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
data := d.s.Bytes()
|
||||||
|
if len(data) == 0 {
|
||||||
|
if d.notFirst {
|
||||||
|
if d.s.Scan() && d.err == nil {
|
||||||
|
d.err = ErrUnexpectedNewline
|
||||||
|
}
|
||||||
|
// trailing newline is allowed (glibc)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// leading newline is allowed (musl)
|
||||||
|
d.notFirst = true
|
||||||
|
return d.Scan(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.notFirst = true
|
||||||
|
d.err = v.UnmarshalText(data)
|
||||||
|
return d.err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the first non-EOF error that was encountered
|
||||||
|
// by the underlying [bufio.Scanner] or [Entry].
|
||||||
|
func (d *Decoder) Err() error {
|
||||||
|
if d.err != nil || d.s == nil {
|
||||||
|
return d.err
|
||||||
|
}
|
||||||
|
return d.s.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode reads from the input stream until there are no more entries
|
||||||
|
// and returns the results in a slice.
|
||||||
|
func (d *Decoder) Decode() ([]*Entry, error) {
|
||||||
|
var entries []*Entry
|
||||||
|
|
||||||
|
e := new(Entry)
|
||||||
|
for d.Scan(e) {
|
||||||
|
entries = append(entries, e)
|
||||||
|
e = new(Entry)
|
||||||
|
}
|
||||||
|
return entries, d.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a slice of addresses to [Entry] decoded from p.
|
||||||
|
func Parse(p []byte) ([]*Entry, error) { return NewDecoder(bytes.NewReader(p)).Decode() }
|
||||||
|
|||||||
104
ldd/ldd_test.go
104
ldd/ldd_test.go
@ -1,10 +1,12 @@
|
|||||||
package ldd_test
|
package ldd_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"hakurei.app/container/check"
|
||||||
"hakurei.app/ldd"
|
"hakurei.app/ldd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,15 +22,19 @@ func TestParseError(t *testing.T) {
|
|||||||
|
|
||||||
libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||||
`, ldd.ErrUnexpectedNewline},
|
`, ldd.ErrUnexpectedNewline},
|
||||||
|
|
||||||
{"unexpected separator", `
|
{"unexpected separator", `
|
||||||
libzstd.so.1 = /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
libzstd.so.1 = /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||||
`, ldd.ErrUnexpectedSeparator},
|
`, ldd.ErrUnexpectedSeparator},
|
||||||
|
|
||||||
{"path not absolute", `
|
{"path not absolute", `
|
||||||
libzstd.so.1 => usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
libzstd.so.1 => usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||||
`, ldd.ErrPathNotAbsolute},
|
`, &check.AbsoluteError{Pathname: "usr/lib/libzstd.so.1"}},
|
||||||
|
|
||||||
{"unexpected segments", `
|
{"unexpected segments", `
|
||||||
meow libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
meow libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
||||||
`, ldd.EntryUnexpectedSegmentsError("meow libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)")},
|
`, ldd.EntryUnexpectedSegmentsError("meow libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)")},
|
||||||
|
|
||||||
{"bad location format", `
|
{"bad location format", `
|
||||||
libzstd.so.1 => /usr/lib/libzstd.so.1 7ff71bfd2000
|
libzstd.so.1 => /usr/lib/libzstd.so.1 7ff71bfd2000
|
||||||
`, ldd.ErrBadLocationFormat},
|
`, ldd.ErrBadLocationFormat},
|
||||||
@ -49,6 +55,7 @@ func TestParse(t *testing.T) {
|
|||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
file, out string
|
file, out string
|
||||||
want []*ldd.Entry
|
want []*ldd.Entry
|
||||||
|
paths []*check.Absolute
|
||||||
}{
|
}{
|
||||||
{"musl /bin/kmod", `
|
{"musl /bin/kmod", `
|
||||||
/lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)
|
/lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)
|
||||||
@ -56,30 +63,38 @@ libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
|
|||||||
liblzma.so.5 => /usr/lib/liblzma.so.5 (0x7ff71bf9a000)
|
liblzma.so.5 => /usr/lib/liblzma.so.5 (0x7ff71bf9a000)
|
||||||
libz.so.1 => /lib/libz.so.1 (0x7ff71bf80000)
|
libz.so.1 => /lib/libz.so.1 (0x7ff71bf80000)
|
||||||
libcrypto.so.3 => /lib/libcrypto.so.3 (0x7ff71ba00000)
|
libcrypto.so.3 => /lib/libcrypto.so.3 (0x7ff71ba00000)
|
||||||
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`,
|
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`, []*ldd.Entry{
|
||||||
[]*ldd.Entry{
|
{"/lib/ld-musl-x86_64.so.1", nil, 0x7ff71c0a4000},
|
||||||
{"/lib/ld-musl-x86_64.so.1", "", 0x7ff71c0a4000},
|
{"libzstd.so.1", check.MustAbs("/usr/lib/libzstd.so.1"), 0x7ff71bfd2000},
|
||||||
{"libzstd.so.1", "/usr/lib/libzstd.so.1", 0x7ff71bfd2000},
|
{"liblzma.so.5", check.MustAbs("/usr/lib/liblzma.so.5"), 0x7ff71bf9a000},
|
||||||
{"liblzma.so.5", "/usr/lib/liblzma.so.5", 0x7ff71bf9a000},
|
{"libz.so.1", check.MustAbs("/lib/libz.so.1"), 0x7ff71bf80000},
|
||||||
{"libz.so.1", "/lib/libz.so.1", 0x7ff71bf80000},
|
{"libcrypto.so.3", check.MustAbs("/lib/libcrypto.so.3"), 0x7ff71ba00000},
|
||||||
{"libcrypto.so.3", "/lib/libcrypto.so.3", 0x7ff71ba00000},
|
{"libc.musl-x86_64.so.1", check.MustAbs("/lib/ld-musl-x86_64.so.1"), 0x7ff71c0a4000},
|
||||||
{"libc.musl-x86_64.so.1", "/lib/ld-musl-x86_64.so.1", 0x7ff71c0a4000},
|
}, []*check.Absolute{
|
||||||
}},
|
check.MustAbs("/lib"),
|
||||||
|
check.MustAbs("/usr/lib"),
|
||||||
|
}},
|
||||||
|
|
||||||
{"glibc /nix/store/rc3n2r3nffpib2gqpxlkjx36frw6n34z-kmod-31/bin/kmod", `
|
{"glibc /nix/store/rc3n2r3nffpib2gqpxlkjx36frw6n34z-kmod-31/bin/kmod", `
|
||||||
linux-vdso.so.1 (0x00007ffed65be000)
|
linux-vdso.so.1 (0x00007ffed65be000)
|
||||||
libzstd.so.1 => /nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1 (0x00007f3199cd1000)
|
libzstd.so.1 => /nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1 (0x00007f3199cd1000)
|
||||||
liblzma.so.5 => /nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5 (0x00007f3199ca2000)
|
liblzma.so.5 => /nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5 (0x00007f3199ca2000)
|
||||||
libc.so.6 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6 (0x00007f3199ab5000)
|
libc.so.6 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6 (0x00007f3199ab5000)
|
||||||
libpthread.so.0 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0 (0x00007f3199ab0000)
|
libpthread.so.0 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0 (0x00007f3199ab0000)
|
||||||
/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2 (0x00007f3199da5000)`,
|
/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2 => /nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2 (0x00007f3199da5000)`, []*ldd.Entry{
|
||||||
[]*ldd.Entry{
|
{"linux-vdso.so.1", nil, 0x00007ffed65be000},
|
||||||
{"linux-vdso.so.1", "", 0x00007ffed65be000},
|
{"libzstd.so.1", check.MustAbs("/nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1"), 0x00007f3199cd1000},
|
||||||
{"libzstd.so.1", "/nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib/libzstd.so.1", 0x00007f3199cd1000},
|
{"liblzma.so.5", check.MustAbs("/nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5"), 0x00007f3199ca2000},
|
||||||
{"liblzma.so.5", "/nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib/liblzma.so.5", 0x00007f3199ca2000},
|
{"libc.so.6", check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6"), 0x00007f3199ab5000},
|
||||||
{"libc.so.6", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libc.so.6", 0x00007f3199ab5000},
|
{"libpthread.so.0", check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0"), 0x00007f3199ab0000},
|
||||||
{"libpthread.so.0", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/libpthread.so.0", 0x00007f3199ab0000},
|
{"/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2", check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2"), 0x00007f3199da5000},
|
||||||
{"/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib/ld-linux-x86-64.so.2", "/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64/ld-linux-x86-64.so.2", 0x00007f3199da5000},
|
}, []*check.Absolute{
|
||||||
}},
|
check.MustAbs("/nix/store/80pxmvb9q43kh9rkjagc4h41vf6dh1y6-zstd-1.5.6/lib"),
|
||||||
|
check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib"),
|
||||||
|
check.MustAbs("/nix/store/c10zhkbp6jmyh0xc5kd123ga8yy2p4hk-glibc-2.39-52/lib64"),
|
||||||
|
check.MustAbs("/nix/store/g78jna1i5qhh8gqs4mr64648f0szqgw4-xz-5.4.7/lib"),
|
||||||
|
}},
|
||||||
|
|
||||||
{"glibc /usr/bin/xdg-dbus-proxy", `
|
{"glibc /usr/bin/xdg-dbus-proxy", `
|
||||||
linux-vdso.so.1 (0x00007725f5772000)
|
linux-vdso.so.1 (0x00007725f5772000)
|
||||||
libglib-2.0.so.0 => /usr/lib/libglib-2.0.so.0 (0x00007725f55d5000)
|
libglib-2.0.so.0 => /usr/lib/libglib-2.0.so.0 (0x00007725f55d5000)
|
||||||
@ -93,31 +108,46 @@ libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`,
|
|||||||
libmount.so.1 => /usr/lib/libmount.so.1 (0x00007725f5076000)
|
libmount.so.1 => /usr/lib/libmount.so.1 (0x00007725f5076000)
|
||||||
libffi.so.8 => /usr/lib/libffi.so.8 (0x00007725f506b000)
|
libffi.so.8 => /usr/lib/libffi.so.8 (0x00007725f506b000)
|
||||||
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007725f5774000)
|
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007725f5774000)
|
||||||
libblkid.so.1 => /usr/lib/libblkid.so.1 (0x00007725f5032000)`,
|
libblkid.so.1 => /usr/lib/libblkid.so.1 (0x00007725f5032000)`, []*ldd.Entry{
|
||||||
[]*ldd.Entry{
|
{"linux-vdso.so.1", nil, 0x00007725f5772000},
|
||||||
{"linux-vdso.so.1", "", 0x00007725f5772000},
|
{"libglib-2.0.so.0", check.MustAbs("/usr/lib/libglib-2.0.so.0"), 0x00007725f55d5000},
|
||||||
{"libglib-2.0.so.0", "/usr/lib/libglib-2.0.so.0", 0x00007725f55d5000},
|
{"libgio-2.0.so.0", check.MustAbs("/usr/lib/libgio-2.0.so.0"), 0x00007725f5406000},
|
||||||
{"libgio-2.0.so.0", "/usr/lib/libgio-2.0.so.0", 0x00007725f5406000},
|
{"libgobject-2.0.so.0", check.MustAbs("/usr/lib/libgobject-2.0.so.0"), 0x00007725f53a6000},
|
||||||
{"libgobject-2.0.so.0", "/usr/lib/libgobject-2.0.so.0", 0x00007725f53a6000},
|
{"libgcc_s.so.1", check.MustAbs("/usr/lib/libgcc_s.so.1"), 0x00007725f5378000},
|
||||||
{"libgcc_s.so.1", "/usr/lib/libgcc_s.so.1", 0x00007725f5378000},
|
{"libc.so.6", check.MustAbs("/usr/lib/libc.so.6"), 0x00007725f5187000},
|
||||||
{"libc.so.6", "/usr/lib/libc.so.6", 0x00007725f5187000},
|
{"libpcre2-8.so.0", check.MustAbs("/usr/lib/libpcre2-8.so.0"), 0x00007725f50e8000},
|
||||||
{"libpcre2-8.so.0", "/usr/lib/libpcre2-8.so.0", 0x00007725f50e8000},
|
{"libgmodule-2.0.so.0", check.MustAbs("/usr/lib/libgmodule-2.0.so.0"), 0x00007725f50df000},
|
||||||
{"libgmodule-2.0.so.0", "/usr/lib/libgmodule-2.0.so.0", 0x00007725f50df000},
|
{"libz.so.1", check.MustAbs("/usr/lib/libz.so.1"), 0x00007725f50c6000},
|
||||||
{"libz.so.1", "/usr/lib/libz.so.1", 0x00007725f50c6000},
|
{"libmount.so.1", check.MustAbs("/usr/lib/libmount.so.1"), 0x00007725f5076000},
|
||||||
{"libmount.so.1", "/usr/lib/libmount.so.1", 0x00007725f5076000},
|
{"libffi.so.8", check.MustAbs("/usr/lib/libffi.so.8"), 0x00007725f506b000},
|
||||||
{"libffi.so.8", "/usr/lib/libffi.so.8", 0x00007725f506b000},
|
{"/lib64/ld-linux-x86-64.so.2", check.MustAbs("/usr/lib64/ld-linux-x86-64.so.2"), 0x00007725f5774000},
|
||||||
{"/lib64/ld-linux-x86-64.so.2", "/usr/lib64/ld-linux-x86-64.so.2", 0x00007725f5774000},
|
{"libblkid.so.1", check.MustAbs("/usr/lib/libblkid.so.1"), 0x00007725f5032000},
|
||||||
{"libblkid.so.1", "/usr/lib/libblkid.so.1", 0x00007725f5032000},
|
}, []*check.Absolute{
|
||||||
}},
|
check.MustAbs("/lib64"),
|
||||||
|
check.MustAbs("/usr/lib"),
|
||||||
|
check.MustAbs("/usr/lib64"),
|
||||||
|
}},
|
||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.file, func(t *testing.T) {
|
t.Run(tc.file, func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
if got, err := ldd.Parse([]byte(tc.out)); err != nil {
|
if got, err := ldd.Parse([]byte(tc.out)); err != nil {
|
||||||
t.Errorf("Parse() error = %v", err)
|
t.Errorf("Parse: error = %v", err)
|
||||||
} else if !reflect.DeepEqual(got, tc.want) {
|
} else if !reflect.DeepEqual(got, tc.want) {
|
||||||
t.Errorf("Parse() got = %#v, want %#v", got, tc.want)
|
t.Errorf("Parse: \n%s\nwant\n%s", mustMarshalJSON(got), mustMarshalJSON(tc.want))
|
||||||
|
} else if paths := ldd.Path(got); !reflect.DeepEqual(paths, tc.paths) {
|
||||||
|
t.Errorf("Paths: %v, want %v", paths, tc.paths)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mustMarshalJSON calls [json.Marshal] and returns the resulting data.
|
||||||
|
func mustMarshalJSON(v any) []byte {
|
||||||
|
if data, err := json.Marshal(v); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
20
ldd/path.go
20
ldd/path.go
@ -1,20 +0,0 @@
|
|||||||
package ldd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hakurei.app/container/check"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Path returns a deterministic, deduplicated slice of absolute directory paths in entries.
|
|
||||||
func Path(entries []*Entry) []*check.Absolute {
|
|
||||||
p := make([]*check.Absolute, 0, len(entries)*2)
|
|
||||||
for _, entry := range entries {
|
|
||||||
if a, err := check.NewAbs(entry.Path); err == nil {
|
|
||||||
p = append(p, a.Dir())
|
|
||||||
}
|
|
||||||
if a, err := check.NewAbs(entry.Name); err == nil {
|
|
||||||
p = append(p, a.Dir())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
check.SortAbs(p)
|
|
||||||
return check.CompactAbs(p)
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user