aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ldd/error.go27
-rw-r--r--ldd/exec.go33
-rw-r--r--ldd/ldd.go220
-rw-r--r--ldd/ldd_test.go104
-rw-r--r--ldd/path.go20
5 files changed, 269 insertions, 135 deletions
diff --git a/ldd/error.go b/ldd/error.go
deleted file mode 100644
index 39b63ef9..00000000
--- a/ldd/error.go
+++ /dev/null
@@ -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))
-}
diff --git a/ldd/exec.go b/ldd/exec.go
index c52eff1c..d91cd652 100644
--- a/ldd/exec.go
+++ b/ldd/exec.go
@@ -3,6 +3,7 @@ package ldd
import (
"bytes"
"context"
+ "errors"
"io"
"os"
"os/exec"
@@ -16,11 +17,14 @@ import (
"hakurei.app/message"
)
-var (
- msgStatic = []byte("Not a valid dynamic program")
- msgStaticGlibc = []byte("not a dynamic executable")
+const (
+ // msgStaticSuffix is the suffix of message printed to stderr by musl on a statically linked program.
+ 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) {
const (
lddName = "ldd"
@@ -41,14 +45,20 @@ func Exec(ctx context.Context, msg message.Msg, p string) ([]*Entry, error) {
z.Hostname = "hakurei-" + lddName
z.SeccompFlags |= seccomp.AllowMultiarch
z.SeccompPresets |= std.PresetStrict
- stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
- z.Stdout = stdout
+ stderr := new(bytes.Buffer)
z.Stderr = stderr
z.
Bind(fhs.AbsRoot, fhs.AbsRoot, 0).
Proc(fhs.AbsProc).
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 {
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 {
return nil, err
}
+
+ entries, decodeErr := d.Decode()
if err := z.Wait(); err != nil {
m := stderr.Bytes()
- if bytes.Contains(m, append([]byte(p+": "), msgStatic...)) ||
- bytes.Contains(m, msgStaticGlibc) {
+ if bytes.Contains(m, []byte(msgStaticSuffix)) || bytes.Contains(m, []byte(msgStaticGlibc)) {
return nil, nil
}
+
+ if decodeErr != nil {
+ return nil, errors.Join(decodeErr, err)
+ }
return nil, err
}
-
- v := stdout.Bytes()
- return Parse(v)
+ return entries, decodeErr
}
diff --git a/ldd/ldd.go b/ldd/ldd.go
index ca4156c4..307d8484 100644
--- a/ldd/ldd.go
+++ b/ldd/ldd.go
@@ -2,65 +2,203 @@
package ldd
import (
- "math"
- "path"
+ "bufio"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
"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 {
- Name string `json:"name,omitempty"`
- Path string `json:"path,omitempty"`
+ // File name of required object.
+ 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"`
}
-func Parse(p []byte) ([]*Entry, error) {
- payload := strings.Split(strings.TrimSpace(string(p)), "\n")
- result := make([]*Entry, len(payload))
+// Path returns a 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 entry.Path != nil {
+ p = append(p, entry.Path.Dir())
+ }
+ if a, err := check.NewAbs(entry.Name); err == nil {
+ p = append(p, a.Dir())
+ }
+ }
+ check.SortAbs(p)
+ return check.CompactAbs(p)
+}
+
+const (
+ // 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]))
- for i, ent := range payload {
- if len(ent) == 0 {
- return nil, ErrUnexpectedNewline
+ 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 {
+ e.Path = a
+ }
+ e.Name = string(bytes.TrimSpace(segments[entrySegmentIndexName]))
- segment := strings.SplitN(ent, " ", 5)
+ default:
+ return EntryUnexpectedSegmentsError(data)
+ }
- // location index
- var iL int
+ return e.decodeLocationSegment(segments[iL])
+}
- 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],
+// A Decoder reads and decodes [Entry] values from an input stream.
+//
+// The zero value is not safe for use.
+type Decoder struct {
+ 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
}
- default:
- return nil, EntryUnexpectedSegmentsError(ent)
+ // trailing newline is allowed (glibc)
+ return false
}
- if loc, err := parseLocation(segment[iL]); err != nil {
- return nil, err
- } else {
- result[i].Location = loc
- }
+ // leading newline is allowed (musl)
+ d.notFirst = true
+ return d.Scan(v)
}
- return result, nil
+ d.notFirst = true
+ d.err = v.UnmarshalText(data)
+ return d.err == nil
}
-func parseLocation(s string) (uint64, error) {
- if len(s) < 4 || s[len(s)-1] != ')' || s[:3] != "(0x" {
- return math.MaxUint64, ErrBadLocationFormat
+// 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 strconv.ParseUint(s[3:len(s)-1], 16, 64)
+ 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() }
diff --git a/ldd/ldd_test.go b/ldd/ldd_test.go
index a6044f6b..0288a7c7 100644
--- a/ldd/ldd_test.go
+++ b/ldd/ldd_test.go
@@ -1,10 +1,12 @@
package ldd_test
import (
+ "encoding/json"
"errors"
"reflect"
"testing"
+ "hakurei.app/container/check"
"hakurei.app/ldd"
)
@@ -20,15 +22,19 @@ func TestParseError(t *testing.T) {
libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
`, ldd.ErrUnexpectedNewline},
+
{"unexpected separator", `
libzstd.so.1 = /usr/lib/libzstd.so.1 (0x7ff71bfd2000)
`, ldd.ErrUnexpectedSeparator},
+
{"path not absolute", `
libzstd.so.1 => usr/lib/libzstd.so.1 (0x7ff71bfd2000)
-`, ldd.ErrPathNotAbsolute},
+`, &check.AbsoluteError{Pathname: "usr/lib/libzstd.so.1"}},
+
{"unexpected segments", `
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", `
libzstd.so.1 => /usr/lib/libzstd.so.1 7ff71bfd2000
`, ldd.ErrBadLocationFormat},
@@ -49,6 +55,7 @@ func TestParse(t *testing.T) {
testCases := []struct {
file, out string
want []*ldd.Entry
+ paths []*check.Absolute
}{
{"musl /bin/kmod", `
/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)
libz.so.1 => /lib/libz.so.1 (0x7ff71bf80000)
libcrypto.so.3 => /lib/libcrypto.so.3 (0x7ff71ba00000)
-libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7ff71c0a4000)`,
- []*ldd.Entry{
- {"/lib/ld-musl-x86_64.so.1", "", 0x7ff71c0a4000},
- {"libzstd.so.1", "/usr/lib/libzstd.so.1", 0x7ff71bfd2000},
- {"liblzma.so.5", "/usr/lib/liblzma.so.5", 0x7ff71bf9a000},
- {"libz.so.1", "/lib/libz.so.1", 0x7ff71bf80000},
- {"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{
+ {"/lib/ld-musl-x86_64.so.1", nil, 0x7ff71c0a4000},
+ {"libzstd.so.1", check.MustAbs("/usr/lib/libzstd.so.1"), 0x7ff71bfd2000},
+ {"liblzma.so.5", check.MustAbs("/usr/lib/liblzma.so.5"), 0x7ff71bf9a000},
+ {"libz.so.1", check.MustAbs("/lib/libz.so.1"), 0x7ff71bf80000},
+ {"libcrypto.so.3", check.MustAbs("/lib/libcrypto.so.3"), 0x7ff71ba00000},
+ {"libc.musl-x86_64.so.1", check.MustAbs("/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", `
linux-vdso.so.1 (0x00007ffed65be000)
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)
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)
- /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{
- {"linux-vdso.so.1", "", 0x00007ffed65be000},
- {"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},
- {"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},
- {"/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{
+ {"linux-vdso.so.1", nil, 0x00007ffed65be000},
+ {"libzstd.so.1", check.MustAbs("/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},
+ {"libc.so.6", check.MustAbs("/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},
+ {"/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},
+ }, []*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", `
linux-vdso.so.1 (0x00007725f5772000)
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)
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)
- libblkid.so.1 => /usr/lib/libblkid.so.1 (0x00007725f5032000)`,
- []*ldd.Entry{
- {"linux-vdso.so.1", "", 0x00007725f5772000},
- {"libglib-2.0.so.0", "/usr/lib/libglib-2.0.so.0", 0x00007725f55d5000},
- {"libgio-2.0.so.0", "/usr/lib/libgio-2.0.so.0", 0x00007725f5406000},
- {"libgobject-2.0.so.0", "/usr/lib/libgobject-2.0.so.0", 0x00007725f53a6000},
- {"libgcc_s.so.1", "/usr/lib/libgcc_s.so.1", 0x00007725f5378000},
- {"libc.so.6", "/usr/lib/libc.so.6", 0x00007725f5187000},
- {"libpcre2-8.so.0", "/usr/lib/libpcre2-8.so.0", 0x00007725f50e8000},
- {"libgmodule-2.0.so.0", "/usr/lib/libgmodule-2.0.so.0", 0x00007725f50df000},
- {"libz.so.1", "/usr/lib/libz.so.1", 0x00007725f50c6000},
- {"libmount.so.1", "/usr/lib/libmount.so.1", 0x00007725f5076000},
- {"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},
- {"libblkid.so.1", "/usr/lib/libblkid.so.1", 0x00007725f5032000},
- }},
+ libblkid.so.1 => /usr/lib/libblkid.so.1 (0x00007725f5032000)`, []*ldd.Entry{
+ {"linux-vdso.so.1", nil, 0x00007725f5772000},
+ {"libglib-2.0.so.0", check.MustAbs("/usr/lib/libglib-2.0.so.0"), 0x00007725f55d5000},
+ {"libgio-2.0.so.0", check.MustAbs("/usr/lib/libgio-2.0.so.0"), 0x00007725f5406000},
+ {"libgobject-2.0.so.0", check.MustAbs("/usr/lib/libgobject-2.0.so.0"), 0x00007725f53a6000},
+ {"libgcc_s.so.1", check.MustAbs("/usr/lib/libgcc_s.so.1"), 0x00007725f5378000},
+ {"libc.so.6", check.MustAbs("/usr/lib/libc.so.6"), 0x00007725f5187000},
+ {"libpcre2-8.so.0", check.MustAbs("/usr/lib/libpcre2-8.so.0"), 0x00007725f50e8000},
+ {"libgmodule-2.0.so.0", check.MustAbs("/usr/lib/libgmodule-2.0.so.0"), 0x00007725f50df000},
+ {"libz.so.1", check.MustAbs("/usr/lib/libz.so.1"), 0x00007725f50c6000},
+ {"libmount.so.1", check.MustAbs("/usr/lib/libmount.so.1"), 0x00007725f5076000},
+ {"libffi.so.8", check.MustAbs("/usr/lib/libffi.so.8"), 0x00007725f506b000},
+ {"/lib64/ld-linux-x86-64.so.2", check.MustAbs("/usr/lib64/ld-linux-x86-64.so.2"), 0x00007725f5774000},
+ {"libblkid.so.1", check.MustAbs("/usr/lib/libblkid.so.1"), 0x00007725f5032000},
+ }, []*check.Absolute{
+ check.MustAbs("/lib64"),
+ check.MustAbs("/usr/lib"),
+ check.MustAbs("/usr/lib64"),
+ }},
}
for _, tc := range testCases {
t.Run(tc.file, func(t *testing.T) {
t.Parallel()
+
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) {
- 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
+ }
+}
diff --git a/ldd/path.go b/ldd/path.go
deleted file mode 100644
index c8290c34..00000000
--- a/ldd/path.go
+++ /dev/null
@@ -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)
-}