Merge pull request #150 from runcom/update-go-systemd-dbus-v3

Update go systemd dbus v3
This commit is contained in:
Michael Crosby 2015-08-03 16:11:52 -04:00
commit b1821a4edc
14 changed files with 480 additions and 240 deletions

9
Godeps/Godeps.json generated
View File

@ -14,8 +14,13 @@
}, },
{ {
"ImportPath": "github.com/coreos/go-systemd/dbus", "ImportPath": "github.com/coreos/go-systemd/dbus",
"Comment": "v2", "Comment": "v3",
"Rev": "f743bc15d6bddd23662280b4ad20f7c874cdd5ad" "Rev": "be94bc700879ae8217780e9d141789a2defa302b"
},
{
"ImportPath": "github.com/coreos/go-systemd/util",
"Comment": "v3",
"Rev": "be94bc700879ae8217780e9d141789a2defa302b"
}, },
{ {
"ImportPath": "github.com/docker/docker/pkg/mount", "ImportPath": "github.com/docker/docker/pkg/mount",

View File

@ -1,23 +1,22 @@
/* // Copyright 2015 CoreOS, Inc.
Copyright 2013 CoreOS Inc. //
// Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License. // You may obtain a copy of the License at
You may obtain a copy of the License at //
// http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0 //
// Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
See the License for the specific language governing permissions and // limitations under the License.
limitations under the License.
*/
// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ // Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/
package dbus package dbus
import ( import (
"fmt"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@ -26,24 +25,53 @@ import (
"github.com/godbus/dbus" "github.com/godbus/dbus"
) )
const signalBuffer = 100 const (
alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
num = `0123456789`
alphanum = alpha + num
signalBuffer = 100
)
// ObjectPath creates a dbus.ObjectPath using the rules that systemd uses for // needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped
// serializing special characters. func needsEscape(i int, b byte) bool {
func ObjectPath(path string) dbus.ObjectPath { // Escape everything that is not a-z-A-Z-0-9
path = strings.Replace(path, ".", "_2e", -1) // Also escape 0-9 if it's the first character
path = strings.Replace(path, "-", "_2d", -1) return strings.IndexByte(alphanum, b) == -1 ||
path = strings.Replace(path, "@", "_40", -1) (i == 0 && strings.IndexByte(num, b) != -1)
return dbus.ObjectPath(path)
} }
// Conn is a connection to systemds dbus endpoint. // PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the
// rules that systemd uses for serializing special characters.
func PathBusEscape(path string) string {
// Special case the empty string
if len(path) == 0 {
return "_"
}
n := []byte{}
for i := 0; i < len(path); i++ {
c := path[i]
if needsEscape(i, c) {
e := fmt.Sprintf("_%x", c)
n = append(n, []byte(e)...)
} else {
n = append(n, c)
}
}
return string(n)
}
// Conn is a connection to systemd's dbus endpoint.
type Conn struct { type Conn struct {
sysconn *dbus.Conn // sysconn/sysobj are only used to call dbus methods
sysobj *dbus.Object sysconn *dbus.Conn
sysobj *dbus.Object
// sigconn/sigobj are only used to receive dbus signals
sigconn *dbus.Conn
sigobj *dbus.Object
jobListener struct { jobListener struct {
jobs map[dbus.ObjectPath]chan string jobs map[dbus.ObjectPath]chan<- string
sync.Mutex sync.Mutex
} }
subscriber struct { subscriber struct {
@ -53,26 +81,61 @@ type Conn struct {
ignore map[dbus.ObjectPath]int64 ignore map[dbus.ObjectPath]int64
cleanIgnore int64 cleanIgnore int64
} }
dispatch map[string]func(dbus.Signal)
} }
// New() establishes a connection to the system bus and authenticates. // New establishes a connection to the system bus and authenticates.
// Callers should call Close() when done with the connection.
func New() (*Conn, error) { func New() (*Conn, error) {
c := new(Conn) return newConnection(dbus.SystemBusPrivate)
}
if err := c.initConnection(); err != nil { // NewUserConnection establishes a connection to the session bus and
// authenticates. This can be used to connect to systemd user instances.
// Callers should call Close() when done with the connection.
func NewUserConnection() (*Conn, error) {
return newConnection(dbus.SessionBusPrivate)
}
// Close closes an established connection
func (c *Conn) Close() {
c.sysconn.Close()
c.sigconn.Close()
}
func newConnection(createBus func() (*dbus.Conn, error)) (*Conn, error) {
sysconn, err := dbusConnection(createBus)
if err != nil {
return nil, err return nil, err
} }
c.initJobs() sigconn, err := dbusConnection(createBus)
if err != nil {
sysconn.Close()
return nil, err
}
c := &Conn{
sysconn: sysconn,
sysobj: systemdObject(sysconn),
sigconn: sigconn,
sigobj: systemdObject(sigconn),
}
c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string)
// Setup the listeners on jobs so that we can get completions
c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
c.dispatch()
return c, nil return c, nil
} }
func (c *Conn) initConnection() error { func dbusConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
var err error conn, err := createBus()
c.sysconn, err = dbus.SystemBusPrivate()
if err != nil { if err != nil {
return err return nil, err
} }
// Only use EXTERNAL method, and hardcode the uid (not username) // Only use EXTERNAL method, and hardcode the uid (not username)
@ -80,25 +143,21 @@ func (c *Conn) initConnection() error {
// libc) // libc)
methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
err = c.sysconn.Auth(methods) err = conn.Auth(methods)
if err != nil { if err != nil {
c.sysconn.Close() conn.Close()
return err return nil, err
} }
err = c.sysconn.Hello() err = conn.Hello()
if err != nil { if err != nil {
c.sysconn.Close() conn.Close()
return err return nil, err
} }
c.sysobj = c.sysconn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1")) return conn, nil
}
// Setup the listeners on jobs so that we can get completions
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, func systemdObject(conn *dbus.Conn) *dbus.Object {
"type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'") return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
c.initSubscription()
c.initDispatch()
return nil
} }

View File

@ -1,18 +1,16 @@
/* // Copyright 2015 CoreOS, Inc.
Copyright 2013 CoreOS Inc. //
// Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License. // You may obtain a copy of the License at
You may obtain a copy of the License at //
// http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0 //
// Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
See the License for the specific language governing permissions and // limitations under the License.
limitations under the License.
*/
package dbus package dbus
@ -20,15 +18,53 @@ import (
"testing" "testing"
) )
// TestObjectPath ensures path encoding of the systemd rules works. func TestNeedsEscape(t *testing.T) {
func TestObjectPath(t *testing.T) { // Anything not 0-9a-zA-Z should always be escaped
input := "/silly-path/to@a/unit..service" for want, vals := range map[bool][]byte{
output := ObjectPath(input) false: []byte{'a', 'b', 'z', 'A', 'Q', '1', '4', '9'},
expected := "/silly_2dpath/to_40a/unit_2e_2eservice" true: []byte{'#', '%', '$', '!', '.', '_', '-', '%', '\\'},
} {
if string(output) != expected { for i := 1; i < 10; i++ {
t.Fatalf("Output '%s' did not match expected '%s'", output, expected) for _, b := range vals {
got := needsEscape(i, b)
if got != want {
t.Errorf("needsEscape(%d, %c) returned %t, want %t", i, b, got, want)
}
}
}
} }
// 0-9 in position 0 should be escaped
for want, vals := range map[bool][]byte{
false: []byte{'A', 'a', 'e', 'x', 'Q', 'Z'},
true: []byte{'0', '4', '5', '9'},
} {
for _, b := range vals {
got := needsEscape(0, b)
if got != want {
t.Errorf("needsEscape(0, %c) returned %t, want %t", b, got, want)
}
}
}
}
func TestPathBusEscape(t *testing.T) {
for in, want := range map[string]string{
"": "_",
"foo.service": "foo_2eservice",
"foobar": "foobar",
"woof@woof.service": "woof_40woof_2eservice",
"0123456": "_30123456",
"account_db.service": "account_5fdb_2eservice",
"got-dashes": "got_2ddashes",
} {
got := PathBusEscape(in)
if got != want {
t.Errorf("bad result for PathBusEscape(%s): got %q, want %q", in, got, want)
}
}
} }
// TestNew ensures that New() works without errors. // TestNew ensures that New() works without errors.

View File

@ -1,30 +1,27 @@
/* // Copyright 2015 CoreOS, Inc.
Copyright 2013 CoreOS Inc. //
// Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License. // You may obtain a copy of the License at
You may obtain a copy of the License at //
// http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0 //
// Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
See the License for the specific language governing permissions and // limitations under the License.
limitations under the License.
*/
package dbus package dbus
import ( import (
"errors" "errors"
"path"
"strconv"
"github.com/godbus/dbus" "github.com/godbus/dbus"
) )
func (c *Conn) initJobs() {
c.jobListener.jobs = make(map[dbus.ObjectPath]chan string)
}
func (c *Conn) jobComplete(signal *dbus.Signal) { func (c *Conn) jobComplete(signal *dbus.Signal) {
var id uint32 var id uint32
var job dbus.ObjectPath var job dbus.ObjectPath
@ -40,29 +37,29 @@ func (c *Conn) jobComplete(signal *dbus.Signal) {
c.jobListener.Unlock() c.jobListener.Unlock()
} }
func (c *Conn) startJob(job string, args ...interface{}) (<-chan string, error) { func (c *Conn) startJob(ch chan<- string, job string, args ...interface{}) (int, error) {
c.jobListener.Lock() if ch != nil {
defer c.jobListener.Unlock() c.jobListener.Lock()
defer c.jobListener.Unlock()
ch := make(chan string, 1)
var path dbus.ObjectPath
err := c.sysobj.Call(job, 0, args...).Store(&path)
if err != nil {
return nil, err
} }
c.jobListener.jobs[path] = ch
return ch, nil var p dbus.ObjectPath
err := c.sysobj.Call(job, 0, args...).Store(&p)
if err != nil {
return 0, err
}
if ch != nil {
c.jobListener.jobs[p] = ch
}
// ignore error since 0 is fine if conversion fails
jobID, _ := strconv.Atoi(path.Base(string(p)))
return jobID, nil
} }
func (c *Conn) runJob(job string, args ...interface{}) (string, error) { // StartUnit enqueues a start job and depending jobs, if any (unless otherwise
respCh, err := c.startJob(job, args...)
if err != nil {
return "", err
}
return <-respCh, nil
}
// StartUnit enqeues a start job and depending jobs, if any (unless otherwise
// specified by the mode string). // specified by the mode string).
// //
// Takes the unit to activate, plus a mode string. The mode needs to be one of // Takes the unit to activate, plus a mode string. The mode needs to be one of
@ -77,50 +74,58 @@ func (c *Conn) runJob(job string, args ...interface{}) (string, error) {
// requirement dependencies. It is not recommended to make use of the latter // requirement dependencies. It is not recommended to make use of the latter
// two options. // two options.
// //
// Result string: one of done, canceled, timeout, failed, dependency, skipped. // If the provided channel is non-nil, a result string will be sent to it upon
// job completion: one of done, canceled, timeout, failed, dependency, skipped.
// done indicates successful execution of a job. canceled indicates that a job // done indicates successful execution of a job. canceled indicates that a job
// has been canceled before it finished execution. timeout indicates that the // has been canceled before it finished execution. timeout indicates that the
// job timeout was reached. failed indicates that the job failed. dependency // job timeout was reached. failed indicates that the job failed. dependency
// indicates that a job this job has been depending on failed and the job hence // indicates that a job this job has been depending on failed and the job hence
// has been removed too. skipped indicates that a job was skipped because it // has been removed too. skipped indicates that a job was skipped because it
// didn't apply to the units current state. // didn't apply to the units current state.
func (c *Conn) StartUnit(name string, mode string) (string, error) { //
return c.runJob("org.freedesktop.systemd1.Manager.StartUnit", name, mode) // If no error occurs, the ID of the underlying systemd job will be returned. There
// does exist the possibility for no error to be returned, but for the returned job
// ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint
// should not be considered authoritative.
//
// If an error does occur, it will be returned to the user alongside a job ID of 0.
func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode)
} }
// StopUnit is similar to StartUnit but stops the specified unit rather // StopUnit is similar to StartUnit but stops the specified unit rather
// than starting it. // than starting it.
func (c *Conn) StopUnit(name string, mode string) (string, error) { func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) {
return c.runJob("org.freedesktop.systemd1.Manager.StopUnit", name, mode) return c.startJob(ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode)
} }
// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise. // ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise.
func (c *Conn) ReloadUnit(name string, mode string) (string, error) { func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) {
return c.runJob("org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode)
} }
// RestartUnit restarts a service. If a service is restarted that isn't // RestartUnit restarts a service. If a service is restarted that isn't
// running it will be started. // running it will be started.
func (c *Conn) RestartUnit(name string, mode string) (string, error) { func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.runJob("org.freedesktop.systemd1.Manager.RestartUnit", name, mode) return c.startJob(ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode)
} }
// TryRestartUnit is like RestartUnit, except that a service that isn't running // TryRestartUnit is like RestartUnit, except that a service that isn't running
// is not affected by the restart. // is not affected by the restart.
func (c *Conn) TryRestartUnit(name string, mode string) (string, error) { func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.runJob("org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) return c.startJob(ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode)
} }
// ReloadOrRestart attempts a reload if the unit supports it and use a restart // ReloadOrRestart attempts a reload if the unit supports it and use a restart
// otherwise. // otherwise.
func (c *Conn) ReloadOrRestartUnit(name string, mode string) (string, error) { func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode)
} }
// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try" // ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try"
// flavored restart otherwise. // flavored restart otherwise.
func (c *Conn) ReloadOrTryRestartUnit(name string, mode string) (string, error) { func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode)
} }
// StartTransientUnit() may be used to create and start a transient unit, which // StartTransientUnit() may be used to create and start a transient unit, which
@ -128,8 +133,8 @@ func (c *Conn) ReloadOrTryRestartUnit(name string, mode string) (string, error)
// system is rebooted. name is the unit name including suffix, and must be // system is rebooted. name is the unit name including suffix, and must be
// unique. mode is the same as in StartUnit(), properties contains properties // unique. mode is the same as in StartUnit(), properties contains properties
// of the unit. // of the unit.
func (c *Conn) StartTransientUnit(name string, mode string, properties ...Property) (string, error) { func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) {
return c.runJob("org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0))
} }
// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's // KillUnit takes the unit name and a UNIX signal number to send. All of the unit's
@ -138,12 +143,17 @@ func (c *Conn) KillUnit(name string, signal int32) {
c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store() c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
} }
// ResetFailedUnit resets the "failed" state of a specific unit.
func (c *Conn) ResetFailedUnit(name string) error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store()
}
// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface // getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) { func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
var err error var err error
var props map[string]dbus.Variant var props map[string]dbus.Variant
path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit) path := unitPath(unit)
if !path.IsValid() { if !path.IsValid() {
return nil, errors.New("invalid unit name: " + unit) return nil, errors.New("invalid unit name: " + unit)
} }
@ -171,7 +181,7 @@ func (c *Conn) getProperty(unit string, dbusInterface string, propertyName strin
var err error var err error
var prop dbus.Variant var prop dbus.Variant
path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit) path := unitPath(unit)
if !path.IsValid() { if !path.IsValid() {
return nil, errors.New("invalid unit name: " + unit) return nil, errors.New("invalid unit name: " + unit)
} }
@ -208,7 +218,7 @@ func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Proper
} }
func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1." + unitType, propertyName) return c.getProperty(unit, "org.freedesktop.systemd1."+unitType, propertyName)
} }
// ListUnits returns an array with all currently loaded units. Note that // ListUnits returns an array with all currently loaded units. Note that
@ -394,3 +404,7 @@ type DisableUnitFileChange struct {
func (c *Conn) Reload() error { func (c *Conn) Reload() error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store() return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
} }
func unitPath(name string) dbus.ObjectPath {
return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name))
}

View File

@ -1,18 +1,16 @@
/* // Copyright 2015 CoreOS, Inc.
Copyright 2013 CoreOS Inc. //
// Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License. // You may obtain a copy of the License at
You may obtain a copy of the License at //
// http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0 //
// Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
See the License for the specific language governing permissions and // limitations under the License.
limitations under the License.
*/
package dbus package dbus
@ -46,7 +44,7 @@ func findFixture(target string, t *testing.T) string {
func setupUnit(target string, conn *Conn, t *testing.T) { func setupUnit(target string, conn *Conn, t *testing.T) {
// Blindly stop the unit in case it is running // Blindly stop the unit in case it is running
conn.StopUnit(target, "replace") conn.StopUnit(target, "replace", nil)
// Blindly remove the symlink in case it exists // Blindly remove the symlink in case it exists
targetRun := filepath.Join("/run/systemd/system/", target) targetRun := filepath.Join("/run/systemd/system/", target)
@ -81,11 +79,13 @@ func TestStartStopUnit(t *testing.T) {
linkUnit(target, conn, t) linkUnit(target, conn, t)
// 2. Start the unit // 2. Start the unit
job, err := conn.StartUnit(target, "replace") reschan := make(chan string)
_, err := conn.StartUnit(target, "replace", reschan)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
job := <-reschan
if job != "done" { if job != "done" {
t.Fatal("Job is not done:", job) t.Fatal("Job is not done:", job)
} }
@ -108,11 +108,14 @@ func TestStartStopUnit(t *testing.T) {
} }
// 3. Stop the unit // 3. Stop the unit
job, err = conn.StopUnit(target, "replace") _, err = conn.StopUnit(target, "replace", reschan)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// wait for StopUnit job to complete
<-reschan
units, err = conn.ListUnits() units, err = conn.ListUnits()
unit = nil unit = nil
@ -260,11 +263,13 @@ func TestStartStopTransientUnit(t *testing.T) {
target := fmt.Sprintf("testing-transient-%d.service", rand.Int()) target := fmt.Sprintf("testing-transient-%d.service", rand.Int())
// Start the unit // Start the unit
job, err := conn.StartTransientUnit(target, "replace", props...) reschan := make(chan string)
_, err := conn.StartTransientUnit(target, "replace", props, reschan)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
job := <-reschan
if job != "done" { if job != "done" {
t.Fatal("Job is not done:", job) t.Fatal("Job is not done:", job)
} }
@ -287,11 +292,14 @@ func TestStartStopTransientUnit(t *testing.T) {
} }
// 3. Stop the unit // 3. Stop the unit
job, err = conn.StopUnit(target, "replace") _, err = conn.StopUnit(target, "replace", reschan)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// wait for StopUnit job to complete
<-reschan
units, err = conn.ListUnits() units, err = conn.ListUnits()
unit = nil unit = nil
@ -315,16 +323,21 @@ func TestConnJobListener(t *testing.T) {
jobSize := len(conn.jobListener.jobs) jobSize := len(conn.jobListener.jobs)
_, err := conn.StartUnit(target, "replace") reschan := make(chan string)
_, err := conn.StartUnit(target, "replace", reschan)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, err = conn.StopUnit(target, "replace") <-reschan
_, err = conn.StopUnit(target, "replace", reschan)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
<-reschan
currentJobSize := len(conn.jobListener.jobs) currentJobSize := len(conn.jobListener.jobs)
if jobSize != currentJobSize { if jobSize != currentJobSize {
t.Fatal("JobListener jobs leaked") t.Fatal("JobListener jobs leaked")

View File

@ -1,18 +1,16 @@
/* // Copyright 2015 CoreOS, Inc.
Copyright 2013 CoreOS Inc. //
// Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License. // You may obtain a copy of the License at
You may obtain a copy of the License at //
// http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0 //
// Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
See the License for the specific language governing permissions and // limitations under the License.
limitations under the License.
*/
package dbus package dbus

View File

@ -1,3 +1,17 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus package dbus
type set struct { type set struct {
@ -17,17 +31,17 @@ func (s *set) Contains(value string) (exists bool) {
return return
} }
func (s *set) Length() (int) { func (s *set) Length() int {
return len(s.data) return len(s.data)
} }
func (s *set) Values() (values []string) { func (s *set) Values() (values []string) {
for val, _ := range s.data { for val, _ := range s.data {
values = append(values, val) values = append(values, val)
} }
return return
} }
func newSet() (*set) { func newSet() *set {
return &set{make(map[string] bool)} return &set{make(map[string]bool)}
} }

View File

@ -1,3 +1,17 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus package dbus
import ( import (

View File

@ -1,18 +1,16 @@
/* // Copyright 2015 CoreOS, Inc.
Copyright 2013 CoreOS Inc. //
// Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License. // You may obtain a copy of the License at
You may obtain a copy of the License at //
// http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0 //
// Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
See the License for the specific language governing permissions and // limitations under the License.
limitations under the License.
*/
package dbus package dbus
@ -33,12 +31,12 @@ const (
// systemd will automatically stop sending signals so there is no need to // systemd will automatically stop sending signals so there is no need to
// explicitly call Unsubscribe(). // explicitly call Unsubscribe().
func (c *Conn) Subscribe() error { func (c *Conn) Subscribe() error {
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'") "type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'")
c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'") "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'")
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store() err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
if err != nil { if err != nil {
return err return err
} }
@ -48,7 +46,7 @@ func (c *Conn) Subscribe() error {
// Unsubscribe this connection from systemd dbus events. // Unsubscribe this connection from systemd dbus events.
func (c *Conn) Unsubscribe() error { func (c *Conn) Unsubscribe() error {
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store() err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
if err != nil { if err != nil {
return err return err
} }
@ -56,14 +54,10 @@ func (c *Conn) Unsubscribe() error {
return nil return nil
} }
func (c *Conn) initSubscription() { func (c *Conn) dispatch() {
c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
}
func (c *Conn) initDispatch() {
ch := make(chan *dbus.Signal, signalBuffer) ch := make(chan *dbus.Signal, signalBuffer)
c.sysconn.Signal(ch) c.sigconn.Signal(ch)
go func() { go func() {
for { for {
@ -72,24 +66,32 @@ func (c *Conn) initDispatch() {
return return
} }
if signal.Name == "org.freedesktop.systemd1.Manager.JobRemoved" {
c.jobComplete(signal)
}
if c.subscriber.updateCh == nil {
continue
}
var unitPath dbus.ObjectPath
switch signal.Name { switch signal.Name {
case "org.freedesktop.systemd1.Manager.JobRemoved": case "org.freedesktop.systemd1.Manager.JobRemoved":
c.jobComplete(signal)
unitName := signal.Body[2].(string) unitName := signal.Body[2].(string)
var unitPath dbus.ObjectPath
c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath)
if unitPath != dbus.ObjectPath("") {
c.sendSubStateUpdate(unitPath)
}
case "org.freedesktop.systemd1.Manager.UnitNew": case "org.freedesktop.systemd1.Manager.UnitNew":
c.sendSubStateUpdate(signal.Body[1].(dbus.ObjectPath)) unitPath = signal.Body[1].(dbus.ObjectPath)
case "org.freedesktop.DBus.Properties.PropertiesChanged": case "org.freedesktop.DBus.Properties.PropertiesChanged":
if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" { if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
// we only care about SubState updates, which are a Unit property unitPath = signal.Path
c.sendSubStateUpdate(signal.Path)
} }
} }
if unitPath == dbus.ObjectPath("") {
continue
}
c.sendSubStateUpdate(unitPath)
} }
}() }()
} }
@ -103,7 +105,7 @@ func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitSt
// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer // SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
// size of the channels, the comparison function for detecting changes and a filter // size of the channels, the comparison function for detecting changes and a filter
// function for cutting down on the noise that your channel receives. // function for cutting down on the noise that your channel receives.
func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func (string) bool) (<-chan map[string]*UnitStatus, <-chan error) { func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) {
old := make(map[string]*UnitStatus) old := make(map[string]*UnitStatus)
statusChan := make(chan map[string]*UnitStatus, buffer) statusChan := make(chan map[string]*UnitStatus, buffer)
errChan := make(chan error, buffer) errChan := make(chan error, buffer)
@ -176,9 +178,6 @@ func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan
func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) { func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) {
c.subscriber.Lock() c.subscriber.Lock()
defer c.subscriber.Unlock() defer c.subscriber.Unlock()
if c.subscriber.updateCh == nil {
return
}
if c.shouldIgnore(path) { if c.shouldIgnore(path) {
return return

View File

@ -1,3 +1,17 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus package dbus
import ( import (
@ -11,7 +25,6 @@ type SubscriptionSet struct {
conn *Conn conn *Conn
} }
func (s *SubscriptionSet) filter(unit string) bool { func (s *SubscriptionSet) filter(unit string) bool {
return !s.Contains(unit) return !s.Contains(unit)
} }
@ -21,12 +34,24 @@ func (s *SubscriptionSet) filter(unit string) bool {
func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) {
// TODO: Make fully evented by using systemd 209 with properties changed values // TODO: Make fully evented by using systemd 209 with properties changed values
return s.conn.SubscribeUnitsCustom(time.Second, 0, return s.conn.SubscribeUnitsCustom(time.Second, 0,
func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, mismatchUnitStatus,
func(unit string) bool { return s.filter(unit) }, func(unit string) bool { return s.filter(unit) },
) )
} }
// NewSubscriptionSet returns a new subscription set. // NewSubscriptionSet returns a new subscription set.
func (conn *Conn) NewSubscriptionSet() (*SubscriptionSet) { func (conn *Conn) NewSubscriptionSet() *SubscriptionSet {
return &SubscriptionSet{newSet(), conn} return &SubscriptionSet{newSet(), conn}
} }
// mismatchUnitStatus returns true if the provided UnitStatus objects
// are not equivalent. false is returned if the objects are equivalent.
// Only the Name, Description and state-related fields are used in
// the comparison.
func mismatchUnitStatus(u1, u2 *UnitStatus) bool {
return u1.Name != u2.Name ||
u1.Description != u2.Description ||
u1.LoadState != u2.LoadState ||
u1.ActiveState != u2.ActiveState ||
u1.SubState != u2.SubState
}

View File

@ -1,3 +1,17 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus package dbus
import ( import (
@ -27,11 +41,13 @@ func TestSubscriptionSetUnit(t *testing.T) {
setupUnit(target, conn, t) setupUnit(target, conn, t)
linkUnit(target, conn, t) linkUnit(target, conn, t)
job, err := conn.StartUnit(target, "replace") reschan := make(chan string)
_, err = conn.StartUnit(target, "replace", reschan)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
job := <-reschan
if job != "done" { if job != "done" {
t.Fatal("Couldn't start", target) t.Fatal("Couldn't start", target)
} }

View File

@ -1,3 +1,17 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus package dbus
import ( import (
@ -49,11 +63,13 @@ func TestSubscribeUnit(t *testing.T) {
setupUnit(target, conn, t) setupUnit(target, conn, t)
linkUnit(target, conn, t) linkUnit(target, conn, t)
job, err := conn.StartUnit(target, "replace") reschan := make(chan string)
_, err = conn.StartUnit(target, "replace", reschan)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
job := <-reschan
if job != "done" { if job != "done" {
t.Fatal("Couldn't start", target) t.Fatal("Couldn't start", target)
} }
@ -87,5 +103,3 @@ func TestSubscribeUnit(t *testing.T) {
success: success:
return return
} }

View File

@ -0,0 +1,33 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package util contains utility functions related to systemd that applications
// can use to check things like whether systemd is running.
package util
import (
"os"
)
// IsRunningSystemd checks whether the host was booted with systemd as its init
// system. This functions similar to systemd's `sd_booted(3)`: internally, it
// checks whether /run/systemd/system/ exists and is a directory.
// http://www.freedesktop.org/software/systemd/man/sd_booted.html
func IsRunningSystemd() bool {
fi, err := os.Lstat("/run/systemd/system")
if err != nil {
return false
}
return fi.IsDir()
}

View File

@ -12,7 +12,8 @@ import (
"sync" "sync"
"time" "time"
systemd "github.com/coreos/go-systemd/dbus" systemdDbus "github.com/coreos/go-systemd/dbus"
systemdUtil "github.com/coreos/go-systemd/util"
"github.com/godbus/dbus" "github.com/godbus/dbus"
"github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fs" "github.com/opencontainers/runc/libcontainer/cgroups/fs"
@ -52,21 +53,20 @@ const (
var ( var (
connLock sync.Mutex connLock sync.Mutex
theConn *systemd.Conn theConn *systemdDbus.Conn
hasStartTransientUnit bool hasStartTransientUnit bool
hasTransientDefaultDependencies bool hasTransientDefaultDependencies bool
) )
func newProp(name string, units interface{}) systemd.Property { func newProp(name string, units interface{}) systemdDbus.Property {
return systemd.Property{ return systemdDbus.Property{
Name: name, Name: name,
Value: dbus.MakeVariant(units), Value: dbus.MakeVariant(units),
} }
} }
func UseSystemd() bool { func UseSystemd() bool {
s, err := os.Stat("/run/systemd/system") if !systemdUtil.IsRunningSystemd() {
if err != nil || !s.IsDir() {
return false return false
} }
@ -75,7 +75,7 @@ func UseSystemd() bool {
if theConn == nil { if theConn == nil {
var err error var err error
theConn, err = systemd.New() theConn, err = systemdDbus.New()
if err != nil { if err != nil {
return false return false
} }
@ -84,7 +84,7 @@ func UseSystemd() bool {
hasStartTransientUnit = true hasStartTransientUnit = true
// But if we get UnknownMethod error we don't // But if we get UnknownMethod error we don't
if _, err := theConn.StartTransientUnit("test.scope", "invalid"); err != nil { if _, err := theConn.StartTransientUnit("test.scope", "invalid", nil, nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok { if dbusError, ok := err.(dbus.Error); ok {
if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" { if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" {
hasStartTransientUnit = false hasStartTransientUnit = false
@ -99,7 +99,7 @@ func UseSystemd() bool {
scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid()) scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid())
testScopeExists := true testScopeExists := true
for i := 0; i <= testScopeWait; i++ { for i := 0; i <= testScopeWait; i++ {
if _, err := theConn.StopUnit(scope, "replace"); err != nil { if _, err := theConn.StopUnit(scope, "replace", nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok { if dbusError, ok := err.(dbus.Error); ok {
if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") { if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") {
testScopeExists = false testScopeExists = false
@ -118,7 +118,7 @@ func UseSystemd() bool {
// Assume StartTransientUnit on a scope allows DefaultDependencies // Assume StartTransientUnit on a scope allows DefaultDependencies
hasTransientDefaultDependencies = true hasTransientDefaultDependencies = true
ddf := newProp("DefaultDependencies", false) ddf := newProp("DefaultDependencies", false)
if _, err := theConn.StartTransientUnit(scope, "replace", ddf); err != nil { if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{ddf}, nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok { if dbusError, ok := err.(dbus.Error); ok {
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") { if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
hasTransientDefaultDependencies = false hasTransientDefaultDependencies = false
@ -127,7 +127,7 @@ func UseSystemd() bool {
} }
// Not critical because of the stop unit logic above. // Not critical because of the stop unit logic above.
theConn.StopUnit(scope, "replace") theConn.StopUnit(scope, "replace", nil)
} }
return hasStartTransientUnit return hasStartTransientUnit
} }
@ -147,7 +147,7 @@ func (m *Manager) Apply(pid int) error {
c = m.Cgroups c = m.Cgroups
unitName = getUnitName(c) unitName = getUnitName(c)
slice = "system.slice" slice = "system.slice"
properties []systemd.Property properties []systemdDbus.Property
) )
if c.Slice != "" { if c.Slice != "" {
@ -155,8 +155,8 @@ func (m *Manager) Apply(pid int) error {
} }
properties = append(properties, properties = append(properties,
systemd.PropSlice(slice), systemdDbus.PropSlice(slice),
systemd.PropDescription("docker container "+c.Name), systemdDbus.PropDescription("docker container "+c.Name),
newProp("PIDs", []uint32{uint32(pid)}), newProp("PIDs", []uint32{uint32(pid)}),
) )
@ -198,7 +198,7 @@ func (m *Manager) Apply(pid int) error {
} }
} }
if _, err := theConn.StartTransientUnit(unitName, "replace", properties...); err != nil { if _, err := theConn.StartTransientUnit(unitName, "replace", properties, nil); err != nil {
return err return err
} }
@ -269,7 +269,7 @@ func (m *Manager) Apply(pid int) error {
func (m *Manager) Destroy() error { func (m *Manager) Destroy() error {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
theConn.StopUnit(getUnitName(m.Cgroups), "replace") theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil)
if err := cgroups.RemovePaths(m.Paths); err != nil { if err := cgroups.RemovePaths(m.Paths); err != nil {
return err return err
} }