Skip to content

Commit

Permalink
interfaces: add ldconfig backend
Browse files Browse the repository at this point in the history
This backend will expose libraries coming from snaps to either the
rootfs or to other snaps (currently supporting only the former).
  • Loading branch information
alfonsosanchezbeato committed Jan 31, 2025
1 parent 8ca67f2 commit e67495c
Show file tree
Hide file tree
Showing 7 changed files with 715 additions and 0 deletions.
2 changes: 2 additions & 0 deletions interfaces/backends/backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/dbus"
"github.com/snapcore/snapd/interfaces/kmod"
"github.com/snapcore/snapd/interfaces/ldconfig"
"github.com/snapcore/snapd/interfaces/mount"
"github.com/snapcore/snapd/interfaces/polkit"
"github.com/snapcore/snapd/interfaces/seccomp"
Expand Down Expand Up @@ -74,6 +75,7 @@ func All() []interfaces.SecurityBackend {
all = append(all, &mount.Backend{},
&kmod.Backend{},
&polkit.Backend{},
&ldconfig.Backend{},
)

// TODO use something like:
Expand Down
2 changes: 2 additions & 0 deletions interfaces/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ const (
SecuritySystemd SecuritySystem = "systemd"
// SecurityPolkit identifies the polkit security system.
SecurityPolkit SecuritySystem = "polkit"
// SecurityLdconfig identifies the ldconfig security system.
SecurityLdconfig SecuritySystem = "ldconfig"
)

var isValidBusName = regexp.MustCompile(`^[a-zA-Z_-][a-zA-Z0-9_-]*(\.[a-zA-Z_-][a-zA-Z0-9_-]*)+$`).MatchString
Expand Down
38 changes: 38 additions & 0 deletions interfaces/ifacetest/testiface.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/snapcore/snapd/interfaces/dbus"
"github.com/snapcore/snapd/interfaces/hotplug"
"github.com/snapcore/snapd/interfaces/kmod"
"github.com/snapcore/snapd/interfaces/ldconfig"
"github.com/snapcore/snapd/interfaces/mount"
"github.com/snapcore/snapd/interfaces/polkit"
"github.com/snapcore/snapd/interfaces/seccomp"
Expand Down Expand Up @@ -84,6 +85,13 @@ type TestInterface struct {
KModPermanentPlugCallback func(spec *kmod.Specification, plug *snap.PlugInfo) error
KModPermanentSlotCallback func(spec *kmod.Specification, slot *snap.SlotInfo) error

// Support for interacting with the ldconfig backend.

LdconfigConnectedPlugCallback func(spec *ldconfig.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
LdconfigConnectedSlotCallback func(spec *ldconfig.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
LdconfigPermanentPlugCallback func(spec *ldconfig.Specification, plug *snap.PlugInfo) error
LdconfigPermanentSlotCallback func(spec *ldconfig.Specification, slot *snap.SlotInfo) error

// Support for interacting with the seccomp backend.

SecCompConnectedPlugCallback func(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
Expand Down Expand Up @@ -360,6 +368,36 @@ func (t *TestInterface) KModPermanentSlot(spec *kmod.Specification, slot *snap.S
return nil
}

// Support for interacting with the ldconfig backend.

func (t *TestInterface) LdconfigConnectedPlug(spec *ldconfig.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
if t.LdconfigConnectedPlugCallback != nil {
return t.LdconfigConnectedPlugCallback(spec, plug, slot)
}
return nil

Check warning on line 377 in interfaces/ifacetest/testiface.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ifacetest/testiface.go#L373-L377

Added lines #L373 - L377 were not covered by tests
}

func (t *TestInterface) LdconfigConnectedSlot(spec *ldconfig.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
if t.LdconfigConnectedSlotCallback != nil {
return t.LdconfigConnectedSlotCallback(spec, plug, slot)
}
return nil

Check warning on line 384 in interfaces/ifacetest/testiface.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ifacetest/testiface.go#L380-L384

Added lines #L380 - L384 were not covered by tests
}

func (t *TestInterface) LdconfigPermanentPlug(spec *ldconfig.Specification, plug *snap.PlugInfo) error {
if t.LdconfigPermanentPlugCallback != nil {
return t.LdconfigPermanentPlugCallback(spec, plug)
}
return nil

Check warning on line 391 in interfaces/ifacetest/testiface.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ifacetest/testiface.go#L387-L391

Added lines #L387 - L391 were not covered by tests
}

func (t *TestInterface) LdconfigPermanentSlot(spec *ldconfig.Specification, slot *snap.SlotInfo) error {
if t.LdconfigPermanentSlotCallback != nil {
return t.LdconfigPermanentSlotCallback(spec, slot)
}
return nil

Check warning on line 398 in interfaces/ifacetest/testiface.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ifacetest/testiface.go#L394-L398

Added lines #L394 - L398 were not covered by tests
}

// Support for interacting with the dbus backend.

func (t *TestInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
Expand Down
163 changes: 163 additions & 0 deletions interfaces/ldconfig/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

package ldconfig

import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/timings"
)

// Backend is responsible for maintaining ldconfig cache.
type Backend struct{}

var _ = interfaces.SecurityBackend(&Backend{})

// Initialize does nothing for this backend.
func (b *Backend) Initialize(opts *interfaces.SecurityBackendOptions) error {
return nil

Check warning on line 42 in interfaces/ldconfig/backend.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ldconfig/backend.go#L41-L42

Added lines #L41 - L42 were not covered by tests
}

// Name returns the name of the backend.
func (b *Backend) Name() interfaces.SecuritySystem {
return "ldconfig"
}

// Setup will make the ldconfig backend generate the needed
// configuration files and re-create the ld cache.
//
// If the method fails it should be re-tried (with a sensible strategy) by the caller.
func (b *Backend) Setup(appSet *interfaces.SnapAppSet, opts interfaces.ConfinementOptions, repo *interfaces.Repository, tm timings.Measurer) error {
// Get the snippets that apply to this snap
spec, err := repo.SnapSpecification(b.Name(), appSet, opts)
if err != nil {
return fmt.Errorf("cannot obtain ldconfig specification for snap %q: %s",
appSet.InstanceName(), err)
}

Check warning on line 60 in interfaces/ldconfig/backend.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ldconfig/backend.go#L58-L60

Added lines #L58 - L60 were not covered by tests

return b.setupLdconfigCache(spec.(*Specification))
}

// Remove removes modules ldconfig files specific to a given snap.
// This method should be called after removing a snap.
//
// If the method fails it should be re-tried (with a sensible strategy) by the caller.
func (b *Backend) Remove(snapName string) error {
// If called for the system (snapd) snap, that is possible only in a
// classic scenario when all other snaps in the system must have been
// removed already to allow the removal of the snapd snap. In that
// case, /etc/ld.so.conf.d/snap.system.conf will have already been
// removed by a Setup call, so we do not need to do anything here.

// TODO but this needs to be revisited for when we start supporting
// ldconfig plugs in snaps.
return nil

Check warning on line 78 in interfaces/ldconfig/backend.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ldconfig/backend.go#L69-L78

Added lines #L69 - L78 were not covered by tests
}

// NewSpecification returns a new specification associated with this backend.
func (b *Backend) NewSpecification(*interfaces.SnapAppSet,
interfaces.ConfinementOptions) interfaces.Specification {
return &Specification{}
}

// SandboxFeatures returns the list of features supported by snapd for ldconfig.
func (b *Backend) SandboxFeatures() []string {
return []string{"mediated-ldconfig"}
}

func (b *Backend) setupLdconfigCache(spec *Specification) error {
// TODO this considers only the case when the libraries are exposed to
// the rootfs. For snaps, we will create files in
// /var/lib/snapd/ldconfig/ that will be used to generate a cache
// specific to each snap.

// We only need one file per plug (we are considering only the system
// plug atm), that will contain information for all connected slots -
// the specification is recreated with all the information even if we
// are refreshing only one of the snaps providing slots.

ldConfigDir := dirs.SnapLdconfigDir
ldconfigPath := filepath.Join(ldConfigDir, "snap.system.conf")
runLdconfig := false
if len(spec.libDirs) > 0 {
// Sort the map, we want the content of the config file to be
// deterministic to be able to detect changes and run ldconfig
// only when necessary.
type SnapSlotLibs struct {
snapSlot SnapSlot
dirs []string
}
sortedSnapSlots := make([]SnapSlotLibs, 0, len(spec.libDirs))
for key, dirs := range spec.libDirs {
sortedSnapSlots = append(sortedSnapSlots, SnapSlotLibs{key, dirs})
}
sort.Slice(sortedSnapSlots, func(i, j int) bool {
n1 := sortedSnapSlots[i].snapSlot.SnapName + "_" + sortedSnapSlots[i].snapSlot.SlotName
n2 := sortedSnapSlots[j].snapSlot.SnapName + "_" + sortedSnapSlots[j].snapSlot.SlotName
return n1 < n2
})
content := "## This file is automatically generated by snapd\n"
for _, ssLibs := range sortedSnapSlots {
content += fmt.Sprintf("\n# Directories from %s snap, %s slot\n",
ssLibs.snapSlot.SnapName, ssLibs.snapSlot.SlotName)
content += strings.Join(ssLibs.dirs, "\n")
content += "\n"
}

if err := os.MkdirAll(ldConfigDir, 0755); err != nil {
return fmt.Errorf("cannot create directory for ldconfig files %q: %s", ldConfigDir, err)
}

Check warning on line 133 in interfaces/ldconfig/backend.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ldconfig/backend.go#L132-L133

Added lines #L132 - L133 were not covered by tests
err := osutil.EnsureFileState(ldconfigPath, &osutil.MemoryFileState{
Content: []byte(content),
Mode: 0644,
})
if err != nil {
if err != osutil.ErrSameState {
return err
}

Check warning on line 141 in interfaces/ldconfig/backend.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ldconfig/backend.go#L140-L141

Added lines #L140 - L141 were not covered by tests
// No change in content, no need to run ldconfig
} else {
runLdconfig = true
}
} else if osutil.FileExists(ldconfigPath) {
// All lib dirs have been removed, remove the file and run ldconfig
if err := os.Remove(ldconfigPath); err != nil {
return err
}

Check warning on line 150 in interfaces/ldconfig/backend.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ldconfig/backend.go#L149-L150

Added lines #L149 - L150 were not covered by tests
runLdconfig = true
} // else: no lib dirs and no file, no change then, don't run ldconfig

// Re-create cache when needed
if runLdconfig {
out, stderr, err := osutil.RunSplitOutput("ldconfig")
if err != nil {
return osutil.OutputErrCombine(out, stderr, err)
}

Check warning on line 159 in interfaces/ldconfig/backend.go

View check run for this annotation

Codecov / codecov/patch

interfaces/ldconfig/backend.go#L158-L159

Added lines #L158 - L159 were not covered by tests
}

return nil
}
Loading

0 comments on commit e67495c

Please sign in to comment.