summaryrefslogtreecommitdiff
diff options
authorPawel Stolowski <stolowski@gmail.com>2018-05-16 13:05:29 +0200
committerPawel Stolowski <stolowski@gmail.com>2018-05-16 13:05:29 +0200
commit99e81c0fb6ce2a5ffec3404bf24b0f9d3cbfc3b6 (patch)
tree70edb0a874232a75b0c68a69b35f592a15e1c99f
parent5aa8a71b2931f94aba285833a0a3121b65618acd (diff)
parent761506d292271cb7c5c452adf21a505fa35e82ae (diff)
Merge branch 'master' into remove-stale-connectionsremove-stale-connections
-rw-r--r--asserts/ifacedecls.go17
-rw-r--r--asserts/ifacedecls_test.go87
-rw-r--r--asserts/snap_asserts_test.go81
-rw-r--r--client/client.go5
-rw-r--r--client/client_test.go8
-rw-r--r--cmd/snap-update-ns/main.go5
-rw-r--r--cmd/snap-update-ns/main_test.go9
-rw-r--r--cmd/snap/cmd_can_manage_refreshes.go2
-rw-r--r--cmd/snap/cmd_confinement.go4
-rw-r--r--cmd/snap/cmd_ensure_state_soon.go2
-rw-r--r--cmd/snap/cmd_get_base_declaration.go2
-rw-r--r--cmd/snap/cmd_sandbox_features.go88
-rw-r--r--cmd/snap/cmd_sandbox_features_test.go61
-rw-r--r--cmd/snap/cmd_snap_op.go4
-rw-r--r--cmd/snap/cmd_snap_op_test.go2
-rw-r--r--cmd/snap/main.go37
-rw-r--r--daemon/api.go38
-rw-r--r--daemon/api_test.go18
-rwxr-xr-xdata/systemd/snapd.core-fixup.sh8
-rw-r--r--interfaces/apparmor/backend.go13
-rw-r--r--interfaces/apparmor/backend_test.go7
-rw-r--r--interfaces/apparmor/export_test.go8
-rw-r--r--interfaces/apparmor/template.go4
-rw-r--r--interfaces/backend.go3
-rw-r--r--interfaces/builtin/desktop.go33
-rw-r--r--interfaces/builtin/desktop_test.go33
-rw-r--r--interfaces/connection.go49
-rw-r--r--interfaces/connection_test.go100
-rw-r--r--interfaces/core.go8
-rw-r--r--interfaces/core_test.go2
-rw-r--r--interfaces/dbus/backend.go5
-rw-r--r--interfaces/dbus/backend_test.go4
-rw-r--r--interfaces/ifacetest/backend.go9
-rw-r--r--interfaces/kmod/backend.go5
-rw-r--r--interfaces/kmod/backend_test.go4
-rw-r--r--interfaces/mount/backend.go14
-rw-r--r--interfaces/mount/backend_test.go13
-rw-r--r--interfaces/policy/helpers.go12
-rw-r--r--interfaces/policy/helpers_test.go50
-rw-r--r--interfaces/policy/policy.go62
-rw-r--r--interfaces/repo.go49
-rw-r--r--interfaces/repo_test.go100
-rw-r--r--interfaces/seccomp/backend.go18
-rw-r--r--interfaces/seccomp/backend_test.go7
-rw-r--r--interfaces/seccomp/export_test.go8
-rw-r--r--interfaces/systemd/backend.go5
-rw-r--r--interfaces/systemd/backend_test.go4
-rw-r--r--interfaces/udev/backend.go8
-rw-r--r--interfaces/udev/backend_test.go7
-rw-r--r--overlord/configstate/handler_test.go24
-rw-r--r--overlord/devicestate/devicestate_test.go2
-rw-r--r--overlord/hookstate/ctlcmd/get_test.go1
-rw-r--r--overlord/ifacestate/handlers.go2
-rw-r--r--overlord/ifacestate/ifacestate_test.go2
-rw-r--r--overlord/managers_test.go2
-rw-r--r--overlord/snapstate/handlers_prereq_test.go14
-rw-r--r--overlord/snapstate/snapstate.go26
-rw-r--r--overlord/snapstate/snapstate_test.go66
-rw-r--r--packaging/arch/PKGBUILD2
-rw-r--r--packaging/fedora/snapd.spec7
-rw-r--r--packaging/opensuse-42.2/snapd.changes5
-rw-r--r--packaging/opensuse-42.2/snapd.spec2
-rw-r--r--packaging/ubuntu-14.04/changelog8
-rw-r--r--packaging/ubuntu-14.04/control2
-rw-r--r--packaging/ubuntu-16.04/changelog8
-rw-r--r--packaging/ubuntu-16.04/control1
-rw-r--r--packaging/ubuntu-16.04/tests/integrationtests5
-rw-r--r--snap/info.go56
-rw-r--r--snap/info_test.go63
-rw-r--r--spread.yaml5
-rw-r--r--tests/lib/journalctl.sh28
-rwxr-xr-xtests/lib/prepare-restore.sh34
-rw-r--r--tests/lib/store.sh5
-rw-r--r--tests/main/catalog-update/task.yaml9
-rw-r--r--tests/main/debug-sandbox/task.yaml25
-rw-r--r--tests/main/enable-disable-units-gpio/task.yaml5
-rw-r--r--tests/main/install-cache/task.yaml7
-rw-r--r--tests/main/install-closed-channel/task.yaml9
-rw-r--r--tests/main/interfaces-cups-control/task.yaml5
-rw-r--r--tests/main/interfaces-desktop-document-portal/task.yaml60
-rw-r--r--tests/main/interfaces-many/task.yaml5
-rw-r--r--tests/main/interfaces-snapd-control-with-manage/task.yaml5
-rw-r--r--tests/main/lxd/task.yaml5
-rw-r--r--tests/main/refresh-delta-from-core/task.yaml5
-rw-r--r--tests/main/refresh-delta/task.yaml5
-rw-r--r--tests/main/refresh-undo/task.yaml4
-rw-r--r--tests/main/snap-confine-from-core/task.yaml5
-rw-r--r--tests/main/snap-core-fixup/task.yaml4
-rw-r--r--tests/main/snap-service-refresh-mode/task.yaml10
-rw-r--r--tests/main/snap-service-stop-mode-sigkill/task.yaml2
-rw-r--r--tests/main/snap-service-stop-mode/task.yaml14
-rw-r--r--tests/main/snap-userd-desktop-app-autostart/task.yaml20
-rw-r--r--tests/main/snapctl-services/task.yaml8
-rw-r--r--tests/main/snapd-notify/task.yaml5
-rw-r--r--tests/main/snapd-reexec/task.yaml7
-rw-r--r--tests/main/ubuntu-core-gadget-config-defaults/task.yaml14
-rwxr-xr-xtests/main/user-mounts/check-user-mount.sh16
-rw-r--r--tests/main/user-mounts/task.yaml38
-rw-r--r--tests/main/validate-container-failures/task.yaml5
-rw-r--r--tests/unit/spread-shellcheck/must1
-rw-r--r--testutil/lowlevel.go230
-rw-r--r--testutil/lowlevel_test.go211
102 files changed, 1715 insertions, 516 deletions
diff --git a/asserts/ifacedecls.go b/asserts/ifacedecls.go
index ce8f77c6fc..d9ceb353d2 100644
--- a/asserts/ifacedecls.go
+++ b/asserts/ifacedecls.go
@@ -150,6 +150,14 @@ func (matcher mapAttrMatcher) feature(flabel string) bool {
func (matcher mapAttrMatcher) match(apath string, v interface{}, ctx AttrMatchContext) error {
switch x := v.(type) {
+ case Attrer:
+ // we get Atter from root-level Check (apath is "")
+ for k, matcher1 := range matcher {
+ v, _ := x.Lookup(k)
+ if err := matchEntry("", k, matcher1, v, ctx); err != nil {
+ return err
+ }
+ }
case map[string]interface{}: // maps in attributes look like this
for k, matcher1 := range matcher {
if err := matchEntry(apath, k, matcher1, x[k], ctx); err != nil {
@@ -340,9 +348,14 @@ var (
NeverMatchAttributes = &AttributeConstraints{matcher: fixedAttrMatcher{errors.New("not allowed")}}
)
+// Attrer reflects part of the Attrer interface (see interfaces.Attrer).
+type Attrer interface {
+ Lookup(path string) (interface{}, bool)
+}
+
// Check checks whether attrs don't match the constraints.
-func (c *AttributeConstraints) Check(attrs map[string]interface{}, ctx AttrMatchContext) error {
- return c.matcher.match("", attrs, ctx)
+func (c *AttributeConstraints) Check(attrer Attrer, ctx AttrMatchContext) error {
+ return c.matcher.match("", attrer, ctx)
}
// OnClassicConstraint specifies a constraint based whether the system is classic and optional specific distros' sets.
diff --git a/asserts/ifacedecls_test.go b/asserts/ifacedecls_test.go
index 405630a746..5006ba2d05 100644
--- a/asserts/ifacedecls_test.go
+++ b/asserts/ifacedecls_test.go
@@ -41,7 +41,14 @@ type attrConstraintsSuite struct {
testutil.BaseTest
}
-func attrs(yml string) map[string]interface{} {
+type attrerObject map[string]interface{}
+
+func (o attrerObject) Lookup(path string) (interface{}, bool) {
+ v, ok := o[path]
+ return v, ok
+}
+
+func attrs(yml string) *attrerObject {
var attrs map[string]interface{}
err := yaml.Unmarshal([]byte(yml), &attrs)
if err != nil {
@@ -56,11 +63,17 @@ func attrs(yml string) map[string]interface{} {
if err != nil {
panic(err)
}
+
+ // NOTE: it's important to go through snap yaml here even though we're really interested in Attrs only,
+ // as InfoFromSnapYaml normalizes yaml values.
info, err := snap.InfoFromSnapYaml(snapYaml)
if err != nil {
panic(err)
}
- return info.Plugs["plug"].Attrs
+
+ var ao attrerObject
+ ao = info.Plugs["plug"].Attrs
+ return &ao
}
func (s *attrConstraintsSuite) SetUpTest(c *C) {
@@ -81,24 +94,27 @@ func (s *attrConstraintsSuite) TestSimple(c *C) {
cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
c.Assert(err, IsNil)
- err = cstrs.Check(map[string]interface{}{
+ plug := attrerObject(map[string]interface{}{
"foo": "FOO",
"bar": "BAR",
"baz": "BAZ",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, IsNil)
- err = cstrs.Check(map[string]interface{}{
+ plug = attrerObject(map[string]interface{}{
"foo": "FOO",
"bar": "BAZ",
"baz": "BAZ",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, ErrorMatches, `attribute "bar" value "BAZ" does not match \^\(BAR\)\$`)
- err = cstrs.Check(map[string]interface{}{
+ plug = attrerObject(map[string]interface{}{
"foo": "FOO",
"baz": "BAZ",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, ErrorMatches, `attribute "bar" has constraints but is unset`)
}
@@ -110,29 +126,34 @@ func (s *attrConstraintsSuite) TestSimpleAnchorsVsRegexpAlt(c *C) {
cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].(map[string]interface{}))
c.Assert(err, IsNil)
- err = cstrs.Check(map[string]interface{}{
+ plug := attrerObject(map[string]interface{}{
"bar": "BAR",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, IsNil)
- err = cstrs.Check(map[string]interface{}{
+ plug = attrerObject(map[string]interface{}{
"bar": "BARR",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, ErrorMatches, `attribute "bar" value "BARR" does not match \^\(BAR|BAZ\)\$`)
- err = cstrs.Check(map[string]interface{}{
+ plug = attrerObject(map[string]interface{}{
"bar": "BBAZ",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, ErrorMatches, `attribute "bar" value "BAZZ" does not match \^\(BAR|BAZ\)\$`)
- err = cstrs.Check(map[string]interface{}{
+ plug = attrerObject(map[string]interface{}{
"bar": "BABAZ",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, ErrorMatches, `attribute "bar" value "BABAZ" does not match \^\(BAR|BAZ\)\$`)
- err = cstrs.Check(map[string]interface{}{
+ plug = attrerObject(map[string]interface{}{
"bar": "BARAZ",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, ErrorMatches, `attribute "bar" value "BARAZ" does not match \^\(BAR|BAZ\)\$`)
}
@@ -199,25 +220,28 @@ func (s *attrConstraintsSuite) TestAlternative(c *C) {
cstrs, err := asserts.CompileAttributeConstraints(m["attrs"].([]interface{}))
c.Assert(err, IsNil)
- err = cstrs.Check(map[string]interface{}{
+ plug := attrerObject(map[string]interface{}{
"foo": "FOO",
"bar": "BAR",
"baz": "BAZ",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, IsNil)
- err = cstrs.Check(map[string]interface{}{
+ plug = attrerObject(map[string]interface{}{
"foo": "FOO",
"bar": "BAZ",
"baz": "BAZ",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, IsNil)
- err = cstrs.Check(map[string]interface{}{
+ plug = attrerObject(map[string]interface{}{
"foo": "FOO",
"bar": "BARR",
"baz": "BAR",
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, ErrorMatches, `no alternative matches: attribute "bar" value "BARR" does not match \^\(BAR\)\$`)
}
@@ -274,10 +298,11 @@ bar: true
`), nil)
c.Check(err, IsNil)
- err = cstrs.Check(map[string]interface{}{
+ plug := attrerObject(map[string]interface{}{
"foo": int64(1),
"bar": true,
- }, nil)
+ })
+ err = cstrs.Check(plug, nil)
c.Check(err, IsNil)
}
@@ -461,12 +486,14 @@ func (s *attrConstraintsSuite) TestNeverMatchAttributeConstraints(c *C) {
type plugSlotRulesSuite struct{}
func checkAttrs(c *C, attrs *asserts.AttributeConstraints, witness, expected string) {
- c.Check(attrs.Check(map[string]interface{}{
+ plug := attrerObject(map[string]interface{}{
witness: "XYZ",
- }, nil), ErrorMatches, fmt.Sprintf(`attribute "%s".*does not match.*`, witness))
- c.Check(attrs.Check(map[string]interface{}{
+ })
+ c.Check(attrs.Check(plug, nil), ErrorMatches, fmt.Sprintf(`attribute "%s".*does not match.*`, witness))
+ plug = attrerObject(map[string]interface{}{
witness: expected,
- }, nil), IsNil)
+ })
+ c.Check(attrs.Check(plug, nil), IsNil)
}
func checkBoolPlugConnConstraints(c *C, cstrs []*asserts.PlugConnectionConstraints, always bool) {
diff --git a/asserts/snap_asserts_test.go b/asserts/snap_asserts_test.go
index 60457fd11b..78dc0a675e 100644
--- a/asserts/snap_asserts_test.go
+++ b/asserts/snap_asserts_test.go
@@ -49,6 +49,12 @@ type snapDeclSuite struct {
tsLine string
}
+type emptyAttrerObject struct{}
+
+func (o emptyAttrerObject) Lookup(path string) (interface{}, bool) {
+ return nil, false
+}
+
func (sds *snapDeclSuite) SetUpSuite(c *C) {
sds.ts = time.Now().Truncate(time.Second).UTC()
sds.tsLine = "timestamp: " + sds.ts.Format(time.RFC3339) + "\n"
@@ -298,23 +304,27 @@ AXNpZw==`
c.Assert(plugRule1.DenyInstallation, HasLen, 1)
c.Check(plugRule1.DenyInstallation[0].PlugAttributes, Equals, asserts.NeverMatchAttributes)
c.Assert(plugRule1.AllowAutoConnection, HasLen, 1)
- c.Check(plugRule1.AllowAutoConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "a1".*`)
- c.Check(plugRule1.AllowAutoConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "b1".*`)
+
+ plug := emptyAttrerObject{}
+ slot := emptyAttrerObject{}
+
+ c.Check(plugRule1.AllowAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "a1".*`)
+ c.Check(plugRule1.AllowAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "b1".*`)
c.Check(plugRule1.AllowAutoConnection[0].SlotSnapTypes, DeepEquals, []string{"app"})
c.Check(plugRule1.AllowAutoConnection[0].SlotPublisherIDs, DeepEquals, []string{"acme"})
c.Assert(plugRule1.DenyAutoConnection, HasLen, 1)
- c.Check(plugRule1.DenyAutoConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "a1".*`)
- c.Check(plugRule1.DenyAutoConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "b1".*`)
+ c.Check(plugRule1.DenyAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "a1".*`)
+ c.Check(plugRule1.DenyAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "b1".*`)
plugRule2 := snapDecl.PlugRule("interface2")
c.Assert(plugRule2, NotNil)
c.Assert(plugRule2.AllowInstallation, HasLen, 1)
c.Check(plugRule2.AllowInstallation[0].PlugAttributes, Equals, asserts.AlwaysMatchAttributes)
c.Assert(plugRule2.AllowConnection, HasLen, 1)
- c.Check(plugRule2.AllowConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "a2".*`)
- c.Check(plugRule2.AllowConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "b2".*`)
+ c.Check(plugRule2.AllowConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "a2".*`)
+ c.Check(plugRule2.AllowConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "b2".*`)
c.Assert(plugRule2.DenyConnection, HasLen, 1)
- c.Check(plugRule2.DenyConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "a2".*`)
- c.Check(plugRule2.DenyConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "b2".*`)
+ c.Check(plugRule2.DenyConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "a2".*`)
+ c.Check(plugRule2.DenyConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "b2".*`)
c.Check(plugRule2.DenyConnection[0].SlotSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
slotRule3 := snapDecl.SlotRule("interface3")
@@ -322,24 +332,24 @@ AXNpZw==`
c.Assert(slotRule3.DenyInstallation, HasLen, 1)
c.Check(slotRule3.DenyInstallation[0].SlotAttributes, Equals, asserts.NeverMatchAttributes)
c.Assert(slotRule3.AllowAutoConnection, HasLen, 1)
- c.Check(slotRule3.AllowAutoConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "c1".*`)
- c.Check(slotRule3.AllowAutoConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "d1".*`)
+ c.Check(slotRule3.AllowAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "c1".*`)
+ c.Check(slotRule3.AllowAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "d1".*`)
c.Check(slotRule3.AllowAutoConnection[0].PlugSnapTypes, DeepEquals, []string{"app"})
c.Check(slotRule3.AllowAutoConnection[0].PlugPublisherIDs, DeepEquals, []string{"acme"})
c.Assert(slotRule3.DenyAutoConnection, HasLen, 1)
- c.Check(slotRule3.DenyAutoConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "c1".*`)
- c.Check(slotRule3.DenyAutoConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "d1".*`)
+ c.Check(slotRule3.DenyAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "c1".*`)
+ c.Check(slotRule3.DenyAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "d1".*`)
slotRule4 := snapDecl.SlotRule("interface4")
c.Assert(slotRule4, NotNil)
c.Assert(slotRule4.AllowAutoConnection, HasLen, 1)
- c.Check(slotRule4.AllowConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "c2".*`)
- c.Check(slotRule4.AllowConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "d2".*`)
+ c.Check(slotRule4.AllowConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "c2".*`)
+ c.Check(slotRule4.AllowConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "d2".*`)
c.Assert(slotRule4.DenyAutoConnection, HasLen, 1)
- c.Check(slotRule4.DenyConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "c2".*`)
- c.Check(slotRule4.DenyConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "d2".*`)
+ c.Check(slotRule4.DenyConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "c2".*`)
+ c.Check(slotRule4.DenyConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "d2".*`)
c.Check(slotRule4.DenyConnection[0].PlugSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
c.Assert(slotRule4.AllowInstallation, HasLen, 1)
- c.Check(slotRule4.AllowInstallation[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "e1".*`)
+ c.Check(slotRule4.AllowInstallation[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "e1".*`)
c.Check(slotRule4.AllowInstallation[0].SlotSnapTypes, DeepEquals, []string{"app"})
}
@@ -1197,28 +1207,31 @@ AXNpZw==`
c.Check(baseDecl.PlugRule("interfaceX"), IsNil)
c.Check(baseDecl.SlotRule("interfaceX"), IsNil)
+ plug := emptyAttrerObject{}
+ slot := emptyAttrerObject{}
+
plugRule1 := baseDecl.PlugRule("interface1")
c.Assert(plugRule1, NotNil)
c.Assert(plugRule1.DenyInstallation, HasLen, 1)
c.Check(plugRule1.DenyInstallation[0].PlugAttributes, Equals, asserts.NeverMatchAttributes)
c.Assert(plugRule1.AllowAutoConnection, HasLen, 1)
- c.Check(plugRule1.AllowAutoConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "a1".*`)
- c.Check(plugRule1.AllowAutoConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "b1".*`)
+ c.Check(plugRule1.AllowAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "a1".*`)
+ c.Check(plugRule1.AllowAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "b1".*`)
c.Check(plugRule1.AllowAutoConnection[0].SlotSnapTypes, DeepEquals, []string{"app"})
c.Check(plugRule1.AllowAutoConnection[0].SlotPublisherIDs, DeepEquals, []string{"acme"})
c.Assert(plugRule1.DenyAutoConnection, HasLen, 1)
- c.Check(plugRule1.DenyAutoConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "a1".*`)
- c.Check(plugRule1.DenyAutoConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "b1".*`)
+ c.Check(plugRule1.DenyAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "a1".*`)
+ c.Check(plugRule1.DenyAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "b1".*`)
plugRule2 := baseDecl.PlugRule("interface2")
c.Assert(plugRule2, NotNil)
c.Assert(plugRule2.AllowInstallation, HasLen, 1)
c.Check(plugRule2.AllowInstallation[0].PlugAttributes, Equals, asserts.AlwaysMatchAttributes)
c.Assert(plugRule2.AllowConnection, HasLen, 1)
- c.Check(plugRule2.AllowConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "a2".*`)
- c.Check(plugRule2.AllowConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "b2".*`)
+ c.Check(plugRule2.AllowConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "a2".*`)
+ c.Check(plugRule2.AllowConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "b2".*`)
c.Assert(plugRule2.DenyConnection, HasLen, 1)
- c.Check(plugRule2.DenyConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "a2".*`)
- c.Check(plugRule2.DenyConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "b2".*`)
+ c.Check(plugRule2.DenyConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "a2".*`)
+ c.Check(plugRule2.DenyConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "b2".*`)
c.Check(plugRule2.DenyConnection[0].SlotSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
slotRule3 := baseDecl.SlotRule("interface3")
@@ -1226,24 +1239,24 @@ AXNpZw==`
c.Assert(slotRule3.DenyInstallation, HasLen, 1)
c.Check(slotRule3.DenyInstallation[0].SlotAttributes, Equals, asserts.NeverMatchAttributes)
c.Assert(slotRule3.AllowAutoConnection, HasLen, 1)
- c.Check(slotRule3.AllowAutoConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "c1".*`)
- c.Check(slotRule3.AllowAutoConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "d1".*`)
+ c.Check(slotRule3.AllowAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "c1".*`)
+ c.Check(slotRule3.AllowAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "d1".*`)
c.Check(slotRule3.AllowAutoConnection[0].PlugSnapTypes, DeepEquals, []string{"app"})
c.Check(slotRule3.AllowAutoConnection[0].PlugPublisherIDs, DeepEquals, []string{"acme"})
c.Assert(slotRule3.DenyAutoConnection, HasLen, 1)
- c.Check(slotRule3.DenyAutoConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "c1".*`)
- c.Check(slotRule3.DenyAutoConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "d1".*`)
+ c.Check(slotRule3.DenyAutoConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "c1".*`)
+ c.Check(slotRule3.DenyAutoConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "d1".*`)
slotRule4 := baseDecl.SlotRule("interface4")
c.Assert(slotRule4, NotNil)
c.Assert(slotRule4.AllowConnection, HasLen, 1)
- c.Check(slotRule4.AllowConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "c2".*`)
- c.Check(slotRule4.AllowConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "d2".*`)
+ c.Check(slotRule4.AllowConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "c2".*`)
+ c.Check(slotRule4.AllowConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "d2".*`)
c.Assert(slotRule4.DenyConnection, HasLen, 1)
- c.Check(slotRule4.DenyConnection[0].PlugAttributes.Check(nil, nil), ErrorMatches, `attribute "c2".*`)
- c.Check(slotRule4.DenyConnection[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "d2".*`)
+ c.Check(slotRule4.DenyConnection[0].PlugAttributes.Check(plug, nil), ErrorMatches, `attribute "c2".*`)
+ c.Check(slotRule4.DenyConnection[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "d2".*`)
c.Check(slotRule4.DenyConnection[0].PlugSnapIDs, DeepEquals, []string{"snapidsnapidsnapidsnapidsnapid01", "snapidsnapidsnapidsnapidsnapid02"})
c.Assert(slotRule4.AllowInstallation, HasLen, 1)
- c.Check(slotRule4.AllowInstallation[0].SlotAttributes.Check(nil, nil), ErrorMatches, `attribute "e1".*`)
+ c.Check(slotRule4.AllowInstallation[0].SlotAttributes.Check(slot, nil), ErrorMatches, `attribute "e1".*`)
c.Check(slotRule4.AllowInstallation[0].SlotSnapTypes, DeepEquals, []string{"app"})
}
diff --git a/client/client.go b/client/client.go
index c447a60a7c..02622bfa3f 100644
--- a/client/client.go
+++ b/client/client.go
@@ -436,8 +436,9 @@ type SysInfo struct {
KernelVersion string `json:"kernel-version,omitempty"`
- Refresh RefreshInfo `json:"refresh,omitempty"`
- Confinement string `json:"confinement"`
+ Refresh RefreshInfo `json:"refresh,omitempty"`
+ Confinement string `json:"confinement"`
+ SandboxFeatures map[string][]string `json:"sandbox-features,omitempty"`
}
func (rsp *response) err() error {
diff --git a/client/client_test.go b/client/client_test.go
index 36337ad812..ee4e12a1fd 100644
--- a/client/client_test.go
+++ b/client/client_test.go
@@ -217,7 +217,8 @@ func (cs *clientSuite) TestClientSysInfo(c *C) {
"os-release": {"id": "ubuntu", "version-id": "16.04"},
"on-classic": true,
"build-id": "1234",
- "confinement": "strict"}}`
+ "confinement": "strict",
+ "sandbox-features": {"backend": ["feature-1", "feature-2"]}}}`
sysInfo, err := cs.cli.SysInfo()
c.Check(err, IsNil)
c.Check(sysInfo, DeepEquals, &client.SysInfo{
@@ -229,7 +230,10 @@ func (cs *clientSuite) TestClientSysInfo(c *C) {
},
OnClassic: true,
Confinement: "strict",
- BuildID: "1234",
+ SandboxFeatures: map[string][]string{
+ "backend": {"feature-1", "feature-2"},
+ },
+ BuildID: "1234",
})
}
diff --git a/cmd/snap-update-ns/main.go b/cmd/snap-update-ns/main.go
index 49e046236e..5e5667bad7 100644
--- a/cmd/snap-update-ns/main.go
+++ b/cmd/snap-update-ns/main.go
@@ -239,10 +239,7 @@ func applyUserFstab(snapName string) error {
}
// Replace XDG_RUNTIME_DIR in mount profile
- xdgRuntimeDir, ok := os.LookupEnv("XDG_RUNTIME_DIR")
- if !ok {
- return fmt.Errorf("XDG_RUNTIME_DIR is not set")
- }
+ xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, os.Getuid())
for i := range desired.Entries {
if strings.HasPrefix(desired.Entries[i].Name, "$XDG_RUNTIME_DIR/") {
desired.Entries[i].Name = strings.Replace(desired.Entries[i].Name, "$XDG_RUNTIME_DIR", xdgRuntimeDir, 1)
diff --git a/cmd/snap-update-ns/main_test.go b/cmd/snap-update-ns/main_test.go
index 763efdf4bd..460b4739b3 100644
--- a/cmd/snap-update-ns/main_test.go
+++ b/cmd/snap-update-ns/main_test.go
@@ -317,9 +317,6 @@ func (s *mainSuite) TestApplyUserFstab(c *C) {
dirs.SetRootDir(c.MkDir())
defer dirs.SetRootDir("/")
- os.Setenv("XDG_RUNTIME_DIR", filepath.Join(c.MkDir(), "run/user/42"))
- defer os.Unsetenv("XDG_RUNTIME_DIR")
-
var changes []update.Change
restore := update.MockChangePerform(func(chg *update.Change, sec *update.Secure) ([]*update.Change, error) {
changes = append(changes, *chg)
@@ -339,8 +336,10 @@ func (s *mainSuite) TestApplyUserFstab(c *C) {
err = update.ApplyUserFstab("foo")
c.Assert(err, IsNil)
+ xdgRuntimeDir := fmt.Sprintf("%s/%d", dirs.XdgRuntimeDirBase, os.Getuid())
+
c.Assert(changes, HasLen, 1)
c.Assert(changes[0].Action, Equals, update.Mount)
- c.Assert(changes[0].Entry.Name, Matches, `.*/run/user/42/doc/by-app/snap\.foo`)
- c.Assert(changes[0].Entry.Dir, Matches, `.*/run/user/42/doc`)
+ c.Assert(changes[0].Entry.Name, Equals, xdgRuntimeDir+"/doc/by-app/snap.foo")
+ c.Assert(changes[0].Entry.Dir, Matches, xdgRuntimeDir+"/doc")
}
diff --git a/cmd/snap/cmd_can_manage_refreshes.go b/cmd/snap/cmd_can_manage_refreshes.go
index a1295cce5d..4367e4f1de 100644
--- a/cmd/snap/cmd_can_manage_refreshes.go
+++ b/cmd/snap/cmd_can_manage_refreshes.go
@@ -33,7 +33,7 @@ func init() {
"(internal) return if refresh.schedule=managed can be used",
func() flags.Commander {
return &cmdCanManageRefreshes{}
- })
+ }, nil, nil)
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_confinement.go b/cmd/snap/cmd_confinement.go
index 64ec7e20a0..05ba11803d 100644
--- a/cmd/snap/cmd_confinement.go
+++ b/cmd/snap/cmd_confinement.go
@@ -36,7 +36,9 @@ partial or none) the system operates in.
type cmdConfinement struct{}
func init() {
- addDebugCommand("confinement", shortConfinementHelp, longConfinementHelp, func() flags.Commander { return &cmdConfinement{} })
+ addDebugCommand("confinement", shortConfinementHelp, longConfinementHelp, func() flags.Commander {
+ return &cmdConfinement{}
+ }, nil, nil)
}
func (cmd cmdConfinement) Execute(args []string) error {
diff --git a/cmd/snap/cmd_ensure_state_soon.go b/cmd/snap/cmd_ensure_state_soon.go
index cb44363b62..3f029ecb66 100644
--- a/cmd/snap/cmd_ensure_state_soon.go
+++ b/cmd/snap/cmd_ensure_state_soon.go
@@ -31,7 +31,7 @@ func init() {
"(internal) trigger an ensure run in the state engine",
func() flags.Commander {
return &cmdEnsureStateSoon{}
- })
+ }, nil, nil)
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_get_base_declaration.go b/cmd/snap/cmd_get_base_declaration.go
index c0795001af..9c96134927 100644
--- a/cmd/snap/cmd_get_base_declaration.go
+++ b/cmd/snap/cmd_get_base_declaration.go
@@ -33,7 +33,7 @@ func init() {
"(internal) obtain the base declaration for all interfaces",
func() flags.Commander {
return &cmdGetBaseDeclaration{}
- })
+ }, nil, nil)
}
func (x *cmdGetBaseDeclaration) Execute(args []string) error {
diff --git a/cmd/snap/cmd_sandbox_features.go b/cmd/snap/cmd_sandbox_features.go
new file mode 100644
index 0000000000..8c9395bfb7
--- /dev/null
+++ b/cmd/snap/cmd_sandbox_features.go
@@ -0,0 +1,88 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 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 main
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/i18n"
+)
+
+var shortSandboxFeaturesHelp = i18n.G("Print sandbox features available on the system")
+var longSandboxFeaturesHelp = i18n.G(`
+The sandbox command prints tags describing features of individual sandbox
+components used by snapd on a given system.
+`)
+
+type cmdSandboxFeatures struct {
+ Required []string `long:"required" arg-name:"<backend feature>"`
+}
+
+func init() {
+ addDebugCommand("sandbox-features", shortSandboxFeaturesHelp, longSandboxFeaturesHelp, func() flags.Commander {
+ return &cmdSandboxFeatures{}
+ }, map[string]string{
+ "required": i18n.G("Ensure that given backend:feature is available"),
+ }, nil)
+}
+
+func (cmd cmdSandboxFeatures) Execute(args []string) error {
+ if len(args) > 0 {
+ return ErrExtraArgs
+ }
+
+ cli := Client()
+ sysInfo, err := cli.SysInfo()
+ if err != nil {
+ return err
+ }
+
+ sandboxFeatures := sysInfo.SandboxFeatures
+
+ if len(cmd.Required) > 0 {
+ avail := make(map[string]bool)
+ for backend := range sandboxFeatures {
+ for _, feature := range sandboxFeatures[backend] {
+ avail[fmt.Sprintf("%s:%s", backend, feature)] = true
+ }
+ }
+ for _, required := range cmd.Required {
+ if !avail[required] {
+ return fmt.Errorf("sandbox feature not available: %q", required)
+ }
+ }
+ } else {
+ backends := make([]string, 0, len(sandboxFeatures))
+ for backend := range sandboxFeatures {
+ backends = append(backends, backend)
+ }
+ sort.Strings(backends)
+ w := tabWriter()
+ defer w.Flush()
+ for _, backend := range backends {
+ fmt.Fprintf(w, "%s:\t%s\n", backend, strings.Join(sandboxFeatures[backend], " "))
+ }
+ }
+ return nil
+}
diff --git a/cmd/snap/cmd_sandbox_features_test.go b/cmd/snap/cmd_sandbox_features_test.go
new file mode 100644
index 0000000000..c53ca56d7e
--- /dev/null
+++ b/cmd/snap/cmd_sandbox_features_test.go
@@ -0,0 +1,61 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2018 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 main_test
+
+import (
+ "fmt"
+ "net/http"
+
+ . "gopkg.in/check.v1"
+
+ snap "github.com/snapcore/snapd/cmd/snap"
+)
+
+func (s *SnapSuite) TestSandboxFeatures(c *C) {
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, `{"type": "sync", "result": {"sandbox-features": {"apparmor": ["a", "b", "c"], "selinux": ["1", "2", "3"]}}}`)
+ })
+ _, err := snap.Parser().ParseArgs([]string{"debug", "sandbox-features"})
+ c.Assert(err, IsNil)
+ c.Assert(s.Stdout(), Equals, ""+
+ "apparmor: a b c\n"+
+ "selinux: 1 2 3\n")
+ c.Assert(s.Stderr(), Equals, "")
+}
+
+func (s *SnapSuite) TestSandboxFeaturesRequired(c *C) {
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, `{"type": "sync", "result": {"sandbox-features": {"apparmor": ["a", "b", "c"], "selinux": ["1", "2", "3"]}}}`)
+ })
+ _, err := snap.Parser().ParseArgs([]string{"debug", "sandbox-features", "--required=apparmor:a", "--required=selinux:2"})
+ c.Assert(err, IsNil)
+ c.Assert(s.Stdout(), Equals, "")
+ c.Assert(s.Stderr(), Equals, "")
+}
+
+func (s *SnapSuite) TestSandboxFeaturesRequiredButMissing(c *C) {
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, `{"type": "sync", "result": {"sandbox-features": {"apparmor": ["a", "b", "c"], "selinux": ["1", "2", "3"]}}}`)
+ })
+ _, err := snap.Parser().ParseArgs([]string{"debug", "sandbox-features", "--required=magic:thing"})
+ c.Assert(err, ErrorMatches, `sandbox feature not available: "magic:thing"`)
+ c.Assert(s.Stdout(), Equals, "")
+ c.Assert(s.Stderr(), Equals, "")
+}
diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go
index 269d027e13..2fa23f1a47 100644
--- a/cmd/snap/cmd_snap_op.go
+++ b/cmd/snap/cmd_snap_op.go
@@ -281,8 +281,8 @@ func showDone(names []string, op string) error {
fmt.Fprintf(Stdout, "internal error: unknown op %q", op)
}
if snap.TrackingChannel != snap.Channel && snap.Channel != "" {
- // TRANSLATORS: first %s is a snap name, following %s is a channel name
- fmt.Fprintf(Stdout, i18n.G("Snap %s is no longer tracking %s.\n"), snap.Name, snap.TrackingChannel)
+ // TRANSLATORS: first %s is a channel name, following %s is a snap name, last %s is a channel name again.
+ fmt.Fprintf(Stdout, i18n.G("Channel %s for %s is closed; temporarily forwarding to %s.\n"), snap.TrackingChannel, snap.Name, snap.Channel)
}
}
diff --git a/cmd/snap/cmd_snap_op_test.go b/cmd/snap/cmd_snap_op_test.go
index ae7db521a6..2906d548b5 100644
--- a/cmd/snap/cmd_snap_op_test.go
+++ b/cmd/snap/cmd_snap_op_test.go
@@ -444,7 +444,7 @@ func (s *SnapOpSuite) TestRevertRunthrough(c *check.C) {
c.Assert(rest, check.DeepEquals, []string{})
// tracking channel is "" in the test server
c.Check(s.Stdout(), check.Equals, `foo reverted to 1.0
-Snap foo is no longer tracking .
+Channel for foo is closed; temporarily forwarding to potato.
`)
c.Check(s.Stderr(), check.Equals, "")
// ensure that the fake server api was actually hit
diff --git a/cmd/snap/main.go b/cmd/snap/main.go
index f556b86137..3babe24800 100644
--- a/cmd/snap/main.go
+++ b/cmd/snap/main.go
@@ -114,12 +114,14 @@ func addCommand(name, shortHelp, longHelp string, builder func() flags.Commander
// addDebugCommand replaces parser.addCommand() in a way that is
// compatible with re-constructing a pristine parser. It is meant for
// adding debug commands.
-func addDebugCommand(name, shortHelp, longHelp string, builder func() flags.Commander) *cmdInfo {
+func addDebugCommand(name, shortHelp, longHelp string, builder func() flags.Commander, optDescs map[string]string, argDescs []argDesc) *cmdInfo {
info := &cmdInfo{
name: name,
shortHelp: shortHelp,
longHelp: longHelp,
builder: builder,
+ optDescs: optDescs,
+ argDescs: argDescs,
}
debugCommands = append(debugCommands, info)
return info
@@ -247,6 +249,39 @@ snaps on the system. Start with 'snap list' to see installed snaps.`)
logger.Panicf("cannot add debug command %q: %v", c.name, err)
}
cmd.Hidden = c.hidden
+ opts := cmd.Options()
+ if c.optDescs != nil && len(opts) != len(c.optDescs) {
+ logger.Panicf("wrong number of option descriptions for %s: expected %d, got %d", c.name, len(opts), len(c.optDescs))
+ }
+ for _, opt := range opts {
+ name := opt.LongName
+ if name == "" {
+ name = string(opt.ShortName)
+ }
+ desc, ok := c.optDescs[name]
+ if !(c.optDescs == nil || ok) {
+ logger.Panicf("%s missing description for %s", c.name, name)
+ }
+ lintDesc(c.name, name, desc, opt.Description)
+ if desc != "" {
+ opt.Description = desc
+ }
+ }
+
+ args := cmd.Args()
+ if c.argDescs != nil && len(args) != len(c.argDescs) {
+ logger.Panicf("wrong number of argument descriptions for %s: expected %d, got %d", c.name, len(args), len(c.argDescs))
+ }
+ for i, arg := range args {
+ name, desc := arg.Name, ""
+ if c.argDescs != nil {
+ name = c.argDescs[i].name
+ desc = c.argDescs[i].desc
+ }
+ lintArg(c.name, name, desc, arg.Description)
+ arg.Name = name
+ arg.Description = desc
+ }
}
return parser
}
diff --git a/daemon/api.go b/daemon/api.go
index 2b47b3551c..97874fed7d 100644
--- a/daemon/api.go
+++ b/daemon/api.go
@@ -324,9 +324,39 @@ func sysInfo(c *Command, r *http.Request, user *auth.UserState) Response {
m["confinement"] = "strict"
}
+ // Convey richer information about features of available security backends.
+ if features := sandboxFeatures(c.d.overlord.InterfaceManager().Repository().Backends()); features != nil {
+ m["sandbox-features"] = features
+ }
+
return SyncResponse(m, nil)
}
+func sandboxFeatures(backends []interfaces.SecurityBackend) map[string][]string {
+ result := make(map[string][]string, len(backends)+1)
+ for _, backend := range backends {
+ features := backend.SandboxFeatures()
+ if len(features) > 0 {
+ sort.Strings(features)
+ result[string(backend.Name())] = features
+ }
+ }
+
+ // Add information about supported confinement types as a fake backend
+ features := make([]string, 1, 3)
+ features[0] = "devmode"
+ if !release.ReleaseInfo.ForceDevMode() {
+ features = append(features, "strict")
+ }
+ if dirs.SupportsClassicConfinement() {
+ features = append(features, "classic")
+ }
+ sort.Strings(features)
+ result["confinement-options"] = features
+
+ return result
+}
+
// userResponseData contains the data releated to user creation/login/query
type userResponseData struct {
ID int `json:"id,omitempty"`
@@ -1815,7 +1845,7 @@ type interfaceAction struct {
Slots []slotJSON `json:"slots,omitempty"`
}
-func snapNamesFromConns(conns []interfaces.ConnRef) []string {
+func snapNamesFromConns(conns []*interfaces.ConnRef) []string {
m := make(map[string]bool)
for _, conn := range conns {
m[conn.PlugRef.Snap] = true
@@ -1867,7 +1897,7 @@ func changeInterfaces(c *Command, r *http.Request, user *auth.UserState) Respons
switch a.Action {
case "connect":
- var connRef interfaces.ConnRef
+ var connRef *interfaces.ConnRef
repo := c.d.overlord.InterfaceManager().Repository()
connRef, err = repo.ResolveConnect(a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name)
if err == nil {
@@ -1875,10 +1905,10 @@ func changeInterfaces(c *Command, r *http.Request, user *auth.UserState) Respons
summary = fmt.Sprintf("Connect %s:%s to %s:%s", connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name)
ts, err = ifacestate.Connect(st, connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name)
tasksets = append(tasksets, ts)
- affected = snapNamesFromConns([]interfaces.ConnRef{connRef})
+ affected = snapNamesFromConns([]*interfaces.ConnRef{connRef})
}
case "disconnect":
- var conns []interfaces.ConnRef
+ var conns []*interfaces.ConnRef
repo := c.d.overlord.InterfaceManager().Repository()
summary = fmt.Sprintf("Disconnect %s:%s from %s:%s", a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name)
conns, err = repo.ResolveDisconnect(a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name)
diff --git a/daemon/api_test.go b/daemon/api_test.go
index b8dfd71d30..6e3e94f062 100644
--- a/daemon/api_test.go
+++ b/daemon/api_test.go
@@ -766,7 +766,8 @@ func (s *apiSuite) TestSysInfo(c *check.C) {
// only the "timer" field
"timer": "8:00~9:00/2",
},
- "confinement": "partial",
+ "confinement": "partial",
+ "sandbox-features": map[string]interface{}{"confinement-options": []interface{}{"classic", "devmode"}},
}
var rsp resp
c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
@@ -801,6 +802,13 @@ func (s *apiSuite) TestSysInfoLegacyRefresh(c *check.C) {
tr.Commit()
st.Unlock()
+ // add a test security backend
+ err := d.overlord.InterfaceManager().Repository().AddBackend(&ifacetest.TestSecurityBackend{
+ BackendName: "apparmor",
+ SandboxFeaturesCallback: func() []string { return []string{"feature-1", "feature-2"} },
+ })
+ c.Assert(err, check.IsNil)
+
buildID, err := osutil.MyBuildID()
c.Assert(err, check.IsNil)
@@ -827,6 +835,10 @@ func (s *apiSuite) TestSysInfoLegacyRefresh(c *check.C) {
"schedule": "00:00-9:00/12:00-13:00",
},
"confinement": "partial",
+ "sandbox-features": map[string]interface{}{
+ "apparmor": []interface{}{"feature-1", "feature-2"},
+ "confinement-options": []interface{}{"classic", "devmode"}, // we know it's this because of the release.Mock... calls above
+ },
}
var rsp resp
c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
@@ -3510,7 +3522,7 @@ func (s *apiSuite) TestInterfaces(c *check.C) {
s.mockSnap(c, producerYaml)
repo := d.overlord.InterfaceManager().Repository()
- connRef := interfaces.ConnRef{
+ connRef := &interfaces.ConnRef{
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
}
@@ -3853,7 +3865,7 @@ func (s *apiSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slot
s.mockSnap(c, producerYaml)
repo := d.overlord.InterfaceManager().Repository()
- connRef := interfaces.ConnRef{
+ connRef := &interfaces.ConnRef{
PlugRef: interfaces.PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: "producer", Name: "slot"},
}
diff --git a/data/systemd/snapd.core-fixup.sh b/data/systemd/snapd.core-fixup.sh
index 3543bda0d1..795939a4d9 100755
--- a/data/systemd/snapd.core-fixup.sh
+++ b/data/systemd/snapd.core-fixup.sh
@@ -19,9 +19,13 @@ fi
# the other has a "uboot.env" lfn name and a FSCK0000.000 FAT16
# name. The only known workaround is to remove all dupes and put
# one file back in place.
-if [ $(ls /boot/uboot | grep ^uboot.env$ | wc -l) -gt 1 ]; then
+if [ "$(find /boot/uboot -name uboot.env | wc -l)" -gt 1 ]; then
echo "Corrupted uboot.env file detected"
- # ensure we have one uboot.env to go back to
+ # Ensure we have one uboot.env to go back to. Note that it does
+ # not matter which one we pick (we can't choose anyway, we get
+ # whatever the kernel gives us). The key part is that there is
+ # only a single one after this script finishes. The bootloader
+ # logic will recover in any case.
cp -a /boot/uboot/uboot.env /boot/uboot/uboot.env.save
# now delete all dupes
while ls /boot/uboot/uboot.env 2>/dev/null; do
diff --git a/interfaces/apparmor/backend.go b/interfaces/apparmor/backend.go
index d72014509f..31c0e6d698 100644
--- a/interfaces/apparmor/backend.go
+++ b/interfaces/apparmor/backend.go
@@ -60,6 +60,7 @@ var (
procSelfExe = "/proc/self/exe"
isHomeUsingNFS = osutil.IsHomeUsingNFS
isRootWritableOverlay = osutil.IsRootWritableOverlay
+ kernelFeatures = release.AppArmorFeatures
)
// Backend is responsible for maintaining apparmor profiles for snaps and parts of snapd.
@@ -492,3 +493,15 @@ func unloadProfiles(profiles []string, cacheDir string) error {
func (b *Backend) NewSpecification() interfaces.Specification {
return &Specification{}
}
+
+// SandboxFeatures returns the list of apparmor features supported by the kernel.
+func (b *Backend) SandboxFeatures() []string {
+ features := kernelFeatures()
+ tags := make([]string, 0, len(features))
+ for _, feature := range features {
+ // Prepend "kernel:" to apparmor kernel features to namespace them and
+ // allow us to introduce our own tags later.
+ tags = append(tags, "kernel:"+feature)
+ }
+ return tags
+}
diff --git a/interfaces/apparmor/backend_test.go b/interfaces/apparmor/backend_test.go
index 8fcf49a2ad..fb0754abb7 100644
--- a/interfaces/apparmor/backend_test.go
+++ b/interfaces/apparmor/backend_test.go
@@ -1200,3 +1200,10 @@ func (s *backendSuite) TestProfileGlobs(c *C) {
func (s *backendSuite) TestNsProfile(c *C) {
c.Assert(apparmor.NsProfile("foo"), Equals, "snap-update-ns.foo")
}
+
+func (s *backendSuite) TestSandboxFeatures(c *C) {
+ restore := apparmor.MockKernelFeatures(func() []string { return []string{"foo", "bar"} })
+ defer restore()
+
+ c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar"})
+}
diff --git a/interfaces/apparmor/export_test.go b/interfaces/apparmor/export_test.go
index c1ac85230f..791f56b4ff 100644
--- a/interfaces/apparmor/export_test.go
+++ b/interfaces/apparmor/export_test.go
@@ -88,3 +88,11 @@ func MockClassicTemplate(fakeTemplate string) (restore func()) {
func SetSpecScope(spec *Specification, securityTags []string) (restore func()) {
return spec.setScope(securityTags)
}
+
+func MockKernelFeatures(f func() []string) (resture func()) {
+ old := kernelFeatures
+ kernelFeatures = f
+ return func() {
+ kernelFeatures = old
+ }
+}
diff --git a/interfaces/apparmor/template.go b/interfaces/apparmor/template.go
index 577cb56eb3..3839cf0bb2 100644
--- a/interfaces/apparmor/template.go
+++ b/interfaces/apparmor/template.go
@@ -609,11 +609,7 @@ profile snap-update-ns.###SNAP_NAME### (attach_disconnected) {
umount /snap/###SNAP_NAME###/*/**,
# set up user mount namespace
- # FIXME: this should be moved to the desktop interface when
- # xdg-desktop-portal support is integrated
mount options=(rslave) -> /,
- mount options=(ro bind) /run/user/*/** -> /run/user/*/**,
- mount options=(rw bind) /run/user/*/** -> /run/user/*/**,
# Allow traversing from the root directory and several well-known places.
# Specific directory permissions are added by snippets below.
diff --git a/interfaces/backend.go b/interfaces/backend.go
index f5d8cecb6f..e1cfee54c5 100644
--- a/interfaces/backend.go
+++ b/interfaces/backend.go
@@ -91,4 +91,7 @@ type SecurityBackend interface {
// NewSpecification returns a new specification associated with this backend.
NewSpecification() Specification
+
+ // SandboxFeatures returns a list of tags that identify sandbox features.
+ SandboxFeatures() []string
}
diff --git a/interfaces/builtin/desktop.go b/interfaces/builtin/desktop.go
index fc5833cc0e..de968e6f01 100644
--- a/interfaces/builtin/desktop.go
+++ b/interfaces/builtin/desktop.go
@@ -180,19 +180,20 @@ dbus (send)
## Allow access to xdg-document-portal file system. Access control is
## handled by bind mounting a snap-specific sub-tree to this location.
-#owner /run/user/[0-9]*/doc/** rw,
+owner /run/user/[0-9]*/doc/ r,
+owner /run/user/[0-9]*/doc/** rw,
# Allow access to xdg-desktop-portal and xdg-document-portal
dbus (receive, send)
bus=session
interface=org.freedesktop.portal.*
- path=/org/freedesktop/portal/{desktop,documents}
+ path=/org/freedesktop/portal/{desktop,documents}{,/**}
peer=(label=unconfined),
dbus (receive, send)
bus=session
interface=org.freedesktop.DBus.Properties
- path=/org/freedesktop/portal/{desktop,documents}
+ path=/org/freedesktop/portal/{desktop,documents}{,/**}
peer=(label=unconfined),
`
@@ -230,11 +231,19 @@ func (iface *desktopInterface) fontconfigDirs() []string {
func (iface *desktopInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
spec.AddSnippet(desktopConnectedPlugAppArmor)
+ // Allow mounting document portal
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, " # Mount the document portal\n")
+ fmt.Fprintf(&buf, " mount options=(bind) /run/user/[0-9]*/doc/by-app/snap.%s/ -> /run/user/[0-9]*/doc/,\n", plug.Snap().Name())
+ fmt.Fprintf(&buf, " umount /run/user/[0-9]*/doc/,\n\n")
+ spec.AddUpdateNS(buf.String())
+
if !release.OnClassic {
// We only need the font mount rules on classic systems
return nil
}
+ // Allow mounting fonts
for _, dir := range iface.fontconfigDirs() {
var buf bytes.Buffer
source := "/var/lib/snapd/hostfs" + dir
@@ -250,8 +259,15 @@ func (iface *desktopInterface) AppArmorConnectedPlug(spec *apparmor.Specificatio
}
func (iface *desktopInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
+ appId := "snap." + plug.Snap().Name()
+ spec.AddUserMountEntry(osutil.MountEntry{
+ Name: "$XDG_RUNTIME_DIR/doc/by-app/" + appId,
+ Dir: "$XDG_RUNTIME_DIR/doc",
+ Options: []string{"bind", "rw", osutil.XSnapdIgnoreMissing()},
+ })
+
if !release.OnClassic {
- // There is nothing to expose on an all-snaps system
+ // We only need the font mount rules on classic systems
return nil
}
@@ -266,15 +282,6 @@ func (iface *desktopInterface) MountConnectedPlug(spec *mount.Specification, plu
})
}
- /*
- appId := "snap.pkg." + plug.Snap.Name()
- spec.AddUserMount(mount.Entry{
- Name: "$XDG_RUNTIME_DIR/doc/by-app/" + appId,
- Dir: "$XDG_RUNTIME_DIR/doc",
- Options: []string{"bind", "rw"},
- })
- */
-
return nil
}
diff --git a/interfaces/builtin/desktop_test.go b/interfaces/builtin/desktop_test.go
index fe556d7b71..0a24cd361d 100644
--- a/interfaces/builtin/desktop_test.go
+++ b/interfaces/builtin/desktop_test.go
@@ -109,9 +109,15 @@ func (s *DesktopInterfaceSuite) TestAppArmorSpec(c *C) {
c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "/etc/gtk-3.0/settings.ini r,")
c.Assert(spec.SnippetForTag("snap.consumer.app"), testutil.Contains, "# Allow access to xdg-desktop-portal and xdg-document-portal")
- // No UpdateNS rules on an all-snaps system
+ // On an all-snaps system, the only UpdateNS rule is for the
+ // document portal.
updateNS := spec.UpdateNS()
- c.Assert(updateNS, HasLen, 0)
+ c.Assert(updateNS, HasLen, 1)
+ c.Check(updateNS[0], Equals, ` # Mount the document portal
+ mount options=(bind) /run/user/[0-9]*/doc/by-app/snap.consumer/ -> /run/user/[0-9]*/doc/,
+ umount /run/user/[0-9]*/doc/,
+
+`)
// On a classic system, there are UpdateNS rules for the host
// system font mounts
@@ -120,10 +126,11 @@ func (s *DesktopInterfaceSuite) TestAppArmorSpec(c *C) {
spec = &apparmor.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
updateNS = spec.UpdateNS()
- c.Assert(updateNS, HasLen, 3)
- c.Check(updateNS[0], testutil.Contains, "# Read-only access to /usr/share/fonts")
- c.Check(updateNS[1], testutil.Contains, "# Read-only access to /usr/local/share/fonts")
- c.Check(updateNS[2], testutil.Contains, "# Read-only access to /var/cache/fontconfig")
+ c.Assert(updateNS, HasLen, 4)
+ c.Check(updateNS[0], testutil.Contains, "# Mount the document portal")
+ c.Check(updateNS[1], testutil.Contains, "# Read-only access to /usr/share/fonts")
+ c.Check(updateNS[2], testutil.Contains, "# Read-only access to /usr/local/share/fonts")
+ c.Check(updateNS[3], testutil.Contains, "# Read-only access to /var/cache/fontconfig")
// connected plug to core slot
spec = &apparmor.Specification{}
@@ -141,11 +148,17 @@ func (s *DesktopInterfaceSuite) TestMountSpec(c *C) {
restore := release.MockOnClassic(false)
defer restore()
- // On all-snaps systems, no mount entries are added
+ // On all-snaps systems, the font related mount entries are missing
spec := &mount.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
c.Check(spec.MountEntries(), HasLen, 0)
+ entries := spec.UserMountEntries()
+ c.Check(entries, HasLen, 1)
+ c.Check(entries[0].Name, Equals, "$XDG_RUNTIME_DIR/doc/by-app/snap.consumer")
+ c.Check(entries[0].Dir, Equals, "$XDG_RUNTIME_DIR/doc")
+ c.Check(entries[0].Options, DeepEquals, []string{"bind", "rw", "x-snapd.ignore-missing"})
+
// On classic systems, a number of font related directories
// are bind mounted from the host system if they exist.
restore = release.MockOnClassic(true)
@@ -153,7 +166,7 @@ func (s *DesktopInterfaceSuite) TestMountSpec(c *C) {
spec = &mount.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.coreSlot), IsNil)
- entries := spec.MountEntries()
+ entries = spec.MountEntries()
c.Assert(entries, HasLen, 3)
const hostfs = "/var/lib/snapd/hostfs"
@@ -168,6 +181,10 @@ func (s *DesktopInterfaceSuite) TestMountSpec(c *C) {
c.Check(entries[2].Name, Equals, hostfs+dirs.SystemFontconfigCacheDir)
c.Check(entries[2].Dir, Equals, "/var/cache/fontconfig")
c.Check(entries[2].Options, DeepEquals, []string{"bind", "ro"})
+
+ entries = spec.UserMountEntries()
+ c.Assert(entries, HasLen, 1)
+ c.Check(entries[0].Dir, Equals, "$XDG_RUNTIME_DIR/doc")
}
func (s *DesktopInterfaceSuite) TestStaticInfo(c *C) {
diff --git a/interfaces/connection.go b/interfaces/connection.go
index 292c79b63a..362cc7a389 100644
--- a/interfaces/connection.go
+++ b/interfaces/connection.go
@@ -22,6 +22,7 @@ package interfaces
import (
"fmt"
"reflect"
+ "strings"
"github.com/snapcore/snapd/snap"
)
@@ -49,29 +50,51 @@ type ConnectedSlot struct {
// Attrer is an interface with Attr getter method common
// to ConnectedSlot, ConnectedPlug, PlugInfo and SlotInfo types.
type Attrer interface {
- Attr(key string, val interface{}) error
+ // Attr returns attribute value for given path, or an error. Dotted paths are supported.
+ Attr(path string, value interface{}) error
+ // Lookup returns attribute value for given path, or false. Dotted paths are supported.
+ Lookup(path string) (value interface{}, ok bool)
}
-func getAttribute(snapName string, ifaceName string, staticAttrs map[string]interface{}, dynamicAttrs map[string]interface{}, key string, val interface{}) error {
+func lookupAttr(staticAttrs map[string]interface{}, dynamicAttrs map[string]interface{}, path string) (interface{}, bool) {
var v interface{}
- var ok bool
+ comps := strings.FieldsFunc(path, func(r rune) bool { return r == '.' })
+ if len(comps) == 0 {
+ return nil, false
+ }
+ if _, ok := dynamicAttrs[comps[0]]; ok {
+ v = dynamicAttrs
+ } else {
+ v = staticAttrs
+ }
- v, ok = dynamicAttrs[key]
- if !ok {
- v, ok = staticAttrs[key]
+ for _, comp := range comps {
+ m, ok := v.(map[string]interface{})
+ if !ok {
+ return nil, false
+ }
+ v, ok = m[comp]
+ if !ok {
+ return nil, false
+ }
}
+ return v, true
+}
+
+func getAttribute(snapName string, ifaceName string, staticAttrs map[string]interface{}, dynamicAttrs map[string]interface{}, path string, val interface{}) error {
+ v, ok := lookupAttr(staticAttrs, dynamicAttrs, path)
if !ok {
- return fmt.Errorf("snap %q does not have attribute %q for interface %q", snapName, key, ifaceName)
+ return fmt.Errorf("snap %q does not have attribute %q for interface %q", snapName, path, ifaceName)
}
rt := reflect.TypeOf(val)
if rt.Kind() != reflect.Ptr || val == nil {
- return fmt.Errorf("internal error: cannot get %q attribute of interface %q with non-pointer value", key, ifaceName)
+ return fmt.Errorf("internal error: cannot get %q attribute of interface %q with non-pointer value", path, ifaceName)
}
if reflect.TypeOf(v) != rt.Elem() {
- return fmt.Errorf("snap %q has interface %q with invalid value type for %q attribute", snapName, ifaceName, key)
+ return fmt.Errorf("snap %q has interface %q with invalid value type for %q attribute", snapName, ifaceName, path)
}
rv := reflect.ValueOf(val)
rv.Elem().Set(reflect.ValueOf(v))
@@ -148,6 +171,10 @@ func (plug *ConnectedPlug) Attr(key string, val interface{}) error {
return getAttribute(plug.Snap().Name(), plug.Interface(), plug.staticAttrs, plug.dynamicAttrs, key, val)
}
+func (plug *ConnectedPlug) Lookup(path string) (interface{}, bool) {
+ return lookupAttr(plug.staticAttrs, plug.dynamicAttrs, path)
+}
+
// SetAttr sets the given dynamic attribute. Error is returned if the key is already used by a static attribute.
func (plug *ConnectedPlug) SetAttr(key string, value interface{}) error {
if _, ok := plug.staticAttrs[key]; ok {
@@ -217,6 +244,10 @@ func (slot *ConnectedSlot) Attr(key string, val interface{}) error {
return getAttribute(slot.Snap().Name(), slot.Interface(), slot.staticAttrs, slot.dynamicAttrs, key, val)
}
+func (slot *ConnectedSlot) Lookup(path string) (interface{}, bool) {
+ return lookupAttr(slot.staticAttrs, slot.dynamicAttrs, path)
+}
+
// SetAttr sets the given dynamic attribute. Error is returned if the key is already used by a static attribute.
func (slot *ConnectedSlot) SetAttr(key string, value interface{}) error {
if _, ok := slot.staticAttrs[key]; ok {
diff --git a/interfaces/connection_test.go b/interfaces/connection_test.go
index 10cc706a36..8fe98b4c58 100644
--- a/interfaces/connection_test.go
+++ b/interfaces/connection_test.go
@@ -47,6 +47,8 @@ plugs:
plug:
interface: interface
attr: value
+ complex:
+ c: d
`, nil)
s.plug = consumer.Plugs["plug"]
producer := snaptest.MockInfo(c, `
@@ -58,6 +60,8 @@ slots:
slot:
interface: interface
attr: value
+ complex:
+ a: b
`, nil)
s.slot = producer.Slots["slot"]
}
@@ -82,7 +86,8 @@ func (s *connSuite) TestStaticSlotAttrs(c *C) {
attrs := slot.StaticAttrs()
c.Assert(attrs, DeepEquals, map[string]interface{}{
- "attr": "value",
+ "attr": "value",
+ "complex": map[string]interface{}{"a": "b"},
})
slot.StaticAttr("attr", &val)
c.Assert(val, Equals, "value")
@@ -114,7 +119,8 @@ func (s *connSuite) TestStaticPlugAttrs(c *C) {
attrs := plug.StaticAttrs()
c.Assert(attrs, DeepEquals, map[string]interface{}{
- "attr": "value",
+ "attr": "value",
+ "complex": map[string]interface{}{"c": "d"},
})
plug.StaticAttr("attr", &val)
c.Assert(val, Equals, "value")
@@ -156,6 +162,96 @@ func (s *connSuite) TestDynamicSlotAttrs(c *C) {
c.Check(slot.Attr("number", intVal), ErrorMatches, `internal error: cannot get "number" attribute of interface "interface" with non-pointer value`)
}
+func (s *connSuite) TestDottedPathSlot(c *C) {
+ attrs := map[string]interface{}{
+ "nested": map[string]interface{}{
+ "foo": "bar",
+ },
+ }
+ var strVal string
+
+ slot := NewConnectedSlot(s.slot, attrs)
+ c.Assert(slot, NotNil)
+
+ // static attribute complex.a
+ c.Assert(slot.Attr("complex.a", &strVal), IsNil)
+ c.Assert(strVal, Equals, "b")
+
+ v, ok := slot.Lookup("complex.a")
+ c.Assert(ok, Equals, true)
+ c.Assert(v, Equals, "b")
+
+ // dynamic attribute nested.foo
+ c.Assert(slot.Attr("nested.foo", &strVal), IsNil)
+ c.Assert(strVal, Equals, "bar")
+
+ v, ok = slot.Lookup("nested.foo")
+ c.Assert(ok, Equals, true)
+ c.Assert(v, Equals, "bar")
+
+ _, ok = slot.Lookup("..")
+ c.Assert(ok, Equals, false)
+}
+
+func (s *connSuite) TestDottedPathPlug(c *C) {
+ attrs := map[string]interface{}{
+ "a": "b",
+ "nested": map[string]interface{}{
+ "foo": "bar",
+ },
+ }
+ var strVal string
+
+ plug := NewConnectedPlug(s.plug, attrs)
+ c.Assert(plug, NotNil)
+
+ v, ok := plug.Lookup("a")
+ c.Assert(ok, Equals, true)
+ c.Assert(v, Equals, "b")
+
+ // static attribute complex.c
+ c.Assert(plug.Attr("complex.c", &strVal), IsNil)
+ c.Assert(strVal, Equals, "d")
+
+ v, ok = plug.Lookup("complex.c")
+ c.Assert(ok, Equals, true)
+ c.Assert(v, Equals, "d")
+
+ // dynamic attribute nested.foo
+ c.Assert(plug.Attr("nested.foo", &strVal), IsNil)
+ c.Assert(strVal, Equals, "bar")
+
+ v, ok = plug.Lookup("nested.foo")
+ c.Assert(ok, Equals, true)
+ c.Assert(v, Equals, "bar")
+
+ _, ok = plug.Lookup("nested.x")
+ c.Assert(ok, Equals, false)
+
+ _, ok = plug.Lookup("nested.foo.y")
+ c.Assert(ok, Equals, false)
+
+ _, ok = plug.Lookup("..")
+ c.Assert(ok, Equals, false)
+}
+
+func (s *connSuite) TestLookupFailure(c *C) {
+ attrs := map[string]interface{}{}
+
+ slot := NewConnectedSlot(s.slot, attrs)
+ c.Assert(slot, NotNil)
+ plug := NewConnectedPlug(s.plug, attrs)
+ c.Assert(plug, NotNil)
+
+ v, ok := slot.Lookup("a")
+ c.Assert(ok, Equals, false)
+ c.Assert(v, IsNil)
+
+ v, ok = plug.Lookup("a")
+ c.Assert(ok, Equals, false)
+ c.Assert(v, IsNil)
+}
+
func (s *connSuite) TestDynamicPlugAttrs(c *C) {
attrs := map[string]interface{}{
"foo": "bar",
diff --git a/interfaces/core.go b/interfaces/core.go
index 48d38c1174..6dfe292090 100644
--- a/interfaces/core.go
+++ b/interfaces/core.go
@@ -133,22 +133,22 @@ func (conn *ConnRef) ID() string {
}
// ParseConnRef parses an ID string
-func ParseConnRef(id string) (ConnRef, error) {
+func ParseConnRef(id string) (*ConnRef, error) {
var conn ConnRef
parts := strings.SplitN(id, " ", 2)
if len(parts) != 2 {
- return conn, fmt.Errorf("malformed connection identifier: %q", id)
+ return nil, fmt.Errorf("malformed connection identifier: %q", id)
}
plugParts := strings.Split(parts[0], ":")
slotParts := strings.Split(parts[1], ":")
if len(plugParts) != 2 || len(slotParts) != 2 {
- return conn, fmt.Errorf("malformed connection identifier: %q", id)
+ return nil, fmt.Errorf("malformed connection identifier: %q", id)
}
conn.PlugRef.Snap = plugParts[0]
conn.PlugRef.Name = plugParts[1]
conn.SlotRef.Snap = slotParts[0]
conn.SlotRef.Name = slotParts[1]
- return conn, nil
+ return &conn, nil
}
// Interface describes a group of interchangeable capabilities with common features.
diff --git a/interfaces/core_test.go b/interfaces/core_test.go
index 236a32b89a..94c4887d30 100644
--- a/interfaces/core_test.go
+++ b/interfaces/core_test.go
@@ -181,7 +181,7 @@ func (s *CoreSuite) TestConnRefID(c *C) {
func (s *CoreSuite) TestParseConnRef(c *C) {
ref, err := ParseConnRef("consumer:plug producer:slot")
c.Assert(err, IsNil)
- c.Check(ref, DeepEquals, ConnRef{
+ c.Check(ref, DeepEquals, &ConnRef{
PlugRef: PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: SlotRef{Snap: "producer", Name: "slot"},
})
diff --git a/interfaces/dbus/backend.go b/interfaces/dbus/backend.go
index e7f0cc6af3..816fea100b 100644
--- a/interfaces/dbus/backend.go
+++ b/interfaces/dbus/backend.go
@@ -168,3 +168,8 @@ func addContent(securityTag string, snippet string, content map[string]*osutil.F
func (b *Backend) NewSpecification() interfaces.Specification {
return &Specification{}
}
+
+// SandboxFeatures returns list of features supported by snapd for dbus communication.
+func (b *Backend) SandboxFeatures() []string {
+ return []string{"mediated-bus-access"}
+}
diff --git a/interfaces/dbus/backend_test.go b/interfaces/dbus/backend_test.go
index 41dbeb7bbf..40f6602593 100644
--- a/interfaces/dbus/backend_test.go
+++ b/interfaces/dbus/backend_test.go
@@ -274,3 +274,7 @@ func (s *backendSuite) TestAppBoundIfaces(c *C) {
_, err = os.Stat(filepath.Join(dirs.SnapBusPolicyDir, "snap.samba.nmbd.conf"))
c.Check(err, IsNil)
}
+
+func (s *backendSuite) TestSandboxFeatures(c *C) {
+ c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"mediated-bus-access"})
+}
diff --git a/interfaces/ifacetest/backend.go b/interfaces/ifacetest/backend.go
index 910bc4f5d3..d4b8b09d62 100644
--- a/interfaces/ifacetest/backend.go
+++ b/interfaces/ifacetest/backend.go
@@ -35,6 +35,8 @@ type TestSecurityBackend struct {
SetupCallback func(snapInfo *snap.Info, opts interfaces.ConfinementOptions, repo *interfaces.Repository) error
// RemoveCallback is a callback that is optionally called in Remove
RemoveCallback func(snapName string) error
+ // SandboxFeaturesCallback is a callback that is optionally called in SandboxFeatures
+ SandboxFeaturesCallback func() []string
}
// TestSetupCall stores details about calls to TestSecurityBackend.Setup
@@ -76,3 +78,10 @@ func (b *TestSecurityBackend) Remove(snapName string) error {
func (b *TestSecurityBackend) NewSpecification() interfaces.Specification {
return &Specification{}
}
+
+func (b *TestSecurityBackend) SandboxFeatures() []string {
+ if b.SandboxFeaturesCallback == nil {
+ return nil
+ }
+ return b.SandboxFeaturesCallback()
+}
diff --git a/interfaces/kmod/backend.go b/interfaces/kmod/backend.go
index 7cc5a3295c..3896b561ff 100644
--- a/interfaces/kmod/backend.go
+++ b/interfaces/kmod/backend.go
@@ -131,3 +131,8 @@ func deriveContent(spec *Specification, snapInfo *snap.Info) (map[string]*osutil
func (b *Backend) NewSpecification() interfaces.Specification {
return &Specification{}
}
+
+// SandboxFeatures returns the list of features supported by snapd for loading kernel modules.
+func (b *Backend) SandboxFeatures() []string {
+ return []string{"mediated-modprobe"}
+}
diff --git a/interfaces/kmod/backend_test.go b/interfaces/kmod/backend_test.go
index 0894ba2538..c89e79ef20 100644
--- a/interfaces/kmod/backend_test.go
+++ b/interfaces/kmod/backend_test.go
@@ -131,3 +131,7 @@ func (s *backendSuite) TestSecurityIsStable(c *C) {
s.RemoveSnap(c, snapInfo)
}
}
+
+func (s *backendSuite) TestSandboxFeatures(c *C) {
+ c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"mediated-modprobe"})
+}
diff --git a/interfaces/mount/backend.go b/interfaces/mount/backend.go
index e66412be80..34cbccc211 100644
--- a/interfaces/mount/backend.go
+++ b/interfaces/mount/backend.go
@@ -121,3 +121,17 @@ func deriveContent(spec *Specification, snapInfo *snap.Info) map[string]*osutil.
func (b *Backend) NewSpecification() interfaces.Specification {
return &Specification{}
}
+
+// SandboxFeatures returns the list of features supported by snapd for composing mount namespaces.
+func (b *Backend) SandboxFeatures() []string {
+ return []string{
+ "freezer-cgroup-v1", /* Snapd creates a freezer cgroup (v1) for each snap */
+ "layouts-beta", /* Mount profiles take layout data into account (experimental) */
+ "mount-namespace", /* Snapd creates a mount namespace for each snap */
+ "per-snap-persistency", /* Per-snap profiles are persisted across invocations */
+ "per-snap-profiles", /* Per-snap profiles allow changing mount namespace of a given snap */
+ "per-snap-updates", /* Changes to per-snap mount profiles are applied instantly */
+ "per-snap-user-profiles", /* Per-snap profiles allow changing mount namespace of a given snap for a given user */
+ "stale-base-invalidation", /* Mount namespaces that go stale because base snap changes are automatically invalidated */
+ }
+}
diff --git a/interfaces/mount/backend_test.go b/interfaces/mount/backend_test.go
index 9358e39641..49e97164e0 100644
--- a/interfaces/mount/backend_test.go
+++ b/interfaces/mount/backend_test.go
@@ -169,3 +169,16 @@ func (s *backendSuite) TestSetupSetsupWithoutDir(c *C) {
os.Remove(dirs.SnapMountPolicyDir)
s.InstallSnap(c, interfaces.ConfinementOptions{}, mockSnapYaml, 0)
}
+
+func (s *backendSuite) TestSandboxFeatures(c *C) {
+ c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{
+ "freezer-cgroup-v1",
+ "layouts-beta",
+ "mount-namespace",
+ "per-snap-persistency",
+ "per-snap-profiles",
+ "per-snap-updates",
+ "per-snap-user-profiles",
+ "stale-base-invalidation",
+ })
+}
diff --git a/interfaces/policy/helpers.go b/interfaces/policy/helpers.go
index 105e7085ea..7e78519225 100644
--- a/interfaces/policy/helpers.go
+++ b/interfaces/policy/helpers.go
@@ -81,10 +81,10 @@ func checkOnClassic(c *asserts.OnClassicConstraint) error {
}
func checkPlugConnectionConstraints1(connc *ConnectCandidate, cstrs *asserts.PlugConnectionConstraints) error {
- if err := cstrs.PlugAttributes.Check(connc.plugAttrs(), connc); err != nil {
+ if err := cstrs.PlugAttributes.Check(connc.Plug, connc); err != nil {
return err
}
- if err := cstrs.SlotAttributes.Check(connc.slotAttrs(), connc); err != nil {
+ if err := cstrs.SlotAttributes.Check(connc.Slot, connc); err != nil {
return err
}
if err := checkSnapType(connc.slotSnapType(), cstrs.SlotSnapTypes); err != nil {
@@ -121,10 +121,10 @@ func checkPlugConnectionConstraints(connc *ConnectCandidate, cstrs []*asserts.Pl
}
func checkSlotConnectionConstraints1(connc *ConnectCandidate, cstrs *asserts.SlotConnectionConstraints) error {
- if err := cstrs.PlugAttributes.Check(connc.plugAttrs(), connc); err != nil {
+ if err := cstrs.PlugAttributes.Check(connc.Plug, connc); err != nil {
return err
}
- if err := cstrs.SlotAttributes.Check(connc.slotAttrs(), connc); err != nil {
+ if err := cstrs.SlotAttributes.Check(connc.Slot, connc); err != nil {
return err
}
if err := checkSnapType(connc.plugSnapType(), cstrs.PlugSnapTypes); err != nil {
@@ -162,7 +162,7 @@ func checkSlotConnectionConstraints(connc *ConnectCandidate, cstrs []*asserts.Sl
func checkSlotInstallationConstraints1(slot *snap.SlotInfo, cstrs *asserts.SlotInstallationConstraints) error {
// TODO: allow evaluated attr constraints here too?
- if err := cstrs.SlotAttributes.Check(slot.Attrs, nil); err != nil {
+ if err := cstrs.SlotAttributes.Check(slot, nil); err != nil {
return err
}
if err := checkSnapType(slot.Snap.Type, cstrs.SlotSnapTypes); err != nil {
@@ -191,7 +191,7 @@ func checkSlotInstallationConstraints(slot *snap.SlotInfo, cstrs []*asserts.Slot
func checkPlugInstallationConstraints1(plug *snap.PlugInfo, cstrs *asserts.PlugInstallationConstraints) error {
// TODO: allow evaluated attr constraints here too?
- if err := cstrs.PlugAttributes.Check(plug.Attrs, nil); err != nil {
+ if err := cstrs.PlugAttributes.Check(plug, nil); err != nil {
return err
}
if err := checkSnapType(plug.Snap.Type, cstrs.PlugSnapTypes); err != nil {
diff --git a/interfaces/policy/helpers_test.go b/interfaces/policy/helpers_test.go
index cfaf766782..2d5c08ced4 100644
--- a/interfaces/policy/helpers_test.go
+++ b/interfaces/policy/helpers_test.go
@@ -20,9 +20,11 @@
package policy_test
import (
- . "gopkg.in/check.v1"
-
+ "github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/policy"
+ "github.com/snapcore/snapd/snap/snaptest"
+
+ . "gopkg.in/check.v1"
)
type helpersSuite struct{}
@@ -30,25 +32,51 @@ type helpersSuite struct{}
var _ = Suite(&helpersSuite{})
func (s *helpersSuite) TestNestedGet(c *C) {
- _, err := policy.NestedGet("slot", nil, "a")
- c.Check(err, ErrorMatches, `slot attribute "a" not found`)
+ consumer := snaptest.MockInfo(c, `
+name: consumer
+version: 0
+apps:
+ app:
+plugs:
+ plug:
+ interface: interface
+`, nil)
+ plugInfo := consumer.Plugs["plug"]
+ plug := interfaces.NewConnectedPlug(plugInfo, map[string]interface{}{
+ "a": "123",
+ })
- _, err = policy.NestedGet("plug", map[string]interface{}{
+ producer := snaptest.MockInfo(c, `
+name: producer
+version: 0
+apps:
+ app:
+slots:
+ slot:
+ interface: interface
+`, nil)
+ slotInfo := producer.Slots["slot"]
+ slot := interfaces.NewConnectedSlot(slotInfo, map[string]interface{}{
"a": "123",
- }, "a.b")
+ })
+
+ _, err := policy.NestedGet("slot", slot, "b")
+ c.Check(err, ErrorMatches, `slot attribute "b" not found`)
+
+ _, err = policy.NestedGet("plug", plug, "a.b")
c.Check(err, ErrorMatches, `plug attribute "a\.b" not found`)
- v, err := policy.NestedGet("slot", map[string]interface{}{
- "a": "123",
- }, "a")
+ v, err := policy.NestedGet("slot", slot, "a")
c.Check(err, IsNil)
c.Check(v, Equals, "123")
- v, err = policy.NestedGet("slot", map[string]interface{}{
+ slot = interfaces.NewConnectedSlot(slotInfo, map[string]interface{}{
"a": map[string]interface{}{
"b": []interface{}{"1", "2", "3"},
},
- }, "a.b")
+ })
+
+ v, err = policy.NestedGet("slot", slot, "a.b")
c.Check(err, IsNil)
c.Check(v, DeepEquals, []interface{}{"1", "2", "3"})
}
diff --git a/interfaces/policy/policy.go b/interfaces/policy/policy.go
index ec122c21d7..7d9c5b3052 100644
--- a/interfaces/policy/policy.go
+++ b/interfaces/policy/policy.go
@@ -24,11 +24,9 @@ package policy
import (
"fmt"
- "strings"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/interfaces"
- "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/snap"
)
@@ -125,68 +123,22 @@ type ConnectCandidate struct {
SlotSnapDeclaration *asserts.SnapDeclaration
BaseDeclaration *asserts.BaseDeclaration
-
- // Static + dynamic attributes, merged lazily by plugAttrs/slotAttrs below (FIXME: remove it)
- mergedPlugAttrs map[string]interface{}
- mergedSlotAttrs map[string]interface{}
}
-func mergedAttributes(staticAttrs, dynamicAttrs map[string]interface{}, errorContext string) map[string]interface{} {
- merged := make(map[string]interface{})
- for k, v := range staticAttrs {
- merged[k] = v
- }
- for k, v := range dynamicAttrs {
- if _, ok := merged[k]; ok {
- // Safeguard. This should never happen as it's prevented
- // when attributes are populated at higher levels.
- logger.Noticef("internal error: attempted to overwrite static attribute %q (%s)", k, errorContext)
- continue
- }
- merged[k] = v
- }
- return merged
-}
-
-func (connc *ConnectCandidate) plugAttrs() map[string]interface{} {
- // FIXME: change policy code to use Attrer interface, remove merging.
- if connc.mergedPlugAttrs == nil {
- connc.mergedPlugAttrs = mergedAttributes(connc.Plug.StaticAttrs(), connc.Plug.DynamicAttrs(), fmt.Sprintf("plug %q of snap %q", connc.Plug.Name(), connc.Plug.Snap().Name()))
- }
- return connc.mergedPlugAttrs
-}
-
-func (connc *ConnectCandidate) slotAttrs() map[string]interface{} {
- // FIXME: change policy code to use Attrer interface, remove merging.
- if connc.mergedSlotAttrs == nil {
- connc.mergedSlotAttrs = mergedAttributes(connc.Slot.StaticAttrs(), connc.Slot.DynamicAttrs(), fmt.Sprintf("slot %q of snap %q", connc.Slot.Name(), connc.Slot.Snap().Name()))
- }
- return connc.mergedSlotAttrs
-}
-
-func nestedGet(which string, attrs map[string]interface{}, path string) (interface{}, error) {
- notFound := fmt.Errorf("%s attribute %q not found", which, path)
- comps := strings.Split(path, ".")
- var v interface{} = attrs
- for _, comp := range comps {
- m, ok := v.(map[string]interface{})
- if !ok {
- return nil, notFound
- }
- v, ok = m[comp]
- if !ok {
- return nil, notFound
- }
+func nestedGet(which string, attrs interfaces.Attrer, path string) (interface{}, error) {
+ val, ok := attrs.Lookup(path)
+ if !ok {
+ return nil, fmt.Errorf("%s attribute %q not found", which, path)
}
- return v, nil
+ return val, nil
}
func (connc *ConnectCandidate) PlugAttr(arg string) (interface{}, error) {
- return nestedGet("plug", connc.plugAttrs(), arg)
+ return nestedGet("plug", connc.Plug, arg)
}
func (connc *ConnectCandidate) SlotAttr(arg string) (interface{}, error) {
- return nestedGet("slot", connc.slotAttrs(), arg)
+ return nestedGet("slot", connc.Slot, arg)
}
func (connc *ConnectCandidate) plugSnapType() snap.Type {
diff --git a/interfaces/repo.go b/interfaces/repo.go
index c4b97c3026..e4321573d5 100644
--- a/interfaces/repo.go
+++ b/interfaces/repo.go
@@ -397,22 +397,20 @@ func (r *Repository) RemoveSlot(snapName, slotName string) error {
// ResolveConnect resolves potentially missing plug or slot names and returns a
// fully populated connection reference.
-func (r *Repository) ResolveConnect(plugSnapName, plugName, slotSnapName, slotName string) (ConnRef, error) {
+func (r *Repository) ResolveConnect(plugSnapName, plugName, slotSnapName, slotName string) (*ConnRef, error) {
r.m.Lock()
defer r.m.Unlock()
- ref := ConnRef{}
-
if plugSnapName == "" {
- return ref, fmt.Errorf("cannot resolve connection, plug snap name is empty")
+ return nil, fmt.Errorf("cannot resolve connection, plug snap name is empty")
}
if plugName == "" {
- return ref, fmt.Errorf("cannot resolve connection, plug name is empty")
+ return nil, fmt.Errorf("cannot resolve connection, plug name is empty")
}
// Ensure that such plug exists
plug := r.plugs[plugSnapName][plugName]
if plug == nil {
- return ref, fmt.Errorf("snap %q has no plug named %q", plugSnapName, plugName)
+ return nil, fmt.Errorf("snap %q has no plug named %q", plugSnapName, plugName)
}
if slotSnapName == "" {
@@ -425,7 +423,7 @@ func (r *Repository) ResolveConnect(plugSnapName, plugName, slotSnapName, slotNa
default:
// XXX: perhaps this should not be an error and instead it should
// silently assume "core" now?
- return ref, fmt.Errorf("cannot resolve connection, slot snap name is empty")
+ return nil, fmt.Errorf("cannot resolve connection, slot snap name is empty")
}
}
if slotName == "" {
@@ -439,27 +437,26 @@ func (r *Repository) ResolveConnect(plugSnapName, plugName, slotSnapName, slotNa
}
switch len(candidates) {
case 0:
- return ref, fmt.Errorf("snap %q has no %q interface slots", slotSnapName, plug.Interface)
+ return nil, fmt.Errorf("snap %q has no %q interface slots", slotSnapName, plug.Interface)
case 1:
slotName = candidates[0]
default:
sort.Strings(candidates)
- return ref, fmt.Errorf("snap %q has multiple %q interface slots: %s", slotSnapName, plug.Interface, strings.Join(candidates, ", "))
+ return nil, fmt.Errorf("snap %q has multiple %q interface slots: %s", slotSnapName, plug.Interface, strings.Join(candidates, ", "))
}
}
// Ensure that such slot exists
slot := r.slots[slotSnapName][slotName]
if slot == nil {
- return ref, fmt.Errorf("snap %q has no slot named %q", slotSnapName, slotName)
+ return nil, fmt.Errorf("snap %q has no slot named %q", slotSnapName, slotName)
}
// Ensure that plug and slot are compatible
if slot.Interface != plug.Interface {
- return ref, fmt.Errorf("cannot connect %s:%s (%q interface) to %s:%s (%q interface)",
+ return nil, fmt.Errorf("cannot connect %s:%s (%q interface) to %s:%s (%q interface)",
plugSnapName, plugName, plug.Interface, slotSnapName, slotName, slot.Interface)
}
- ref = *NewConnRef(plug, slot)
- return ref, nil
+ return NewConnRef(plug, slot), nil
}
// ResolveDisconnect resolves potentially missing plug or slot names and
@@ -477,7 +474,7 @@ func (r *Repository) ResolveConnect(plugSnapName, plugName, slotSnapName, slotNa
// In both cases the snap name can be omitted to implicitly refer to the core
// snap. If there's no core snap it is simply assumed to be called "core" to
// provide consistent error messages.
-func (r *Repository) ResolveDisconnect(plugSnapName, plugName, slotSnapName, slotName string) ([]ConnRef, error) {
+func (r *Repository) ResolveDisconnect(plugSnapName, plugName, slotSnapName, slotName string) ([]*ConnRef, error) {
r.m.Lock()
defer r.m.Unlock()
@@ -517,7 +514,7 @@ func (r *Repository) ResolveDisconnect(plugSnapName, plugName, slotSnapName, slo
return nil, fmt.Errorf("cannot disconnect %s:%s from %s:%s, it is not connected",
plugSnapName, plugName, slotSnapName, slotName)
}
- return []ConnRef{*NewConnRef(plug, slot)}, nil
+ return []*ConnRef{NewConnRef(plug, slot)}, nil
// 2: <snap>:<plug or slot> (through 1st pair)
// Return a list of connections involving specified plug or slot.
case plugName != "" && slotName == "" && slotSnapName == "":
@@ -554,7 +551,7 @@ type PolicyFunc func(*ConnectedPlug, *ConnectedSlot) (bool, error)
// Connect establishes a connection between a plug and a slot.
// The plug and the slot must have the same interface.
// When connections are reloaded policyCheck is null (we don't check policy again).
-func (r *Repository) Connect(ref ConnRef, plugDynamicAttrs, slotDynamicAttrs map[string]interface{}, policyCheck PolicyFunc) (*Connection, error) {
+func (r *Repository) Connect(ref *ConnRef, plugDynamicAttrs, slotDynamicAttrs map[string]interface{}, policyCheck PolicyFunc) (*Connection, error) {
r.m.Lock()
defer r.m.Unlock()
@@ -665,21 +662,21 @@ func (r *Repository) Disconnect(plugSnapName, plugName, slotSnapName, slotName s
// Connected returns references for all connections that are currently
// established with the provided plug or slot.
-func (r *Repository) Connected(snapName, plugOrSlotName string) ([]ConnRef, error) {
+func (r *Repository) Connected(snapName, plugOrSlotName string) ([]*ConnRef, error) {
r.m.Lock()
defer r.m.Unlock()
return r.connected(snapName, plugOrSlotName)
}
-func (r *Repository) connected(snapName, plugOrSlotName string) ([]ConnRef, error) {
+func (r *Repository) connected(snapName, plugOrSlotName string) ([]*ConnRef, error) {
if snapName == "" {
snapName, _ = r.guessCoreSnapName()
if snapName == "" {
return nil, fmt.Errorf("internal error: cannot obtain core snap name while computing connections")
}
}
- var conns []ConnRef
+ var conns []*ConnRef
if plugOrSlotName == "" {
return nil, fmt.Errorf("plug or slot name is empty")
}
@@ -691,14 +688,14 @@ func (r *Repository) connected(snapName, plugOrSlotName string) ([]ConnRef, erro
if plug, ok := r.plugs[snapName][plugOrSlotName]; ok {
for slotInfo := range r.plugSlots[plug] {
- connRef := *NewConnRef(plug, slotInfo)
+ connRef := NewConnRef(plug, slotInfo)
conns = append(conns, connRef)
}
}
if slot, ok := r.slots[snapName][plugOrSlotName]; ok {
for plugInfo := range r.slotPlugs[slot] {
- connRef := *NewConnRef(plugInfo, slot)
+ connRef := NewConnRef(plugInfo, slot)
conns = append(conns, connRef)
}
}
@@ -706,7 +703,7 @@ func (r *Repository) connected(snapName, plugOrSlotName string) ([]ConnRef, erro
return conns, nil
}
-func (r *Repository) Connections(snapName string) ([]ConnRef, error) {
+func (r *Repository) Connections(snapName string) ([]*ConnRef, error) {
r.m.Lock()
defer r.m.Unlock()
@@ -717,16 +714,16 @@ func (r *Repository) Connections(snapName string) ([]ConnRef, error) {
}
}
- var conns []ConnRef
+ var conns []*ConnRef
for _, plugInfo := range r.plugs[snapName] {
for slotInfo := range r.plugSlots[plugInfo] {
- connRef := *NewConnRef(plugInfo, slotInfo)
+ connRef := NewConnRef(plugInfo, slotInfo)
conns = append(conns, connRef)
}
}
for _, slotInfo := range r.slots[snapName] {
for plugInfo := range r.slotPlugs[slotInfo] {
- connRef := *NewConnRef(plugInfo, slotInfo)
+ connRef := NewConnRef(plugInfo, slotInfo)
conns = append(conns, connRef)
}
}
@@ -747,7 +744,7 @@ func (r *Repository) guessCoreSnapName() (string, error) {
}
// DisconnectAll disconnects all provided connection references.
-func (r *Repository) DisconnectAll(conns []ConnRef) {
+func (r *Repository) DisconnectAll(conns []*ConnRef) {
r.m.Lock()
defer r.m.Unlock()
diff --git a/interfaces/repo_test.go b/interfaces/repo_test.go
index a47f3ff728..9e6a05e9b0 100644
--- a/interfaces/repo_test.go
+++ b/interfaces/repo_test.go
@@ -313,7 +313,7 @@ func (s *RepositorySuite) TestRemovePlugFailsWhenPlugIsConnected(c *C) {
err = s.testRepo.AddSlot(s.slot)
c.Assert(err, IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(*connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
// Removing a plug used by a slot returns an appropriate error
err = s.testRepo.RemovePlug(s.plug.Snap.Name(), s.plug.Name)
@@ -559,7 +559,7 @@ func (s *RepositorySuite) TestRemoveSlotFailsWhenSlotIsConnected(c *C) {
err = s.testRepo.AddSlot(s.slot)
c.Assert(err, IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(*connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
// Removing a slot occupied by a plug returns an appropriate error
err = s.testRepo.RemoveSlot(s.slot.Snap.Name(), s.slot.Name)
@@ -576,7 +576,7 @@ func (s *RepositorySuite) TestResolveConnectExplicit(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "slot")
c.Check(err, IsNil)
- c.Check(conn, Equals, ConnRef{
+ c.Check(conn, DeepEquals, &ConnRef{
PlugRef: PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: SlotRef{Snap: "producer", Name: "slot"},
})
@@ -596,7 +596,7 @@ slots:
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot")
c.Check(err, IsNil)
- c.Check(conn, Equals, ConnRef{
+ c.Check(conn, DeepEquals, &ConnRef{
PlugRef: PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: SlotRef{Snap: "core", Name: "slot"},
})
@@ -616,7 +616,7 @@ slots:
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot")
c.Check(err, IsNil)
- c.Check(conn, Equals, ConnRef{
+ c.Check(conn, DeepEquals, &ConnRef{
PlugRef: PlugRef{Snap: "consumer", Name: "plug"},
SlotRef: SlotRef{Snap: "ubuntu-core", Name: "slot"},
})
@@ -664,7 +664,7 @@ slots:
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "")
c.Check(err, ErrorMatches, `snap "core" has no "interface" interface slots`)
- c.Check(conn, Equals, ConnRef{})
+ c.Check(conn, IsNil)
}
// ResolveConnect detects ambiguities when slot snap name is empty
@@ -684,28 +684,28 @@ slots:
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "")
c.Check(err, ErrorMatches, `snap "core" has multiple "interface" interface slots: slot-a, slot-b`)
- c.Check(conn, Equals, ConnRef{})
+ c.Check(conn, IsNil)
}
// Pug snap name cannot be empty
func (s *RepositorySuite) TestResolveConnectEmptyPlugSnapName(c *C) {
conn, err := s.testRepo.ResolveConnect("", "plug", "producer", "slot")
c.Check(err, ErrorMatches, "cannot resolve connection, plug snap name is empty")
- c.Check(conn, Equals, ConnRef{})
+ c.Check(conn, IsNil)
}
// Plug name cannot be empty
func (s *RepositorySuite) TestResolveConnectEmptyPlugName(c *C) {
conn, err := s.testRepo.ResolveConnect("consumer", "", "producer", "slot")
c.Check(err, ErrorMatches, "cannot resolve connection, plug name is empty")
- c.Check(conn, Equals, ConnRef{})
+ c.Check(conn, IsNil)
}
// Plug must exist
func (s *RepositorySuite) TestResolveNoSuchPlug(c *C) {
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "consumer", "slot")
c.Check(err, ErrorMatches, `snap "consumer" has no plug named "plug"`)
- c.Check(conn, Equals, ConnRef{})
+ c.Check(conn, IsNil)
}
// Slot snap name cannot be empty if there's no core snap around
@@ -713,7 +713,7 @@ func (s *RepositorySuite) TestResolveConnectEmptySlotSnapName(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "", "slot")
c.Check(err, ErrorMatches, "cannot resolve connection, slot snap name is empty")
- c.Check(conn, Equals, ConnRef{})
+ c.Check(conn, IsNil)
}
// Slot name cannot be empty if there's no core snap around
@@ -721,7 +721,7 @@ func (s *RepositorySuite) TestResolveConnectEmptySlotName(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "")
c.Check(err, ErrorMatches, `snap "producer" has no "interface" interface slots`)
- c.Check(conn, Equals, ConnRef{})
+ c.Check(conn, IsNil)
}
// Slot must exists
@@ -729,7 +729,7 @@ func (s *RepositorySuite) TestResolveNoSuchSlot(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "slot")
c.Check(err, ErrorMatches, `snap "producer" has no slot named "slot"`)
- c.Check(conn, Equals, ConnRef{})
+ c.Check(conn, IsNil)
}
// Plug and slot must have matching types
@@ -746,7 +746,7 @@ func (s *RepositorySuite) TestResolveIncompatibleTypes(c *C) {
conn, err := s.testRepo.ResolveConnect("consumer", "plug", "producer", "slot")
c.Check(err, ErrorMatches,
`cannot connect consumer:plug \("other-interface" interface\) to producer:slot \("interface" interface\)`)
- c.Check(conn, Equals, ConnRef{})
+ c.Check(conn, IsNil)
}
// Tests for Repository.ResolveDisconnect()
@@ -977,7 +977,7 @@ func (s *RepositorySuite) TestResolveDisconnectMatrixTypical(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err := s.testRepo.Connect(*connRef, nil, nil, nil)
+ _, err := s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
scenarios := []struct {
@@ -1047,7 +1047,7 @@ func (s *RepositorySuite) TestResolveDisconnectMatrixTypical(c *C) {
c.Check(connRefList, HasLen, 0)
} else {
c.Check(err, IsNil)
- c.Check(connRefList, DeepEquals, []ConnRef{*connRef})
+ c.Check(connRefList, DeepEquals, []*ConnRef{connRef})
}
}
}
@@ -1059,7 +1059,7 @@ func (s *RepositorySuite) TestConnectFailsWhenPlugDoesNotExist(c *C) {
c.Assert(err, IsNil)
// Connecting an unknown plug returns an appropriate error
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(*connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, ErrorMatches, `cannot connect plug "plug" from snap "consumer": no such plug`)
}
@@ -1068,7 +1068,7 @@ func (s *RepositorySuite) TestConnectFailsWhenSlotDoesNotExist(c *C) {
c.Assert(err, IsNil)
// Connecting to an unknown slot returns an error
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(*connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, ErrorMatches, `cannot connect slot "slot" from snap "producer": no such slot`)
}
@@ -1078,7 +1078,7 @@ func (s *RepositorySuite) TestConnectSucceedsWhenIdenticalConnectExists(c *C) {
err = s.testRepo.AddSlot(s.slot)
c.Assert(err, IsNil)
connRef := NewConnRef(s.plug, s.slot)
- conn, err := s.testRepo.Connect(*connRef, nil, nil, nil)
+ conn, err := s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
c.Assert(conn, NotNil)
c.Assert(conn.Plug, NotNil)
@@ -1086,7 +1086,7 @@ func (s *RepositorySuite) TestConnectSucceedsWhenIdenticalConnectExists(c *C) {
c.Assert(conn.Plug.Name(), Equals, "plug")
c.Assert(conn.Slot.Name(), Equals, "slot")
// Connecting exactly the same thing twice succeeds without an error but does nothing.
- _, err = s.testRepo.Connect(*connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
// Only one connection is actually present.
c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{
@@ -1111,7 +1111,7 @@ func (s *RepositorySuite) TestConnectFailsWhenSlotAndPlugAreIncompatible(c *C) {
c.Assert(err, IsNil)
// Connecting a plug to an incompatible slot fails with an appropriate error
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(*connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, ErrorMatches, `cannot connect plug "consumer:plug" \(interface "other-interface"\) to "producer:slot" \(interface "interface"\)`)
}
@@ -1122,7 +1122,7 @@ func (s *RepositorySuite) TestConnectSucceeds(c *C) {
c.Assert(err, IsNil)
// Connecting a plug works okay
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(*connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
}
@@ -1166,9 +1166,9 @@ func (s *RepositorySuite) TestDisconnectFailsWhenNotConnected(c *C) {
func (s *RepositorySuite) TestDisconnectSucceeds(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- _, err := s.testRepo.Connect(*NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
c.Assert(err, IsNil)
- _, err = s.testRepo.Connect(*NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err = s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
c.Assert(err, IsNil)
err = s.testRepo.Disconnect(s.plug.Snap.Name(), s.plug.Name, s.slot.Snap.Name(), s.slot.Name)
c.Assert(err, IsNil)
@@ -1204,16 +1204,16 @@ func (s *RepositorySuite) TestConnectedFailsWithoutPlugOrSlot(c *C) {
func (s *RepositorySuite) TestConnectedFindsConnections(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- _, err := s.testRepo.Connect(*NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
c.Assert(err, IsNil)
conns, err := s.testRepo.Connected(s.plug.Snap.Name(), s.plug.Name)
c.Assert(err, IsNil)
- c.Check(conns, DeepEquals, []ConnRef{*NewConnRef(s.plug, s.slot)})
+ c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)})
conns, err = s.testRepo.Connected(s.slot.Snap.Name(), s.slot.Name)
c.Assert(err, IsNil)
- c.Check(conns, DeepEquals, []ConnRef{*NewConnRef(s.plug, s.slot)})
+ c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)})
}
// Connected uses the core snap if snap name is empty
@@ -1225,28 +1225,28 @@ func (s *RepositorySuite) TestConnectedFindsCoreSnap(c *C) {
}
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(slot), IsNil)
- _, err := s.testRepo.Connect(*NewConnRef(s.plug, slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, slot), nil, nil, nil)
c.Assert(err, IsNil)
conns, err := s.testRepo.Connected("", s.slot.Name)
c.Assert(err, IsNil)
- c.Check(conns, DeepEquals, []ConnRef{*NewConnRef(s.plug, slot)})
+ c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, slot)})
}
// Connected finds connections when asked from plug or from slot side
func (s *RepositorySuite) TestConnections(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- _, err := s.testRepo.Connect(*NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
c.Assert(err, IsNil)
conns, err := s.testRepo.Connections(s.plug.Snap.Name())
c.Assert(err, IsNil)
- c.Check(conns, DeepEquals, []ConnRef{*NewConnRef(s.plug, s.slot)})
+ c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)})
conns, err = s.testRepo.Connections(s.slot.Snap.Name())
c.Assert(err, IsNil)
- c.Check(conns, DeepEquals, []ConnRef{*NewConnRef(s.plug, s.slot)})
+ c.Check(conns, DeepEquals, []*ConnRef{NewConnRef(s.plug, s.slot)})
conns, err = s.testRepo.Connections("abc")
c.Assert(err, IsNil)
@@ -1258,10 +1258,10 @@ func (s *RepositorySuite) TestConnections(c *C) {
func (s *RepositorySuite) TestDisconnectAll(c *C) {
c.Assert(s.testRepo.AddPlug(s.plug), IsNil)
c.Assert(s.testRepo.AddSlot(s.slot), IsNil)
- _, err := s.testRepo.Connect(*NewConnRef(s.plug, s.slot), nil, nil, nil)
+ _, err := s.testRepo.Connect(NewConnRef(s.plug, s.slot), nil, nil, nil)
c.Assert(err, IsNil)
- conns := []ConnRef{*NewConnRef(s.plug, s.slot)}
+ conns := []*ConnRef{NewConnRef(s.plug, s.slot)}
s.testRepo.DisconnectAll(conns)
c.Assert(s.testRepo.Interfaces(), DeepEquals, &Interfaces{
Plugs: []*snap.PlugInfo{s.plug},
@@ -1278,7 +1278,7 @@ func (s *RepositorySuite) TestInterfacesSmokeTest(c *C) {
c.Assert(err, IsNil)
// After connecting the result is as expected
connRef := NewConnRef(s.plug, s.slot)
- _, err = s.testRepo.Connect(*connRef, nil, nil, nil)
+ _, err = s.testRepo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
ifaces := s.testRepo.Interfaces()
c.Assert(ifaces, DeepEquals, &Interfaces{
@@ -1339,7 +1339,7 @@ func (s *RepositorySuite) TestSnapSpecification(c *C) {
// Establish connection between plug and slot
connRef := NewConnRef(s.plug, s.slot)
- _, err = repo.Connect(*connRef, nil, nil, nil)
+ _, err = repo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
// Snaps should get static and connection-specific security now
@@ -1377,7 +1377,7 @@ func (s *RepositorySuite) TestSnapSpecificationFailureWithConnectionSnippets(c *
c.Assert(repo.AddPlug(s.plug), IsNil)
c.Assert(repo.AddSlot(s.slot), IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err := repo.Connect(*connRef, nil, nil, nil)
+ _, err := repo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.Name())
@@ -1407,7 +1407,7 @@ func (s *RepositorySuite) TestSnapSpecificationFailureWithPermanentSnippets(c *C
c.Assert(repo.AddPlug(s.plug), IsNil)
c.Assert(repo.AddSlot(s.slot), IsNil)
connRef := NewConnRef(s.plug, s.slot)
- _, err := repo.Connect(*connRef, nil, nil, nil)
+ _, err := repo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
spec, err := repo.SnapSpecification(testSecurity, s.plug.Snap.Name())
@@ -1662,7 +1662,7 @@ func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedPlug(c *C) {
c.Assert(err, IsNil)
_, err = s.addSnap(c, testProducerYaml)
c.Assert(err, IsNil)
- connRef := ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}}
+ connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}}
_, err = s.repo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
err = s.repo.RemoveSnap("consumer")
@@ -1674,7 +1674,7 @@ func (s *AddRemoveSuite) TestRemoveSnapErrorsOnStillConnectedSlot(c *C) {
c.Assert(err, IsNil)
_, err = s.addSnap(c, testProducerYaml)
c.Assert(err, IsNil)
- connRef := ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}}
+ connRef := &ConnRef{PlugRef: PlugRef{Snap: "consumer", Name: "iface"}, SlotRef: SlotRef{Snap: "producer", Name: "iface"}}
_, err = s.repo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
err = s.repo.RemoveSnap("producer")
@@ -1735,7 +1735,7 @@ func (s *DisconnectSnapSuite) TestNotConnected(c *C) {
}
func (s *DisconnectSnapSuite) TestOutgoingConnection(c *C) {
- connRef := ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}}
+ connRef := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}}
_, err := s.repo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
// Disconnect s1 with which has an outgoing connection to s2
@@ -1746,7 +1746,7 @@ func (s *DisconnectSnapSuite) TestOutgoingConnection(c *C) {
}
func (s *DisconnectSnapSuite) TestIncomingConnection(c *C) {
- connRef := ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}
+ connRef := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}
_, err := s.repo.Connect(connRef, nil, nil, nil)
c.Assert(err, IsNil)
// Disconnect s1 with which has an incoming connection from s2
@@ -1759,10 +1759,10 @@ func (s *DisconnectSnapSuite) TestIncomingConnection(c *C) {
func (s *DisconnectSnapSuite) TestCrossConnection(c *C) {
// This test is symmetric wrt s1 <-> s2 connections
for _, snapName := range []string{"s1", "s2"} {
- connRef1 := ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}}
+ connRef1 := &ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "iface-a"}, SlotRef: SlotRef{Snap: "s2", Name: "iface-a"}}
_, err := s.repo.Connect(connRef1, nil, nil, nil)
c.Assert(err, IsNil)
- connRef2 := ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}
+ connRef2 := &ConnRef{PlugRef: PlugRef{Snap: "s2", Name: "iface-b"}, SlotRef: SlotRef{Snap: "s1", Name: "iface-b"}}
_, err = s.repo.Connect(connRef2, nil, nil, nil)
c.Assert(err, IsNil)
affected, err := s.repo.DisconnectSnap(snapName)
@@ -1892,11 +1892,11 @@ slots:
c.Assert(r.AddSnap(s3), IsNil)
// Connect a few things for the tests below.
- _, err := r.Connect(ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil)
+ _, err := r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil)
c.Assert(err, IsNil)
- _, err = r.Connect(ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil)
+ _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i1"}, SlotRef: SlotRef{Snap: "s2", Name: "i1"}}, nil, nil, nil)
c.Assert(err, IsNil)
- _, err = r.Connect(ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i2"}, SlotRef: SlotRef{Snap: "s3", Name: "i2"}}, nil, nil, nil)
+ _, err = r.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "i2"}, SlotRef: SlotRef{Snap: "s3", Name: "i2"}}, nil, nil, nil)
c.Assert(err, IsNil)
// Without any names or options we get the summary of all the interfaces.
@@ -1986,7 +1986,7 @@ func (s *RepositorySuite) TestBeforeConnectValidation(c *C) {
slotDynAttrs := map[string]interface{}{"attr1": "val1"}
policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil }
- conn, err := s.emptyRepo.Connect(ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, plugDynAttrs, slotDynAttrs, policyCheck)
+ conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, plugDynAttrs, slotDynAttrs, policyCheck)
c.Assert(err, IsNil)
c.Assert(conn, NotNil)
@@ -2021,7 +2021,7 @@ func (s *RepositorySuite) TestBeforeConnectValidationFailure(c *C) {
policyCheck := func(plug *ConnectedPlug, slot *ConnectedSlot) (bool, error) { return true, nil }
- conn, err := s.emptyRepo.Connect(ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, plugDynAttrs, slotDynAttrs, policyCheck)
+ conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, plugDynAttrs, slotDynAttrs, policyCheck)
c.Assert(err, NotNil)
c.Assert(err, ErrorMatches, `cannot connect plug "consumer" of snap "s1": invalid plug`)
c.Assert(conn, IsNil)
@@ -2047,7 +2047,7 @@ func (s *RepositorySuite) TestBeforeConnectValidationPolicyCheckFailure(c *C) {
return false, fmt.Errorf("policy check failed")
}
- conn, err := s.emptyRepo.Connect(ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, plugDynAttrs, slotDynAttrs, policyCheck)
+ conn, err := s.emptyRepo.Connect(&ConnRef{PlugRef: PlugRef{Snap: "s1", Name: "consumer"}, SlotRef: SlotRef{Snap: "s2", Name: "producer"}}, plugDynAttrs, slotDynAttrs, policyCheck)
c.Assert(err, NotNil)
c.Assert(err, ErrorMatches, `policy check failed`)
c.Assert(conn, IsNil)
diff --git a/interfaces/seccomp/backend.go b/interfaces/seccomp/backend.go
index d72bd3a506..40de971199 100644
--- a/interfaces/seccomp/backend.go
+++ b/interfaces/seccomp/backend.go
@@ -48,7 +48,10 @@ import (
"github.com/snapcore/snapd/snap"
)
-var osReadlink = os.Readlink
+var (
+ osReadlink = os.Readlink
+ kernelFeatures = release.SecCompActions
+)
func seccompToBpfPath() string {
// FIXME: use cmd.InternalToolPath here once:
@@ -193,3 +196,16 @@ func addContent(securityTag string, opts interfaces.ConfinementOptions, snippetF
func (b *Backend) NewSpecification() interfaces.Specification {
return &Specification{}
}
+
+// SandboxFeatures returns the list of seccomp features supported by the kernel.
+func (b *Backend) SandboxFeatures() []string {
+ features := kernelFeatures()
+ tags := make([]string, 0, len(features)+1)
+ for _, feature := range features {
+ // Prepend "kernel:" to apparmor kernel features to namespace them and
+ // allow us to introduce our own tags later.
+ tags = append(tags, "kernel:"+feature)
+ }
+ tags = append(tags, "bpf-argument-filtering")
+ return tags
+}
diff --git a/interfaces/seccomp/backend_test.go b/interfaces/seccomp/backend_test.go
index 55fa5b3feb..a6613355c1 100644
--- a/interfaces/seccomp/backend_test.go
+++ b/interfaces/seccomp/backend_test.go
@@ -385,3 +385,10 @@ func (s *backendSuite) TestSystemKeyRetLogUnsupported(c *C) {
c.Assert(profile+".src", Not(testutil.FileContains), "# complain mode logging unavailable\n")
s.RemoveSnap(c, snapInfo)
}
+
+func (s *backendSuite) TestSandboxFeatures(c *C) {
+ restore := seccomp.MockKernelFeatures(func() []string { return []string{"foo", "bar"} })
+ defer restore()
+
+ c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{"kernel:foo", "kernel:bar", "bpf-argument-filtering"})
+}
diff --git a/interfaces/seccomp/export_test.go b/interfaces/seccomp/export_test.go
index c1e460c4c8..285c3f6220 100644
--- a/interfaces/seccomp/export_test.go
+++ b/interfaces/seccomp/export_test.go
@@ -36,3 +36,11 @@ func MockOsReadlink(f func(string) (string, error)) (restore func()) {
osReadlink = realOsReadlink
}
}
+
+func MockKernelFeatures(f func() []string) (resture func()) {
+ old := kernelFeatures
+ kernelFeatures = f
+ return func() {
+ kernelFeatures = old
+ }
+}
diff --git a/interfaces/systemd/backend.go b/interfaces/systemd/backend.go
index 34c0cb3a00..01c3b7c47f 100644
--- a/interfaces/systemd/backend.go
+++ b/interfaces/systemd/backend.go
@@ -126,6 +126,11 @@ func (b *Backend) NewSpecification() interfaces.Specification {
return &Specification{}
}
+// SandboxFeatures returns nil
+func (b *Backend) SandboxFeatures() []string {
+ return nil
+}
+
// deriveContent computes .service files based on requests made to the specification.
func deriveContent(spec *Specification, snapInfo *snap.Info) map[string]*osutil.FileState {
services := spec.Services()
diff --git a/interfaces/systemd/backend_test.go b/interfaces/systemd/backend_test.go
index ba99aa2661..4d538c4778 100644
--- a/interfaces/systemd/backend_test.go
+++ b/interfaces/systemd/backend_test.go
@@ -153,3 +153,7 @@ func (s *backendSuite) TestSettingUpSecurityWithFewerServices(c *C) {
{"systemctl", "daemon-reload"},
})
}
+
+func (s *backendSuite) TestSandboxFeatures(c *C) {
+ c.Assert(s.Backend.SandboxFeatures(), IsNil)
+}
diff --git a/interfaces/udev/backend.go b/interfaces/udev/backend.go
index aac31b85d3..b90070b494 100644
--- a/interfaces/udev/backend.go
+++ b/interfaces/udev/backend.go
@@ -150,3 +150,11 @@ func (b *Backend) deriveContent(spec *Specification, snapInfo *snap.Info) (conte
func (b *Backend) NewSpecification() interfaces.Specification {
return &Specification{}
}
+
+// SandboxFeatures returns the list of features supported by snapd for mediating access to kernel devices.
+func (b *Backend) SandboxFeatures() []string {
+ return []string{
+ "device-cgroup-v1", /* Snapd creates a device group (v1) for each snap */
+ "tagging", /* Tagging dynamically associates new devices with specific snaps */
+ }
+}
diff --git a/interfaces/udev/backend_test.go b/interfaces/udev/backend_test.go
index 95138c5609..a0cf183736 100644
--- a/interfaces/udev/backend_test.go
+++ b/interfaces/udev/backend_test.go
@@ -413,3 +413,10 @@ func (s *backendSuite) TestUpdatingSnapWithoutSlotsToOneWithoutSlots(c *C) {
s.RemoveSnap(c, snapInfo)
}
}
+
+func (s *backendSuite) TestSandboxFeatures(c *C) {
+ c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{
+ "device-cgroup-v1",
+ "tagging",
+ })
+}
diff --git a/overlord/configstate/handler_test.go b/overlord/configstate/handler_test.go
index 90c6bb639b..9fbd4da5ec 100644
--- a/overlord/configstate/handler_test.go
+++ b/overlord/configstate/handler_test.go
@@ -49,10 +49,29 @@ type configureHandlerSuite struct {
var _ = Suite(&configureHandlerSuite{})
func (s *configureHandlerSuite) SetUpTest(c *C) {
+ dirs.SetRootDir(c.MkDir())
+
s.state = state.New(nil)
s.state.Lock()
defer s.state.Unlock()
+ coreSnapYaml := `name: core
+version: 1.0
+type: os
+`
+ snaptest.MockSnap(c, coreSnapYaml, &snap.SideInfo{
+ RealName: "core",
+ Revision: snap.R(1),
+ })
+ snapstate.Set(s.state, "core", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "core", Revision: snap.R(1), SnapID: "core-snap-id"},
+ },
+ Current: snap.R(1),
+ SnapType: "os",
+ })
+
s.restore = snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})
task := s.state.NewTask("test-task", "my test task")
@@ -67,6 +86,7 @@ func (s *configureHandlerSuite) SetUpTest(c *C) {
func (s *configureHandlerSuite) TearDownTest(c *C) {
s.restore()
+ dirs.SetRootDir("/")
}
func (s *configureHandlerSuite) TestBeforeInitializesTransaction(c *C) {
@@ -91,8 +111,6 @@ func (s *configureHandlerSuite) TestBeforeInitializesTransaction(c *C) {
func (s *configureHandlerSuite) TestBeforeInitializesTransactionUseDefaults(c *C) {
r := release.MockOnClassic(false)
defer r()
- dirs.SetRootDir(c.MkDir())
- defer dirs.SetRootDir("/")
const mockGadgetSnapYaml = `
name: canonical-pc
@@ -162,8 +180,6 @@ hooks:
func (s *configureHandlerSuite) TestBeforeUseDefaultsMissingHook(c *C) {
r := release.MockOnClassic(false)
defer r()
- dirs.SetRootDir(c.MkDir())
- defer dirs.SetRootDir("/")
const mockGadgetSnapYaml = `
name: canonical-pc
diff --git a/overlord/devicestate/devicestate_test.go b/overlord/devicestate/devicestate_test.go
index a1e87fdefa..733fbb5093 100644
--- a/overlord/devicestate/devicestate_test.go
+++ b/overlord/devicestate/devicestate_test.go
@@ -2120,7 +2120,7 @@ func makeMockRepoWithConnectedSnaps(c *C, st *state.State, info11, core11 *snap.
c.Assert(err, IsNil)
err = repo.AddSnap(core11)
c.Assert(err, IsNil)
- _, err = repo.Connect(interfaces.ConnRef{
+ _, err = repo.Connect(&interfaces.ConnRef{
PlugRef: interfaces.PlugRef{Snap: info11.Name(), Name: ifname},
SlotRef: interfaces.SlotRef{Snap: core11.Name(), Name: ifname},
}, nil, nil, nil)
diff --git a/overlord/hookstate/ctlcmd/get_test.go b/overlord/hookstate/ctlcmd/get_test.go
index 48945867e4..a64a263a14 100644
--- a/overlord/hookstate/ctlcmd/get_test.go
+++ b/overlord/hookstate/ctlcmd/get_test.go
@@ -200,6 +200,7 @@ func (s *getAttrSuite) SetUpTest(c *C) {
attrsTask.Set("plug-dynamic", dynamicPlugAttrs)
attrsTask.Set("slot-static", staticSlotAttrs)
attrsTask.Set("slot-dynamic", dynamicSlotAttrs)
+
ch.AddTask(attrsTask)
state.Unlock()
diff --git a/overlord/ifacestate/handlers.go b/overlord/ifacestate/handlers.go
index 97d23d742d..dc7bb33968 100644
--- a/overlord/ifacestate/handlers.go
+++ b/overlord/ifacestate/handlers.go
@@ -388,7 +388,7 @@ func (m *InterfaceManager) doConnect(task *state.Task, _ *tomb.Tomb) error {
return err
}
- connRef := interfaces.ConnRef{PlugRef: plugRef, SlotRef: slotRef}
+ connRef := &interfaces.ConnRef{PlugRef: plugRef, SlotRef: slotRef}
var plugSnapst snapstate.SnapState
if err := snapstate.Get(st, plugRef.Snap, &plugSnapst); err != nil {
diff --git a/overlord/ifacestate/ifacestate_test.go b/overlord/ifacestate/ifacestate_test.go
index 85678bc833..4baa4f1f54 100644
--- a/overlord/ifacestate/ifacestate_test.go
+++ b/overlord/ifacestate/ifacestate_test.go
@@ -1950,7 +1950,7 @@ func (s *interfaceManagerSuite) TestSetupProfilesDevModeMultiple(c *C) {
Interface: "test",
})
c.Assert(err, IsNil)
- connRef := interfaces.ConnRef{
+ connRef := &interfaces.ConnRef{
PlugRef: interfaces.PlugRef{Snap: siP.Name(), Name: "plug"},
SlotRef: interfaces.SlotRef{Snap: siC.Name(), Name: "slot"},
}
diff --git a/overlord/managers_test.go b/overlord/managers_test.go
index dda907a3ba..54d69f005d 100644
--- a/overlord/managers_test.go
+++ b/overlord/managers_test.go
@@ -2075,7 +2075,7 @@ func (ms *mgrsSuite) testTwoInstalls(c *C, snapName1, snapYaml1, snapName2, snap
cn, err := repo.Connected("snap1", "shared-data-plug")
c.Assert(err, IsNil)
c.Assert(cn, HasLen, 1)
- c.Assert(cn, DeepEquals, []interfaces.ConnRef{{
+ c.Assert(cn, DeepEquals, []*interfaces.ConnRef{{
PlugRef: interfaces.PlugRef{Snap: "snap1", Name: "shared-data-plug"},
SlotRef: interfaces.SlotRef{Snap: "snap2", Name: "shared-data-slot"},
}})
diff --git a/overlord/snapstate/handlers_prereq_test.go b/overlord/snapstate/handlers_prereq_test.go
index a0fc5a9619..69097a6ad9 100644
--- a/overlord/snapstate/handlers_prereq_test.go
+++ b/overlord/snapstate/handlers_prereq_test.go
@@ -247,16 +247,18 @@ func (s *prereqSuite) TestDoPrereqRetryWhenBaseInFlight(c *C) {
c.Check(t.Status(), Equals, state.DoingStatus)
c.Check(tCore.Status(), Equals, state.DoneStatus)
- s.state.Unlock()
- // wait the prereq-retry-timeout
- for i := 0; i < 10; i++ {
+ // wait, we will hit prereq-retry-timeout eventually
+ // (this can take a while on very slow machines)
+ for i := 0; i < 50; i++ {
time.Sleep(10 * time.Millisecond)
+ s.state.Unlock()
s.snapmgr.Ensure()
s.snapmgr.Wait()
+ s.state.Lock()
+ if t.Status() == state.DoneStatus {
+ break
+ }
}
- s.state.Lock()
- defer s.state.Unlock()
-
c.Check(t.Status(), Equals, state.DoneStatus)
}
diff --git a/overlord/snapstate/snapstate.go b/overlord/snapstate/snapstate.go
index ee8e505f05..8552d2609a 100644
--- a/overlord/snapstate/snapstate.go
+++ b/overlord/snapstate/snapstate.go
@@ -1844,7 +1844,9 @@ func CoreInfo(st *state.State) (*snap.Info, error) {
return nil, fmt.Errorf("unexpected number of cores, got %d", len(res))
}
-// ConfigDefaults returns the configuration defaults for the snap specified in the gadget. If gadget is absent or the snap has no snap-id it returns ErrNoState.
+// ConfigDefaults returns the configuration defaults for the snap specified in
+// the gadget. If gadget is absent or the snap has no snap-id it returns
+// ErrNoState.
func ConfigDefaults(st *state.State, snapName string) (map[string]interface{}, error) {
gadget, err := GadgetInfo(st)
if err != nil {
@@ -1856,8 +1858,17 @@ func ConfigDefaults(st *state.State, snapName string) (map[string]interface{}, e
return nil, err
}
+ core, err := CoreInfo(st)
+ if err != nil {
+ return nil, err
+ }
+ isCoreDefaults := core.Name() == snapName
+
si := snapst.CurrentSideInfo()
- if si.SnapID == "" {
+ // core snaps can be addressed even without a snap-id via the special
+ // "system" value in the config; first-boot always configures the core
+ // snap with UseConfigDefaults
+ if si.SnapID == "" && !isCoreDefaults {
return nil, state.ErrNoState
}
@@ -1866,6 +1877,17 @@ func ConfigDefaults(st *state.State, snapName string) (map[string]interface{}, e
return nil, err
}
+ // we support setting core defaults via "system"
+ if isCoreDefaults {
+ if defaults, ok := gadgetInfo.Defaults["system"]; ok {
+ if _, ok := gadgetInfo.Defaults[si.SnapID]; ok && si.SnapID != "" {
+ logger.Noticef("core snap configuration defaults found under both 'system' key and core-snap-id, preferring 'system'")
+ }
+
+ return defaults, nil
+ }
+ }
+
defaults, ok := gadgetInfo.Defaults[si.SnapID]
if !ok {
return nil, state.ErrNoState
diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go
index 095b982f85..fcfee371a5 100644
--- a/overlord/snapstate/snapstate_test.go
+++ b/overlord/snapstate/snapstate_test.go
@@ -27,6 +27,7 @@ import (
"os"
"path/filepath"
"sort"
+ "strings"
"testing"
"time"
@@ -7568,7 +7569,7 @@ volumes:
bootloader: grub
`
-func (s *snapmgrTestSuite) prepareGadget(c *C) {
+func (s *snapmgrTestSuite) prepareGadget(c *C, extraGadgetYaml ...string) {
gadgetSideInfo := &snap.SideInfo{RealName: "the-gadget", SnapID: "the-gadget-id", Revision: snap.R(1)}
gadgetInfo := snaptest.MockSnap(c, `
name: the-gadget
@@ -7576,7 +7577,8 @@ type: gadget
version: 1.0
`, gadgetSideInfo)
- err := ioutil.WriteFile(filepath.Join(gadgetInfo.MountDir(), "meta/gadget.yaml"), []byte(gadgetYaml), 0600)
+ gadgetYamlWhole := strings.Join(append([]string{gadgetYaml}, extraGadgetYaml...), "")
+ err := ioutil.WriteFile(filepath.Join(gadgetInfo.MountDir(), "meta/gadget.yaml"), []byte(gadgetYamlWhole), 0600)
c.Assert(err, IsNil)
snapstate.Set(s.state, "the-gadget", &snapstate.SnapState{
@@ -7607,6 +7609,7 @@ func (s *snapmgrTestSuite) TestConfigDefaults(c *C) {
Current: snap.R(11),
SnapType: "app",
})
+ makeInstalledMockCoreSnap(c)
defls, err := snapstate.ConfigDefaults(s.state, "some-snap")
c.Assert(err, IsNil)
@@ -7624,6 +7627,65 @@ func (s *snapmgrTestSuite) TestConfigDefaults(c *C) {
c.Assert(err, Equals, state.ErrNoState)
}
+func (s *snapmgrTestSuite) TestConfigDefaultsSystem(c *C) {
+ r := release.MockOnClassic(false)
+ defer r()
+
+ // using MockSnapReadInfo, we want to read the bits on disk
+ snapstate.MockSnapReadInfo(snap.ReadInfo)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ s.prepareGadget(c, `
+defaults:
+ system:
+ foo: bar
+`)
+
+ makeInstalledMockCoreSnap(c)
+
+ defls, err := snapstate.ConfigDefaults(s.state, "core")
+ c.Assert(err, IsNil)
+ c.Assert(defls, DeepEquals, map[string]interface{}{"foo": "bar"})
+}
+
+func (s *snapmgrTestSuite) TestConfigDefaultsSystemConflictsCoreSnapId(c *C) {
+ r := release.MockOnClassic(false)
+ defer r()
+
+ // using MockSnapReadInfo, we want to read the bits on disk
+ snapstate.MockSnapReadInfo(snap.ReadInfo)
+
+ s.state.Lock()
+ defer s.state.Unlock()
+
+ s.prepareGadget(c, `
+defaults:
+ system:
+ foo: bar
+ the-core-snap:
+ foo: other-bar
+ other-key: other-key-default
+`)
+
+ snapstate.Set(s.state, "core", &snapstate.SnapState{
+ Active: true,
+ Sequence: []*snap.SideInfo{
+ {RealName: "core", SnapID: "the-core-snap", Revision: snap.R(1)},
+ },
+ Current: snap.R(1),
+ SnapType: "os",
+ })
+
+ makeInstalledMockCoreSnap(c)
+
+ // 'system' key defaults take precedence over snap-id ones
+ defls, err := snapstate.ConfigDefaults(s.state, "core")
+ c.Assert(err, IsNil)
+ c.Assert(defls, DeepEquals, map[string]interface{}{"foo": "bar"})
+}
+
func (s *snapmgrTestSuite) TestGadgetDefaultsAreNormalizedForConfigHook(c *C) {
var mockGadgetSnapYaml = `
name: canonical-pc
diff --git a/packaging/arch/PKGBUILD b/packaging/arch/PKGBUILD
index de5acb55cf..73071c032e 100644
--- a/packaging/arch/PKGBUILD
+++ b/packaging/arch/PKGBUILD
@@ -10,7 +10,7 @@ pkgname=snapd
pkgdesc="Service and tools for management of snap packages."
depends=('squashfs-tools' 'libseccomp' 'libsystemd')
optdepends=('bash-completion: bash completion support')
-pkgver=2.32.8.r734.gd79c34ba4
+pkgver=2.32.9.r734.gd79c34ba4
pkgrel=1
arch=('x86_64')
url="https://github.com/snapcore/snapd"
diff --git a/packaging/fedora/snapd.spec b/packaging/fedora/snapd.spec
index 4cef21b7d9..ef50be4689 100644
--- a/packaging/fedora/snapd.spec
+++ b/packaging/fedora/snapd.spec
@@ -70,7 +70,7 @@
%endif
Name: snapd
-Version: 2.32.8
+Version: 2.32.9
Release: 0%{?dist}
Summary: A transactional software package manager
Group: System Environment/Base
@@ -724,6 +724,11 @@ fi
%changelog
+* Tue May 16 2018 Michael Vogt <mvo@ubuntu.com>
+- New upstream release 2.32.9
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
* Fri May 11 2018 Michael Vogt <mvo@ubuntu.com>
- New upstream release 2.32.8
- snapd.core-fixup.sh: add workaround for corrupted uboot.env
diff --git a/packaging/opensuse-42.2/snapd.changes b/packaging/opensuse-42.2/snapd.changes
index 05b802b18d..1414eb3b6d 100644
--- a/packaging/opensuse-42.2/snapd.changes
+++ b/packaging/opensuse-42.2/snapd.changes
@@ -1,4 +1,9 @@
-------------------------------------------------------------------
+Wed May 16 10:20:08 UTC 2018 - mvo@fastmail.fm
+
+- Update to upstream release 2.32.9
+
+-------------------------------------------------------------------
Fri May 11 14:36:16 UTC 2018 - mvo@fastmail.fm
- Update to upstream release 2.32.8
diff --git a/packaging/opensuse-42.2/snapd.spec b/packaging/opensuse-42.2/snapd.spec
index d6c965d6cc..cbe26efe7c 100644
--- a/packaging/opensuse-42.2/snapd.spec
+++ b/packaging/opensuse-42.2/snapd.spec
@@ -32,7 +32,7 @@
%define systemd_services_list snapd.socket snapd.service
Name: snapd
-Version: 2.32.8
+Version: 2.32.9
Release: 0
Summary: Tools enabling systems to work with .snap files
License: GPL-3.0
diff --git a/packaging/ubuntu-14.04/changelog b/packaging/ubuntu-14.04/changelog
index 980ca94145..7366ee86cf 100644
--- a/packaging/ubuntu-14.04/changelog
+++ b/packaging/ubuntu-14.04/changelog
@@ -1,3 +1,11 @@
+snapd (2.32.9~14.04) trusty; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Wed, 16 May 2018 10:20:08 +0200
+
snapd (2.32.8~14.04) trusty; urgency=medium
* New upstream release, LP: #1767833
diff --git a/packaging/ubuntu-14.04/control b/packaging/ubuntu-14.04/control
index 14bba81273..859686f037 100644
--- a/packaging/ubuntu-14.04/control
+++ b/packaging/ubuntu-14.04/control
@@ -33,6 +33,8 @@ Build-Depends: autoconf,
squashfs-tools,
udev,
xfslibs-dev
+Recommends: gnupg1 | gnupg
+Suggests: zenity | kdialog
Standards-Version: 3.9.7
Homepage: https://github.com/snapcore/snapd
Vcs-Browser: https://github.com/snapcore/snapd
diff --git a/packaging/ubuntu-16.04/changelog b/packaging/ubuntu-16.04/changelog
index 360a31d029..bfe07d0238 100644
--- a/packaging/ubuntu-16.04/changelog
+++ b/packaging/ubuntu-16.04/changelog
@@ -1,3 +1,11 @@
+snapd (2.32.9) xenial; urgency=medium
+
+ * New upstream release, LP: #1767833
+ - tests: run all spread tests inside GCE
+ - tests: build spread in the autopkgtests with a more recent go
+
+ -- Michael Vogt <michael.vogt@ubuntu.com> Wed, 16 May 2018 10:20:08 +0200
+
snapd (2.32.8) xenial; urgency=medium
* New upstream release, LP: #1767833
diff --git a/packaging/ubuntu-16.04/control b/packaging/ubuntu-16.04/control
index effa6a2e7a..e86a698506 100644
--- a/packaging/ubuntu-16.04/control
+++ b/packaging/ubuntu-16.04/control
@@ -71,6 +71,7 @@ Depends: adduser,
Replaces: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22), snapd-xdg-open (<= 0.0.0)
Breaks: ubuntu-snappy (<< 1.9), ubuntu-snappy-cli (<< 1.9), snap-confine (<< 2.23), ubuntu-core-launcher (<< 2.22), snapd-xdg-open (<= 0.0.0)
Recommends: gnupg
+Suggests: zenity | kdialog
Conflicts: snap (<< 2013-11-29-1ubuntu1)
Built-Using: ${Built-Using} ${misc:Built-Using}
Description: Daemon and tooling that enable snap packages
diff --git a/packaging/ubuntu-16.04/tests/integrationtests b/packaging/ubuntu-16.04/tests/integrationtests
index 191ab592a3..f9065a25fe 100644
--- a/packaging/ubuntu-16.04/tests/integrationtests
+++ b/packaging/ubuntu-16.04/tests/integrationtests
@@ -36,11 +36,14 @@ elif apt -qq list snapd | grep -q -- -updates; then
export SPREAD_CORE_CHANNEL=stable
fi
+# Spread will only buid with recent go
+snap install --classic go
+
# and now run spread against localhost
# shellcheck disable=SC1091
. /etc/os-release
export GOPATH=/tmp/go
-go get -u github.com/snapcore/spread/cmd/spread
+/snap/bin/go get -u github.com/snapcore/spread/cmd/spread
/tmp/go/bin/spread -v "autopkgtest:${ID}-${VERSION_ID}-$(dpkg --print-architecture)"
# store journal info for inspectsion
diff --git a/snap/info.go b/snap/info.go
index f1ad1fc271..ca111cda91 100644
--- a/snap/info.go
+++ b/snap/info.go
@@ -464,27 +464,55 @@ type PlugInfo struct {
Hooks map[string]*HookInfo
}
-func getAttribute(snapName string, ifaceName string, attrs map[string]interface{}, key string, val interface{}) error {
- if v, ok := attrs[key]; ok {
- rt := reflect.TypeOf(val)
- if rt.Kind() != reflect.Ptr || val == nil {
- return fmt.Errorf("internal error: cannot get %q attribute of interface %q with non-pointer value", key, ifaceName)
+func lookupAttr(attrs map[string]interface{}, path string) (interface{}, bool) {
+ var v interface{}
+ comps := strings.FieldsFunc(path, func(r rune) bool { return r == '.' })
+ if len(comps) == 0 {
+ return nil, false
+ }
+ v = attrs
+ for _, comp := range comps {
+ m, ok := v.(map[string]interface{})
+ if !ok {
+ return nil, false
}
-
- if reflect.TypeOf(v) != rt.Elem() {
- return fmt.Errorf("snap %q has interface %q with invalid value type for %q attribute", snapName, ifaceName, key)
+ v, ok = m[comp]
+ if !ok {
+ return nil, false
}
- rv := reflect.ValueOf(val)
- rv.Elem().Set(reflect.ValueOf(v))
- return nil
}
- return fmt.Errorf("snap %q does not have attribute %q for interface %q", snapName, key, ifaceName)
+
+ return v, true
+}
+
+func getAttribute(snapName string, ifaceName string, attrs map[string]interface{}, key string, val interface{}) error {
+ v, ok := lookupAttr(attrs, key)
+ if !ok {
+ return fmt.Errorf("snap %q does not have attribute %q for interface %q", snapName, key, ifaceName)
+ }
+
+ rt := reflect.TypeOf(val)
+ if rt.Kind() != reflect.Ptr || val == nil {
+ return fmt.Errorf("internal error: cannot get %q attribute of interface %q with non-pointer value", key, ifaceName)
+ }
+
+ if reflect.TypeOf(v) != rt.Elem() {
+ return fmt.Errorf("snap %q has interface %q with invalid value type for %q attribute", snapName, ifaceName, key)
+ }
+ rv := reflect.ValueOf(val)
+ rv.Elem().Set(reflect.ValueOf(v))
+
+ return nil
}
func (plug *PlugInfo) Attr(key string, val interface{}) error {
return getAttribute(plug.Snap.Name(), plug.Interface, plug.Attrs, key, val)
}
+func (plug *PlugInfo) Lookup(key string) (interface{}, bool) {
+ return lookupAttr(plug.Attrs, key)
+}
+
// SecurityTags returns security tags associated with a given plug.
func (plug *PlugInfo) SecurityTags() []string {
tags := make([]string, 0, len(plug.Apps)+len(plug.Hooks))
@@ -507,6 +535,10 @@ func (slot *SlotInfo) Attr(key string, val interface{}) error {
return getAttribute(slot.Snap.Name(), slot.Interface, slot.Attrs, key, val)
}
+func (slot *SlotInfo) Lookup(key string) (interface{}, bool) {
+ return lookupAttr(slot.Attrs, key)
+}
+
// SecurityTags returns security tags associated with a given slot.
func (slot *SlotInfo) SecurityTags() []string {
tags := make([]string, 0, len(slot.Apps))
diff --git a/snap/info_test.go b/snap/info_test.go
index 20be43c82a..1cb686d8a5 100644
--- a/snap/info_test.go
+++ b/snap/info_test.go
@@ -934,6 +934,69 @@ func (s *infoSuite) TestSlotInfoAttr(c *C) {
c.Check(slot.Attr("key", intVal), ErrorMatches, `internal error: cannot get "key" attribute of interface "interface" with non-pointer value`)
}
+func (s *infoSuite) TestDottedPathSlot(c *C) {
+ attrs := map[string]interface{}{
+ "nested": map[string]interface{}{
+ "foo": "bar",
+ },
+ }
+
+ slot := &snap.SlotInfo{Attrs: attrs}
+ c.Assert(slot, NotNil)
+
+ v, ok := slot.Lookup("nested.foo")
+ c.Assert(ok, Equals, true)
+ c.Assert(v, Equals, "bar")
+
+ v, ok = slot.Lookup("nested")
+ c.Assert(ok, Equals, true)
+ c.Assert(v, DeepEquals, map[string]interface{}{
+ "foo": "bar",
+ })
+
+ _, ok = slot.Lookup("x")
+ c.Assert(ok, Equals, false)
+
+ _, ok = slot.Lookup("..")
+ c.Assert(ok, Equals, false)
+
+ _, ok = slot.Lookup("nested.foo.x")
+ c.Assert(ok, Equals, false)
+
+ _, ok = slot.Lookup("nested.x")
+ c.Assert(ok, Equals, false)
+}
+
+func (s *infoSuite) TestDottedPathPlug(c *C) {
+ attrs := map[string]interface{}{
+ "nested": map[string]interface{}{
+ "foo": "bar",
+ },
+ }
+
+ plug := &snap.PlugInfo{Attrs: attrs}
+ c.Assert(plug, NotNil)
+
+ v, ok := plug.Lookup("nested")
+ c.Assert(ok, Equals, true)
+ c.Assert(v, DeepEquals, map[string]interface{}{
+ "foo": "bar",
+ })
+
+ v, ok = plug.Lookup("nested.foo")
+ c.Assert(ok, Equals, true)
+ c.Assert(v, Equals, "bar")
+
+ _, ok = plug.Lookup("x")
+ c.Assert(ok, Equals, false)
+
+ _, ok = plug.Lookup("..")
+ c.Assert(ok, Equals, false)
+
+ _, ok = plug.Lookup("nested.foo.x")
+ c.Assert(ok, Equals, false)
+}
+
func (s *infoSuite) TestExpandSnapVariables(c *C) {
dirs.SetRootDir("")
info, err := snap.InfoFromSnapYaml([]byte(`name: foo`))
diff --git a/spread.yaml b/spread.yaml
index d789805234..5cde2e1483 100644
--- a/spread.yaml
+++ b/spread.yaml
@@ -288,8 +288,11 @@ exclude:
debug-each: |
if [ "$SPREAD_DEBUG_EACH" = 1 ]; then
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
echo '# journal messages for snapd'
- journalctl -u snapd
+ get_journalctl_log -u snapd
echo '# apparmor denials '
dmesg --ctime | grep DENIED || true
echo '# seccomp denials (kills) '
diff --git a/tests/lib/journalctl.sh b/tests/lib/journalctl.sh
new file mode 100644
index 0000000000..ad54b4b215
--- /dev/null
+++ b/tests/lib/journalctl.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+JOURNALCTL_CURSOR_FILE="${SPREAD_PATH}"/journalctl_cursor
+
+get_last_journalctl_cursor(){
+ journalctl --output=export -n1 | grep --binary-files=text -o '__CURSOR=.*' | sed -e 's/^__CURSOR=//'
+}
+
+start_new_journalctl_log(){
+ cursor=$(get_last_journalctl_cursor)
+ if [ -z "$cursor" ]; then
+ echo "Empty journalctl cursor, exiting..."
+ exit 1
+ else
+ echo "$cursor" > "$JOURNALCTL_CURSOR_FILE"
+ fi
+}
+
+get_journalctl_log(){
+ cursor=$(cat "$JOURNALCTL_CURSOR_FILE")
+ get_journalctl_log_from_cursor "$cursor" "$@"
+}
+
+get_journalctl_log_from_cursor(){
+ cursor=$1
+ shift
+ journalctl "$@" --cursor "$cursor"
+}
diff --git a/tests/lib/prepare-restore.sh b/tests/lib/prepare-restore.sh
index 317596cc3a..82b7f8d4bc 100755
--- a/tests/lib/prepare-restore.sh
+++ b/tests/lib/prepare-restore.sh
@@ -28,6 +28,10 @@ set -o pipefail
# shellcheck source=tests/lib/spread-funcs.sh
. "$TESTSLIB/spread-funcs.sh"
+# shellcheck source=tests/lib/journalctl.sh
+. "$TESTSLIB/journalctl.sh"
+
+
###
### Utility functions reused below.
###
@@ -369,34 +373,8 @@ prepare_project_each() {
# We want to rotate the logs so that when inspecting or dumping them we
# will just see logs since the test has started.
- # Clear the systemd journal. Unfortunately the deputy-systemd on Ubuntu
- # 14.04 does not know about --rotate or --vacuum-time so we need to remove
- # the journal the hard way.
- case "$SPREAD_SYSTEM" in
- ubuntu-14.04-*)
- # Force a log rotation with small size
- sed -i.bak s/#SystemMaxUse=/SystemMaxUse=1K/g /etc/systemd/journald.conf
- systemctl kill --kill-who=main --signal=SIGUSR2 systemd-journald.service
-
- # Restore the initial configuration and rotate logs
- mv /etc/systemd/journald.conf.bak /etc/systemd/journald.conf
- systemctl kill --kill-who=main --signal=SIGUSR2 systemd-journald.service
-
- # Remove rotated journal logs
- systemctl stop systemd-journald.service
- find /run/log/journal/ -name "*@*.journal" -delete
- systemctl start systemd-journald.service
- ;;
- *)
- # per journalctl's implementation, --rotate and --sync 'override'
- # each other if used in a single command, with the one appearing
- # later being effective
- journalctl --sync
- journalctl --rotate
- sleep .1
- journalctl --vacuum-time=1ms
- ;;
- esac
+ # Reset systemd journal cursor.
+ start_new_journalctl_log
# Clear the kernel ring buffer.
dmesg -c > /dev/null
diff --git a/tests/lib/store.sh b/tests/lib/store.sh
index 84db342d44..178f88203d 100644
--- a/tests/lib/store.sh
+++ b/tests/lib/store.sh
@@ -5,6 +5,9 @@ STORE_CONFIG=/etc/systemd/system/snapd.service.d/store.conf
# shellcheck source=tests/lib/systemd.sh
. "$TESTSLIB/systemd.sh"
+# shellcheck source=tests/lib/journalctl.sh
+. "$TESTSLIB/journalctl.sh"
+
_configure_store_backends(){
systemctl stop snapd.service snapd.socket
mkdir -p "$(dirname $STORE_CONFIG)"
@@ -76,7 +79,7 @@ setup_fake_store(){
echo "fakestore service not started properly"
netstat -ntlp | grep "127.0.0.1:11028" || true
- journalctl -u fakestore || true
+ get_journalctl_log -u fakestore || true
systemctl status fakestore || true
exit 1
}
diff --git a/tests/main/catalog-update/task.yaml b/tests/main/catalog-update/task.yaml
index 590d1e7a28..425fbffa8a 100644
--- a/tests/main/catalog-update/task.yaml
+++ b/tests/main/catalog-update/task.yaml
@@ -1,17 +1,20 @@
summary: Ensure catalog update works
execute: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
echo "Ensure that catalog refresh happens on startup"
for _ in $(seq 60); do
- if journalctl -u snapd | MATCH "Catalog refresh"; then
+ if get_journalctl_log -u snapd | MATCH "Catalog refresh"; then
break
fi
sleep 1
done
- journalctl -u snapd | MATCH "Catalog refresh"
+ get_journalctl_log -u snapd | MATCH "Catalog refresh"
echo "Ensure that we don't log all catalog body data"
- if journalctl -u snapd | MATCH "Tools for testing the snapd application"; then
+ if get_journalctl_log -u snapd | MATCH "Tools for testing the snapd application"; then
echo "Catalog update is doing verbose http logging (it should not)."
exit 1
fi
diff --git a/tests/main/debug-sandbox/task.yaml b/tests/main/debug-sandbox/task.yaml
new file mode 100644
index 0000000000..e0a84e2f42
--- /dev/null
+++ b/tests/main/debug-sandbox/task.yaml
@@ -0,0 +1,25 @@
+summary: Verify sandbox is correctly reported
+
+execute: |
+ case "$SPREAD_SYSTEM" in
+ ubuntu-*)
+ snap debug sandbox-features | MATCH "apparmor: .+"
+ ;;
+ fedora-*|debian-*|opensuse-*)
+ # Fedora because it uses SELinux
+ # Debian and openSUSE because partial apparmor is not enabled
+ snap debug sandbox-features | MATCH -v "apparmor: .+"
+ ;;
+ esac
+ snap debug sandbox-features | MATCH "dbus: .+"
+ snap debug sandbox-features | MATCH "kmod: .+"
+ snap debug sandbox-features | MATCH "mount: .+"
+ snap debug sandbox-features | MATCH "seccomp: .+"
+ snap debug sandbox-features | MATCH "udev: .+"
+
+ # The command can be used as script helper
+ snap debug sandbox-features --required kmod:mediated-modprobe
+ ! snap debug sandbox-features --required magic:evil-bit
+
+ # Multiple requirements may be listed
+ snap debug sandbox-features --required kmod:mediated-modprobe --required mount:stale-base-invalidation
diff --git a/tests/main/enable-disable-units-gpio/task.yaml b/tests/main/enable-disable-units-gpio/task.yaml
index 9de48cf966..ccf27fce37 100644
--- a/tests/main/enable-disable-units-gpio/task.yaml
+++ b/tests/main/enable-disable-units-gpio/task.yaml
@@ -69,6 +69,9 @@ restore: |
rm -rf $GPIO_MOCK_DIR /etc/systemd/system/gpio-mock.service /home/test/gpio-mock.sh
execute: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
if [ "$TRUST_TEST_KEYS" = "false" ]; then
echo "This test needs test keys to be trusted"
exit
@@ -76,7 +79,7 @@ execute: |
echo "Then the snap service units concerning the gpio device must be run before and after a reboot"
expected="Unit snap.core.interface.gpio-100.service has finished starting up"
- journalctl -xe --no-pager | MATCH "$expected"
+ get_journalctl_log -xe --no-pager | MATCH "$expected"
if [ "$SPREAD_REBOOT" = "1" ]; then
cat $GPIO_MOCK_DIR/export | MATCH "^100$"
diff --git a/tests/main/install-cache/task.yaml b/tests/main/install-cache/task.yaml
index fabece8398..3c80d4d986 100644
--- a/tests/main/install-cache/task.yaml
+++ b/tests/main/install-cache/task.yaml
@@ -1,13 +1,16 @@
summary: Check that download caching works
execute: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
snap install test-snapd-tools
snap remove test-snapd-tools
snap install test-snapd-tools
for i in $(seq 10); do
- if journalctl -u snapd | MATCH "using cache for .*/test-snapd-tools.*\.snap"; then
+ if get_journalctl_log -u snapd | MATCH "using cache for .*/test-snapd-tools.*\.snap"; then
break
fi
sleep 1
done
- journalctl -u snapd | MATCH "using cache for .*/test-snapd-tools.*\.snap"
+ get_journalctl_log -u snapd | MATCH "using cache for .*/test-snapd-tools.*\.snap"
diff --git a/tests/main/install-closed-channel/task.yaml b/tests/main/install-closed-channel/task.yaml
new file mode 100644
index 0000000000..7b729ee422
--- /dev/null
+++ b/tests/main/install-closed-channel/task.yaml
@@ -0,0 +1,9 @@
+summary: Check that installing a snap from a closed channel DTRT
+
+details: |
+ When a snap is installed from a closed channel, we're supposed to
+ "forward" to the next available risk level.
+
+execute: |
+ snap install --beta test-snapd-tools | MATCH '^Channel beta for test-snapd-tools is closed; temporarily forwarding to stable\.$'
+ snap info test-snapd-tools | MATCH 'tracking:.*beta'
diff --git a/tests/main/interfaces-cups-control/task.yaml b/tests/main/interfaces-cups-control/task.yaml
index f52be8e98a..1f1c2a9a31 100644
--- a/tests/main/interfaces-cups-control/task.yaml
+++ b/tests/main/interfaces-cups-control/task.yaml
@@ -42,8 +42,11 @@ restore: |
rm -rf $HOME/PDF $TEST_FILE print.error
debug: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
systemctl status cups || true
- journalctl -u cpus || true
+ get_journalctl_log -u cpus || true
execute: |
echo "Then the plug is disconnected by default"
diff --git a/tests/main/interfaces-desktop-document-portal/task.yaml b/tests/main/interfaces-desktop-document-portal/task.yaml
new file mode 100644
index 0000000000..b63e76a083
--- /dev/null
+++ b/tests/main/interfaces-desktop-document-portal/task.yaml
@@ -0,0 +1,60 @@
+summary: The document portal is mounted by snaps using the desktop interface
+details: |
+ The document portal is a component of xdg-desktop-portal that
+ provides a way to share files with a confined application in a
+ controlled fashion. To provide proper security, a subtree of the
+ portal needs to be mounted over $XDG_RUNTIME_DIR/doc inside the
+ sandbox.
+systems: [-ubuntu-core-*]
+prepare: |
+ snap try "$TESTSLIB"/snaps/test-snapd-desktop
+ snap disconnect test-snapd-desktop:desktop
+restore: |
+ rm -rf /run/user/12345/*
+ rm -f /tmp/check-doc-portal.sh
+execute: |
+ #shellcheck source=tests/lib/dirs.sh
+ . $TESTSLIB/dirs.sh
+
+ echo "Create XDG_RUNTIME_DIR for test user"
+ USER_RUNTIME_DIR=/run/user/12345
+ mkdir -p $USER_RUNTIME_DIR || true
+ #shellcheck disable=SC2115
+ rm -rf $USER_RUNTIME_DIR/*
+ chmod u=rwX,go= $USER_RUNTIME_DIR
+ chown test:test $USER_RUNTIME_DIR
+
+ cat << EOF > /tmp/check-doc-portal.sh
+ set -eu
+ mkdir -p /run/user/12345/doc/by-app/snap.test-snapd-desktop
+ touch /run/user/12345/doc/is-unconfined
+ touch /run/user/12345/doc/by-app/snap.test-snapd-desktop/is-confined
+ test-snapd-desktop.check-dirs /run/user/12345/doc
+ EOF
+
+ if [ "$(snap debug confinement)" = strict ]; then
+ echo "Without desktop interface connected"
+ if su -l -c "sh /tmp/check-doc-portal.sh" test 2> check.error; then
+ cat check.error >&2
+ echo "Expected permission error when checking document portal dir"
+ exit 1
+ fi
+ MATCH "Permission denied" < check.error
+ fi
+
+ "$LIBEXECDIR"/snapd/snap-discard-ns test-snapd-desktop
+
+ echo "With desktop connected, we see confined version"
+ snap connect test-snapd-desktop:desktop
+ su -l -c "sh /tmp/check-doc-portal.sh" test | MATCH is-confined
+
+ "$LIBEXECDIR"/snapd/snap-discard-ns test-snapd-desktop
+
+ echo "It is not an error if the document portal is missing"
+ rm -rf /run/user/12345/*
+ if su -l -c "test-snapd-desktop.check-dirs /run/user/12345/doc" test 2> check.error; then
+ cat check.error >&2
+ echo "Expected not found error when checking document portal dir"
+ exit 1
+ fi
+ MATCH "No such file or directory" < check.error
diff --git a/tests/main/interfaces-many/task.yaml b/tests/main/interfaces-many/task.yaml
index e8dc71cbfb..328e5ec374 100644
--- a/tests/main/interfaces-many/task.yaml
+++ b/tests/main/interfaces-many/task.yaml
@@ -4,8 +4,11 @@ summary: Ensure that commands run when their interfaces are connected
priority: 100
debug: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
# get the full journal to see any out-of-memory errors
- journalctl
+ get_journalctl_log
details: |
Install a test snap that plugs as many interfaces as is possible and
diff --git a/tests/main/interfaces-snapd-control-with-manage/task.yaml b/tests/main/interfaces-snapd-control-with-manage/task.yaml
index e4f0721b84..3046318eb2 100644
--- a/tests/main/interfaces-snapd-control-with-manage/task.yaml
+++ b/tests/main/interfaces-snapd-control-with-manage/task.yaml
@@ -54,6 +54,9 @@ debug: |
jq .data.auth.device /var/lib/snapd/state.json
execute: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
if [ "$TRUST_TEST_KEYS" = "false" ]; then
echo "This test needs test keys to be trusted"
exit
@@ -72,7 +75,7 @@ execute: |
echo "Then the core refresh.schedule can be set to 'managed'"
snap set core refresh.schedule=managed
- if journalctl -u snapd |grep 'cannot parse "managed"'; then
+ if get_journalctl_log -u snapd |grep 'cannot parse "managed"'; then
echo "refresh.schedule=managed was not rejected as it should be"
exit 1
fi
diff --git a/tests/main/lxd/task.yaml b/tests/main/lxd/task.yaml
index e5c61e3167..285e9ee445 100644
--- a/tests/main/lxd/task.yaml
+++ b/tests/main/lxd/task.yaml
@@ -23,8 +23,11 @@ restore: |
lxd.lxc delete my-ubuntu
debug: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
# debug output from lxd
- journalctl -u snap.lxd.daemon.service
+ get_journalctl_log -u snap.lxd.daemon.service
execute: |
if [[ $(ls -1 "$GOHOME"/snapd_*.deb | wc -l || echo 0) -eq 0 ]]; then
diff --git a/tests/main/refresh-delta-from-core/task.yaml b/tests/main/refresh-delta-from-core/task.yaml
index c80c6eb073..28f7192bb5 100644
--- a/tests/main/refresh-delta-from-core/task.yaml
+++ b/tests/main/refresh-delta-from-core/task.yaml
@@ -21,7 +21,10 @@ restore: |
fi
execute: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
echo "When the snap is refreshed"
snap refresh --beta $SNAP_NAME
echo "Then deltas are successfully applied"
- journalctl -u snapd | MATCH "Successfully applied delta"
+ get_journalctl_log -u snapd | MATCH "Successfully applied delta"
diff --git a/tests/main/refresh-delta/task.yaml b/tests/main/refresh-delta/task.yaml
index 7632a8339d..a48c9ab7b4 100644
--- a/tests/main/refresh-delta/task.yaml
+++ b/tests/main/refresh-delta/task.yaml
@@ -19,8 +19,11 @@ prepare: |
snap install --edge $SNAP_NAME
execute: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
echo "When the snap is refreshed"
snap refresh --beta $SNAP_NAME
echo "Then deltas are successfully applied"
- journalctl -u snapd | MATCH "Successfully applied delta"
+ get_journalctl_log -u snapd | MATCH "Successfully applied delta"
diff --git a/tests/main/refresh-undo/task.yaml b/tests/main/refresh-undo/task.yaml
index 81d8009291..af8f156f14 100644
--- a/tests/main/refresh-undo/task.yaml
+++ b/tests/main/refresh-undo/task.yaml
@@ -19,7 +19,9 @@ prepare: |
snap pack $TESTSLIB/snaps/$SNAP_NAME_BAD
debug: |
- journalctl -u snap.test-snapd-service.service.service
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB"/journalctl.sh
+ get_journalctl_log -u snap.test-snapd-service.service.service
execute: |
wait_for_service_status() {
diff --git a/tests/main/snap-confine-from-core/task.yaml b/tests/main/snap-confine-from-core/task.yaml
index cec9e75852..c96015d3e4 100644
--- a/tests/main/snap-confine-from-core/task.yaml
+++ b/tests/main/snap-confine-from-core/task.yaml
@@ -14,6 +14,9 @@ restore: |
chmod 4755 /usr/lib/snapd/snap-confine
execute: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
if [ "${SNAP_REEXEC:-}" = "0" ]; then
echo "skipping test when SNAP_REEXEC is disabled"
exit 0
@@ -21,7 +24,7 @@ execute: |
echo "Ensure we re-exec by default"
snap list
- journalctl | MATCH "DEBUG: restarting into"
+ get_journalctl_log | MATCH "DEBUG: restarting into"
echo "Ensure snap-confine from the core snap is run"
# do not use "strace -f" for unknown reasons that hangs
diff --git a/tests/main/snap-core-fixup/task.yaml b/tests/main/snap-core-fixup/task.yaml
index 2e2264705b..62ce93668f 100644
--- a/tests/main/snap-core-fixup/task.yaml
+++ b/tests/main/snap-core-fixup/task.yaml
@@ -28,7 +28,7 @@ execute: |
echo "Now test with the real corrupted image"
unxz -c -d test.img.xz >test.img
mount -t vfat test.img /boot/uboot
- n=$(ls /boot/uboot | grep ^uboot.env$ | wc -l)
+ n=$(ls /boot/uboot | grep -c ^uboot.env$)
if [ "$n" != "2" ]; then
echo "Image not broken in the right way, expected two uboot.env files"
ls /boot/uboot
@@ -38,7 +38,7 @@ execute: |
echo "Trigger cleanup"
systemctl restart snapd.core-fixup.service
- n=$(ls /boot/uboot | grep ^uboot.env$ | wc -l)
+ n=$(ls /boot/uboot | grep -c ^uboot.env$)
if [ "$n" != "1" ]; then
echo "Image not repaired"
ls /boot/uboot
diff --git a/tests/main/snap-service-refresh-mode/task.yaml b/tests/main/snap-service-refresh-mode/task.yaml
index f71d1e6543..b742c08ba4 100644
--- a/tests/main/snap-service-refresh-mode/task.yaml
+++ b/tests/main/snap-service-refresh-mode/task.yaml
@@ -1,14 +1,18 @@
summary: "Check that refresh-modes works"
-kill-timeout: 3m
+kill-timeout: 5m
debug: |
grep -n '' *.pid || true
systemctl status snap.test-snapd-service.test-snapd-endure-service || true
execute: |
+ # shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB/snaps.sh"
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
echo "When the service snap is installed"
- . $TESTSLIB/snaps.sh
install_local test-snapd-service
echo "We can see it running"
@@ -25,7 +29,7 @@ execute: |
echo "Once the snap is removed, the service is stopped"
snap remove test-snapd-service
- journalctl | MATCH "stop endure"
+ get_journalctl_log | MATCH "stop endure"
restore:
rm -f *.pid || true
diff --git a/tests/main/snap-service-stop-mode-sigkill/task.yaml b/tests/main/snap-service-stop-mode-sigkill/task.yaml
index 0d0bd4619c..37f6fdd097 100644
--- a/tests/main/snap-service-stop-mode-sigkill/task.yaml
+++ b/tests/main/snap-service-stop-mode-sigkill/task.yaml
@@ -1,6 +1,6 @@
summary: "Check that refresh-modes sigkill works"
-kill-timeout: 3m
+kill-timeout: 5m
restore: |
# remove to ensure all services are stopped
diff --git a/tests/main/snap-service-stop-mode/task.yaml b/tests/main/snap-service-stop-mode/task.yaml
index ab1f72043c..7d17de66ba 100644
--- a/tests/main/snap-service-stop-mode/task.yaml
+++ b/tests/main/snap-service-stop-mode/task.yaml
@@ -1,6 +1,6 @@
summary: "Check that stop-modes works"
-kill-timeout: 3m
+kill-timeout: 5m
# journald in ubuntu-14.04 not reliable
systems: [-ubuntu-14.04-*]
@@ -12,8 +12,12 @@ debug: |
done
execute: |
+ # shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB/snaps.sh"
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
echo "When the service snap is installed"
- . $TESTSLIB/snaps.sh
install_local test-snapd-service
echo "We can see it running"
@@ -36,11 +40,11 @@ execute: |
echo "and it got the right signal"
echo "checking that the right signal was sent"
sleep 1
- journalctl -u snap.test-snapd-service.test-snapd-${s}-service | MATCH "got ${s%%-all}"
+ get_journalctl_log -u snap.test-snapd-service.test-snapd-${s}-service | MATCH "got ${s%%-all}"
done
echo "Regular services are restarted normally"
- journalctl -u snap.test-snapd-service.test-snapd-service | MATCH "stop service"
+ get_journalctl_log -u snap.test-snapd-service.test-snapd-service | MATCH "stop service"
systemctl show -p MainPID snap.test-snapd-service.test-snapd-service > new-main.pid
test -e new-main.pid && test -e old-main.pid
test "$(cat new-main.pid)" != "$(cat old-main.pid)"
@@ -48,7 +52,7 @@ execute: |
echo "Once the snap is removed, all services are stopped"
snap remove test-snapd-service
for s in $stop_modes; do
- journalctl | MATCH "stop ${s}"
+ get_journalctl_log | MATCH "stop ${s}"
done
restore: |
diff --git a/tests/main/snap-userd-desktop-app-autostart/task.yaml b/tests/main/snap-userd-desktop-app-autostart/task.yaml
index 8df99f3da3..42ff2b8414 100644
--- a/tests/main/snap-userd-desktop-app-autostart/task.yaml
+++ b/tests/main/snap-userd-desktop-app-autostart/task.yaml
@@ -1,8 +1,12 @@
summary: Check that snap userd can autostart user session applications
execute: |
+ # shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB/snaps.sh"
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
echo "When the snap is installed"
- . $TESTSLIB/snaps.sh
install_local test-snapd-xdg-autostart
# run the app directly, it will dump a *.desktop file
@@ -14,10 +18,7 @@ execute: |
echo "Applications can be automatically started by snap userd --autostart"
test ! -e ~/snap/test-xdg-snap-autostart/current/foo-autostarted
- if [[ "$SPREAD_SYSTEM" != ubuntu-14.04-* ]]; then
- # 14.04 journalctl does not have cursor support
- cursor=$(journalctl --show-cursor -n 0 |grep cursor | cut -f3 -d' ')
- fi
+ cursor=$(get_last_journalctl_cursor)
snap userd --autostart > autostart.log 2>&1
# when app is autostarted it dumps a file at $SNAP_USER_DATA/foo-autostarted,
@@ -28,14 +29,7 @@ execute: |
done
test -e ~/snap/test-snapd-xdg-autostart/current/foo-autostarted
- if [[ "$SPREAD_SYSTEM" != ubuntu-14.04-* ]]; then
- journalctl --flush
- test "$(journalctl -t foo.desktop --after-cursor=$cursor | wc -l)" -eq 1
- else
- # 14.04 journalctl does not have cursor, neither --identifier support,
- # nor --flush
- test "$(journalctl | grep foo.desktop | wc -l)" -eq 1
- fi
+ test "$(get_journalctl_log_from_cursor "$cursor" | grep foo.desktop | wc -l)" -gt 0
restore: |
rm -f ~/snap/test-snapd-xdg-autostart/current/foo-autostarted
diff --git a/tests/main/snapctl-services/task.yaml b/tests/main/snapctl-services/task.yaml
index 3c0df175d4..259d81f3a6 100644
--- a/tests/main/snapctl-services/task.yaml
+++ b/tests/main/snapctl-services/task.yaml
@@ -33,8 +33,12 @@ execute: |
done
}
+ # shellcheck source=tests/lib/snaps.sh
+ . "$TESTSLIB/snaps.sh"
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
echo "When the service snap is installed"
- . $TESTSLIB/snaps.sh
install_local test-snapd-service
echo "We can see it running"
@@ -73,7 +77,7 @@ execute: |
echo "Reinstalling the snap with configure hook calling snapctl restart works"
snap set test-snapd-service command=restart
install_local test-snapd-service
- if journalctl|grep -q "error running snapctl"; then
+ if get_journalctl_log | MATCH "error running snapctl"; then
echo "snapctl should not report errors"
exit 1
fi
diff --git a/tests/main/snapd-notify/task.yaml b/tests/main/snapd-notify/task.yaml
index 46299aa496..3768b0acb4 100644
--- a/tests/main/snapd-notify/task.yaml
+++ b/tests/main/snapd-notify/task.yaml
@@ -5,9 +5,12 @@ summary: Ensure snapd notify feature is working
backends: [-external]
execute: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
for _ in $(seq 5); do
if systemctl status snapd.service | MATCH "Active: active"; then
- journalctl -u snapd | MATCH "activation done in"
+ get_journalctl_log -u snapd | MATCH "activation done in"
exit
fi
sleep 1
diff --git a/tests/main/snapd-reexec/task.yaml b/tests/main/snapd-reexec/task.yaml
index b1b80c5d9d..c525a37053 100644
--- a/tests/main/snapd-reexec/task.yaml
+++ b/tests/main/snapd-reexec/task.yaml
@@ -29,7 +29,10 @@ execute: |
echo "Ensure we re-exec by default"
/usr/bin/env SNAPD_DEBUG=1 snap list 2>&1 | MATCH "DEBUG: restarting into"
- . $TESTSLIB/dirs.sh
+ # shellcheck source=tests/lib/dirs.sh
+ . "$TESTSLIB/dirs.sh"
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
echo "Ensure that we do not re-exec into older versions"
systemctl stop snapd.service snapd.socket
@@ -38,7 +41,7 @@ execute: |
mount --bind /tmp/old-info $SNAP_MOUNT_DIR/core/current/usr/lib/snapd/info
systemctl start snapd.service snapd.socket
snap list
- journalctl | MATCH "core snap \(at .*\) is older \(.*\) than distribution package"
+ get_journalctl_log | MATCH "core snap \(at .*\) is older \(.*\) than distribution package"
echo "Revert back to normal"
systemctl stop snapd.service snapd.socket
diff --git a/tests/main/ubuntu-core-gadget-config-defaults/task.yaml b/tests/main/ubuntu-core-gadget-config-defaults/task.yaml
index 4a3201fbf8..80fd1c6d21 100644
--- a/tests/main/ubuntu-core-gadget-config-defaults/task.yaml
+++ b/tests/main/ubuntu-core-gadget-config-defaults/task.yaml
@@ -27,6 +27,10 @@ prepare: |
${TEST_SNAP_ID}:
a: A
b: B
+ system:
+ service:
+ rsyslog:
+ disable: true
EOF
mksquashfs squashfs-root pc_x1.snap -comp xz -no-fragments
rm -rf squashfs-root
@@ -47,6 +51,11 @@ restore: |
echo "This test needs test keys to be trusted"
exit
fi
+ echo "Undo the rsyslog disable"
+ systemctl unmask rsyslog.service || true
+ systemctl enable rsyslog.service || true
+ systemctl start rsyslog.service || true
+
. $TESTSLIB/systemd.sh
systemctl stop snapd.service snapd.socket
rm -rf /var/lib/snapd/assertions/*
@@ -100,3 +109,8 @@ execute: |
echo "The configuration defaults from the gadget where applied"
snap get test-snapd-with-configure a|MATCH "^A$"
snap get test-snapd-with-configure b|MATCH "^B$"
+
+ echo "The configuration for core is applied"
+ snap get core service.rsyslog.disable|MATCH true
+ echo "And the service is masked"
+ systemctl status rsyslog.service|MATCH masked
diff --git a/tests/main/user-mounts/check-user-mount.sh b/tests/main/user-mounts/check-user-mount.sh
deleted file mode 100755
index 9281579014..0000000000
--- a/tests/main/user-mounts/check-user-mount.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-
-set -eux
-
-export XDG_RUNTIME_DIR="$1"
-
-# Add some content to the runtime dir
-rm -rf $XDG_RUNTIME_DIR/snap.test-snapd-sh
-mkdir $XDG_RUNTIME_DIR/snap.test-snapd-sh
-mkdir $XDG_RUNTIME_DIR/snap.test-snapd-sh/source
-mkdir $XDG_RUNTIME_DIR/snap.test-snapd-sh/target
-touch $XDG_RUNTIME_DIR/snap.test-snapd-sh/source/in-source
-touch $XDG_RUNTIME_DIR/snap.test-snapd-sh/target/in-target
-
-# Check target directory from sandbox
-test-snapd-sh -c 'ls $XDG_RUNTIME_DIR/target'
diff --git a/tests/main/user-mounts/task.yaml b/tests/main/user-mounts/task.yaml
index 190d9e994c..5b47a852ff 100644
--- a/tests/main/user-mounts/task.yaml
+++ b/tests/main/user-mounts/task.yaml
@@ -4,29 +4,45 @@ details: |
be performed prior to running the application. These mounts are private
to the invocation of the application, so can be used to manipulate how
per-user data is presented.
+# This test makes use of the user mount provided by the desktop
+# interface. When we have an interface providing user mounts that is
+# available on core, we can switch to that.
+systems: [-ubuntu-core-*]
prepare: |
- . $TESTSLIB/snaps.sh
- install_local test-snapd-sh
- rm -f /var/lib/snapd/mount/snap.test-snapd-sh.user-fstab
+ snap try "$TESTSLIB"/snaps/test-snapd-desktop
+ snap disconnect test-snapd-desktop:desktop
+restore: |
+ rm -rf /run/user/12345/*
+ rm -f /tmp/check-user-mount.sh
execute: |
+ #shellcheck source=tests/lib/dirs.sh
. $TESTSLIB/dirs.sh
echo "Create XDG_RUNTIME_DIR for test user"
USER_RUNTIME_DIR=/run/user/12345
mkdir -p $USER_RUNTIME_DIR || true
+ #shellcheck disable=SC2115
rm -rf $USER_RUNTIME_DIR/*
chmod u=rwX,go= $USER_RUNTIME_DIR
chown test:test $USER_RUNTIME_DIR
- echo "Without user-fstab we see target"
- su -l -c "$(pwd)/check-user-mount.sh $USER_RUNTIME_DIR" test | MATCH in-target
+ echo "Without desktop interface connected, there is no user-fstab file"
+ [ ! -f /var/lib/snapd/mount/snap.test-snapd-desktop.user-fstab ]
- echo "Configuring user-fstab"
- cat << \EOF > /var/lib/snapd/mount/snap.test-snapd-sh.user-fstab
- $XDG_RUNTIME_DIR/source $XDG_RUNTIME_DIR/target none bind,rw 0 0
+ echo "With desktop interface connected, it is created"
+ snap connect test-snapd-desktop:desktop
+ [ -f /var/lib/snapd/mount/snap.test-snapd-desktop.user-fstab ]
+ diff -u /var/lib/snapd/mount/snap.test-snapd-desktop.user-fstab - << \EOF
+ $XDG_RUNTIME_DIR/doc/by-app/snap.test-snapd-desktop $XDG_RUNTIME_DIR/doc none bind,rw,x-snapd.ignore-missing 0 0
EOF
- $LIBEXECDIR/snapd/snap-discard-ns test-snapd-sh
+ "$LIBEXECDIR"/snapd/snap-discard-ns test-snapd-sh
- echo "With user-fstab we see source"
- su -l -c "$(pwd)/check-user-mount.sh $USER_RUNTIME_DIR" test | MATCH in-source
+ echo "The user-fstab file is used to prepare the confinement sandbox"
+ cat << \EOF > /tmp/check-user-mount.sh
+ mkdir -p /run/user/12345/doc/by-app/snap.test-snapd-desktop
+ touch /run/user/12345/doc/by-app/snap.test-snapd-desktop/in-source
+ touch /run/user/12345/doc/in-target
+ test-snapd-desktop.check-dirs /run/user/12345/doc
+ EOF
+ su -l -c "sh /tmp/check-user-mount.sh" test | MATCH in-source
diff --git a/tests/main/validate-container-failures/task.yaml b/tests/main/validate-container-failures/task.yaml
index a5a015be6f..ab1d0d7673 100644
--- a/tests/main/validate-container-failures/task.yaml
+++ b/tests/main/validate-container-failures/task.yaml
@@ -15,13 +15,16 @@ prepare: |
chmod 0644 "$p/meta/hooks/what"
execute: |
+ # shellcheck source=tests/lib/journalctl.sh
+ . "$TESTSLIB/journalctl.sh"
+
echo "Snap refuses to install"
snap try $TESTSLIB/snaps/$SNAP 2>error.log && exit 1 || true
echo "The error tells you to ask the dev"
tr -s "\n " " " < error.log | MATCH 'contact developer'
echo "And the journal counts the ways"
- journalctl -u snapd > journal.log
+ get_journalctl_log -u snapd > journal.log
MATCH '"comp.sh" should be world-readable' < journal.log
MATCH '"bin/bar" should be executable' < journal.log
MATCH '"bin/foo" should be world-readable and executable' < journal.log
diff --git a/tests/unit/spread-shellcheck/must b/tests/unit/spread-shellcheck/must
index e28e40de74..151c58fb14 100644
--- a/tests/unit/spread-shellcheck/must
+++ b/tests/unit/spread-shellcheck/must
@@ -21,3 +21,4 @@ tests/main/classic-ubuntu-core-transition-two-cores/task.yaml
tests/main/classic-ubuntu-core-transition/task.yaml
tests/main/completion/task.yaml
tests/main/config-versions/task.yaml
+tests/main/install-closed-channel/task.yaml
diff --git a/testutil/lowlevel.go b/testutil/lowlevel.go
index 0356d24a40..73e3fc85e7 100644
--- a/testutil/lowlevel.go
+++ b/testutil/lowlevel.go
@@ -158,6 +158,16 @@ func formatUnmountFlags(flags int) string {
return strings.Join(fl, "|")
}
+// CallResultError describes a system call and the corresponding result or error.
+//
+// The field names stand for Call, Result and Error respectively. They are
+// abbreviated due to the nature of their use (in large quantity).
+type CallResultError struct {
+ C string
+ R interface{}
+ E error
+}
+
// SyscallRecorder stores which system calls were invoked.
//
// The recorder supports a small set of features useful for testing: injecting
@@ -165,7 +175,7 @@ func formatUnmountFlags(flags int) string {
// verification of file descriptors.
type SyscallRecorder struct {
// History of all the system calls made.
- calls []string
+ rcalls []CallResultError
// Error function for a given system call.
errors map[string]func() error
// pre-arranged result of lstat, fstat and readdir calls.
@@ -218,16 +228,35 @@ func (sys *SyscallRecorder) InsertFaultFunc(call string, fn func() error) {
// Calls returns the sequence of mocked calls that have been made.
func (sys *SyscallRecorder) Calls() []string {
- return sys.calls
+ if len(sys.rcalls) == 0 {
+ return nil
+ }
+ calls := make([]string, 0, len(sys.rcalls))
+ for _, rc := range sys.rcalls {
+ calls = append(calls, rc.C)
+ }
+ return calls
}
-// call remembers that a given call has occurred and returns a pre-arranged error, if any
-func (sys *SyscallRecorder) call(call string) error {
- sys.calls = append(sys.calls, call)
- if fn := sys.errors[call]; fn != nil {
- return fn()
+// RCalls returns the sequence of mocked calls that have been made along with their results.
+func (sys *SyscallRecorder) RCalls() []CallResultError {
+ return sys.rcalls
+}
+
+// rcall remembers that a given call has occurred and returns a pre-arranged error or value, if any
+func (sys *SyscallRecorder) rcall(call string, resultFn func(call string) (interface{}, error)) (val interface{}, err error) {
+ if errorFn := sys.errors[call]; errorFn != nil {
+ err = errorFn()
}
- return nil
+ if err == nil && resultFn != nil {
+ val, err = resultFn(call)
+ }
+ if err != nil {
+ sys.rcalls = append(sys.rcalls, CallResultError{C: call, E: err})
+ } else {
+ sys.rcalls = append(sys.rcalls, CallResultError{C: call, R: val})
+ }
+ return val, err
}
// allocFd assigns a file descriptor to a given operation.
@@ -271,61 +300,75 @@ func (sys *SyscallRecorder) CheckForStrayDescriptors(c *check.C) {
// Open is a fake implementation of syscall.Open
func (sys *SyscallRecorder) Open(path string, flags int, mode uint32) (int, error) {
call := fmt.Sprintf("open %q %s %#o", path, formatOpenFlags(flags), mode)
- if err := sys.call(call); err != nil {
+ fd, err := sys.rcall(call, func(call string) (interface{}, error) {
+ return sys.allocFd(call), nil
+ })
+ if err != nil {
return -1, err
}
- return sys.allocFd(call), nil
+ return fd.(int), nil
}
// Openat is a fake implementation of syscall.Openat
func (sys *SyscallRecorder) Openat(dirfd int, path string, flags int, mode uint32) (int, error) {
call := fmt.Sprintf("openat %d %q %s %#o", dirfd, path, formatOpenFlags(flags), mode)
- if _, ok := sys.fds[dirfd]; !ok {
- sys.calls = append(sys.calls, call)
- return -1, fmt.Errorf("attempting to openat with an invalid file descriptor %d", dirfd)
- }
- if err := sys.call(call); err != nil {
+ fd, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if _, ok := sys.fds[dirfd]; !ok {
+ return -1, fmt.Errorf("attempting to openat with an invalid file descriptor %d", dirfd)
+ }
+ return sys.allocFd(call), nil
+ })
+ if err != nil {
return -1, err
}
- return sys.allocFd(call), nil
+ return fd.(int), nil
}
// Close is a fake implementation of syscall.Close
func (sys *SyscallRecorder) Close(fd int) error {
- if err := sys.call(fmt.Sprintf("close %d", fd)); err != nil {
- return err
- }
- return sys.freeFd(fd)
+ call := fmt.Sprintf("close %d", fd)
+ _, err := sys.rcall(call, func(call string) (interface{}, error) {
+ return nil, sys.freeFd(fd)
+ })
+ return err
}
// Fchown is a fake implementation of syscall.Fchown
func (sys *SyscallRecorder) Fchown(fd int, uid sys.UserID, gid sys.GroupID) error {
call := fmt.Sprintf("fchown %d %d %d", fd, uid, gid)
- if _, ok := sys.fds[fd]; !ok {
- sys.calls = append(sys.calls, call)
- return fmt.Errorf("attempting to fchown an invalid file descriptor %d", fd)
- }
- return sys.call(call)
+ _, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if _, ok := sys.fds[fd]; !ok {
+ return nil, fmt.Errorf("attempting to fchown an invalid file descriptor %d", fd)
+ }
+ return nil, nil
+ })
+ return err
}
// Mkdirat is a fake implementation of syscall.Mkdirat
func (sys *SyscallRecorder) Mkdirat(dirfd int, path string, mode uint32) error {
call := fmt.Sprintf("mkdirat %d %q %#o", dirfd, path, mode)
- if _, ok := sys.fds[dirfd]; !ok {
- sys.calls = append(sys.calls, call)
- return fmt.Errorf("attempting to mkdirat with an invalid file descriptor %d", dirfd)
- }
- return sys.call(call)
+ _, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if _, ok := sys.fds[dirfd]; !ok {
+ return nil, fmt.Errorf("attempting to mkdirat with an invalid file descriptor %d", dirfd)
+ }
+ return nil, nil
+ })
+ return err
}
// Mount is a fake implementation of syscall.Mount
-func (sys *SyscallRecorder) Mount(source string, target string, fstype string, flags uintptr, data string) (err error) {
- return sys.call(fmt.Sprintf("mount %q %q %q %s %q", source, target, fstype, formatMountFlags(int(flags)), data))
+func (sys *SyscallRecorder) Mount(source string, target string, fstype string, flags uintptr, data string) error {
+ call := fmt.Sprintf("mount %q %q %q %s %q", source, target, fstype, formatMountFlags(int(flags)), data)
+ _, err := sys.rcall(call, nil)
+ return err
}
// Unmount is a fake implementation of syscall.Unmount
-func (sys *SyscallRecorder) Unmount(target string, flags int) (err error) {
- return sys.call(fmt.Sprintf("unmount %q %s", target, formatUnmountFlags(flags)))
+func (sys *SyscallRecorder) Unmount(target string, flags int) error {
+ call := fmt.Sprintf("unmount %q %s", target, formatUnmountFlags(flags))
+ _, err := sys.rcall(call, nil)
+ return err
}
// InsertOsLstatResult makes given subsequent call to OsLstat return the specified fake file info.
@@ -348,27 +391,32 @@ func (sys *SyscallRecorder) InsertSysLstatResult(call string, sb syscall.Stat_t)
func (sys *SyscallRecorder) OsLstat(name string) (os.FileInfo, error) {
// NOTE the syscall.Lstat uses a different signature `lstat %q <ptr>`.
call := fmt.Sprintf("lstat %q", name)
- if err := sys.call(call); err != nil {
+ val, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if fi, ok := sys.osLstats[call]; ok {
+ return fi, nil
+ }
+ panic(fmt.Sprintf("one of InsertOsLstatResult() or InsertFault() for %s must be used", call))
+ })
+ if err != nil {
return nil, err
}
- if fi, ok := sys.osLstats[call]; ok {
- return fi, nil
- }
- panic(fmt.Sprintf("one of InsertOsLstatResult() or InsertFault() for %s must be used", call))
+ return val.(os.FileInfo), err
}
// SysLstat is a fake implementation of syscall.Lstat
func (sys *SyscallRecorder) SysLstat(name string, sb *syscall.Stat_t) error {
// NOTE the os.Lstat uses a different signature `lstat %q`.
call := fmt.Sprintf("lstat %q <ptr>", name)
- if err := sys.call(call); err != nil {
- return err
- }
- if ssb, ok := sys.sysLstats[call]; ok {
- *sb = ssb
- return nil
+ val, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if buf, ok := sys.sysLstats[call]; ok {
+ return buf, nil
+ }
+ panic(fmt.Sprintf("one of InsertSysLstatResult() or InsertFault() for %s must be used", call))
+ })
+ if err == nil && sb != nil {
+ *sb = val.(syscall.Stat_t)
}
- panic(fmt.Sprintf("one of InsertSysLstatResult() or InsertFault() for %s must be used", call))
+ return err
}
// InsertFstatResult makes given subsequent call fstat return the specified stat buffer.
@@ -382,18 +430,19 @@ func (sys *SyscallRecorder) InsertFstatResult(call string, buf syscall.Stat_t) {
// Fstat is a fake implementation of syscall.Fstat
func (sys *SyscallRecorder) Fstat(fd int, buf *syscall.Stat_t) error {
call := fmt.Sprintf("fstat %d <ptr>", fd)
- if _, ok := sys.fds[fd]; !ok {
- sys.calls = append(sys.calls, call)
- return fmt.Errorf("attempting to fstat with an invalid file descriptor %d", fd)
- }
- if err := sys.call(call); err != nil {
- return err
- }
- if b, ok := sys.fstats[call]; ok {
- *buf = b
- return nil
+ val, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if _, ok := sys.fds[fd]; !ok {
+ return nil, fmt.Errorf("attempting to fstat with an invalid file descriptor %d", fd)
+ }
+ if buf, ok := sys.fstats[call]; ok {
+ return buf, nil
+ }
+ panic(fmt.Sprintf("one of InsertFstatResult() or InsertFault() for %s must be used", call))
+ })
+ if err == nil && buf != nil {
+ *buf = val.(syscall.Stat_t)
}
- panic(fmt.Sprintf("one of InsertFstatResult() or InsertFault() for %s must be used", call))
+ return err
}
// InsertReadDirResult makes given subsequent call readdir return the specified fake file infos.
@@ -407,29 +456,35 @@ func (sys *SyscallRecorder) InsertReadDirResult(call string, infos []os.FileInfo
// ReadDir is a fake implementation of os.ReadDir
func (sys *SyscallRecorder) ReadDir(dirname string) ([]os.FileInfo, error) {
call := fmt.Sprintf("readdir %q", dirname)
- if err := sys.call(call); err != nil {
- return nil, err
- }
- if fi, ok := sys.readdirs[call]; ok {
- return fi, nil
+ val, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if fi, ok := sys.readdirs[call]; ok {
+ return fi, nil
+ }
+ panic(fmt.Sprintf("one of InsertReadDirResult() or InsertFault() for %s must be used", call))
+ })
+ if err == nil {
+ return val.([]os.FileInfo), nil
}
- panic(fmt.Sprintf("one of InsertReadDirResult() or InsertFault() for %s must be used", call))
+ return nil, err
}
// Symlink is a fake implementation of syscall.Symlink
func (sys *SyscallRecorder) Symlink(oldname, newname string) error {
call := fmt.Sprintf("symlink %q -> %q", newname, oldname)
- return sys.call(call)
+ _, err := sys.rcall(call, nil)
+ return err
}
// Symlinkat is a fake implementation of osutil.Symlinkat (syscall.Symlinkat is not exposed)
func (sys *SyscallRecorder) Symlinkat(oldname string, dirfd int, newname string) error {
call := fmt.Sprintf("symlinkat %q %d %q", oldname, dirfd, newname)
- if _, ok := sys.fds[dirfd]; !ok {
- sys.calls = append(sys.calls, call)
- return fmt.Errorf("attempting to symlinkat with an invalid file descriptor %d", dirfd)
- }
- return sys.call(call)
+ _, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if _, ok := sys.fds[dirfd]; !ok {
+ return nil, fmt.Errorf("attempting to symlinkat with an invalid file descriptor %d", dirfd)
+ }
+ return nil, nil
+ })
+ return err
}
// InsertReadlinkatResult makes given subsequent call to readlinkat return the specified oldname.
@@ -443,32 +498,37 @@ func (sys *SyscallRecorder) InsertReadlinkatResult(call, oldname string) {
// Readlinkat is a fake implementation of osutil.Readlinkat (syscall.Readlinkat is not exposed)
func (sys *SyscallRecorder) Readlinkat(dirfd int, path string, buf []byte) (int, error) {
call := fmt.Sprintf("readlinkat %d %q <ptr>", dirfd, path)
- if _, ok := sys.fds[dirfd]; !ok {
- sys.calls = append(sys.calls, call)
- return 0, fmt.Errorf("attempting to readlinkat with an invalid file descriptor %d", dirfd)
- }
- if err := sys.call(call); err != nil {
- return 0, err
- }
- if oldname, ok := sys.readlinkats[call]; ok {
- n := copy(buf, oldname)
+ val, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if _, ok := sys.fds[dirfd]; !ok {
+ return nil, fmt.Errorf("attempting to readlinkat with an invalid file descriptor %d", dirfd)
+ }
+ if oldname, ok := sys.readlinkats[call]; ok {
+ return oldname, nil
+ }
+ panic(fmt.Sprintf("one of InsertReadlinkatResult() or InsertFault() for %s must be used", call))
+ })
+ if err == nil {
+ n := copy(buf, val.(string))
return n, nil
}
- panic(fmt.Sprintf("one of InsertReadlinkatResult() or InsertFault() for %s must be used", call))
+ return 0, err
}
// Remove is a fake implementation of os.Remove
func (sys *SyscallRecorder) Remove(name string) error {
call := fmt.Sprintf("remove %q", name)
- return sys.call(call)
+ _, err := sys.rcall(call, nil)
+ return err
}
// Fchdir is a fake implementation of syscall.Fchdir
func (sys *SyscallRecorder) Fchdir(fd int) error {
call := fmt.Sprintf("fchdir %d", fd)
- if _, ok := sys.fds[fd]; !ok {
- sys.calls = append(sys.calls, call)
- return fmt.Errorf("attempting to fchdir with an invalid file descriptor %d", fd)
- }
- return sys.call(call)
+ _, err := sys.rcall(call, func(call string) (interface{}, error) {
+ if _, ok := sys.fds[fd]; !ok {
+ return nil, fmt.Errorf("attempting to fchdir with an invalid file descriptor %d", fd)
+ }
+ return nil, nil
+ })
+ return err
}
diff --git a/testutil/lowlevel_test.go b/testutil/lowlevel_test.go
index e7db54d505..abe2aa84ce 100644
--- a/testutil/lowlevel_test.go
+++ b/testutil/lowlevel_test.go
@@ -65,6 +65,9 @@ func (s *lowLevelSuite) TestOpenSuccess(c *C) {
c.Assert(s.sys.Calls(), DeepEquals, []string{
`open "/some/path" O_NOFOLLOW|O_CLOEXEC|O_RDWR|O_CREAT|O_EXCL 0`, // -> 3
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/some/path" O_NOFOLLOW|O_CLOEXEC|O_RDWR|O_CREAT|O_EXCL 0`, R: 3},
+ })
}
func (s *lowLevelSuite) TestOpenFailure(c *C) {
@@ -73,6 +76,12 @@ func (s *lowLevelSuite) TestOpenFailure(c *C) {
fd, err := s.sys.Open("/some/path", 0, 0)
c.Assert(err, ErrorMatches, "no such file or directory")
c.Assert(fd, Equals, -1)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `open "/some/path" 0 0`, // -> ENOENT
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/some/path" 0 0`, E: syscall.ENOENT},
+ })
}
func (s *lowLevelSuite) TestOpenVariableFailure(c *C) {
@@ -90,6 +99,16 @@ func (s *lowLevelSuite) TestOpenVariableFailure(c *C) {
fd, err = s.sys.Open("/some/path", syscall.O_RDWR, 0)
c.Assert(err, IsNil)
c.Assert(fd, Equals, 3)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `open "/some/path" O_RDWR 0`, // -> ENOENT
+ `open "/some/path" O_RDWR 0`, // -> EPERM
+ `open "/some/path" O_RDWR 0`, // -> 3
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/some/path" O_RDWR 0`, E: syscall.ENOENT},
+ {C: `open "/some/path" O_RDWR 0`, E: syscall.EPERM},
+ {C: `open "/some/path" O_RDWR 0`, R: 3},
+ })
}
func (s *lowLevelSuite) TestOpenCustomFailure(c *C) {
@@ -112,6 +131,18 @@ func (s *lowLevelSuite) TestOpenCustomFailure(c *C) {
fd, err := s.sys.Open("/some/path", syscall.O_RDWR, 0)
c.Assert(err, IsNil)
c.Assert(fd, Equals, 3)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `open "/some/path" O_RDWR 0`, // -> 3 more
+ `open "/some/path" O_RDWR 0`, // -> 2 more
+ `open "/some/path" O_RDWR 0`, // -> 1 more
+ `open "/some/path" O_RDWR 0`, // -> 3
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/some/path" O_RDWR 0`, E: fmt.Errorf("3 more")},
+ {C: `open "/some/path" O_RDWR 0`, E: fmt.Errorf("2 more")},
+ {C: `open "/some/path" O_RDWR 0`, E: fmt.Errorf("1 more")},
+ {C: `open "/some/path" O_RDWR 0`, R: 3},
+ })
}
func (s *lowLevelSuite) TestUnclosedFile(c *C) {
@@ -123,6 +154,9 @@ func (s *lowLevelSuite) TestUnclosedFile(c *C) {
c.Assert(s.sys.Calls(), DeepEquals, []string{
`open "/some/path" O_RDWR 0`, // -> 3
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/some/path" O_RDWR 0`, R: 3},
+ })
c.Assert(s.sys.StrayDescriptorsError(), ErrorMatches,
`unclosed file descriptor 3 \(open "/some/path" O_RDWR 0\)`)
}
@@ -132,6 +166,9 @@ func (s *lowLevelSuite) TestUnopenedFile(c *C) {
err := s.sys.Close(7)
c.Assert(err, ErrorMatches, "attempting to close a closed file descriptor 7")
c.Assert(s.sys.Calls(), DeepEquals, []string{`close 7`})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `close 7`, E: fmt.Errorf("attempting to close a closed file descriptor 7")},
+ })
}
func (s *lowLevelSuite) TestCloseSuccess(c *C) {
@@ -144,6 +181,10 @@ func (s *lowLevelSuite) TestCloseSuccess(c *C) {
`open "/some/path" O_RDWR 0`, // -> 3
`close 3`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/some/path" O_RDWR 0`, R: 3},
+ {C: `close 3`},
+ })
c.Assert(s.sys.StrayDescriptorsError(), IsNil)
}
@@ -155,6 +196,9 @@ func (s *lowLevelSuite) TestCloseFailure(c *C) {
c.Assert(s.sys.Calls(), DeepEquals, []string{
`close 3`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `close 3`, E: syscall.ENOSYS},
+ })
}
func (s *lowLevelSuite) TestOpenatSuccess(c *C) {
@@ -170,6 +214,12 @@ func (s *lowLevelSuite) TestOpenatSuccess(c *C) {
`close 4`,
`close 3`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/" O_DIRECTORY 0`, R: 3},
+ {C: `openat 3 "foo" O_DIRECTORY 0`, R: 4},
+ {C: `close 4`},
+ {C: `close 3`},
+ })
}
func (s *lowLevelSuite) TestOpenatFailure(c *C) {
@@ -185,6 +235,11 @@ func (s *lowLevelSuite) TestOpenatFailure(c *C) {
`openat 3 "foo" O_DIRECTORY 0`, // -> ENOENT
`close 3`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/" O_DIRECTORY 0`, R: 3},
+ {C: `openat 3 "foo" O_DIRECTORY 0`, E: syscall.ENOENT},
+ {C: `close 3`},
+ })
}
func (s *lowLevelSuite) TestOpenatBadFd(c *C) {
@@ -194,6 +249,9 @@ func (s *lowLevelSuite) TestOpenatBadFd(c *C) {
c.Assert(s.sys.Calls(), DeepEquals, []string{
`openat 3 "foo" O_DIRECTORY 0`, // -> error
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `openat 3 "foo" O_DIRECTORY 0`, E: fmt.Errorf("attempting to openat with an invalid file descriptor 3")},
+ })
}
func (s *lowLevelSuite) TestFchownSuccess(c *C) {
@@ -207,6 +265,11 @@ func (s *lowLevelSuite) TestFchownSuccess(c *C) {
`fchown 3 0 0`,
`close 3`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/" O_DIRECTORY 0`, R: 3},
+ {C: `fchown 3 0 0`},
+ {C: `close 3`},
+ })
}
func (s *lowLevelSuite) TestFchownFailure(c *C) {
@@ -221,6 +284,11 @@ func (s *lowLevelSuite) TestFchownFailure(c *C) {
`fchown 3 0 0`, // -> EPERM
`close 3`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/" O_DIRECTORY 0`, R: 3},
+ {C: `fchown 3 0 0`, E: syscall.EPERM},
+ {C: `close 3`},
+ })
}
func (s *lowLevelSuite) TestFchownBadFd(c *C) {
@@ -229,6 +297,9 @@ func (s *lowLevelSuite) TestFchownBadFd(c *C) {
c.Assert(s.sys.Calls(), DeepEquals, []string{
`fchown 3 0 0`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `fchown 3 0 0`, E: fmt.Errorf("attempting to fchown an invalid file descriptor 3")},
+ })
}
func (s *lowLevelSuite) TestMkdiratSuccess(c *C) {
@@ -242,6 +313,11 @@ func (s *lowLevelSuite) TestMkdiratSuccess(c *C) {
`mkdirat 3 "foo" 0755`,
`close 3`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/" O_DIRECTORY 0`, R: 3},
+ {C: `mkdirat 3 "foo" 0755`},
+ {C: `close 3`},
+ })
}
func (s *lowLevelSuite) TestMkdiratFailure(c *C) {
@@ -256,12 +332,22 @@ func (s *lowLevelSuite) TestMkdiratFailure(c *C) {
`mkdirat 3 "foo" 0755`, // -> EPERM
`close 3`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/" O_DIRECTORY 0`, R: 3},
+ {C: `mkdirat 3 "foo" 0755`, E: syscall.EPERM},
+ {C: `close 3`},
+ })
}
func (s *lowLevelSuite) TestMkdiratBadFd(c *C) {
err := s.sys.Mkdirat(3, "foo", 0755)
c.Assert(err, ErrorMatches, "attempting to mkdirat with an invalid file descriptor 3")
- c.Assert(s.sys.Calls(), DeepEquals, []string{`mkdirat 3 "foo" 0755`})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `mkdirat 3 "foo" 0755`,
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `mkdirat 3 "foo" 0755`, E: fmt.Errorf("attempting to mkdirat with an invalid file descriptor 3")},
+ })
}
func (s *lowLevelSuite) TestMountSuccess(c *C) {
@@ -270,6 +356,9 @@ func (s *lowLevelSuite) TestMountSuccess(c *C) {
c.Assert(s.sys.Calls(), DeepEquals, []string{
`mount "source" "target" "fstype" MS_BIND|MS_REC|MS_RDONLY ""`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `mount "source" "target" "fstype" MS_BIND|MS_REC|MS_RDONLY ""`},
+ })
}
func (s *lowLevelSuite) TestMountFailure(c *C) {
@@ -279,12 +368,18 @@ func (s *lowLevelSuite) TestMountFailure(c *C) {
c.Assert(s.sys.Calls(), DeepEquals, []string{
`mount "source" "target" "fstype" 0 ""`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `mount "source" "target" "fstype" 0 ""`, E: syscall.EPERM},
+ })
}
func (s *lowLevelSuite) TestUnmountSuccess(c *C) {
err := s.sys.Unmount("target", testutil.UmountNoFollow|syscall.MNT_DETACH)
c.Assert(err, IsNil)
c.Assert(s.sys.Calls(), DeepEquals, []string{`unmount "target" UMOUNT_NOFOLLOW|MNT_DETACH`})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `unmount "target" UMOUNT_NOFOLLOW|MNT_DETACH`},
+ })
}
func (s *lowLevelSuite) TestUnmountFailure(c *C) {
@@ -292,6 +387,9 @@ func (s *lowLevelSuite) TestUnmountFailure(c *C) {
err := s.sys.Unmount("target", 0)
c.Assert(err, ErrorMatches, "operation not permitted")
c.Assert(s.sys.Calls(), DeepEquals, []string{`unmount "target" 0`})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `unmount "target" 0`, E: syscall.EPERM},
+ })
}
func (s *lowLevelSuite) TestOsLstat(c *C) {
@@ -306,6 +404,10 @@ func (s *lowLevelSuite) TestOsLstatSuccess(c *C) {
fi, err := s.sys.OsLstat("/foo")
c.Assert(err, IsNil)
c.Assert(fi, DeepEquals, testutil.FileInfoFile)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{`lstat "/foo"`})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `lstat "/foo"`, R: testutil.FileInfoFile},
+ })
}
func (s *lowLevelSuite) TestOsLstatFailure(c *C) {
@@ -315,6 +417,10 @@ func (s *lowLevelSuite) TestOsLstatFailure(c *C) {
fi, err := s.sys.OsLstat("/foo")
c.Assert(err, ErrorMatches, "no such file or directory")
c.Assert(fi, IsNil)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{`lstat "/foo"`})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `lstat "/foo"`, E: syscall.ENOENT},
+ })
}
func (s *lowLevelSuite) TestSysLstat(c *C) {
@@ -331,6 +437,12 @@ func (s *lowLevelSuite) TestSysLstatSuccess(c *C) {
err := s.sys.SysLstat("/foo", &buf)
c.Assert(err, IsNil)
c.Assert(buf, DeepEquals, syscall.Stat_t{Uid: 123})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `lstat "/foo" <ptr>`,
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `lstat "/foo" <ptr>`, R: syscall.Stat_t{Uid: 123}},
+ })
}
func (s *lowLevelSuite) TestSysLstatFailure(c *C) {
@@ -341,6 +453,12 @@ func (s *lowLevelSuite) TestSysLstatFailure(c *C) {
err := s.sys.SysLstat("/foo", &buf)
c.Assert(err, ErrorMatches, "no such file or directory")
c.Assert(buf, DeepEquals, syscall.Stat_t{})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `lstat "/foo" <ptr>`, // -> ENOENT
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `lstat "/foo" <ptr>`, E: syscall.ENOENT},
+ })
}
func (s *lowLevelSuite) TestFstat(c *C) {
@@ -355,7 +473,12 @@ func (s *lowLevelSuite) TestFstatBadFd(c *C) {
var buf syscall.Stat_t
err := s.sys.Fstat(3, &buf)
c.Assert(err, ErrorMatches, "attempting to fstat with an invalid file descriptor 3")
- c.Assert(s.sys.Calls(), DeepEquals, []string{`fstat 3 <ptr>`})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `fstat 3 <ptr>`,
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `fstat 3 <ptr>`, E: fmt.Errorf("attempting to fstat with an invalid file descriptor 3")},
+ })
}
func (s *lowLevelSuite) TestFstatSuccess(c *C) {
@@ -366,6 +489,14 @@ func (s *lowLevelSuite) TestFstatSuccess(c *C) {
err = s.sys.Fstat(fd, &buf)
c.Assert(err, IsNil)
c.Assert(buf, Equals, syscall.Stat_t{Dev: 0xC0FE})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `open "/foo" 0 0`, // -> 3
+ `fstat 3 <ptr>`,
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/foo" 0 0`, R: 3},
+ {C: `fstat 3 <ptr>`, R: syscall.Stat_t{Dev: 0xC0FE}},
+ })
}
func (s *lowLevelSuite) TestFstatFailure(c *C) {
@@ -376,6 +507,14 @@ func (s *lowLevelSuite) TestFstatFailure(c *C) {
err = s.sys.Fstat(fd, &buf)
c.Assert(err, ErrorMatches, "operation not permitted")
c.Assert(buf, Equals, syscall.Stat_t{})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `open "/foo" 0 0`, // -> 3
+ `fstat 3 <ptr>`, // -> EPERM
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/foo" 0 0`, R: 3},
+ {C: `fstat 3 <ptr>`, E: syscall.EPERM},
+ })
}
func (s *lowLevelSuite) TestReadDir(c *C) {
@@ -384,13 +523,20 @@ func (s *lowLevelSuite) TestReadDir(c *C) {
}
func (s *lowLevelSuite) TestReadDirSuccess(c *C) {
- s.sys.InsertReadDirResult(`readdir "/foo"`, []os.FileInfo{
+ files := []os.FileInfo{
testutil.FakeFileInfo("file", 0644),
testutil.FakeFileInfo("dir", 0755|os.ModeDir),
- })
+ }
+ s.sys.InsertReadDirResult(`readdir "/foo"`, files)
files, err := s.sys.ReadDir("/foo")
c.Assert(err, IsNil)
c.Assert(files, HasLen, 2)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `readdir "/foo"`,
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `readdir "/foo"`, R: files},
+ })
}
func (s *lowLevelSuite) TestReadDirFailure(c *C) {
@@ -398,25 +544,46 @@ func (s *lowLevelSuite) TestReadDirFailure(c *C) {
files, err := s.sys.ReadDir("/foo")
c.Assert(err, ErrorMatches, "no such file or directory")
c.Assert(files, IsNil)
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `readdir "/foo"`, // -> ENOENT
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `readdir "/foo"`, E: syscall.ENOENT},
+ })
}
func (s *lowLevelSuite) TestSymlinkSuccess(c *C) {
err := s.sys.Symlink("oldname", "newname")
c.Assert(err, IsNil)
- c.Assert(s.sys.Calls(), DeepEquals, []string{`symlink "newname" -> "oldname"`})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `symlink "newname" -> "oldname"`,
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `symlink "newname" -> "oldname"`},
+ })
}
func (s *lowLevelSuite) TestSymlinkFailure(c *C) {
s.sys.InsertFault(`symlink "newname" -> "oldname"`, syscall.EPERM)
err := s.sys.Symlink("oldname", "newname")
c.Assert(err, ErrorMatches, "operation not permitted")
- c.Assert(s.sys.Calls(), DeepEquals, []string{`symlink "newname" -> "oldname"`})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `symlink "newname" -> "oldname"`, // -> EPERM
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `symlink "newname" -> "oldname"`, E: syscall.EPERM},
+ })
}
func (s *lowLevelSuite) TestRemoveSuccess(c *C) {
err := s.sys.Remove("file")
c.Assert(err, IsNil)
- c.Assert(s.sys.Calls(), DeepEquals, []string{`remove "file"`})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `remove "file"`,
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `remove "file"`},
+ })
}
func (s *lowLevelSuite) TestRemoveFailure(c *C) {
@@ -424,12 +591,23 @@ func (s *lowLevelSuite) TestRemoveFailure(c *C) {
err := s.sys.Remove("file")
c.Assert(err, ErrorMatches, "operation not permitted")
c.Assert(s.sys.Calls(), DeepEquals, []string{`remove "file"`})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `remove "file"`, // -> EPERM
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `remove "file"`, E: syscall.EPERM},
+ })
}
func (s *lowLevelSuite) TestSymlinkatBadFd(c *C) {
err := s.sys.Symlinkat("/old", 3, "new")
c.Assert(err, ErrorMatches, "attempting to symlinkat with an invalid file descriptor 3")
- c.Assert(s.sys.Calls(), DeepEquals, []string{`symlinkat "/old" 3 "new"`})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `symlinkat "/old" 3 "new"`,
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `symlinkat "/old" 3 "new"`, E: fmt.Errorf("attempting to symlinkat with an invalid file descriptor 3")},
+ })
}
func (s *lowLevelSuite) TestSymlinkatSuccess(c *C) {
@@ -441,6 +619,10 @@ func (s *lowLevelSuite) TestSymlinkatSuccess(c *C) {
`open "/foo" 0 0`,
`symlinkat "/old" 3 "new"`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/foo" 0 0`, R: 3},
+ {C: `symlinkat "/old" 3 "new"`},
+ })
}
func (s *lowLevelSuite) TestSymlinkatFailure(c *C) {
@@ -450,9 +632,13 @@ func (s *lowLevelSuite) TestSymlinkatFailure(c *C) {
err = s.sys.Symlinkat("/old", fd, "new")
c.Assert(err, ErrorMatches, "operation not permitted")
c.Assert(s.sys.Calls(), DeepEquals, []string{
- `open "/foo" 0 0`,
+ `open "/foo" 0 0`, // -> 3
`symlinkat "/old" 3 "new"`,
})
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `open "/foo" 0 0`, R: 3},
+ {C: `symlinkat "/old" 3 "new"`, E: syscall.EPERM},
+ })
}
func (s *lowLevelSuite) TestReadlinkat(c *C) {
@@ -468,7 +654,12 @@ func (s *lowLevelSuite) TestReadlinkatBadFd(c *C) {
n, err := s.sys.Readlinkat(3, "new", buf)
c.Assert(err, ErrorMatches, "attempting to readlinkat with an invalid file descriptor 3")
c.Assert(n, Equals, 0)
- c.Assert(s.sys.Calls(), DeepEquals, []string{`readlinkat 3 "new" <ptr>`})
+ c.Assert(s.sys.Calls(), DeepEquals, []string{
+ `readlinkat 3 "new" <ptr>`,
+ })
+ c.Assert(s.sys.RCalls(), DeepEquals, []testutil.CallResultError{
+ {C: `readlinkat 3 "new" <ptr>`, E: fmt.Errorf("attempting to readlinkat with an invalid file descriptor 3")},
+ })
}
func (s *lowLevelSuite) TestReadlinkatSuccess(c *C) {