diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b362fdc1..043f276c 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -14,8 +14,8 @@ }, { "ImportPath": "github.com/coreos/go-systemd/dbus", - "Comment": "v2", - "Rev": "f743bc15d6bddd23662280b4ad20f7c874cdd5ad" + "Comment": "v3", + "Rev": "be94bc700879ae8217780e9d141789a2defa302b" }, { "ImportPath": "github.com/docker/docker/pkg/mount", diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go index 91d71121..625a32b8 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus.go @@ -1,23 +1,22 @@ -/* -Copyright 2013 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. -*/ +// 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. // Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ package dbus import ( + "fmt" "os" "strconv" "strings" @@ -26,24 +25,53 @@ import ( "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 -// serializing special characters. -func ObjectPath(path string) dbus.ObjectPath { - path = strings.Replace(path, ".", "_2e", -1) - path = strings.Replace(path, "-", "_2d", -1) - path = strings.Replace(path, "@", "_40", -1) - - return dbus.ObjectPath(path) +// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped +func needsEscape(i int, b byte) bool { + // Escape everything that is not a-z-A-Z-0-9 + // Also escape 0-9 if it's the first character + return strings.IndexByte(alphanum, b) == -1 || + (i == 0 && strings.IndexByte(num, b) != -1) } -// 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 { - sysconn *dbus.Conn - sysobj *dbus.Object + // sysconn/sysobj are only used to call dbus methods + sysconn *dbus.Conn + sysobj *dbus.Object + + // sigconn/sigobj are only used to receive dbus signals + sigconn *dbus.Conn + sigobj *dbus.Object + jobListener struct { - jobs map[dbus.ObjectPath]chan string + jobs map[dbus.ObjectPath]chan<- string sync.Mutex } subscriber struct { @@ -53,26 +81,61 @@ type Conn struct { ignore map[dbus.ObjectPath]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) { - 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 } - 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 } -func (c *Conn) initConnection() error { - var err error - c.sysconn, err = dbus.SystemBusPrivate() +func dbusConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) { + conn, err := createBus() if err != nil { - return err + return nil, err } // Only use EXTERNAL method, and hardcode the uid (not username) @@ -80,25 +143,21 @@ func (c *Conn) initConnection() error { // libc) methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} - err = c.sysconn.Auth(methods) + err = conn.Auth(methods) if err != nil { - c.sysconn.Close() - return err + conn.Close() + return nil, err } - err = c.sysconn.Hello() + err = conn.Hello() if err != nil { - c.sysconn.Close() - return err + conn.Close() + return nil, err } - c.sysobj = c.sysconn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1")) - - // Setup the listeners on jobs so that we can get completions - c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, - "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'") - c.initSubscription() - c.initDispatch() - - return nil + return conn, nil +} + +func systemdObject(conn *dbus.Conn) *dbus.Object { + return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1")) } diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus_test.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus_test.go index 2e80f73e..3ea131e2 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus_test.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/dbus_test.go @@ -1,18 +1,16 @@ -/* -Copyright 2013 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. -*/ +// 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 @@ -20,15 +18,53 @@ import ( "testing" ) -// TestObjectPath ensures path encoding of the systemd rules works. -func TestObjectPath(t *testing.T) { - input := "/silly-path/to@a/unit..service" - output := ObjectPath(input) - expected := "/silly_2dpath/to_40a/unit_2e_2eservice" - - if string(output) != expected { - t.Fatalf("Output '%s' did not match expected '%s'", output, expected) +func TestNeedsEscape(t *testing.T) { + // Anything not 0-9a-zA-Z should always be escaped + for want, vals := range map[bool][]byte{ + false: []byte{'a', 'b', 'z', 'A', 'Q', '1', '4', '9'}, + true: []byte{'#', '%', '$', '!', '.', '_', '-', '%', '\\'}, + } { + for i := 1; i < 10; i++ { + 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. diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go index a60de059..ab614c7c 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods.go @@ -1,30 +1,27 @@ -/* -Copyright 2013 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. -*/ +// 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 import ( "errors" + "path" + "strconv" + "github.com/godbus/dbus" ) -func (c *Conn) initJobs() { - c.jobListener.jobs = make(map[dbus.ObjectPath]chan string) -} - func (c *Conn) jobComplete(signal *dbus.Signal) { var id uint32 var job dbus.ObjectPath @@ -40,29 +37,29 @@ func (c *Conn) jobComplete(signal *dbus.Signal) { c.jobListener.Unlock() } -func (c *Conn) startJob(job string, args ...interface{}) (<-chan string, error) { - 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 +func (c *Conn) startJob(ch chan<- string, job string, args ...interface{}) (int, error) { + if ch != nil { + c.jobListener.Lock() + defer c.jobListener.Unlock() } - 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) { - 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 +// StartUnit enqueues a start job and depending jobs, if any (unless otherwise // specified by the mode string). // // 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 // 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 // has been canceled before it finished execution. timeout indicates that the // 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 // has been removed too. skipped indicates that a job was skipped because it // 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 // than starting it. -func (c *Conn) StopUnit(name string, mode string) (string, error) { - return c.runJob("org.freedesktop.systemd1.Manager.StopUnit", name, mode) +func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) { + 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. -func (c *Conn) ReloadUnit(name string, mode string) (string, error) { - return c.runJob("org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) +func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) } // RestartUnit restarts a service. If a service is restarted that isn't // running it will be started. -func (c *Conn) RestartUnit(name string, mode string) (string, error) { - return c.runJob("org.freedesktop.systemd1.Manager.RestartUnit", name, mode) +func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode) } // TryRestartUnit is like RestartUnit, except that a service that isn't running // is not affected by the restart. -func (c *Conn) TryRestartUnit(name string, mode string) (string, error) { - return c.runJob("org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) +func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) } // ReloadOrRestart attempts a reload if the unit supports it and use a restart // otherwise. -func (c *Conn) ReloadOrRestartUnit(name string, mode string) (string, error) { - return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) +func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) } // ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try" // flavored restart otherwise. -func (c *Conn) ReloadOrTryRestartUnit(name string, mode string) (string, error) { - return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) +func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) { + return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) } // 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 // unique. mode is the same as in StartUnit(), properties contains properties // of the unit. -func (c *Conn) StartTransientUnit(name string, mode string, properties ...Property) (string, error) { - return c.runJob("org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) +func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) { + 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 @@ -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() } +// 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 func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) { var err error var props map[string]dbus.Variant - path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit) + path := unitPath(unit) if !path.IsValid() { 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 prop dbus.Variant - path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit) + path := unitPath(unit) if !path.IsValid() { 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) { - 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 @@ -394,3 +404,7 @@ type DisableUnitFileChange struct { func (c *Conn) Reload() error { 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)) +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods_test.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods_test.go index 8c7ab93e..c9f9ccde 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods_test.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/methods_test.go @@ -1,18 +1,16 @@ -/* -Copyright 2013 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. -*/ +// 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 @@ -46,7 +44,7 @@ func findFixture(target string, t *testing.T) string { func setupUnit(target string, conn *Conn, t *testing.T) { // 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 targetRun := filepath.Join("/run/systemd/system/", target) @@ -81,11 +79,13 @@ func TestStartStopUnit(t *testing.T) { linkUnit(target, conn, t) // 2. Start the unit - job, err := conn.StartUnit(target, "replace") + reschan := make(chan string) + _, err := conn.StartUnit(target, "replace", reschan) if err != nil { t.Fatal(err) } + job := <-reschan if job != "done" { t.Fatal("Job is not done:", job) } @@ -108,11 +108,14 @@ func TestStartStopUnit(t *testing.T) { } // 3. Stop the unit - job, err = conn.StopUnit(target, "replace") + _, err = conn.StopUnit(target, "replace", reschan) if err != nil { t.Fatal(err) } + // wait for StopUnit job to complete + <-reschan + units, err = conn.ListUnits() unit = nil @@ -260,11 +263,13 @@ func TestStartStopTransientUnit(t *testing.T) { target := fmt.Sprintf("testing-transient-%d.service", rand.Int()) // Start the unit - job, err := conn.StartTransientUnit(target, "replace", props...) + reschan := make(chan string) + _, err := conn.StartTransientUnit(target, "replace", props, reschan) if err != nil { t.Fatal(err) } + job := <-reschan if job != "done" { t.Fatal("Job is not done:", job) } @@ -287,11 +292,14 @@ func TestStartStopTransientUnit(t *testing.T) { } // 3. Stop the unit - job, err = conn.StopUnit(target, "replace") + _, err = conn.StopUnit(target, "replace", reschan) if err != nil { t.Fatal(err) } + // wait for StopUnit job to complete + <-reschan + units, err = conn.ListUnits() unit = nil @@ -315,16 +323,21 @@ func TestConnJobListener(t *testing.T) { jobSize := len(conn.jobListener.jobs) - _, err := conn.StartUnit(target, "replace") + reschan := make(chan string) + _, err := conn.StartUnit(target, "replace", reschan) if err != nil { t.Fatal(err) } - _, err = conn.StopUnit(target, "replace") + <-reschan + + _, err = conn.StopUnit(target, "replace", reschan) if err != nil { t.Fatal(err) } + <-reschan + currentJobSize := len(conn.jobListener.jobs) if jobSize != currentJobSize { t.Fatal("JobListener jobs leaked") diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go index a06ccda7..75200115 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/properties.go @@ -1,18 +1,16 @@ -/* -Copyright 2013 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. -*/ +// 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 diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go index 45ad1fb3..f92e6fbe 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set.go @@ -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 type set struct { @@ -17,17 +31,17 @@ func (s *set) Contains(value string) (exists bool) { return } -func (s *set) Length() (int) { +func (s *set) Length() int { return len(s.data) } func (s *set) Values() (values []string) { - for val, _ := range s.data { + for val, _ := range s.data { values = append(values, val) - } - return + } + return } -func newSet() (*set) { - return &set{make(map[string] bool)} +func newSet() *set { + return &set{make(map[string]bool)} } diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set_test.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set_test.go index c4435f88..2f04096f 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set_test.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/set_test.go @@ -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 import ( diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go index fcd29b6e..99645144 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription.go @@ -1,18 +1,16 @@ -/* -Copyright 2013 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. -*/ +// 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 @@ -33,12 +31,12 @@ const ( // systemd will automatically stop sending signals so there is no need to // explicitly call Unsubscribe(). 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'") - 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'") - 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 { return err } @@ -48,7 +46,7 @@ func (c *Conn) Subscribe() error { // Unsubscribe this connection from systemd dbus events. 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 { return err } @@ -56,14 +54,10 @@ func (c *Conn) Unsubscribe() error { return nil } -func (c *Conn) initSubscription() { - c.subscriber.ignore = make(map[dbus.ObjectPath]int64) -} - -func (c *Conn) initDispatch() { +func (c *Conn) dispatch() { ch := make(chan *dbus.Signal, signalBuffer) - c.sysconn.Signal(ch) + c.sigconn.Signal(ch) go func() { for { @@ -72,24 +66,32 @@ func (c *Conn) initDispatch() { 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 { case "org.freedesktop.systemd1.Manager.JobRemoved": - c.jobComplete(signal) - unitName := signal.Body[2].(string) - var unitPath dbus.ObjectPath 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": - c.sendSubStateUpdate(signal.Body[1].(dbus.ObjectPath)) + unitPath = signal.Body[1].(dbus.ObjectPath) case "org.freedesktop.DBus.Properties.PropertiesChanged": if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" { - // we only care about SubState updates, which are a Unit property - c.sendSubStateUpdate(signal.Path) + unitPath = 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 // size of the channels, the comparison function for detecting changes and a filter // 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) statusChan := make(chan map[string]*UnitStatus, 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) { c.subscriber.Lock() defer c.subscriber.Unlock() - if c.subscriber.updateCh == nil { - return - } if c.shouldIgnore(path) { return diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go index 26257860..5b408d58 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set.go @@ -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 import ( @@ -11,7 +25,6 @@ type SubscriptionSet struct { conn *Conn } - func (s *SubscriptionSet) filter(unit string) bool { 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) { // TODO: Make fully evented by using systemd 209 with properties changed values return s.conn.SubscribeUnitsCustom(time.Second, 0, - func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, + mismatchUnitStatus, func(unit string) bool { return s.filter(unit) }, ) } // NewSubscriptionSet returns a new subscription set. -func (conn *Conn) NewSubscriptionSet() (*SubscriptionSet) { +func (conn *Conn) NewSubscriptionSet() *SubscriptionSet { 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 +} diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go index 4ecd1537..53f75dfb 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go @@ -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 import ( @@ -27,11 +41,13 @@ func TestSubscriptionSetUnit(t *testing.T) { setupUnit(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 { t.Fatal(err) } + job := <-reschan if job != "done" { t.Fatal("Couldn't start", target) } diff --git a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_test.go b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_test.go index f2b5dfc2..e50fc6f9 100644 --- a/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_test.go +++ b/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus/subscription_test.go @@ -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 import ( @@ -49,11 +63,13 @@ func TestSubscribeUnit(t *testing.T) { setupUnit(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 { t.Fatal(err) } + job := <-reschan if job != "done" { t.Fatal("Couldn't start", target) } @@ -87,5 +103,3 @@ func TestSubscribeUnit(t *testing.T) { success: return } - -