snap: auto-import assertions from block devices #2044

Closed
wants to merge 4 commits into
from
Jump to file or symbol
Failed to load files and symbols.
+245 −8
Split
View
@@ -54,17 +54,18 @@ func init() {
}})
}
-func (x *cmdAck) Execute(args []string) error {
- if len(args) > 0 {
- return ErrExtraArgs
- }
-
- assertFile := x.AckOptions.AssertionFile
-
+func ackFile(assertFile string) error {
assertData, err := ioutil.ReadFile(assertFile)
if err != nil {
return err
}
return Client().Ack(assertData)
}
+
+func (x *cmdAck) Execute(args []string) error {
+ if len(args) > 0 {
+ return ErrExtraArgs
+ }
+ return ackFile(x.AckOptions.AssertionFile)
+}
@@ -0,0 +1,127 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2014-2016 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 (
+ "bufio"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "time"
+
+ "github.com/jessevdk/go-flags"
+
+ "github.com/snapcore/snapd/i18n"
+ "github.com/snapcore/snapd/osutil"
+)
+
+const autoImportsName = "auto-imports.assert"
+
+var mountInfoPath = "/proc/self/mountinfo"
+
+func autoImportCandidates() ([]string, error) {
+ var cands []string
+
+ // see https://www.kernel.org/doc/Documentation/filesystems/proc.txt,
+ // sec. 3.5
+ f, err := os.Open(mountInfoPath)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ l := strings.Fields(scanner.Text())
+ if len(l) == 0 {
+ continue
+ }
+ mountPoint := l[4]
+ mountSrc := l[9]
+ // skip internal mounts
+ if !strings.HasPrefix(mountSrc, "/dev/") {
+ continue
+ }
+ // skip snaps
+ if strings.HasPrefix(mountSrc, "/dev/loop") {
+ continue
+ }
+ cand := filepath.Join(mountPoint, autoImportsName)
+ if osutil.FileExists(cand) {
+ cands = append(cands, cand)
+ }
+ }
+
+ return cands, scanner.Err()
+
+}
+
+func autoImportFromAllMounts() error {
+ cands, err := autoImportCandidates()
+ if err != nil {
+ return err
+ }
+
+ added := 0
+ for _, cand := range cands {
+ if err := ackFile(cand); err != nil {
+ fmt.Fprintf(Stderr, "cannot import %q: %s\n", cand, err)
+ continue
+ }
+ fmt.Fprintf(Stdout, "acked %q\n", cand)
+ }
+
+ // FIXME: once we have a way to know if a device is owned
+ // do no longer call this unconditionally
+ if added > 0 {
+ // FIXME: run `snap create-users --known`
+ }
+
+ return nil
+}
+
+type cmdAutoImport struct{}
+
+var shortAutoImportHelp = i18n.G("Imports assertions from mounted devices")
+
+var longAutoImportHelp = i18n.G("The auto-import command imports assertions found in the auto-import.assert file in mounted devices.")
+
+func init() {
+ cmd := addCommand("auto-import",
+ shortAutoImportHelp,
+ longAutoImportHelp,
+ func() flags.Commander {
+ return &cmdAutoImport{}
+ }, nil, nil)
+ cmd.hidden = true
+}
+
+func (x *cmdAutoImport) Execute(args []string) error {
+ if len(args) > 0 {
+ return ErrExtraArgs
+ }
+
+ // SUCKS: racy because the mount is not done when the script is
@niemeyer

niemeyer Sep 30, 2016

Contributor

Can we do something a bit nicer here, such as trying to stat things more than once depending on the resulting error, to account for such delays?

@mvo5

mvo5 Oct 2, 2016

Collaborator

This gets fixed in #2047 which builds on top of this, the race goes away once we start mounting stuff ourself instread of waiting for it.

+ // called
+ time.Sleep(1 * time.Second)
+
+ return autoImportFromAllMounts()
+}
@@ -0,0 +1,77 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2016 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"
+ "io/ioutil"
+ "net/http"
+ "path/filepath"
+
+ . "gopkg.in/check.v1"
+
+ snap "github.com/snapcore/snapd/cmd/snap"
+)
+
+var mockMountInfoFmt = `
+24 0 8:18 / %s rw,relatime shared:1 - ext4 /dev/sdb2 rw,errors=remount-ro,data=ordered
+`
+
+func makeMockMountInfo(c *C, content string) string {
+ fn := filepath.Join(c.MkDir(), "mountinfo")
+ err := ioutil.WriteFile(fn, []byte(content), 0644)
+ c.Assert(err, IsNil)
+ return fn
+}
+
+func (s *SnapSuite) TestAutoImportAssertsHappy(c *C) {
+ fakeAssertData := []byte("my-assertion")
+
+ n := 0
+ total := 1
+ s.RedirectClientToTestServer(func(w http.ResponseWriter, r *http.Request) {
+ switch n {
+ case 0:
+ c.Check(r.Method, Equals, "POST")
+ postData, err := ioutil.ReadAll(r.Body)
+ c.Assert(err, IsNil)
+ c.Check(postData, DeepEquals, fakeAssertData)
+ fmt.Fprintln(w, `{"type": "sync", "result": {"ready": true, "status": "Done"}}`)
+ n++
+ default:
+ c.Fatalf("unexpected request: %v (expected %d got %d)", r, total, n)
+ }
+
+ })
+
+ fakeAssertsFn := filepath.Join(c.MkDir(), "auto-imports.assert")
+ err := ioutil.WriteFile(fakeAssertsFn, fakeAssertData, 0644)
+ c.Assert(err, IsNil)
+
+ content := fmt.Sprintf(mockMountInfoFmt, filepath.Dir(fakeAssertsFn))
+ snap.MockMountInfoPath(makeMockMountInfo(c, content))
+
+ rest, err := snap.Parser().ParseArgs([]string{"auto-import"})
+ c.Assert(err, IsNil)
+ c.Assert(rest, DeepEquals, []string{})
+ c.Check(s.Stdout(), Equals, fmt.Sprintf("acked %q\n", fakeAssertsFn))
+ c.Check(s.Stderr(), Equals, "")
+ c.Check(n, Equals, total)
+}
@@ -75,3 +75,11 @@ func MockStoreNew(f func(*store.Config, auth.AuthContext) *store.Store) (restore
storeNew = storeNewOrig
}
}
+
+func MockMountInfoPath(newMountInfoPath string) (restore func()) {
+ mountInfoPathOrig := mountInfoPath
+ mountInfoPath = newMountInfoPath
+ return func() {
+ mountInfoPath = mountInfoPathOrig
+ }
+}
View
@@ -68,7 +68,11 @@ override_dh_systemd_enable:
dh_systemd_enable \
-psnapd \
snapd.boot-ok.service
- # enable the first boot service
+ # enable auto-import
+ dh_systemd_enable \
+ -psnapd \
+ snapd.autoimport.service
+# enable the first boot service
dh_systemd_enable \
-psnapd \
snapd.firstboot.service
@@ -110,6 +114,10 @@ override_dh_systemd_start:
dh_systemd_start \
-psnapd \
snapd.service
+ # start autoimport
+ dh_systemd_start \
+ -psnapd \
+ snapd.autoimport.service
override_dh_install:
# we do not need this in the package, its just needed during build
@@ -123,6 +131,8 @@ override_dh_install:
# install dev package files
mkdir -p debian/golang-github-snapcore-snapd-dev/usr/share
cp -R debian/tmp/usr/share/gocode debian/golang-github-snapcore-snapd-dev/usr/share
+ # install udev stuff
+ install debian/snapd.autoimport.udev -D debian/snapd/lib/udev/rules.d/95-snapd-autoimport.rules
# install binaries and needed files
install debian/tmp/usr/bin/snap -D debian/snapd/usr/bin/snap
install debian/tmp/usr/bin/snapctl -D debian/snapd/usr/bin/snapctl
@@ -142,6 +152,7 @@ override_dh_install:
mkdir -p debian/snapd/$(SYSTEMD_UNITS_DESTDIR)
install --mode=0644 debian/snapd.refresh.timer debian/snapd/$(SYSTEMD_UNITS_DESTDIR)
install --mode=0644 debian/snapd.refresh.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR)
+ install --mode=0644 debian/snapd.autoimport.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR)
install --mode=0644 debian/*.socket debian/snapd/$(SYSTEMD_UNITS_DESTDIR)
install --mode=0644 debian/snapd.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR)
install --mode=0644 debian/*.target debian/snapd/$(SYSTEMD_UNITS_DESTDIR)
@@ -151,6 +162,7 @@ ifeq ($(RELEASE),trusty)
dh_link debian/snapd/$(SYSTEMD_UNITS_DESTDIR)/snapd.firstboot.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR)/multi-user.target.wants/snapd.firstboot.service
dh_link debian/snapd/$(SYSTEMD_UNITS_DESTDIR)/snapd.boot-ok.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR)/multi-user.target.wants/snapd.boot-ok.service
dh_link debian/snapd/$(SYSTEMD_UNITS_DESTDIR)/snapd.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR)/multi-user.target.wants/snapd.service
+ dh_link debian/snapd/$(SYSTEMD_UNITS_DESTDIR)/snapd.autoimport.service debian/snapd/$(SYSTEMD_UNITS_DESTDIR)/multi-user.target.wants/snapd.autoimport.service
endif
override_dh_auto_install: snap.8
@@ -0,0 +1,10 @@
+[Unit]
+Description=Auto import assertions from block devices
+After=snapd.service snapd.socket
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/snap auto-import
+
+[Install]
+WantedBy=multi-user.target
@@ -0,0 +1,2 @@
+ACTION=="add", SUBSYSTEM=="block" ENV{ID_FS_USAGE}=="filesystem"\
+ ENV{SYSTEMD_WANTS}="snapd.autoimport.service"