diff --git a/dirs/dirs.go b/dirs/dirs.go
index f1d441ee33f..7081f86a05c 100644
--- a/dirs/dirs.go
+++ b/dirs/dirs.go
@@ -48,6 +48,7 @@ var (
snapDataHomeGlob []string
SnapDownloadCacheDir string
SnapAppArmorDir string
+ SnapLdconfigDir string
SnapSeccompBase string
SnapSeccompDir string
SnapMountPolicyDir string
@@ -465,6 +466,7 @@ func SetRootDir(rootdir string) {
SnapDataDir = filepath.Join(rootdir, "/var/snap")
SnapAppArmorDir = filepath.Join(rootdir, snappyDir, "apparmor", "profiles")
+ SnapLdconfigDir = filepath.Join(rootdir, "/etc/ld.so.conf.d")
SnapDownloadCacheDir = filepath.Join(rootdir, snappyDir, "cache")
SnapSeccompBase = filepath.Join(rootdir, snappyDir, "seccomp")
SnapSeccompDir = filepath.Join(SnapSeccompBase, "bpf")
diff --git a/interfaces/backends/backends.go b/interfaces/backends/backends.go
index b65300ad519..7fe0402c03c 100644
--- a/interfaces/backends/backends.go
+++ b/interfaces/backends/backends.go
@@ -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"
@@ -45,6 +46,7 @@ func All() []interfaces.SecurityBackend {
&mount.Backend{},
&kmod.Backend{},
&polkit.Backend{},
+ &ldconfig.Backend{},
}
// TODO use something like:
diff --git a/interfaces/core.go b/interfaces/core.go
index c915e6c55f1..65b9111b0ac 100644
--- a/interfaces/core.go
+++ b/interfaces/core.go
@@ -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
diff --git a/interfaces/ifacetest/testiface.go b/interfaces/ifacetest/testiface.go
index 2bbcd06e98d..bb65b6f9225 100644
--- a/interfaces/ifacetest/testiface.go
+++ b/interfaces/ifacetest/testiface.go
@@ -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"
@@ -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
@@ -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
+}
+
+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
+}
+
+func (t *TestInterface) LdconfigPermanentPlug(spec *ldconfig.Specification, plug *snap.PlugInfo) error {
+ if t.LdconfigPermanentPlugCallback != nil {
+ return t.LdconfigPermanentPlugCallback(spec, plug)
+ }
+ return nil
+}
+
+func (t *TestInterface) LdconfigPermanentSlot(spec *ldconfig.Specification, slot *snap.SlotInfo) error {
+ if t.LdconfigPermanentSlotCallback != nil {
+ return t.LdconfigPermanentSlotCallback(spec, slot)
+ }
+ return nil
+}
+
// Support for interacting with the dbus backend.
func (t *TestInterface) DBusConnectedPlug(spec *dbus.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
diff --git a/interfaces/ldconfig/backend.go b/interfaces/ldconfig/backend.go
new file mode 100644
index 00000000000..b16c22c3347
--- /dev/null
+++ b/interfaces/ldconfig/backend.go
@@ -0,0 +1,121 @@
+// -*- 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 .
+ *
+ */
+
+package ldconfig
+
+import (
+ "fmt"
+ "os"
+ "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
+}
+
+// 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 {
+ snapName := appSet.InstanceName()
+ // 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",
+ snapName, err)
+ }
+
+ 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 {
+ // TODO this will not be called for the rootfs case where the system
+ // has an implicit plug, but needs to be revisited for snaps.
+ return nil
+}
+
+// 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 {
+ ldConfigDir := dirs.SnapLdconfigDir
+ if err := os.MkdirAll(ldConfigDir, 0755); err != nil {
+ return fmt.Errorf("cannot create directory for ldconfig files %q: %s", ldConfigDir, err)
+ }
+
+ // 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.
+
+ allContent := map[string]osutil.FileState{}
+ for key, dirs := range spec.libDirs {
+ content := "# This file is automatically generated by snapd\n"
+ content += strings.Join(dirs, "\n")
+ content += "\n"
+
+ // File name is snap...conf
+ ldconfigPath := fmt.Sprintf("snap.%s.conf", key)
+ allContent[ldconfigPath] = &osutil.MemoryFileState{
+ Content: []byte(content),
+ Mode: 0644,
+ }
+ }
+ _, _, err := osutil.EnsureDirStateGlobs(ldConfigDir, []string{"snap.*.conf"}, allContent)
+ if err != nil {
+ return err
+ }
+
+ // Re-create cache
+ out, stderr, err := osutil.RunSplitOutput("ldconfig")
+ if err != nil {
+ return osutil.OutputErrCombine(out, stderr, err)
+ }
+
+ return nil
+}
diff --git a/interfaces/ldconfig/backend_test.go b/interfaces/ldconfig/backend_test.go
new file mode 100644
index 00000000000..db05ff0d007
--- /dev/null
+++ b/interfaces/ldconfig/backend_test.go
@@ -0,0 +1,123 @@
+// -*- 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 .
+ *
+ */
+
+package ldconfig_test
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/ifacetest"
+ "github.com/snapcore/snapd/interfaces/ldconfig"
+ "github.com/snapcore/snapd/snap"
+ "github.com/snapcore/snapd/testutil"
+)
+
+func Test(t *testing.T) {
+ TestingT(t)
+}
+
+type backendSuite struct {
+ ifacetest.BackendSuite
+ ldconfigCmd *testutil.MockCmd
+}
+
+var _ = Suite(&backendSuite{})
+
+func (s *backendSuite) SetUpTest(c *C) {
+ s.Backend = &ldconfig.Backend{}
+ s.BackendSuite.SetUpTest(c)
+ c.Assert(s.Repo.AddBackend(s.Backend), IsNil)
+ s.ldconfigCmd = testutil.MockCommand(c, "ldconfig", "")
+}
+
+func (s *backendSuite) TearDownTest(c *C) {
+ s.ldconfigCmd.Restore()
+ s.BackendSuite.TearDownTest(c)
+}
+
+func (s *backendSuite) TestName(c *C) {
+ c.Check(s.Backend.Name(), Equals, interfaces.SecurityLdconfig)
+}
+
+func checkLdconfigFile(c *C, snapName, slot string, libDirs []string) {
+ path := filepath.Join(dirs.GlobalRootDir, "etc", "ld.so.conf.d",
+ fmt.Sprintf("snap.%s.%s.conf", snapName, slot))
+ content := "# This file is automatically generated by snapd\n"
+ content += strings.Join(libDirs, "\n")
+ content += "\n"
+ c.Assert(path, testutil.FileEquals, content)
+}
+
+func (s *backendSuite) TestInstallingCreatesLdconf(c *C) {
+ s.Iface.LdconfigPermanentSlotCallback = func(spec *ldconfig.Specification,
+ slot *snap.SlotInfo) error {
+ spec.AddLibDirs("snap1", "slot1", []string{"/dir1/lib1", "/dir1/lib11"})
+ spec.AddLibDirs("snap1", "slot2", []string{"/dir1/lib2", "/dir1/lib22"})
+ spec.AddLibDirs("snap2", "slot1", []string{"/dir2/lib1"})
+ return nil
+ }
+ snapInfo := s.InstallSnap(c, interfaces.ConfinementOptions{}, "", ifacetest.SambaYamlV1, 0)
+
+ confDir := filepath.Join(dirs.GlobalRootDir, "etc", "ld.so.conf.d")
+ libcConfPath := filepath.Join(confDir, "libc.conf")
+ c.Assert(os.WriteFile(libcConfPath, []byte{}, 0644), IsNil)
+
+ checkLdconfigFile(c, "snap1", "slot1", []string{"/dir1/lib1", "/dir1/lib11"})
+ checkLdconfigFile(c, "snap1", "slot2", []string{"/dir1/lib2", "/dir1/lib22"})
+ checkLdconfigFile(c, "snap2", "slot1", []string{"/dir2/lib1"})
+
+ c.Assert(s.ldconfigCmd.Calls(), DeepEquals, [][]string{
+ {"ldconfig"},
+ })
+ s.ldconfigCmd.ForgetCalls()
+
+ // When refreshing a slot is removed
+ s.Iface.LdconfigPermanentSlotCallback = func(spec *ldconfig.Specification,
+ slot *snap.SlotInfo) error {
+ spec.AddLibDirs("snap1", "slot1", []string{"/dir1/lib1", "/dir1/lib11"})
+ spec.AddLibDirs("snap2", "slot1", []string{"/dir2/lib1"})
+ return nil
+ }
+ s.UpdateSnap(c, snapInfo, interfaces.ConfinementOptions{}, ifacetest.SambaYamlV1, 1)
+
+ checkLdconfigFile(c, "snap1", "slot1", []string{"/dir1/lib1", "/dir1/lib11"})
+ c.Check(filepath.Join(confDir, "snap.snap1.slot2.conf"), testutil.FileAbsent)
+ checkLdconfigFile(c, "snap2", "slot1", []string{"/dir2/lib1"})
+
+ c.Assert(s.ldconfigCmd.Calls(), DeepEquals, [][]string{
+ {"ldconfig"},
+ })
+
+ // libc config has not been touched
+ c.Check(libcConfPath, testutil.FilePresent)
+
+ s.RemoveSnap(c, snapInfo)
+}
+
+func (s *backendSuite) TestSandboxFeatures(c *C) {
+ c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"mediated-ldconfig"})
+}
diff --git a/interfaces/ldconfig/spec.go b/interfaces/ldconfig/spec.go
new file mode 100644
index 00000000000..3497ce02b51
--- /dev/null
+++ b/interfaces/ldconfig/spec.go
@@ -0,0 +1,99 @@
+// -*- 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 .
+ *
+ */
+
+package ldconfig
+
+import (
+ "fmt"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/snap"
+)
+
+// Specification assists in collecting library directories associated with an
+// interface.
+//
+// Unlike the Backend itself (which is stateless and non-persistent) this type
+// holds internal state that is used by the ldconfig backend during the
+// interface setup process.
+type Specification struct {
+ // libDirs is the list of directories with libraries coming from
+ // different slots. The key has the form ".".
+ libDirs map[string][]string
+}
+
+// Methods called by interfaces
+
+// AddLibDirs add dirs with libraries to the specification.
+func (spec *Specification) AddLibDirs(snapName, slotName string, dirs []string) {
+ if spec.libDirs == nil {
+ spec.libDirs = make(map[string][]string)
+ }
+ spec.libDirs[fmt.Sprintf("%s.%s", snapName, slotName)] = dirs
+}
+
+func (spec *Specification) LibDirs() map[string][]string {
+ return spec.libDirs
+}
+
+// Implementation of methods required by interfaces.Specification
+
+// AddConnectedPlug records ldconfig-specific side-effects of having a connected plug.
+func (spec *Specification) AddConnectedPlug(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
+ type definer interface {
+ LdconfigConnectedPlug(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
+ }
+ if iface, ok := iface.(definer); ok {
+ return iface.LdconfigConnectedPlug(spec, plug, slot)
+ }
+ return nil
+}
+
+// AddConnectedSlot records ldconfig-specific side-effects of having a connected slot.
+func (spec *Specification) AddConnectedSlot(iface interfaces.Interface, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
+ type definer interface {
+ LdconfigConnectedSlot(spec *Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error
+ }
+ if iface, ok := iface.(definer); ok {
+ return iface.LdconfigConnectedSlot(spec, plug, slot)
+ }
+ return nil
+}
+
+// AddPermanentPlug records ldconfig-specific side-effects of having a plug.
+func (spec *Specification) AddPermanentPlug(iface interfaces.Interface, plug *snap.PlugInfo) error {
+ type definer interface {
+ LdconfigPermanentPlug(spec *Specification, plug *snap.PlugInfo) error
+ }
+ if iface, ok := iface.(definer); ok {
+ return iface.LdconfigPermanentPlug(spec, plug)
+ }
+ return nil
+}
+
+// AddPermanentSlot records ldconfig-specific side-effects of having a slot.
+func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *snap.SlotInfo) error {
+ type definer interface {
+ LdconfigPermanentSlot(spec *Specification, slot *snap.SlotInfo) error
+ }
+ if iface, ok := iface.(definer); ok {
+ return iface.LdconfigPermanentSlot(spec, slot)
+ }
+ return nil
+}
diff --git a/interfaces/ldconfig/spec_test.go b/interfaces/ldconfig/spec_test.go
new file mode 100644
index 00000000000..a0cbd3769a7
--- /dev/null
+++ b/interfaces/ldconfig/spec_test.go
@@ -0,0 +1,112 @@
+// -*- 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 .
+ *
+ */
+
+package ldconfig_test
+
+import (
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/interfaces"
+ "github.com/snapcore/snapd/interfaces/ifacetest"
+ "github.com/snapcore/snapd/interfaces/ldconfig"
+ "github.com/snapcore/snapd/snap"
+)
+
+type specSuite struct {
+ spec *ldconfig.Specification
+ iface1 *ifacetest.TestInterface
+ plugInfo *snap.PlugInfo
+ plug *interfaces.ConnectedPlug
+ slotInfo *snap.SlotInfo
+ slot *interfaces.ConnectedSlot
+}
+
+var _ = Suite(&specSuite{
+ iface1: &ifacetest.TestInterface{
+ InterfaceName: "test",
+ LdconfigConnectedPlugCallback: func(spec *ldconfig.Specification,
+ plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
+ spec.AddLibDirs("snap1", "slot1", []string{"/dir1/lib1"})
+ return nil
+ },
+ LdconfigConnectedSlotCallback: func(spec *ldconfig.Specification,
+ plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
+ spec.AddLibDirs("snap1", "slot2", []string{"/dir1/lib2"})
+ return nil
+ },
+ LdconfigPermanentPlugCallback: func(spec *ldconfig.Specification,
+ plug *snap.PlugInfo) error {
+ spec.AddLibDirs("snap2", "slot1", []string{"/dir2/lib3"})
+ return nil
+ },
+ LdconfigPermanentSlotCallback: func(spec *ldconfig.Specification,
+ slot *snap.SlotInfo) error {
+ spec.AddLibDirs("snap2", "slot2", []string{"/dir2/lib4"})
+ return nil
+ },
+ },
+})
+
+func (s *specSuite) SetUpTest(c *C) {
+ s.spec = &ldconfig.Specification{}
+ const plugYaml = `name: snap
+version: 1
+apps:
+ app:
+ plugs: [name]
+`
+ s.plug, s.plugInfo = ifacetest.MockConnectedPlug(c, plugYaml, nil, "name")
+
+ const slotYaml = `name: snap
+version: 1
+slots:
+ name:
+ interface: test
+`
+ s.slot, s.slotInfo = ifacetest.MockConnectedSlot(c, slotYaml, nil, "name")
+}
+
+// AddLibDirs is not broken
+func (s *specSuite) TestSmoke(c *C) {
+ dirs1 := []string{"/dir1/lib1", "/dir1/lib2"}
+ dirs2 := []string{"/dir2/lib1", "/dir2/lib2"}
+ s.spec.AddLibDirs("snap1", "slot1", dirs1)
+ s.spec.AddLibDirs("snap2", "slot2", dirs2)
+ // no duplication of entries
+ s.spec.AddLibDirs("snap2", "slot2", dirs2)
+ c.Assert(s.spec.LibDirs(), DeepEquals, map[string][]string{
+ "snap1.slot1": {"/dir1/lib1", "/dir1/lib2"},
+ "snap2.slot2": {"/dir2/lib1", "/dir2/lib2"},
+ })
+}
+
+// The ldconfig.Specification can be used through the interfaces.Specification interface
+func (s *specSuite) TestSpecificationIface(c *C) {
+ var r interfaces.Specification = s.spec
+ c.Assert(r.AddConnectedPlug(s.iface1, s.plug, s.slot), IsNil)
+ c.Assert(r.AddConnectedSlot(s.iface1, s.plug, s.slot), IsNil)
+ c.Assert(r.AddPermanentPlug(s.iface1, s.plugInfo), IsNil)
+ c.Assert(r.AddPermanentSlot(s.iface1, s.slotInfo), IsNil)
+ c.Assert(s.spec.LibDirs(), DeepEquals, map[string][]string{
+ "snap1.slot1": {"/dir1/lib1"},
+ "snap1.slot2": {"/dir1/lib2"},
+ "snap2.slot1": {"/dir2/lib3"},
+ "snap2.slot2": {"/dir2/lib4"},
+ })
+}