summaryrefslogtreecommitdiff
diff options
authorMichael Vogt <mvo@ubuntu.com>2017-05-17 07:51:34 +0200
committerMichael Vogt <mvo@ubuntu.com>2017-05-17 07:51:34 +0200
commit72b6f480e3d396ead2888062b29b06c58b5541d7 (patch)
treeb43244bcb8691c30b8241964e8b494dc5d8a5824
parentdac697f7ba99c67f143abc8a31ac7389b77acbf9 (diff)
parent20a059ce553d257a357070928593188845e9e4e4 (diff)
Merge remote-tracking branch 'upstream/master' into release/2.26.2release/2.26.2
-rw-r--r--.travis.yml1
-rw-r--r--README.md6
-rw-r--r--cmd/cmd.go139
-rw-r--r--cmd/cmd_test.go54
-rw-r--r--cmd/export_test.go24
-rw-r--r--cmd/snap-confine/mount-support.c36
-rw-r--r--cmd/snap-confine/snap-confine.apparmor.in2
-rw-r--r--cmd/snap-confine/snap-confine.c12
-rw-r--r--cmd/snap-update-ns/bootstrap.go12
-rw-r--r--cmd/snap-update-ns/main.go67
-rw-r--r--cmd/snap/cmd_snap_op.go6
-rw-r--r--cmd/snap/cmd_snap_op_test.go57
-rw-r--r--daemon/api.go83
-rw-r--r--daemon/api_test.go129
-rw-r--r--dirs/dirs.go2
-rwxr-xr-xget-deps.sh8
-rw-r--r--interfaces/builtin/account_control.go15
-rw-r--r--interfaces/builtin/account_control_test.go5
-rw-r--r--interfaces/builtin/all.go22
-rw-r--r--interfaces/builtin/all_test.go14
-rw-r--r--interfaces/builtin/alsa.go12
-rw-r--r--interfaces/builtin/alsa_test.go6
-rw-r--r--interfaces/builtin/autopilot.go16
-rw-r--r--interfaces/builtin/autopilot_test.go5
-rw-r--r--interfaces/builtin/avahi_observe.go12
-rw-r--r--interfaces/builtin/avahi_observe_test.go5
-rw-r--r--interfaces/builtin/bluetooth_control.go14
-rw-r--r--interfaces/builtin/bluetooth_control_test.go5
-rw-r--r--interfaces/builtin/bluez.go22
-rw-r--r--interfaces/builtin/bluez_test.go5
-rw-r--r--interfaces/builtin/bool_file.go28
-rw-r--r--interfaces/builtin/bool_file_test.go2
-rw-r--r--interfaces/builtin/browser_support.go20
-rw-r--r--interfaces/builtin/browser_support_test.go5
-rw-r--r--interfaces/builtin/camera.go15
-rw-r--r--interfaces/builtin/classic_support.go14
-rw-r--r--interfaces/builtin/classic_support_test.go5
-rw-r--r--interfaces/builtin/content.go24
-rw-r--r--interfaces/builtin/content_test.go12
-rw-r--r--interfaces/builtin/core_support.go17
-rw-r--r--interfaces/builtin/core_support_test.go5
-rw-r--r--interfaces/builtin/cups_control.go13
-rw-r--r--interfaces/builtin/dbus.go26
-rw-r--r--interfaces/builtin/dbus_test.go24
-rw-r--r--interfaces/builtin/dcdbas_control.go15
-rw-r--r--interfaces/builtin/dcdbas_control_test.go5
-rw-r--r--interfaces/builtin/docker.go16
-rw-r--r--interfaces/builtin/docker_support.go18
-rw-r--r--interfaces/builtin/docker_support_test.go5
-rw-r--r--interfaces/builtin/docker_test.go5
-rw-r--r--interfaces/builtin/export_test.go35
-rw-r--r--interfaces/builtin/firewall_control.go18
-rw-r--r--interfaces/builtin/firewall_control_test.go5
-rw-r--r--interfaces/builtin/framebuffer.go18
-rw-r--r--interfaces/builtin/framebuffer_test.go2
-rw-r--r--interfaces/builtin/fuse_support.go13
-rw-r--r--interfaces/builtin/fuse_support_test.go5
-rw-r--r--interfaces/builtin/fwupd.go28
-rw-r--r--interfaces/builtin/fwupd_test.go5
-rw-r--r--interfaces/builtin/gpio.go24
-rw-r--r--interfaces/builtin/gpio_test.go2
-rw-r--r--interfaces/builtin/gsettings.go15
-rw-r--r--interfaces/builtin/gsettings_test.go5
-rw-r--r--interfaces/builtin/hardware_observe.go15
-rw-r--r--interfaces/builtin/hardware_observe_test.go5
-rw-r--r--interfaces/builtin/hardware_random_control.go16
-rw-r--r--interfaces/builtin/hardware_random_control_test.go2
-rw-r--r--interfaces/builtin/hardware_random_observe.go16
-rw-r--r--interfaces/builtin/hardware_random_observe_test.go2
-rw-r--r--interfaces/builtin/hidraw.go26
-rw-r--r--interfaces/builtin/hidraw_test.go2
-rw-r--r--interfaces/builtin/home.go15
-rw-r--r--interfaces/builtin/home_test.go5
-rw-r--r--interfaces/builtin/i2c.go20
-rw-r--r--interfaces/builtin/i2c_test.go2
-rw-r--r--interfaces/builtin/iio.go20
-rw-r--r--interfaces/builtin/iio_test.go2
-rw-r--r--interfaces/builtin/io_ports_control.go20
-rw-r--r--interfaces/builtin/io_ports_control_test.go2
-rw-r--r--interfaces/builtin/joystick.go20
-rw-r--r--interfaces/builtin/joystick_test.go2
-rw-r--r--interfaces/builtin/kernel_module_control.go15
-rw-r--r--interfaces/builtin/kernel_module_control_test.go5
-rw-r--r--interfaces/builtin/kubernetes_support.go15
-rw-r--r--interfaces/builtin/kubernetes_support_test.go5
-rw-r--r--interfaces/builtin/libvirt.go12
-rw-r--r--interfaces/builtin/libvirt_test.go2
-rw-r--r--interfaces/builtin/locale_control.go15
-rw-r--r--interfaces/builtin/locale_control_test.go5
-rw-r--r--interfaces/builtin/location_control.go22
-rw-r--r--interfaces/builtin/location_control_test.go5
-rw-r--r--interfaces/builtin/location_observe.go22
-rw-r--r--interfaces/builtin/location_observe_test.go5
-rw-r--r--interfaces/builtin/log_observe.go15
-rw-r--r--interfaces/builtin/log_observe_test.go5
-rw-r--r--interfaces/builtin/lxd.go16
-rw-r--r--interfaces/builtin/lxd_support.go16
-rw-r--r--interfaces/builtin/lxd_support_test.go5
-rw-r--r--interfaces/builtin/lxd_test.go2
-rw-r--r--interfaces/builtin/maliit.go20
-rw-r--r--interfaces/builtin/maliit_test.go5
-rw-r--r--interfaces/builtin/media_hub.go20
-rw-r--r--interfaces/builtin/media_hub_test.go5
-rw-r--r--interfaces/builtin/mir.go20
-rw-r--r--interfaces/builtin/mir_test.go5
-rw-r--r--interfaces/builtin/modem_manager.go26
-rw-r--r--interfaces/builtin/modem_manager_test.go5
-rw-r--r--interfaces/builtin/mount_observe.go15
-rw-r--r--interfaces/builtin/mount_observe_test.go5
-rw-r--r--interfaces/builtin/mpris.go24
-rw-r--r--interfaces/builtin/mpris_test.go20
-rw-r--r--interfaces/builtin/netlink_audit.go15
-rw-r--r--interfaces/builtin/netlink_audit_test.go5
-rw-r--r--interfaces/builtin/netlink_connector.go15
-rw-r--r--interfaces/builtin/netlink_connector_test.go5
-rw-r--r--interfaces/builtin/network.go13
-rw-r--r--interfaces/builtin/network_bind.go15
-rw-r--r--interfaces/builtin/network_bind_test.go5
-rw-r--r--interfaces/builtin/network_control.go15
-rw-r--r--interfaces/builtin/network_control_test.go5
-rw-r--r--interfaces/builtin/network_manager.go22
-rw-r--r--interfaces/builtin/network_manager_test.go5
-rw-r--r--interfaces/builtin/network_observe.go15
-rw-r--r--interfaces/builtin/network_observe_test.go5
-rw-r--r--interfaces/builtin/network_setup_control.go15
-rw-r--r--interfaces/builtin/network_setup_control_test.go5
-rw-r--r--interfaces/builtin/network_setup_observe.go15
-rw-r--r--interfaces/builtin/network_setup_observe_test.go5
-rw-r--r--interfaces/builtin/network_status.go20
-rw-r--r--interfaces/builtin/network_status_test.go2
-rw-r--r--interfaces/builtin/network_test.go5
-rw-r--r--interfaces/builtin/ofono.go24
-rw-r--r--interfaces/builtin/ofono_test.go5
-rw-r--r--interfaces/builtin/online_accounts_service.go20
-rw-r--r--interfaces/builtin/online_accounts_service_test.go2
-rw-r--r--interfaces/builtin/opengl.go15
-rw-r--r--interfaces/builtin/openvswitch.go12
-rw-r--r--interfaces/builtin/openvswitch_support.go15
-rw-r--r--interfaces/builtin/openvswitch_support_test.go2
-rw-r--r--interfaces/builtin/openvswitch_test.go5
-rw-r--r--interfaces/builtin/optical_drive.go15
-rw-r--r--interfaces/builtin/physical_memory_control.go18
-rw-r--r--interfaces/builtin/physical_memory_control_test.go2
-rw-r--r--interfaces/builtin/physical_memory_observe.go18
-rw-r--r--interfaces/builtin/physical_memory_observe_test.go2
-rw-r--r--interfaces/builtin/ppp.go16
-rw-r--r--interfaces/builtin/ppp_test.go6
-rw-r--r--interfaces/builtin/process_control.go15
-rw-r--r--interfaces/builtin/process_control_test.go5
-rw-r--r--interfaces/builtin/pulseaudio.go20
-rw-r--r--interfaces/builtin/pulseaudio_test.go2
-rw-r--r--interfaces/builtin/raw_usb.go15
-rw-r--r--interfaces/builtin/raw_usb_test.go5
-rw-r--r--interfaces/builtin/removable_media.go15
-rw-r--r--interfaces/builtin/removable_media_test.go5
-rw-r--r--interfaces/builtin/screen_inhibit_control.go15
-rw-r--r--interfaces/builtin/screen_inhibit_control_test.go5
-rw-r--r--interfaces/builtin/serial_port.go26
-rw-r--r--interfaces/builtin/serial_port_test.go2
-rw-r--r--interfaces/builtin/shutdown.go15
-rw-r--r--interfaces/builtin/shutdown_test.go5
-rw-r--r--interfaces/builtin/snapd_control.go15
-rw-r--r--interfaces/builtin/snapd_control_test.go5
-rw-r--r--interfaces/builtin/storage_framework_service.go20
-rw-r--r--interfaces/builtin/storage_framework_service_test.go2
-rw-r--r--interfaces/builtin/system_observe.go15
-rw-r--r--interfaces/builtin/system_observe_test.go5
-rw-r--r--interfaces/builtin/system_trace.go15
-rw-r--r--interfaces/builtin/system_trace_test.go5
-rw-r--r--interfaces/builtin/thumbnailer_service.go18
-rw-r--r--interfaces/builtin/thumbnailer_service_test.go5
-rw-r--r--interfaces/builtin/time_control.go18
-rw-r--r--interfaces/builtin/time_control_test.go2
-rw-r--r--interfaces/builtin/timeserver_control.go15
-rw-r--r--interfaces/builtin/timeserver_control_test.go5
-rw-r--r--interfaces/builtin/timezone_control.go16
-rw-r--r--interfaces/builtin/timezone_control_test.go5
-rw-r--r--interfaces/builtin/tpm.go12
-rw-r--r--interfaces/builtin/tpm_test.go5
-rw-r--r--interfaces/builtin/ubuntu_download_manager.go20
-rw-r--r--interfaces/builtin/ubuntu_download_manager_test.go5
-rw-r--r--interfaces/builtin/udisks2.go26
-rw-r--r--interfaces/builtin/udisks2_test.go5
-rw-r--r--interfaces/builtin/uhid.go18
-rw-r--r--interfaces/builtin/uhid_test.go7
-rw-r--r--interfaces/builtin/unity7.go16
-rw-r--r--interfaces/builtin/unity7_test.go5
-rw-r--r--interfaces/builtin/unity8.go18
-rw-r--r--interfaces/builtin/unity8_calendar.go15
-rw-r--r--interfaces/builtin/unity8_calendar_test.go5
-rw-r--r--interfaces/builtin/unity8_contacts.go15
-rw-r--r--interfaces/builtin/unity8_contacts_test.go5
-rw-r--r--interfaces/builtin/unity8_test.go7
-rw-r--r--interfaces/builtin/upower_observe.go22
-rw-r--r--interfaces/builtin/upower_observe_test.go5
-rw-r--r--interfaces/builtin/x11.go15
-rw-r--r--interfaces/builtin/x11_test.go5
-rw-r--r--interfaces/mount/backend.go3
-rw-r--r--interfaces/mount/change.go18
-rw-r--r--interfaces/mount/change_test.go8
-rw-r--r--interfaces/mount/lock.go75
-rw-r--r--interfaces/mount/lock_test.go116
-rw-r--r--interfaces/mount/ns.go3
-rw-r--r--interfaces/mount/ns_test.go15
-rw-r--r--overlord/hookstate/export_test.go8
-rw-r--r--overlord/hookstate/hookmgr.go4
-rw-r--r--overlord/hookstate/hookstate_test.go24
-rw-r--r--overlord/ifacestate/handlers.go43
-rw-r--r--overlord/ifacestate/ifacestate_test.go10
-rw-r--r--overlord/snapstate/aliasesv2.go23
-rw-r--r--overlord/snapstate/aliasesv2_test.go26
-rw-r--r--overlord/snapstate/backend_test.go2
-rw-r--r--overlord/snapstate/snapstate.go1
-rw-r--r--overlord/snapstate/snapstate_test.go9
-rw-r--r--partition/uboot.go4
-rwxr-xr-xrun-checks22
-rw-r--r--snap/info.go7
-rw-r--r--spread.yaml4
-rwxr-xr-xtests/lib/prepare.sh2
-rwxr-xr-xtests/lib/snaps/log-observe-consumer/bin/consumer2
-rw-r--r--tests/main/ack/task.yaml8
-rw-r--r--tests/main/chattr/task.yaml6
-rw-r--r--tests/main/classic-custom-device-reg/task.yaml10
-rw-r--r--tests/main/classic-firstboot/task.yaml2
-rw-r--r--tests/main/classic-ubuntu-core-transition-auth/task.yaml5
-rw-r--r--tests/main/classic-ubuntu-core-transition-two-cores/task.yaml6
-rw-r--r--tests/main/classic-ubuntu-core-transition/task.yaml5
-rw-r--r--tests/main/cmdline/task.yaml3
-rw-r--r--tests/main/create-user/task.yaml4
-rw-r--r--tests/main/dirs-not-shared-with-host/task.yaml29
-rw-r--r--tests/main/enable-disable-units-gpio/task.yaml4
-rw-r--r--tests/main/enable-disable/task.yaml8
-rw-r--r--tests/main/firstboot/task.yaml2
-rw-r--r--tests/main/install-errors/task.yaml15
-rw-r--r--tests/main/interfaces-firewall-control/task.yaml16
-rw-r--r--tests/main/interfaces-fuse_support/task.yaml9
-rw-r--r--tests/main/interfaces-network-control-ip-netns/task.yaml6
-rw-r--r--tests/main/known-remote/task.yaml8
-rw-r--r--tests/main/known/task.yaml8
-rw-r--r--tests/main/listing/task.yaml16
-rw-r--r--tests/main/local-install-w-metadata/task.yaml4
-rw-r--r--tests/main/op-install-failed-undone/task.yaml2
-rw-r--r--tests/main/prepare-image-grub/task.yaml10
-rw-r--r--tests/main/prepare-image-uboot/task.yaml10
-rw-r--r--tests/main/regression-home-snap-root-owned/task.yaml6
-rw-r--r--tests/main/revert-devmode/task.yaml8
-rw-r--r--tests/main/revert-sideload/task.yaml8
-rw-r--r--tests/main/revert/task.yaml14
-rw-r--r--tests/main/security-device-cgroups/task.yaml15
-rw-r--r--tests/main/security-private-tmp/task.yaml2
-rw-r--r--tests/main/security-profiles/task.yaml4
-rw-r--r--tests/main/snap-auto-import-asserts-spools/task.yaml4
-rw-r--r--tests/main/snap-auto-import-asserts/task.yaml4
-rw-r--r--tests/main/snap-auto-mount/task.yaml4
-rw-r--r--tests/main/snap-connect/task.yaml2
-rw-r--r--tests/main/snap-download/task.yaml6
-rw-r--r--tests/main/snap-env/task.yaml34
-rw-r--r--tests/main/snap-service/task.yaml8
-rw-r--r--tests/main/snap-sign/task.yaml4
-rw-r--r--tests/main/snap-update-ns/task.yaml74
-rw-r--r--tests/main/snapd-reexec/task.yaml2
-rw-r--r--tests/main/try-snap-goes-away/task.yaml6
-rw-r--r--tests/main/try-twice-with-daemon/task.yaml4
-rw-r--r--tests/main/try/task.yaml6
-rw-r--r--tests/main/ubuntu-core-classic/task.yaml7
-rw-r--r--tests/main/ubuntu-core-create-user/task.yaml24
-rw-r--r--tests/main/ubuntu-core-custom-device-reg-extras/task.yaml12
-rw-r--r--tests/main/ubuntu-core-custom-device-reg/task.yaml10
-rw-r--r--tests/main/ubuntu-core-device-reg/task.yaml10
-rw-r--r--tests/main/ubuntu-core-fan/task.yaml4
-rw-r--r--tests/main/ubuntu-core-os-release/task.yaml7
-rw-r--r--tests/main/ubuntu-core-reboot/task.yaml6
-rw-r--r--tests/main/ubuntu-core-upgrade/task.yaml5
-rw-r--r--tests/regression/lp-1580018/task.yaml27
-rw-r--r--tests/regression/lp-1641885/task.yaml (renamed from tests/main/regression-jailmode-1641885/task.yaml)6
-rw-r--r--tests/regression/lp-1667385/task.yaml17
-rw-r--r--vendor/vendor.json12
-rw-r--r--wrappers/binaries.go7
-rw-r--r--wrappers/desktop.go28
-rw-r--r--wrappers/desktop_test.go27
-rw-r--r--wrappers/services.go38
-rw-r--r--wrappers/services_gen_test.go15
-rw-r--r--wrappers/services_test.go11
283 files changed, 2105 insertions, 1733 deletions
diff --git a/.travis.yml b/.travis.yml
index 64bcf08361..00b451b058 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -19,4 +19,5 @@ install:
script:
- ./run-checks --static || travis_terminate 1
+ - ./run-checks --unit || travis_terminate 1
- ./run-checks --spread
diff --git a/README.md b/README.md
index 2113cdeb97..ad64d3ca7f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
[![Build Status][travis-image]][travis-url]
[![Go Report Card][goreportcard-image]][goreportcard-url]
-
+[![codecov][codecov-image]][codecov-url]
+
## Snaps
Package any app for every Linux desktop, server, cloud or device.
@@ -40,3 +41,6 @@ Get news and stay up to date on [Twitter](https://twitter.com/snapcraftio),
[coveralls-image]: https://coveralls.io/repos/snapcore/snapd/badge.svg?branch=master&service=github
[coveralls-url]: https://coveralls.io/github/snapcore/snapd?branch=master
+
+[codecov-url]: https://codecov.io/gh/snapcore/snapd
+[codecov-image]: https://codecov.io/gh/snapcore/snapd/branch/master/graph/badge.svg \ No newline at end of file
diff --git a/cmd/cmd.go b/cmd/cmd.go
index c681b390b2..87228b77ce 100644
--- a/cmd/cmd.go
+++ b/cmd/cmd.go
@@ -26,6 +26,7 @@ import (
"regexp"
"syscall"
+ "github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/release"
@@ -36,7 +37,7 @@ import (
// will attempt to re-exec itself from inside an ubuntu-core snap
// present on the system. If not present in the environ it's assumed
// to be set to 1 (do re-exec); that is: set it to 0 to disable.
-const key = "SNAP_REEXEC"
+const reExecKey = "SNAP_REEXEC"
// newCore is the place to look for the core snap; everything in this
// location will be new enough to re-exec into.
@@ -46,37 +47,119 @@ const newCore = "/snap/core/current"
// newer than minOldRevno will be ok to re-exec into.
const oldCore = "/snap/ubuntu-core/current"
-// ExecInCoreSnap makes sure you're executing the binary that ships in
-// the core snap.
-func ExecInCoreSnap() {
+// distroSupportsReExec returns true if the distribution we are running on can use re-exec.
+//
+// This is true by default except for a "core/all" snap system where it makes
+// no sense and in certain distributions that we don't want to enable re-exec
+// yet because of missing validation or other issues.
+func distroSupportsReExec() bool {
if !release.OnClassic {
- // you're already the real deal, natch
- return
+ return false
+ }
+ switch release.ReleaseInfo.ID {
+ case "fedora", "centos", "rhel", "opensuse", "suse", "poky":
+ logger.Debugf("re-exec not supported on distro %q yet", release.ReleaseInfo.ID)
+ return false
+ }
+ return true
+}
+
+// coreSupportsReExec returns true if the given core snap should be used as re-exec target.
+//
+// Ensure we do not use older version of snapd, look for info file and ignore
+// version of core that do not yet have it.
+func coreSupportsReExec(corePath string) bool {
+ fullInfo := filepath.Join(corePath, "/usr/lib/snapd/info")
+ if !osutil.FileExists(fullInfo) {
+ return false
+ }
+ content, err := ioutil.ReadFile(fullInfo)
+ if err != nil {
+ logger.Noticef("cannot read snapd info file %q: %s", fullInfo, err)
+ return false
+ }
+ ver := regexp.MustCompile("(?m)^VERSION=(.*)$").FindStringSubmatch(string(content))
+ if len(ver) != 2 {
+ logger.Noticef("cannot find snapd version information in %q", content)
+ return false
+ }
+ // > 0 means our Version is bigger than the version of snapd in core
+ res, err := strutil.VersionCompare(Version, ver[1])
+ if err != nil {
+ logger.Debugf("cannot version compare %q and %q: %s", Version, ver[1], res)
+ return false
+ }
+ if res > 0 {
+ logger.Debugf("core snap (at %q) is older (%q) than distribution package (%q)", corePath, ver[1], Version)
+ return false
}
+ return true
+}
- // should we re-exec? no option in the environment means yes
- if !osutil.GetenvBool(key, true) {
+// InternalToolPath returns the path of the internal snapd tool.
+//
+// The return value is either the path of the tool in the current distribution
+// or in the core snap (or the ubuntu-core snap). This handles spiritual
+// "re-exec" where we run the tool from the core snap if the environment allows
+// us to do so.
+func InternalToolPath(tool string) string {
+ distroTool := filepath.Join(dirs.DistroLibExecDir, tool)
+
+ // If we are asked not to re-execute use distribution packages. This is
+ // "spiritual" re-exec so use the same environment variable to decide.
+ if !osutil.GetenvBool(reExecKey, true) {
logger.Debugf("re-exec disabled by user")
- return
+ return distroTool
}
- // can we re-exec? some distributions will need extra work before re-exec really works.
- switch release.ReleaseInfo.ID {
- case "fedora", "centos", "rhel", "opensuse", "suse", "poky":
- logger.Debugf("re-exec not supported on distro %q yet", release.ReleaseInfo.ID)
+ // If the distribution doesn't support re-exec or run-from-core then don't do it.
+ if !distroSupportsReExec() {
+ return distroTool
+ }
+
+ // Is the tool we are after present in the core snap?
+ corePath := newCore
+ coreTool := filepath.Join(newCore, "/usr/lib/snapd", tool)
+ if !osutil.FileExists(coreTool) {
+ corePath = oldCore
+ coreTool = filepath.Join(oldCore, "/usr/lib/snapd", tool)
+ }
+
+ // If the core snap doesn't support re-exec or run-from-core then don't do it.
+ if !coreSupportsReExec(corePath) {
+ return distroTool
+ }
+
+ return coreTool
+}
+
+// ExecInCoreSnap makes sure you're executing the binary that ships in
+// the core snap.
+func ExecInCoreSnap() {
+ // If we are asked not to re-execute use distribution packages. This is
+ // "spiritual" re-exec so use the same environment variable to decide.
+ if !osutil.GetenvBool(reExecKey, true) {
+ logger.Debugf("re-exec disabled by user")
return
}
- // did we already re-exec?
+ // Did we already re-exec?
if osutil.GetenvBool("SNAP_DID_REEXEC") {
return
}
+ // If the distribution doesn't support re-exec or run-from-core then don't do it.
+ if !distroSupportsReExec() {
+ return
+ }
+
+ // Which executable are we?
exe, err := os.Readlink("/proc/self/exe")
if err != nil {
return
}
+ // Is this executable in the core snap too?
corePath := newCore
full := filepath.Join(newCore, exe)
if !osutil.FileExists(full) {
@@ -87,36 +170,12 @@ func ExecInCoreSnap() {
}
}
- // ensure we do not re-exec into an older version of snapd, look
- // for info file and ignore version of core that do not yet have
- // it
- fullInfo := filepath.Join(corePath, "/usr/lib/snapd/info")
- if !osutil.FileExists(fullInfo) {
- logger.Debugf("not restarting into %q (no version info): older than %q (%s)", full, exe, Version)
- return
- }
- content, err := ioutil.ReadFile(fullInfo)
- if err != nil {
- logger.Noticef("cannot read info file %q: %s", fullInfo, err)
- return
- }
- ver := regexp.MustCompile("(?m)^VERSION=(.*)$").FindStringSubmatch(string(content))
- if len(ver) != 2 {
- logger.Noticef("cannot find version information in %q", content)
- }
- // > 0 means our Version is bigger than the version of snapd in core
- res, err := strutil.VersionCompare(Version, ver[1])
- if err != nil {
- logger.Debugf("cannot version compare %q and %q: %s", Version, ver[1], res)
- return
- }
- if res > 0 {
- logger.Debugf("not restarting into %q (%s): older than %q (%s)", full, ver, exe, Version)
+ // If the core snap doesn't support re-exec or run-from-core then don't do it.
+ if !coreSupportsReExec(corePath) {
return
}
logger.Debugf("restarting into %q", full)
-
env := append(os.Environ(), "SNAP_DID_REEXEC=1")
panic(syscall.Exec(full, os.Args, env))
}
diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go
new file mode 100644
index 0000000000..c39fc7d806
--- /dev/null
+++ b/cmd/cmd_test.go
@@ -0,0 +1,54 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 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 cmd_test
+
+import (
+ "testing"
+
+ . "gopkg.in/check.v1"
+
+ "github.com/snapcore/snapd/cmd"
+ "github.com/snapcore/snapd/release"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type cmdSuite struct{}
+
+var _ = Suite(&cmdSuite{})
+
+func (s *cmdSuite) TestDistroSupportsReExec(c *C) {
+ restore := release.MockOnClassic(true)
+ defer restore()
+
+ // Some distributions don't support re-execution yet.
+ for _, id := range []string{"fedora", "centos", "rhel", "opensuse", "suse", "poky"} {
+ restore = release.MockReleaseInfo(&release.OS{ID: id})
+ defer restore()
+ c.Assert(cmd.DistroSupportsReExec(), Equals, false, Commentf("ID: %q", id))
+ }
+
+ // While others do.
+ for _, id := range []string{"debian", "ubuntu"} {
+ restore = release.MockReleaseInfo(&release.OS{ID: id})
+ defer restore()
+ c.Assert(cmd.DistroSupportsReExec(), Equals, true, Commentf("ID: %q", id))
+ }
+}
diff --git a/cmd/export_test.go b/cmd/export_test.go
new file mode 100644
index 0000000000..42d04d5f84
--- /dev/null
+++ b/cmd/export_test.go
@@ -0,0 +1,24 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2017 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 cmd
+
+var (
+ DistroSupportsReExec = distroSupportsReExec
+)
diff --git a/cmd/snap-confine/mount-support.c b/cmd/snap-confine/mount-support.c
index 6bf38e1014..ee63c19dca 100644
--- a/cmd/snap-confine/mount-support.c
+++ b/cmd/snap-confine/mount-support.c
@@ -341,20 +341,28 @@ static void sc_bootstrap_mount_namespace(const struct sc_mount_config *config)
sc_do_mount("none", dst, NULL, MS_REC | MS_SLAVE, NULL);
}
}
- // Since we mounted /etc from the host filesystem to the scratch directory,
- // we may need to put /etc/alternatives from the desired root filesystem
- // (e.g. the core snap) back. This way the behavior of running snaps is not
- // affected by the alternatives directory from the host, if one exists.
- //
- // https://bugs.launchpad.net/snap-confine/+bug/1580018
- const char *etc_alternatives = "/etc/alternatives";
- if (access(etc_alternatives, F_OK) == 0) {
- sc_must_snprintf(src, sizeof src, "%s%s", config->rootfs_dir,
- etc_alternatives);
- sc_must_snprintf(dst, sizeof dst, "%s%s", scratch_dir,
- etc_alternatives);
- sc_do_mount(src, dst, NULL, MS_BIND, NULL);
- sc_do_mount("none", dst, NULL, MS_SLAVE, NULL);
+ if (config->on_classic_distro) {
+ // Since we mounted /etc from the host filesystem to the scratch directory,
+ // we may need to put certain directories from the desired root filesystem
+ // (e.g. the core snap) back. This way the behavior of running snaps is not
+ // affected by the alternatives directory from the host, if one exists.
+ //
+ // Fixes the following bugs:
+ // - https://bugs.launchpad.net/snap-confine/+bug/1580018
+ // - https://bugzilla.opensuse.org/show_bug.cgi?id=1028568
+ const char *dirs_from_core[] =
+ { "/etc/alternatives", "/etc/ssl", NULL };
+ for (const char **dirs = dirs_from_core; *dirs != NULL; dirs++) {
+ const char *dir = *dirs;
+ if (access(dir, F_OK) == 0) {
+ sc_must_snprintf(src, sizeof src, "%s%s",
+ config->rootfs_dir, dir);
+ sc_must_snprintf(dst, sizeof dst, "%s%s",
+ scratch_dir, dir);
+ sc_do_mount(src, dst, NULL, MS_BIND, NULL);
+ sc_do_mount("none", dst, NULL, MS_SLAVE, NULL);
+ }
+ }
}
// Bind mount the directory where all snaps are mounted. The location of
// the this directory on the host filesystem may not match the location in
diff --git a/cmd/snap-confine/snap-confine.apparmor.in b/cmd/snap-confine/snap-confine.apparmor.in
index a999f462ee..9e8064f7a3 100644
--- a/cmd/snap-confine/snap-confine.apparmor.in
+++ b/cmd/snap-confine/snap-confine.apparmor.in
@@ -160,9 +160,11 @@
mount options=(rw rslave) -> /tmp/snap.rootfs_*/usr/src/,
# /etc/alternatives (classic)
mount options=(rw bind) @SNAP_MOUNT_DIR@/{,ubuntu-}core/*/etc/alternatives/ -> /tmp/snap.rootfs_*/etc/alternatives/,
+ mount options=(rw bind) @SNAP_MOUNT_DIR@/{,ubuntu-}core/*/etc/ssl/ -> /tmp/snap.rootfs_*/etc/ssl/,
# /etc/alternatives (core)
mount options=(rw bind) /etc/alternatives/ -> /tmp/snap.rootfs_*/etc/alternatives/,
mount options=(rw slave) -> /tmp/snap.rootfs_*/etc/alternatives/,
+ mount options=(rw slave) -> /tmp/snap.rootfs_*/etc/ssl/,
# the /snap directory
mount options=(rw rbind) @SNAP_MOUNT_DIR@/ -> /tmp/snap.rootfs_*/snap/,
mount options=(rw rslave) -> /tmp/snap.rootfs_*/snap/,
diff --git a/cmd/snap-confine/snap-confine.c b/cmd/snap-confine/snap-confine.c
index eb08ec6962..0db96380f2 100644
--- a/cmd/snap-confine/snap-confine.c
+++ b/cmd/snap-confine/snap-confine.c
@@ -105,12 +105,6 @@ int main(int argc, char **argv)
#endif // ifdef HAVE_SECCOMP
if (geteuid() == 0) {
- // ensure that "/" or "/snap" is mounted with the
- // "shared" option, see LP:#1668659
- int global_lock_fd = sc_lock_global();
- sc_ensure_shared_snap_mount();
- sc_unlock_global(global_lock_fd);
-
if (classic_confinement) {
/* 'classic confinement' is designed to run without the sandbox
* inside the shared namespace. Specifically:
@@ -139,10 +133,12 @@ int main(int argc, char **argv)
sc_reassociate_with_pid1_mount_ns();
// Do global initialization:
int global_lock_fd = sc_lock_global();
+ // ensure that "/" or "/snap" is mounted with the
+ // "shared" option, see LP:#1668659
+ debug("ensuring that snap mount directory is shared");
+ sc_ensure_shared_snap_mount();
debug("unsharing snap namespace directory");
sc_initialize_ns_groups();
- // TODO: implement this.
- debug("share snap directory here...");
sc_unlock_global(global_lock_fd);
// Do per-snap initialization.
diff --git a/cmd/snap-update-ns/bootstrap.go b/cmd/snap-update-ns/bootstrap.go
index b7239c0b03..6adad9618f 100644
--- a/cmd/snap-update-ns/bootstrap.go
+++ b/cmd/snap-update-ns/bootstrap.go
@@ -37,17 +37,27 @@ __attribute__((constructor)) static void init(void) {
import "C"
import (
+ "errors"
"fmt"
"syscall"
"unsafe"
)
-// Error returns error (if any) encountered in pre-main C code.
+var (
+ // ErrNoNamespace is returned when a snap namespace does not exist.
+ ErrNoNamespace = errors.New("cannot update mount namespace that was not created yet")
+)
+
+// BootstrapError returns error (if any) encountered in pre-main C code.
func BootstrapError() error {
if C.bootstrap_msg == nil {
return nil
}
errno := syscall.Errno(C.bootstrap_errno)
+ // Translate EINVAL from setns or ENOENT from open into a dedicated error.
+ if errno == syscall.EINVAL || errno == syscall.ENOENT {
+ return ErrNoNamespace
+ }
if errno != 0 {
return fmt.Errorf("%s: %s", C.GoString(C.bootstrap_msg), errno)
}
diff --git a/cmd/snap-update-ns/main.go b/cmd/snap-update-ns/main.go
index e153baa554..ec530605eb 100644
--- a/cmd/snap-update-ns/main.go
+++ b/cmd/snap-update-ns/main.go
@@ -25,6 +25,9 @@ import (
"github.com/jessevdk/go-flags"
+ "github.com/snapcore/snapd/dirs"
+ "github.com/snapcore/snapd/interfaces/mount"
+ "github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/snap"
)
@@ -53,12 +56,72 @@ func run() error {
if err := parseArgs(os.Args[1:]); err != nil {
return err
}
+
// There is some C code that runs before main() is started.
// That code always runs and sets an error condition if it fails.
// Here we just check for the error.
if err := BootstrapError(); err != nil {
+ // If there is no mount namespace to transition to let's just quit
+ // instantly without any errors as there is nothing to do anymore.
+ if err == ErrNoNamespace {
+ return nil
+ }
return err
}
- // TODO: implement this
- return fmt.Errorf("not implemented")
+ snapName := opts.Positionals.SnapName
+
+ // Lock the mount namespace so that any concurrently attempted invocations
+ // of snap-confine are synchronized and will see consistent state.
+ lock, err := mount.OpenLock(snapName)
+ if err != nil {
+ return fmt.Errorf("cannot open lock file for mount namespace of snap %q: %s", snapName, err)
+ }
+ defer lock.Close()
+ if err := lock.Lock(); err != nil {
+ return fmt.Errorf("cannot lock mount namespace of snap %q: %s", snapName, err)
+ }
+
+ // Read the desired and current mount profiles. Note that missing files
+ // count as empty profiles so that we can gracefully handle a mount
+ // interface connection/disconnection.
+ desiredProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapMountPolicyDir, snapName)
+ desired, err := mount.LoadProfile(desiredProfilePath)
+ if err != nil {
+ return fmt.Errorf("cannot load desired mount profile of snap %q: %s", snapName, err)
+ }
+
+ currentProfilePath := fmt.Sprintf("%s/snap.%s.fstab", dirs.SnapRunNsDir, snapName)
+ currentBefore, err := mount.LoadProfile(currentProfilePath)
+ if err != nil {
+ return fmt.Errorf("cannot load current mount profile of snap %q: %s", snapName, err)
+ }
+
+ // Compute the needed changes and perform each change if needed, collecting
+ // those that we managed to perform or that were performed already.
+ changesNeeded := mount.NeededChanges(currentBefore, desired)
+ var changesMade []mount.Change
+ for _, change := range changesNeeded {
+ if change.Action == mount.Keep {
+ changesMade = append(changesMade, change)
+ continue
+ }
+ if err := change.Perform(); err != nil {
+ logger.Noticef("cannot change mount namespace of snap %q according to change %s: %s", snapName, change, err)
+ continue
+ }
+ changesMade = append(changesMade, change)
+ }
+
+ // Compute the new current profile so that it contains only changes that were made
+ // and save it back for next runs.
+ var currentAfter mount.Profile
+ for _, change := range changesMade {
+ if change.Action == mount.Mount || change.Action == mount.Keep {
+ currentAfter.Entries = append(currentAfter.Entries, change.Entry)
+ }
+ }
+ if err := currentAfter.Save(currentProfilePath); err != nil {
+ return fmt.Errorf("cannot save current mount profile of snap %q: %s", snapName, err)
+ }
+ return nil
}
diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go
index da2260dc8c..a40e71765e 100644
--- a/cmd/snap/cmd_snap_op.go
+++ b/cmd/snap/cmd_snap_op.go
@@ -331,6 +331,12 @@ func (mx *channelMixin) setChannelFromCommandline() error {
mx.Channel = ch.chName
}
+ if !strings.Contains(mx.Channel, "/") && mx.Channel != "" && mx.Channel != "edge" && mx.Channel != "beta" && mx.Channel != "candidate" && mx.Channel != "stable" {
+ // shortcut to jump to a different track, e.g.
+ // snap install foo --channel=3.4 # implies 3.4/stable
+ mx.Channel += "/stable"
+ }
+
return nil
}
diff --git a/cmd/snap/cmd_snap_op_test.go b/cmd/snap/cmd_snap_op_test.go
index 15f7178c96..e29aa33d86 100644
--- a/cmd/snap/cmd_snap_op_test.go
+++ b/cmd/snap/cmd_snap_op_test.go
@@ -172,16 +172,57 @@ func (s *SnapOpSuite) TestInstall(c *check.C) {
c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
"action": "install",
- "channel": "chan",
+ "channel": "candidate",
})
- s.srv.channel = "chan"
+ s.srv.channel = "candidate"
}
s.RedirectClientToTestServer(s.srv.handle)
- rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "chan", "foo"})
+ rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "candidate", "foo"})
c.Assert(err, check.IsNil)
c.Assert(rest, check.DeepEquals, []string{})
- c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(chan\) 1.0 from 'bar' installed`)
+ c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(candidate\) 1.0 from 'bar' installed`)
+ c.Check(s.Stderr(), check.Equals, "")
+ // ensure that the fake server api was actually hit
+ c.Check(s.srv.n, check.Equals, s.srv.total)
+}
+
+func (s *SnapOpSuite) TestInstallFromTrack(c *check.C) {
+ s.srv.checker = func(r *http.Request) {
+ c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
+ c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
+ "action": "install",
+ "channel": "3.4/stable",
+ })
+ s.srv.channel = "3.4/stable"
+ }
+
+ s.RedirectClientToTestServer(s.srv.handle)
+ // snap install --channel=3.4 means 3.4/stable, this is what we test here
+ rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "3.4", "foo"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/stable\) 1.0 from 'bar' installed`)
+ c.Check(s.Stderr(), check.Equals, "")
+ // ensure that the fake server api was actually hit
+ c.Check(s.srv.n, check.Equals, s.srv.total)
+}
+
+func (s *SnapOpSuite) TestInstallFromBranch(c *check.C) {
+ s.srv.checker = func(r *http.Request) {
+ c.Check(r.URL.Path, check.Equals, "/v2/snaps/foo")
+ c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
+ "action": "install",
+ "channel": "3.4/hotfix-1",
+ })
+ s.srv.channel = "3.4/hotfix-1"
+ }
+
+ s.RedirectClientToTestServer(s.srv.handle)
+ rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "3.4/hotfix-1", "foo"})
+ c.Assert(err, check.IsNil)
+ c.Assert(rest, check.DeepEquals, []string{})
+ c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(3.4/hotfix-1\) 1.0 from 'bar' installed`)
c.Check(s.Stderr(), check.Equals, "")
// ensure that the fake server api was actually hit
c.Check(s.srv.n, check.Equals, s.srv.total)
@@ -193,16 +234,16 @@ func (s *SnapOpSuite) TestInstallDevMode(c *check.C) {
c.Check(DecodedRequestBody(c, r), check.DeepEquals, map[string]interface{}{
"action": "install",
"devmode": true,
- "channel": "chan",
+ "channel": "beta",
})
- s.srv.channel = "chan"
+ s.srv.channel = "beta"
}
s.RedirectClientToTestServer(s.srv.handle)
- rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "chan", "--devmode", "foo"})
+ rest, err := snap.Parser().ParseArgs([]string{"install", "--channel", "beta", "--devmode", "foo"})
c.Assert(err, check.IsNil)
c.Assert(rest, check.DeepEquals, []string{})
- c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(chan\) 1.0 from 'bar' installed`)
+ c.Check(s.Stdout(), check.Matches, `(?sm).*foo \(beta\) 1.0 from 'bar' installed`)
c.Check(s.Stderr(), check.Equals, "")
// ensure that the fake server api was actually hit
c.Check(s.srv.n, check.Equals, s.srv.total)
diff --git a/daemon/api.go b/daemon/api.go
index a3dffd5c81..7c0a31e12e 100644
--- a/daemon/api.go
+++ b/daemon/api.go
@@ -32,6 +32,7 @@ import (
"os/user"
"path/filepath"
"regexp"
+ "sort"
"strconv"
"strings"
"time"
@@ -823,7 +824,7 @@ var errNothingToInstall = errors.New("nothing to install")
const oldDefaultSnapCoreName = "ubuntu-core"
const defaultCoreSnapName = "core"
-func ensureUbuntuCore(st *state.State, targetSnap string, userID int) (*state.TaskSet, error) {
+func ensureCore(st *state.State, targetSnap string, userID int) (*state.TaskSet, error) {
if targetSnap == defaultCoreSnapName || targetSnap == oldDefaultSnapCoreName {
return nil, errNothingToInstall
}
@@ -836,8 +837,8 @@ func ensureUbuntuCore(st *state.State, targetSnap string, userID int) (*state.Ta
return snapstateInstall(st, defaultCoreSnapName, "stable", snap.R(0), userID, snapstate.Flags{})
}
-func withEnsureUbuntuCore(st *state.State, targetSnap string, userID int, install func() (*state.TaskSet, error)) ([]*state.TaskSet, error) {
- ubuCoreTs, err := ensureUbuntuCore(st, targetSnap, userID)
+func withEnsureCore(st *state.State, targetSnap string, userID int, install func() (*state.TaskSet, error)) ([]*state.TaskSet, error) {
+ ubuCoreTs, err := ensureCore(st, targetSnap, userID)
if err != nil && err != errNothingToInstall {
return nil, err
}
@@ -910,6 +911,21 @@ func snapUpdateMany(inst *snapInstruction, st *state.State) (msg string, updated
return msg, updated, tasksets, nil
}
+func verifySnapInstructions(inst *snapInstruction) error {
+ switch inst.Action {
+ case "install":
+ for _, snapName := range inst.Snaps {
+ // FIXME: alternatively we could simply mutate *inst
+ // and s/ubuntu-core/core/ ?
+ if snapName == "ubuntu-core" {
+ return fmt.Errorf(`cannot install "ubuntu-core", please use "core" instead`)
+ }
+ }
+ }
+
+ return nil
+}
+
func snapInstallMany(inst *snapInstruction, st *state.State) (msg string, installed []string, tasksets []*state.TaskSet, err error) {
installed, tasksets, err = snapstateInstallMany(st, inst.Snaps, inst.userID)
if err != nil {
@@ -938,7 +954,7 @@ func snapInstall(inst *snapInstruction, st *state.State) (string, []*state.TaskS
logger.Noticef("Installing snap %q revision %s", inst.Snaps[0], inst.Revision)
- tsets, err := withEnsureUbuntuCore(st, inst.Snaps[0], inst.userID,
+ tsets, err := withEnsureCore(st, inst.Snaps[0], inst.userID,
func() (*state.TaskSet, error) {
return snapstateInstall(st, inst.Snaps[0], inst.Channel, inst.Revision, inst.userID, flags)
},
@@ -1127,6 +1143,10 @@ func postSnap(c *Command, r *http.Request, user *auth.UserState) Response {
vars := muxVars(r)
inst.Snaps = []string{vars["name"]}
+ if err := verifySnapInstructions(&inst); err != nil {
+ return BadRequest("%s", err)
+ }
+
impl := inst.dispatch()
if impl == nil {
return BadRequest("unknown action %s", inst.Action)
@@ -1189,7 +1209,7 @@ func trySnap(c *Command, r *http.Request, user *auth.UserState, trydir string, f
if user != nil {
userID = user.ID
}
- tsets, err := withEnsureUbuntuCore(st, info.Name(), userID,
+ tsets, err := withEnsureCore(st, info.Name(), userID,
func() (*state.TaskSet, error) {
return snapstateTryPath(st, info.Name(), trydir, flags)
},
@@ -1415,7 +1435,7 @@ out:
userID = user.ID
}
- tsets, err := withEnsureUbuntuCore(st, snapName, userID,
+ tsets, err := withEnsureCore(st, snapName, userID,
func() (*state.TaskSet, error) {
return snapstateInstallPath(st, sideInfo, tempPath, "", flags)
},
@@ -1565,6 +1585,20 @@ type interfaceAction struct {
Slots []slotJSON `json:"slots,omitempty"`
}
+func snapNamesFromConns(conns []interfaces.ConnRef) []string {
+ m := make(map[string]bool)
+ for _, conn := range conns {
+ m[conn.PlugRef.Snap] = true
+ m[conn.SlotRef.Snap] = true
+ }
+ l := make([]string, 0, len(m))
+ for name := range m {
+ l = append(l, name)
+ }
+ sort.Strings(l)
+ return l
+}
+
// changeInterfaces controls the interfaces system.
// Plugs can be connected to and disconnected from slots.
// When enableInternalInterfaceActions is true plugs and slots can also be
@@ -1592,12 +1626,14 @@ func changeInterfaces(c *Command, r *http.Request, user *auth.UserState) Respons
}
var summary string
- var taskset *state.TaskSet
var err error
- state := c.d.overlord.State()
- state.Lock()
- defer state.Unlock()
+ var tasksets []*state.TaskSet
+ var affected []string
+
+ st := c.d.overlord.State()
+ st.Lock()
+ defer st.Unlock()
switch a.Action {
case "connect":
@@ -1605,22 +1641,37 @@ func changeInterfaces(c *Command, r *http.Request, user *auth.UserState) Respons
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 {
+ var ts *state.TaskSet
summary = fmt.Sprintf("Connect %s:%s to %s:%s", connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name)
- taskset, err = ifacestate.Connect(state, 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})
}
case "disconnect":
+ 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)
- taskset, err = ifacestate.Disconnect(state, 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)
+ if err == nil {
+ for _, connRef := range conns {
+ var ts *state.TaskSet
+ ts, err = ifacestate.Disconnect(st, connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name)
+ if err != nil {
+ break
+ }
+ ts.JoinLane(st.NewLane())
+ tasksets = append(tasksets, ts)
+ }
+ affected = snapNamesFromConns(conns)
+ }
}
if err != nil {
return BadRequest("%v", err)
}
- change := state.NewChange(a.Action+"-snap", summary)
- change.Set("snap-names", []string{a.Plugs[0].Snap, a.Slots[0].Snap})
- change.AddAll(taskset)
+ change := newChange(st, a.Action+"-snap", summary, tasksets, affected)
- state.EnsureBefore(0)
+ st.EnsureBefore(0)
return AsyncResponse(nil, &Meta{Change: change.ID()})
}
diff --git a/daemon/api_test.go b/daemon/api_test.go
index b90be7966a..7200e7b2c2 100644
--- a/daemon/api_test.go
+++ b/daemon/api_test.go
@@ -1702,6 +1702,23 @@ func (s *apiSuite) TestPostSnap(c *check.C) {
c.Check(soon, check.Equals, 1)
}
+func (s *apiSuite) TestPostSnapVerfySnapInstruction(c *check.C) {
+ d := s.daemon(c)
+ d.overlord.Loop()
+ defer d.overlord.Stop()
+
+ buf := bytes.NewBufferString(`{"action": "install"}`)
+ req, err := http.NewRequest("POST", "/v2/snaps/ubuntu-core", buf)
+ c.Assert(err, check.IsNil)
+ s.vars = map[string]string{"name": "ubuntu-core"}
+
+ rsp := postSnap(snapCmd, req, nil).(*resp)
+
+ c.Check(rsp.Type, check.Equals, ResponseTypeError)
+ c.Check(rsp.Status, check.Equals, http.StatusBadRequest)
+ c.Check(rsp.Result.(*errorResult).Message, testutil.Contains, `cannot install "ubuntu-core", please use "core" instead`)
+}
+
func (s *apiSuite) TestPostSnapSetsUser(c *check.C) {
d := s.daemon(c)
ensureStateSoon = func(st *state.State) {}
@@ -3405,7 +3422,7 @@ func (s *apiSuite) TestConnectPlugFailureNoSuchSlot(c *check.C) {
c.Assert(plug.Connections, check.HasLen, 0)
}
-func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) {
+func (s *apiSuite) testDisconnect(c *check.C, plugSnap, plugName, slotSnap, slotName string) {
d := s.daemon(c)
s.mockIface(c, &ifacetest.TestInterface{InterfaceName: "test"})
@@ -3424,8 +3441,8 @@ func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) {
action := &interfaceAction{
Action: "disconnect",
- Plugs: []plugJSON{{Snap: "consumer", Name: "plug"}},
- Slots: []slotJSON{{Snap: "producer", Name: "slot"}},
+ Plugs: []plugJSON{{Snap: plugSnap, Name: plugName}},
+ Slots: []slotJSON{{Snap: slotSnap, Name: slotName}},
}
text, err := json.Marshal(action)
c.Assert(err, check.IsNil)
@@ -3459,6 +3476,18 @@ func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) {
c.Assert(slot.Connections, check.HasLen, 0)
}
+func (s *apiSuite) TestDisconnectPlugSuccess(c *check.C) {
+ s.testDisconnect(c, "consumer", "plug", "producer", "slot")
+}
+
+func (s *apiSuite) TestDisconnectPlugSuccessWithEmptyPlug(c *check.C) {
+ s.testDisconnect(c, "", "", "producer", "slot")
+}
+
+func (s *apiSuite) TestDisconnectPlugSuccessWithEmptySlot(c *check.C) {
+ s.testDisconnect(c, "consumer", "plug", "", "")
+}
+
func (s *apiSuite) TestDisconnectPlugFailureNoSuchPlug(c *check.C) {
d := s.daemon(c)
@@ -3481,30 +3510,18 @@ func (s *apiSuite) TestDisconnectPlugFailureNoSuchPlug(c *check.C) {
c.Assert(err, check.IsNil)
rec := httptest.NewRecorder()
interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
- c.Check(rec.Code, check.Equals, 202)
+ c.Check(rec.Code, check.Equals, 400)
var body map[string]interface{}
err = json.Unmarshal(rec.Body.Bytes(), &body)
c.Check(err, check.IsNil)
- id := body["change"].(string)
-
- st := d.overlord.State()
- st.Lock()
- chg := st.Change(id)
- st.Unlock()
- c.Assert(chg, check.NotNil)
-
- <-chg.Ready()
-
- st.Lock()
- err = chg.Err()
- st.Unlock()
- c.Assert(err, check.NotNil)
- c.Check(err.Error(), check.Equals, `cannot perform the following tasks:
-- Disconnect consumer:plug from producer:slot (snap "consumer" has no plug named "plug")`)
-
- repo := d.overlord.InterfaceManager().Repository()
- slot := repo.Slot("producer", "slot")
- c.Assert(slot.Connections, check.HasLen, 0)
+ c.Check(body, check.DeepEquals, map[string]interface{}{
+ "result": map[string]interface{}{
+ "message": "snap \"consumer\" has no plug named \"plug\"",
+ },
+ "status": "Bad Request",
+ "status-code": 400.0,
+ "type": "error",
+ })
}
func (s *apiSuite) TestDisconnectPlugFailureNoSuchSlot(c *check.C) {
@@ -3529,30 +3546,19 @@ func (s *apiSuite) TestDisconnectPlugFailureNoSuchSlot(c *check.C) {
c.Assert(err, check.IsNil)
rec := httptest.NewRecorder()
interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
- c.Check(rec.Code, check.Equals, 202)
+
+ c.Check(rec.Code, check.Equals, 400)
var body map[string]interface{}
err = json.Unmarshal(rec.Body.Bytes(), &body)
c.Check(err, check.IsNil)
- id := body["change"].(string)
-
- st := d.overlord.State()
- st.Lock()
- chg := st.Change(id)
- st.Unlock()
- c.Assert(chg, check.NotNil)
-
- <-chg.Ready()
-
- st.Lock()
- err = chg.Err()
- st.Unlock()
- c.Assert(err, check.NotNil)
- c.Check(err.Error(), check.Equals, `cannot perform the following tasks:
-- Disconnect consumer:plug from producer:slot (snap "producer" has no slot named "slot")`)
-
- repo := d.overlord.InterfaceManager().Repository()
- plug := repo.Plug("consumer", "plug")
- c.Assert(plug.Connections, check.HasLen, 0)
+ c.Check(body, check.DeepEquals, map[string]interface{}{
+ "result": map[string]interface{}{
+ "message": "snap \"producer\" has no slot named \"slot\"",
+ },
+ "status": "Bad Request",
+ "status-code": 400.0,
+ "type": "error",
+ })
}
func (s *apiSuite) TestDisconnectPlugFailureNotConnected(c *check.C) {
@@ -3577,32 +3583,19 @@ func (s *apiSuite) TestDisconnectPlugFailureNotConnected(c *check.C) {
c.Assert(err, check.IsNil)
rec := httptest.NewRecorder()
interfacesCmd.POST(interfacesCmd, req, nil).ServeHTTP(rec, req)
- c.Check(rec.Code, check.Equals, 202)
+
+ c.Check(rec.Code, check.Equals, 400)
var body map[string]interface{}
err = json.Unmarshal(rec.Body.Bytes(), &body)
c.Check(err, check.IsNil)
- id := body["change"].(string)
-
- st := d.overlord.State()
- st.Lock()
- chg := st.Change(id)
- st.Unlock()
- c.Assert(chg, check.NotNil)
-
- <-chg.Ready()
-
- st.Lock()
- err = chg.Err()
- st.Unlock()
- c.Assert(err, check.NotNil)
- c.Check(err.Error(), check.Equals, `cannot perform the following tasks:
-- Disconnect consumer:plug from producer:slot (cannot disconnect consumer:plug from producer:slot, it is not connected)`)
-
- repo := d.overlord.InterfaceManager().Repository()
- plug := repo.Plug("consumer", "plug")
- slot := repo.Slot("producer", "slot")
- c.Assert(plug.Connections, check.HasLen, 0)
- c.Assert(slot.Connections, check.HasLen, 0)
+ c.Check(body, check.DeepEquals, map[string]interface{}{
+ "result": map[string]interface{}{
+ "message": "cannot disconnect consumer:plug from producer:slot, it is not connected",
+ },
+ "status": "Bad Request",
+ "status-code": 400.0,
+ "type": "error",
+ })
}
func (s *apiSuite) TestUnsupportedInterfaceRequest(c *check.C) {
diff --git a/dirs/dirs.go b/dirs/dirs.go
index 4139d9bd52..e8aa916c92 100644
--- a/dirs/dirs.go
+++ b/dirs/dirs.go
@@ -49,6 +49,7 @@ var (
SnapSocket string
SnapRunDir string
SnapRunNsDir string
+ SnapRunLockDir string
SnapSeedDir string
SnapDeviceDir string
@@ -143,6 +144,7 @@ func SetRootDir(rootdir string) {
SnapDesktopFilesDir = filepath.Join(rootdir, snappyDir, "desktop", "applications")
SnapRunDir = filepath.Join(rootdir, "/run/snapd")
SnapRunNsDir = filepath.Join(SnapRunDir, "/ns")
+ SnapRunLockDir = filepath.Join(SnapRunDir, "/lock")
// keep in sync with the debian/snapd.socket file:
SnapdSocket = filepath.Join(rootdir, "/run/snapd.socket")
diff --git a/get-deps.sh b/get-deps.sh
index d699591607..d14f2e5b2b 100755
--- a/get-deps.sh
+++ b/get-deps.sh
@@ -10,3 +10,11 @@ export PATH=$PATH:$GOPATH/bin
echo Obtaining dependencies
govendor sync
+
+unused="$(govendor list +unused)"
+if [ "$unused" != "" ]; then
+ echo "Found unused ./vendor packages:"
+ echo "$unused"
+ echo "Please fix via 'govendor remove +unused'"
+ exit 1
+fi
diff --git a/interfaces/builtin/account_control.go b/interfaces/builtin/account_control.go
index c456ecf5f9..759c427e1d 100644
--- a/interfaces/builtin/account_control.go
+++ b/interfaces/builtin/account_control.go
@@ -19,10 +19,6 @@
package builtin
-import (
- "github.com/snapcore/snapd/interfaces"
-)
-
const accountControlConnectedPlugAppArmor = `
# Allow creating, modifying and deleting non-system users and account password.
/{,usr/}sbin/chpasswd ixr,
@@ -63,16 +59,11 @@ bind
socket AF_NETLINK - NETLINK_AUDIT
`
-// Interface which allows to handle the user accounts.
-func NewAccountControlInterface() interfaces.Interface {
- return &commonInterface{
+func init() {
+ registerIface(&commonInterface{
name: "account-control",
connectedPlugAppArmor: accountControlConnectedPlugAppArmor,
connectedPlugSecComp: accountControlConnectedPlugSecComp,
reservedForOS: true,
- }
-}
-
-func init() {
- registerIface(NewAccountControlInterface())
+ })
}
diff --git a/interfaces/builtin/account_control_test.go b/interfaces/builtin/account_control_test.go
index da52c53c84..1b762709f3 100644
--- a/interfaces/builtin/account_control_test.go
+++ b/interfaces/builtin/account_control_test.go
@@ -37,7 +37,9 @@ type AccountControlSuite struct {
plug *interfaces.Plug
}
-var _ = Suite(&AccountControlSuite{})
+var _ = Suite(&AccountControlSuite{
+ iface: builtin.MustInterface("account-control"),
+})
const accountCtlMockPlugSnapInfo = `name: other
version: 1.0
@@ -48,7 +50,6 @@ apps:
`
func (s *AccountControlSuite) SetUpTest(c *C) {
- s.iface = builtin.NewAccountControlInterface()
s.slot = &interfaces.Slot{
SlotInfo: &snap.SlotInfo{
Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS},
diff --git a/interfaces/builtin/all.go b/interfaces/builtin/all.go
index 2b895d268a..6582df7203 100644
--- a/interfaces/builtin/all.go
+++ b/interfaces/builtin/all.go
@@ -20,29 +20,35 @@
package builtin
import (
+ "fmt"
"sort"
"github.com/snapcore/snapd/interfaces"
)
var (
- allInterfaces []interfaces.Interface
- sorted bool
+ allInterfaces map[string]interfaces.Interface
)
// Interfaces returns all of the built-in interfaces.
func Interfaces() []interfaces.Interface {
- if !sorted {
- sort.Sort(byIfaceName(allInterfaces))
- sorted = true
+ ifaces := make([]interfaces.Interface, 0, len(allInterfaces))
+ for _, iface := range allInterfaces {
+ ifaces = append(ifaces, iface)
}
- return allInterfaces
+ sort.Sort(byIfaceName(ifaces))
+ return ifaces
}
// registerIface appends the given interface into the list of all known interfaces.
func registerIface(iface interfaces.Interface) {
- allInterfaces = append(allInterfaces, iface)
- sorted = false
+ if allInterfaces[iface.Name()] != nil {
+ panic(fmt.Errorf("cannot register duplicate interface %q", iface.Name()))
+ }
+ if allInterfaces == nil {
+ allInterfaces = make(map[string]interfaces.Interface)
+ }
+ allInterfaces[iface.Name()] = iface
}
type byIfaceName []interfaces.Interface
diff --git a/interfaces/builtin/all_test.go b/interfaces/builtin/all_test.go
index c457cf2017..36443f092a 100644
--- a/interfaces/builtin/all_test.go
+++ b/interfaces/builtin/all_test.go
@@ -26,6 +26,7 @@ import (
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/builtin"
"github.com/snapcore/snapd/interfaces/dbus"
+ "github.com/snapcore/snapd/interfaces/ifacetest"
"github.com/snapcore/snapd/interfaces/kmod"
"github.com/snapcore/snapd/interfaces/mount"
"github.com/snapcore/snapd/interfaces/seccomp"
@@ -293,3 +294,16 @@ func (s *AllSuite) TestNoInterfaceImplementsOldBackendMethods(c *C) {
}
}
}
+
+func (s *AllSuite) TestRegisterIface(c *C) {
+ restore := builtin.MockInterfaces(nil)
+ defer restore()
+
+ // Registering an interface works correctly.
+ iface := &ifacetest.TestInterface{InterfaceName: "foo"}
+ builtin.RegisterIface(iface)
+ c.Assert(builtin.Interface("foo"), DeepEquals, iface)
+
+ // Duplicates are detected.
+ c.Assert(func() { builtin.RegisterIface(iface) }, PanicMatches, `cannot register duplicate interface "foo"`)
+}
diff --git a/interfaces/builtin/alsa.go b/interfaces/builtin/alsa.go
index 3c0715e60e..f4c152c41a 100644
--- a/interfaces/builtin/alsa.go
+++ b/interfaces/builtin/alsa.go
@@ -19,8 +19,6 @@
package builtin
-import "github.com/snapcore/snapd/interfaces"
-
const alsaConnectedPlugAppArmor = `
# Description: Allow access to raw ALSA devices.
@@ -33,14 +31,10 @@ const alsaConnectedPlugAppArmor = `
/var/lib/alsa/{,*} r,
`
-func NewAlsaInterface() interfaces.Interface {
- return &commonInterface{
+func init() {
+ registerIface(&commonInterface{
name: "alsa",
connectedPlugAppArmor: alsaConnectedPlugAppArmor,
reservedForOS: true,
- }
-}
-
-func init() {
- registerIface(NewAlsaInterface())
+ })
}
diff --git a/interfaces/builtin/alsa_test.go b/interfaces/builtin/alsa_test.go
index 8a455c0fc0..6f08f54b18 100644
--- a/interfaces/builtin/alsa_test.go
+++ b/interfaces/builtin/alsa_test.go
@@ -36,7 +36,9 @@ type AlsaInterfaceSuite struct {
plug *interfaces.Plug
}
-var _ = Suite(&AlsaInterfaceSuite{})
+var _ = Suite(&AlsaInterfaceSuite{
+ iface: builtin.MustInterface("alsa"),
+})
func (s *AlsaInterfaceSuite) SetUpTest(c *C) {
var mockPlugSnapInfoYaml = `name: other
@@ -46,7 +48,6 @@ apps:
command: foo
plugs: [alsa]
`
- s.iface = builtin.NewAlsaInterface()
s.slot = &interfaces.Slot{
SlotInfo: &snap.SlotInfo{
Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS},
@@ -56,6 +57,7 @@ apps:
}
snapInfo := snaptest.MockInfo(c, mockPlugSnapInfoYaml, nil)
s.plug = &interfaces.Plug{PlugInfo: snapInfo.Plugs["alsa"]}
+ c.Assert(s.iface, NotNil)
}
func (s *AlsaInterfaceSuite) TestName(c *C) {
diff --git a/interfaces/builtin/autopilot.go b/interfaces/builtin/autopilot.go
index d3ba5d7361..dddbff9622 100644
--- a/interfaces/builtin/autopilot.go
+++ b/interfaces/builtin/autopilot.go
@@ -19,10 +19,6 @@
package builtin
-import (
- "github.com/snapcore/snapd/interfaces"
-)
-
const autopilotIntrospectionPlugAppArmor = `
# Description: Allows an application to be introspected and export its ui
# status over DBus
@@ -56,17 +52,11 @@ sendmsg
sendto
`
-// NewAutopilotIntrospectionInterface returns a new "autopilot-introspection"
-// interface.
-func NewAutopilotIntrospectionInterface() interfaces.Interface {
- return &commonInterface{
+func init() {
+ registerIface(&commonInterface{
name: "autopilot-introspection",
connectedPlugAppArmor: autopilotIntrospectionPlugAppArmor,
connectedPlugSecComp: autopilotIntrospectionPlugSecComp,
reservedForOS: true,
- }
-}
-
-func init() {
- registerIface(NewAutopilotIntrospectionInterface())
+ })
}
diff --git a/interfaces/builtin/autopilot_test.go b/interfaces/builtin/autopilot_test.go
index 655c8953d1..e61d46a264 100644
--- a/interfaces/builtin/autopilot_test.go
+++ b/interfaces/builtin/autopilot_test.go
@@ -45,10 +45,11 @@ apps:
plugs: [autopilot-introspection]
`
-var _ = Suite(&AutopilotInterfaceSuite{})
+var _ = Suite(&AutopilotInterfaceSuite{
+ iface: builtin.MustInterface("autopilot-introspection"),
+})
func (s *AutopilotInterfaceSuite) SetUpTest(c *C) {
- s.iface = builtin.NewAutopilotIntrospectionInterface()
s.slot = &interfaces.Slot{
SlotInfo: &snap.SlotInfo{
Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS},
diff --git a/interfaces/builtin/avahi_observe.go b/interfaces/builtin/avahi_observe.go
index e66a500def..c44c5827c1 100644
--- a/interfaces/builtin/avahi_observe.go
+++ b/interfaces/builtin/avahi_observe.go
@@ -19,8 +19,6 @@
package builtin
-import "github.com/snapcore/snapd/interfaces"
-
const avahiObserveConnectedPlugAppArmor = `
# Description: allows domain browsing, service browsing and service resolving
@@ -113,14 +111,10 @@ dbus (receive)
peer=(label=unconfined),
`
-func NewAvahiObserveInterface() interfaces.Interface {
- return &commonInterface{
+func init() {
+ registerIface(&commonInterface{
name: "avahi-observe",
connectedPlugAppArmor: avahiObserveConnectedPlugAppArmor,
reservedForOS: true,
- }
-}
-
-func init() {
- registerIface(NewAvahiObserveInterface())
+ })
}
diff --git a/interfaces/builtin/avahi_observe_test.go b/interfaces/builtin/avahi_observe_test.go
index ebf3f91152..ae571405df 100644
--- a/interfaces/builtin/avahi_observe_test.go
+++ b/interfaces/builtin/avahi_observe_test.go
@@ -36,7 +36,9 @@ type AvahiObserveInterfaceSuite struct {
plug *interfaces.Plug
}
-var _ = Suite(&AvahiObserveInterfaceSuite{})
+var _ = Suite(&AvahiObserveInterfaceSuite{
+ iface: builtin.MustInterface("avahi-observe"),
+})
func (s *AvahiObserveInterfaceSuite) SetUpTest(c *C) {
var mockPlugSnapInfoYaml = `name: other
@@ -46,7 +48,6 @@ apps:
command: foo
plugs: [avahi-observe]
`
- s.iface = builtin.NewAvahiObserveInterface()
s.slot = &interfaces.Slot{
SlotInfo: &snap.SlotInfo{
Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS},
diff --git a/interfaces/builtin/bluetooth_control.go b/interfaces/builtin/bluetooth_control.go
index 2ae7d682d1..55fbebeeff 100644
--- a/interfaces/builtin/bluetooth_control.go
+++ b/interfaces/builtin/bluetooth_control.go
@@ -19,10 +19,6 @@
package builtin
-import (
- "github.com/snapcore/snapd/interfaces"
-)
-
const bluetoothControlConnectedPlugAppArmor = `
# Description: Allow managing the kernel side Bluetooth stack. Reserved
# because this gives privileged access to the system.
@@ -50,15 +46,11 @@ const bluetoothControlConnectedPlugSecComp = `
bind
`
-func NewBluetoothControlInterface() interfaces.Interface {
- return &commonInterface{
+func init() {
+ registerIface(&commonInterface{
name: "bluetooth-control",
connectedPlugAppArmor: bluetoothControlConnectedPlugAppArmor,
connectedPlugSecComp: bluetoothControlConnectedPlugSecComp,
reservedForOS: true,
- }
-}
-
-func init() {
- registerIface(NewBluetoothControlInterface())
+ })
}
diff --git a/interfaces/builtin/bluetooth_control_test.go b/interfaces/builtin/bluetooth_control_test.go
index e93232bfd3..3ab834d80e 100644
--- a/interfaces/builtin/bluetooth_control_test.go
+++ b/interfaces/builtin/bluetooth_control_test.go
@@ -45,10 +45,11 @@ apps:
plugs: [bluetooth-control]
`
-var _ = Suite(&BluetoothControlInterfaceSuite{})
+var _ = Suite(&BluetoothControlInterfaceSuite{
+ iface: builtin.MustInterface("bluetooth-control"),
+})
func (s *BluetoothControlInterfaceSuite) SetUpTest(c *C) {
- s.iface = builtin.NewBluetoothControlInterface()
s.slot = &interfaces.Slot{
SlotInfo: &snap.SlotInfo{
Snap: &snap.Info{SuggestedName: "core", Type: snap.TypeOS},
diff --git a/interfaces/builtin/bluez.go b/interfaces/builtin/bluez.go