From 41ac2be9658b49c68cb793f3a6f0c5932d82e1c2 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sun, 10 Aug 2025 03:10:13 +0900 Subject: [PATCH] container/absolute: wrap safe stdlib functions These functions do not change the absoluteness of a pathname. Signed-off-by: Ophestra --- container/absolute.go | 30 +++++++++++++++++++++++----- container/absolute_test.go | 40 ++++++++++++++++++++++++++++++++++---- ldd/exec.go | 2 +- system/dbus/proc.go | 4 ++-- 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/container/absolute.go b/container/absolute.go index cafb180..8f1fa23 100644 --- a/container/absolute.go +++ b/container/absolute.go @@ -5,10 +5,12 @@ import ( "errors" "fmt" "path" + "slices" + "strings" "syscall" ) -// AbsoluteError is returned by [NewAbsolute] and holds the invalid pathname. +// AbsoluteError is returned by [NewAbs] and holds the invalid pathname. type AbsoluteError struct { Pathname string } @@ -37,23 +39,31 @@ func (a *Absolute) String() string { return a.pathname } -// NewAbsolute checks pathname and returns a new [Absolute] if pathname is absolute. -func NewAbsolute(pathname string) (*Absolute, error) { +// NewAbs checks pathname and returns a new [Absolute] if pathname is absolute. +func NewAbs(pathname string) (*Absolute, error) { if !isAbs(pathname) { return nil, &AbsoluteError{pathname} } return &Absolute{pathname}, nil } -// MustAbs calls [NewAbsolute] and panics on error. +// MustAbs calls [NewAbs] and panics on error. func MustAbs(pathname string) *Absolute { - if a, err := NewAbsolute(pathname); err != nil { + if a, err := NewAbs(pathname); err != nil { panic(err.Error()) } else { return a } } +// Append calls [path.Join] with [Absolute] as the first element. +func (a *Absolute) Append(elem ...string) *Absolute { + return &Absolute{path.Join(append([]string{a.String()}, elem...)...)} +} + +// Dir calls [path.Dir] with [Absolute] as its argument. +func (a *Absolute) Dir() *Absolute { return &Absolute{path.Dir(a.String())} } + func (a *Absolute) GobEncode() ([]byte, error) { return []byte(a.String()), nil } func (a *Absolute) GobDecode(data []byte) error { pathname := string(data) @@ -76,3 +86,13 @@ func (a *Absolute) UnmarshalJSON(data []byte) error { a.pathname = pathname return nil } + +// SortAbs calls [slices.SortFunc] for a slice of [Absolute]. +func SortAbs(x []*Absolute) { + slices.SortFunc(x, func(a, b *Absolute) int { return strings.Compare(a.String(), b.String()) }) +} + +// CompactAbs calls [slices.CompactFunc] for a slice of [Absolute]. +func CompactAbs(s []*Absolute) []*Absolute { + return slices.CompactFunc(s, func(a *Absolute, b *Absolute) bool { return a.String() == b.String() }) +} diff --git a/container/absolute_test.go b/container/absolute_test.go index 2889d82..014ace8 100644 --- a/container/absolute_test.go +++ b/container/absolute_test.go @@ -39,7 +39,7 @@ func TestAbsoluteError(t *testing.T) { }) } -func TestNewAbsolute(t *testing.T) { +func TestNewAbs(t *testing.T) { testCases := []struct { name string @@ -54,12 +54,12 @@ func TestNewAbsolute(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - got, err := NewAbsolute(tc.pathname) + got, err := NewAbs(tc.pathname) if !reflect.DeepEqual(got, tc.want) { - t.Errorf("NewAbsolute: %#v, want %#v", got, tc.want) + t.Errorf("NewAbs: %#v, want %#v", got, tc.want) } if !errors.Is(err, tc.wantErr) { - t.Errorf("NewAbsolute: error = %v, want %v", err, tc.wantErr) + t.Errorf("NewAbs: error = %v, want %v", err, tc.wantErr) } }) } @@ -282,3 +282,35 @@ func TestCodecAbsolute(t *testing.T) { } }) } + +func TestAbsoluteWrap(t *testing.T) { + t.Run("join", func(t *testing.T) { + want := "/etc/nix/nix.conf" + if got := MustAbs("/etc").Append("nix", "nix.conf"); got.String() != want { + t.Errorf("Append: %q, want %q", got, want) + } + }) + + t.Run("dir", func(t *testing.T) { + want := "/" + if got := MustAbs("/etc").Dir(); got.String() != want { + t.Errorf("Dir: %q, want %q", got, want) + } + }) + + t.Run("sort", func(t *testing.T) { + want := []*Absolute{MustAbs("/etc"), MustAbs("/proc"), MustAbs("/sys")} + got := []*Absolute{MustAbs("/proc"), MustAbs("/sys"), MustAbs("/etc")} + SortAbs(got) + if !reflect.DeepEqual(got, want) { + t.Errorf("SortAbs: %#v, want %#v", got, want) + } + }) + + t.Run("compact", func(t *testing.T) { + want := []*Absolute{MustAbs("/etc"), MustAbs("/proc"), MustAbs("/sys")} + if got := CompactAbs([]*Absolute{MustAbs("/etc"), MustAbs("/proc"), MustAbs("/proc"), MustAbs("/sys")}); !reflect.DeepEqual(got, want) { + t.Errorf("CompactAbs: %#v, want %#v", got, want) + } + }) +} diff --git a/ldd/exec.go b/ldd/exec.go index ac311fa..cb0fec7 100644 --- a/ldd/exec.go +++ b/ldd/exec.go @@ -29,7 +29,7 @@ func Exec(ctx context.Context, p string) ([]*Entry, error) { var toolPath *container.Absolute if s, err := exec.LookPath(lddName); err != nil { return nil, err - } else if toolPath, err = container.NewAbsolute(s); err != nil { + } else if toolPath, err = container.NewAbs(s); err != nil { return nil, err } diff --git a/system/dbus/proc.go b/system/dbus/proc.go index ee29809..d6f9890 100644 --- a/system/dbus/proc.go +++ b/system/dbus/proc.go @@ -43,10 +43,10 @@ func (p *Proxy) Start() error { }, nil) } else { var toolPath *container.Absolute - if a, err := container.NewAbsolute(p.name); err != nil { + if a, err := container.NewAbs(p.name); err != nil { if p.name, err = exec.LookPath(p.name); err != nil { return err - } else if toolPath, err = container.NewAbsolute(p.name); err != nil { + } else if toolPath, err = container.NewAbs(p.name); err != nil { return err } } else {