pax_global_header00006660000000000000000000000064123312426030014506gustar00rootroot0000000000000052 comment=f743bc15d6bddd23662280b4ad20f7c874cdd5ad go-systemd-2/000077500000000000000000000000001233124260300133045ustar00rootroot00000000000000go-systemd-2/.travis.yml000066400000000000000000000001111233124260300154060ustar00rootroot00000000000000language: go go: 1.2 install: - echo "Skip install" script: - ./test go-systemd-2/LICENSE000066400000000000000000000240411233124260300143120ustar00rootroot00000000000000Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: You must give any other recipients of the Work or Derivative Works a copy of this License; and You must cause any modified files to carry prominent notices stating that You changed the files; and You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. go-systemd-2/README.md000066400000000000000000000024141233124260300145640ustar00rootroot00000000000000# go-systemd Go bindings to systemd. The project has three packages: - activation - for writing and using socket activation from Go - journal - for writing to systemd's logging service, journal - dbus - for starting/stopping/inspecting running services and units Go docs for the entire project are here: http://godoc.org/github.com/coreos/go-systemd ## Socket Activation An example HTTP server using socket activation can be quickly setup by following this README on a Linux machine running systemd: https://github.com/coreos/go-systemd/tree/master/examples/activation/httpserver ## Journal Using this package you can submit journal entries directly to systemd's journal taking advantage of features like indexed key/value pairs for each log entry. ## D-Bus The D-Bus API lets you start, stop and introspect systemd units. The API docs are here: http://godoc.org/github.com/coreos/go-systemd/dbus ### Debugging Create `/etc/dbus-1/system-local.conf` that looks like this: ``` ``` go-systemd-2/activation/000077500000000000000000000000001233124260300154455ustar00rootroot00000000000000go-systemd-2/activation/files.go000066400000000000000000000026421233124260300171020ustar00rootroot00000000000000/* 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. */ // Package activation implements primitives for systemd socket activation. package activation import ( "os" "strconv" "syscall" ) // based on: https://gist.github.com/alberts/4640792 const ( listenFdsStart = 3 ) func Files(unsetEnv bool) []*os.File { if unsetEnv { // there is no way to unset env in golang os package for now // https://code.google.com/p/go/issues/detail?id=6423 defer os.Setenv("LISTEN_PID", "") defer os.Setenv("LISTEN_FDS", "") } pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) if err != nil || pid != os.Getpid() { return nil } nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) if err != nil || nfds == 0 { return nil } var files []*os.File for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { syscall.CloseOnExec(fd) files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) } return files } go-systemd-2/activation/files_test.go000066400000000000000000000043101233124260300201330ustar00rootroot00000000000000/* 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. */ package activation import ( "bytes" "io" "os" "os/exec" "testing" ) // correctStringWritten fails the text if the correct string wasn't written // to the other side of the pipe. func correctStringWritten(t *testing.T, r *os.File, expected string) bool { bytes := make([]byte, len(expected)) io.ReadAtLeast(r, bytes, len(expected)) if string(bytes) != expected { t.Fatalf("Unexpected string %s", string(bytes)) } return true } // TestActivation forks out a copy of activation.go example and reads back two // strings from the pipes that are passed in. func TestActivation(t *testing.T) { cmd := exec.Command("go", "run", "../examples/activation/activation.go") r1, w1, _ := os.Pipe() r2, w2, _ := os.Pipe() cmd.ExtraFiles = []*os.File{ w1, w2, } cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") err := cmd.Run() if err != nil { t.Fatalf(err.Error()) } correctStringWritten(t, r1, "Hello world") correctStringWritten(t, r2, "Goodbye world") } func TestActivationNoFix(t *testing.T) { cmd := exec.Command("go", "run", "../examples/activation/activation.go") cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "LISTEN_FDS=2") out, _ := cmd.CombinedOutput() if bytes.Contains(out, []byte("No files")) == false { t.Fatalf("Child didn't error out as expected") } } func TestActivationNoFiles(t *testing.T) { cmd := exec.Command("go", "run", "../examples/activation/activation.go") cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "LISTEN_FDS=0", "FIX_LISTEN_PID=1") out, _ := cmd.CombinedOutput() if bytes.Contains(out, []byte("No files")) == false { t.Fatalf("Child didn't error out as expected") } } go-systemd-2/activation/listeners.go000066400000000000000000000020361233124260300200050ustar00rootroot00000000000000/* Copyright 2014 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 activation import ( "fmt" "net" ) // Listeners returns net.Listeners for all socket activated fds passed to this process. func Listeners(unsetEnv bool) ([]net.Listener, error) { files := Files(unsetEnv) listeners := make([]net.Listener, len(files)) for i, f := range files { var err error listeners[i], err = net.FileListener(f) if err != nil { return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) } } return listeners, nil } go-systemd-2/activation/listeners_test.go000066400000000000000000000040131233124260300210410ustar00rootroot00000000000000/* Copyright 2014 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 activation import ( "io" "net" "os" "os/exec" "testing" ) // correctStringWritten fails the text if the correct string wasn't written // to the other side of the pipe. func correctStringWrittenNet(t *testing.T, r net.Conn, expected string) bool { bytes := make([]byte, len(expected)) io.ReadAtLeast(r, bytes, len(expected)) if string(bytes) != expected { t.Fatalf("Unexpected string %s", string(bytes)) } return true } // TestActivation forks out a copy of activation.go example and reads back two // strings from the pipes that are passed in. func TestListeners(t *testing.T) { cmd := exec.Command("go", "run", "../examples/activation/listen.go") l1, err := net.Listen("tcp", ":9999") if err != nil { t.Fatalf(err.Error()) } l2, err := net.Listen("tcp", ":1234") if err != nil { t.Fatalf(err.Error()) } t1 := l1.(*net.TCPListener) t2 := l2.(*net.TCPListener) f1, _ := t1.File() f2, _ := t2.File() cmd.ExtraFiles = []*os.File{ f1, f2, } r1, err := net.Dial("tcp", "127.0.0.1:9999") if err != nil { t.Fatalf(err.Error()) } r1.Write([]byte("Hi")) r2, err := net.Dial("tcp", "127.0.0.1:1234") if err != nil { t.Fatalf(err.Error()) } r2.Write([]byte("Hi")) cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") out, err := cmd.Output() if err != nil { println(string(out)) t.Fatalf(err.Error()) } correctStringWrittenNet(t, r1, "Hello world") correctStringWrittenNet(t, r2, "Goodbye world") } go-systemd-2/dbus/000077500000000000000000000000001233124260300142415ustar00rootroot00000000000000go-systemd-2/dbus/dbus.go000066400000000000000000000050501233124260300155250ustar00rootroot00000000000000/* 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. */ // Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ package dbus import ( "os" "strconv" "strings" "sync" "github.com/godbus/dbus" ) const 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) } // Conn is a connection to systemds dbus endpoint. type Conn struct { sysconn *dbus.Conn sysobj *dbus.Object jobListener struct { jobs map[dbus.ObjectPath]chan string sync.Mutex } subscriber struct { updateCh chan<- *SubStateUpdate errCh chan<- error sync.Mutex ignore map[dbus.ObjectPath]int64 cleanIgnore int64 } dispatch map[string]func(dbus.Signal) } // New() establishes a connection to the system bus and authenticates. func New() (*Conn, error) { c := new(Conn) if err := c.initConnection(); err != nil { return nil, err } c.initJobs() return c, nil } func (c *Conn) initConnection() error { var err error c.sysconn, err = dbus.SystemBusPrivate() if err != nil { return err } // Only use EXTERNAL method, and hardcode the uid (not username) // to avoid a username lookup (which requires a dynamically linked // libc) methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} err = c.sysconn.Auth(methods) if err != nil { c.sysconn.Close() return err } err = c.sysconn.Hello() if err != nil { c.sysconn.Close() return 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 } go-systemd-2/dbus/dbus_test.go000066400000000000000000000020631233124260300165650ustar00rootroot00000000000000/* 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. */ package dbus 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) } } // TestNew ensures that New() works without errors. func TestNew(t *testing.T) { _, err := New() if err != nil { t.Fatal(err) } } go-systemd-2/dbus/methods.go000066400000000000000000000360011233124260300162330ustar00rootroot00000000000000/* 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. */ package dbus import ( "errors" "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 var unit string var result string dbus.Store(signal.Body, &id, &job, &unit, &result) c.jobListener.Lock() out, ok := c.jobListener.jobs[job] if ok { out <- result delete(c.jobListener.jobs, job) } 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 } c.jobListener.jobs[path] = ch return ch, 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 // specified by the mode string). // // Takes the unit to activate, plus a mode string. The mode needs to be one of // replace, fail, isolate, ignore-dependencies, ignore-requirements. If // "replace" the call will start the unit and its dependencies, possibly // replacing already queued jobs that conflict with this. If "fail" the call // will start the unit and its dependencies, but will fail if this would change // an already queued job. If "isolate" the call will start the unit in question // and terminate all units that aren't dependencies of it. If // "ignore-dependencies" it will start a unit but ignore all its dependencies. // If "ignore-requirements" it will start a unit but only ignore the // requirement dependencies. It is not recommended to make use of the latter // two options. // // Result string: 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) } // 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) } // 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) } // 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) } // 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) } // 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) } // 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) } // StartTransientUnit() may be used to create and start a transient unit, which // will be released as soon as it is not running or referenced anymore or the // 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)) } // KillUnit takes the unit name and a UNIX signal number to send. All of the unit's // processes are killed. func (c *Conn) KillUnit(name string, signal int32) { c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).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) if !path.IsValid() { return nil, errors.New("invalid unit name: " + unit) } obj := c.sysconn.Object("org.freedesktop.systemd1", path) err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props) if err != nil { return nil, err } out := make(map[string]interface{}, len(props)) for k, v := range props { out[k] = v.Value() } return out, nil } // GetUnitProperties takes the unit name and returns all of its dbus object properties. func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) { return c.getProperties(unit, "org.freedesktop.systemd1.Unit") } func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) { var err error var prop dbus.Variant path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit) if !path.IsValid() { return nil, errors.New("invalid unit name: " + unit) } obj := c.sysconn.Object("org.freedesktop.systemd1", path) err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop) if err != nil { return nil, err } return &Property{Name: propertyName, Value: prop}, nil } func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) { return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName) } // GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type. // Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope // return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) { return c.getProperties(unit, "org.freedesktop.systemd1."+unitType) } // SetUnitProperties() may be used to modify certain unit properties at runtime. // Not all properties may be changed at runtime, but many resource management // settings (primarily those in systemd.cgroup(5)) may. The changes are applied // instantly, and stored on disk for future boots, unless runtime is true, in which // case the settings only apply until the next reboot. name is the name of the unit // to modify. properties are the settings to set, encoded as an array of property // name and value pairs. func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error { return c.sysobj.Call("org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store() } func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { return c.getProperty(unit, "org.freedesktop.systemd1." + unitType, propertyName) } // ListUnits returns an array with all currently loaded units. Note that // units may be known by multiple names at the same time, and hence there might // be more unit names loaded than actual units behind them. func (c *Conn) ListUnits() ([]UnitStatus, error) { result := make([][]interface{}, 0) err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store(&result) if err != nil { return nil, err } resultInterface := make([]interface{}, len(result)) for i := range result { resultInterface[i] = result[i] } status := make([]UnitStatus, len(result)) statusInterface := make([]interface{}, len(status)) for i := range status { statusInterface[i] = &status[i] } err = dbus.Store(resultInterface, statusInterface...) if err != nil { return nil, err } return status, nil } type UnitStatus struct { Name string // The primary unit name as string Description string // The human readable description string LoadState string // The load state (i.e. whether the unit file has been loaded successfully) ActiveState string // The active state (i.e. whether the unit is currently started or not) SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not) Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string. Path dbus.ObjectPath // The unit object path JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise JobType string // The job type as string JobPath dbus.ObjectPath // The job object path } type LinkUnitFileChange EnableUnitFileChange // LinkUnitFiles() links unit files (that are located outside of the // usual unit search paths) into the unit search path. // // It takes a list of absolute paths to unit files to link and two // booleans. The first boolean controls whether the unit shall be // enabled for runtime only (true, /run), or persistently (false, // /etc). // The second controls whether symlinks pointing to other units shall // be replaced if necessary. // // This call returns a list of the changes made. The list consists of // structures with three strings: the type of the change (one of symlink // or unlink), the file name of the symlink and the destination of the // symlink. func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { result := make([][]interface{}, 0) err := c.sysobj.Call("org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result) if err != nil { return nil, err } resultInterface := make([]interface{}, len(result)) for i := range result { resultInterface[i] = result[i] } changes := make([]LinkUnitFileChange, len(result)) changesInterface := make([]interface{}, len(changes)) for i := range changes { changesInterface[i] = &changes[i] } err = dbus.Store(resultInterface, changesInterface...) if err != nil { return nil, err } return changes, nil } // EnableUnitFiles() may be used to enable one or more units in the system (by // creating symlinks to them in /etc or /run). // // It takes a list of unit files to enable (either just file names or full // absolute paths if the unit files are residing outside the usual unit // search paths), and two booleans: the first controls whether the unit shall // be enabled for runtime only (true, /run), or persistently (false, /etc). // The second one controls whether symlinks pointing to other units shall // be replaced if necessary. // // This call returns one boolean and an array with the changes made. The // boolean signals whether the unit files contained any enablement // information (i.e. an [Install]) section. The changes list consists of // structures with three strings: the type of the change (one of symlink // or unlink), the file name of the symlink and the destination of the // symlink. func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { var carries_install_info bool result := make([][]interface{}, 0) err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result) if err != nil { return false, nil, err } resultInterface := make([]interface{}, len(result)) for i := range result { resultInterface[i] = result[i] } changes := make([]EnableUnitFileChange, len(result)) changesInterface := make([]interface{}, len(changes)) for i := range changes { changesInterface[i] = &changes[i] } err = dbus.Store(resultInterface, changesInterface...) if err != nil { return false, nil, err } return carries_install_info, changes, nil } type EnableUnitFileChange struct { Type string // Type of the change (one of symlink or unlink) Filename string // File name of the symlink Destination string // Destination of the symlink } // DisableUnitFiles() may be used to disable one or more units in the system (by // removing symlinks to them from /etc or /run). // // It takes a list of unit files to disable (either just file names or full // absolute paths if the unit files are residing outside the usual unit // search paths), and one boolean: whether the unit was enabled for runtime // only (true, /run), or persistently (false, /etc). // // This call returns an array with the changes made. The changes list // consists of structures with three strings: the type of the change (one of // symlink or unlink), the file name of the symlink and the destination of the // symlink. func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) { result := make([][]interface{}, 0) err := c.sysobj.Call("org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result) if err != nil { return nil, err } resultInterface := make([]interface{}, len(result)) for i := range result { resultInterface[i] = result[i] } changes := make([]DisableUnitFileChange, len(result)) changesInterface := make([]interface{}, len(changes)) for i := range changes { changesInterface[i] = &changes[i] } err = dbus.Store(resultInterface, changesInterface...) if err != nil { return nil, err } return changes, nil } type DisableUnitFileChange struct { Type string // Type of the change (one of symlink or unlink) Filename string // File name of the symlink Destination string // Destination of the symlink } // Reload instructs systemd to scan for and reload unit files. This is // equivalent to a 'systemctl daemon-reload'. func (c *Conn) Reload() error { return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store() } go-systemd-2/dbus/methods_test.go000066400000000000000000000155331233124260300173010ustar00rootroot00000000000000/* 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. */ package dbus import ( "fmt" "math/rand" "os" "path/filepath" "reflect" "testing" "github.com/godbus/dbus" ) func setupConn(t *testing.T) *Conn { conn, err := New() if err != nil { t.Fatal(err) } return conn } func findFixture(target string, t *testing.T) string { abs, err := filepath.Abs("../fixtures/" + target) if err != nil { t.Fatal(err) } return abs } func setupUnit(target string, conn *Conn, t *testing.T) { // Blindly stop the unit in case it is running conn.StopUnit(target, "replace") // Blindly remove the symlink in case it exists targetRun := filepath.Join("/run/systemd/system/", target) os.Remove(targetRun) } func linkUnit(target string, conn *Conn, t *testing.T) { abs := findFixture(target, t) fixture := []string{abs} changes, err := conn.LinkUnitFiles(fixture, true, true) if err != nil { t.Fatal(err) } if len(changes) < 1 { t.Fatalf("Expected one change, got %v", changes) } runPath := filepath.Join("/run/systemd/system/", target) if changes[0].Filename != runPath { t.Fatal("Unexpected target filename") } } // Ensure that basic unit starting and stopping works. func TestStartStopUnit(t *testing.T) { target := "start-stop.service" conn := setupConn(t) setupUnit(target, conn, t) linkUnit(target, conn, t) // 2. Start the unit job, err := conn.StartUnit(target, "replace") if err != nil { t.Fatal(err) } if job != "done" { t.Fatal("Job is not done:", job) } units, err := conn.ListUnits() var unit *UnitStatus for _, u := range units { if u.Name == target { unit = &u } } if unit == nil { t.Fatalf("Test unit not found in list") } if unit.ActiveState != "active" { t.Fatalf("Test unit not active") } // 3. Stop the unit job, err = conn.StopUnit(target, "replace") if err != nil { t.Fatal(err) } units, err = conn.ListUnits() unit = nil for _, u := range units { if u.Name == target { unit = &u } } if unit != nil { t.Fatalf("Test unit found in list, should be stopped") } } // Enables a unit and then immediately tears it down func TestEnableDisableUnit(t *testing.T) { target := "enable-disable.service" conn := setupConn(t) setupUnit(target, conn, t) abs := findFixture(target, t) runPath := filepath.Join("/run/systemd/system/", target) // 1. Enable the unit install, changes, err := conn.EnableUnitFiles([]string{abs}, true, true) if err != nil { t.Fatal(err) } if install != false { t.Fatal("Install was true") } if len(changes) < 1 { t.Fatalf("Expected one change, got %v", changes) } if changes[0].Filename != runPath { t.Fatal("Unexpected target filename") } // 2. Disable the unit dChanges, err := conn.DisableUnitFiles([]string{abs}, true) if err != nil { t.Fatal(err) } if len(dChanges) != 1 { t.Fatalf("Changes should include the path, %v", dChanges) } if dChanges[0].Filename != runPath { t.Fatalf("Change should include correct filename, %+v", dChanges[0]) } if dChanges[0].Destination != "" { t.Fatalf("Change destination should be empty, %+v", dChanges[0]) } } // TestGetUnitProperties reads the `-.mount` which should exist on all systemd // systems and ensures that one of its properties is valid. func TestGetUnitProperties(t *testing.T) { conn := setupConn(t) unit := "-.mount" info, err := conn.GetUnitProperties(unit) if err != nil { t.Fatal(err) } names := info["Wants"].([]string) if len(names) < 1 { t.Fatal("/ is unwanted") } if names[0] != "system.slice" { t.Fatal("unexpected wants for /") } prop, err := conn.GetUnitProperty(unit, "Wants") if err != nil { t.Fatal(err) } if prop.Name != "Wants" { t.Fatal("unexpected property name") } val := prop.Value.Value().([]string) if !reflect.DeepEqual(val, names) { t.Fatal("unexpected property value") } } // TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a // unit with an invalid name. This test should be run with --test.timeout set, // as a fail will manifest as GetUnitProperties hanging indefinitely. func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) { conn := setupConn(t) unit := "//invalid#$^/" _, err := conn.GetUnitProperties(unit) if err == nil { t.Fatal("Expected an error, got nil") } _, err = conn.GetUnitProperty(unit, "Wants") if err == nil { t.Fatal("Expected an error, got nil") } } // TestSetUnitProperties changes a cgroup setting on the `tmp.mount` // which should exist on all systemd systems and ensures that the // property was set. func TestSetUnitProperties(t *testing.T) { conn := setupConn(t) unit := "tmp.mount" if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil { t.Fatal(err) } info, err := conn.GetUnitTypeProperties(unit, "Mount") if err != nil { t.Fatal(err) } value := info["CPUShares"].(uint64) if value != 1023 { t.Fatal("CPUShares of unit is not 1023:", value) } } // Ensure that basic transient unit starting and stopping works. func TestStartStopTransientUnit(t *testing.T) { conn := setupConn(t) props := []Property{ PropExecStart([]string{"/bin/sleep", "400"}, false), } target := fmt.Sprintf("testing-transient-%d.service", rand.Int()) // Start the unit job, err := conn.StartTransientUnit(target, "replace", props...) if err != nil { t.Fatal(err) } if job != "done" { t.Fatal("Job is not done:", job) } units, err := conn.ListUnits() var unit *UnitStatus for _, u := range units { if u.Name == target { unit = &u } } if unit == nil { t.Fatalf("Test unit not found in list") } if unit.ActiveState != "active" { t.Fatalf("Test unit not active") } // 3. Stop the unit job, err = conn.StopUnit(target, "replace") if err != nil { t.Fatal(err) } units, err = conn.ListUnits() unit = nil for _, u := range units { if u.Name == target { unit = &u } } if unit != nil { t.Fatalf("Test unit found in list, should be stopped") } } func TestConnJobListener(t *testing.T) { target := "start-stop.service" conn := setupConn(t) setupUnit(target, conn, t) linkUnit(target, conn, t) jobSize := len(conn.jobListener.jobs) _, err := conn.StartUnit(target, "replace") if err != nil { t.Fatal(err) } _, err = conn.StopUnit(target, "replace") if err != nil { t.Fatal(err) } currentJobSize := len(conn.jobListener.jobs) if jobSize != currentJobSize { t.Fatal("JobListener jobs leaked") } } go-systemd-2/dbus/properties.go000066400000000000000000000175771233124260300170050ustar00rootroot00000000000000/* 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. */ package dbus import ( "github.com/godbus/dbus" ) // From the systemd docs: // // The properties array of StartTransientUnit() may take many of the settings // that may also be configured in unit files. Not all parameters are currently // accepted though, but we plan to cover more properties with future release. // Currently you may set the Description, Slice and all dependency types of // units, as well as RemainAfterExit, ExecStart for service units, // TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares, // BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth, // BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit, // DevicePolicy, DeviceAllow for services/scopes/slices. These fields map // directly to their counterparts in unit files and as normal D-Bus object // properties. The exception here is the PIDs field of scope units which is // used for construction of the scope only and specifies the initial PIDs to // add to the scope object. type Property struct { Name string Value dbus.Variant } type PropertyCollection struct { Name string Properties []Property } type execStart struct { Path string // the binary path to execute Args []string // an array with all arguments to pass to the executed command, starting with argument 0 UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly } // PropExecStart sets the ExecStart service property. The first argument is a // slice with the binary path to execute followed by the arguments to pass to // the executed command. See // http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= func PropExecStart(command []string, uncleanIsFailure bool) Property { execStarts := []execStart{ execStart{ Path: command[0], Args: command, UncleanIsFailure: uncleanIsFailure, }, } return Property{ Name: "ExecStart", Value: dbus.MakeVariant(execStarts), } } // PropRemainAfterExit sets the RemainAfterExit service property. See // http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit= func PropRemainAfterExit(b bool) Property { return Property{ Name: "RemainAfterExit", Value: dbus.MakeVariant(b), } } // PropDescription sets the Description unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit#Description= func PropDescription(desc string) Property { return Property{ Name: "Description", Value: dbus.MakeVariant(desc), } } func propDependency(name string, units []string) Property { return Property{ Name: name, Value: dbus.MakeVariant(units), } } // PropRequires sets the Requires unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires= func PropRequires(units ...string) Property { return propDependency("Requires", units) } // PropRequiresOverridable sets the RequiresOverridable unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable= func PropRequiresOverridable(units ...string) Property { return propDependency("RequiresOverridable", units) } // PropRequisite sets the Requisite unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite= func PropRequisite(units ...string) Property { return propDependency("Requisite", units) } // PropRequisiteOverridable sets the RequisiteOverridable unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable= func PropRequisiteOverridable(units ...string) Property { return propDependency("RequisiteOverridable", units) } // PropWants sets the Wants unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants= func PropWants(units ...string) Property { return propDependency("Wants", units) } // PropBindsTo sets the BindsTo unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo= func PropBindsTo(units ...string) Property { return propDependency("BindsTo", units) } // PropRequiredBy sets the RequiredBy unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy= func PropRequiredBy(units ...string) Property { return propDependency("RequiredBy", units) } // PropRequiredByOverridable sets the RequiredByOverridable unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable= func PropRequiredByOverridable(units ...string) Property { return propDependency("RequiredByOverridable", units) } // PropWantedBy sets the WantedBy unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy= func PropWantedBy(units ...string) Property { return propDependency("WantedBy", units) } // PropBoundBy sets the BoundBy unit property. See // http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy= func PropBoundBy(units ...string) Property { return propDependency("BoundBy", units) } // PropConflicts sets the Conflicts unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts= func PropConflicts(units ...string) Property { return propDependency("Conflicts", units) } // PropConflictedBy sets the ConflictedBy unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy= func PropConflictedBy(units ...string) Property { return propDependency("ConflictedBy", units) } // PropBefore sets the Before unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before= func PropBefore(units ...string) Property { return propDependency("Before", units) } // PropAfter sets the After unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After= func PropAfter(units ...string) Property { return propDependency("After", units) } // PropOnFailure sets the OnFailure unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure= func PropOnFailure(units ...string) Property { return propDependency("OnFailure", units) } // PropTriggers sets the Triggers unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers= func PropTriggers(units ...string) Property { return propDependency("Triggers", units) } // PropTriggeredBy sets the TriggeredBy unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy= func PropTriggeredBy(units ...string) Property { return propDependency("TriggeredBy", units) } // PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo= func PropPropagatesReloadTo(units ...string) Property { return propDependency("PropagatesReloadTo", units) } // PropRequiresMountsFor sets the RequiresMountsFor unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor= func PropRequiresMountsFor(units ...string) Property { return propDependency("RequiresMountsFor", units) } // PropSlice sets the Slice unit property. See // http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice= func PropSlice(slice string) Property { return Property{ Name: "Slice", Value: dbus.MakeVariant(slice), } } go-systemd-2/dbus/set.go000066400000000000000000000007731233124260300153720ustar00rootroot00000000000000package dbus type set struct { data map[string]bool } func (s *set) Add(value string) { s.data[value] = true } func (s *set) Remove(value string) { delete(s.data, value) } func (s *set) Contains(value string) (exists bool) { _, exists = s.data[value] return } func (s *set) Length() (int) { return len(s.data) } func (s *set) Values() (values []string) { for val, _ := range s.data { values = append(values, val) } return } func newSet() (*set) { return &set{make(map[string] bool)} } go-systemd-2/dbus/set_test.go000066400000000000000000000012451233124260300164240ustar00rootroot00000000000000package dbus import ( "testing" ) // TestBasicSetActions asserts that Add & Remove behavior is correct func TestBasicSetActions(t *testing.T) { s := newSet() if s.Contains("foo") { t.Fatal("set should not contain 'foo'") } s.Add("foo") if !s.Contains("foo") { t.Fatal("set should contain 'foo'") } v := s.Values() if len(v) != 1 { t.Fatal("set.Values did not report correct number of values") } if v[0] != "foo" { t.Fatal("set.Values did not report value") } s.Remove("foo") if s.Contains("foo") { t.Fatal("set should not contain 'foo'") } v = s.Values() if len(v) != 0 { t.Fatal("set.Values did not report correct number of values") } } go-systemd-2/dbus/subscription.go000066400000000000000000000166211233124260300173220ustar00rootroot00000000000000/* 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. */ package dbus import ( "errors" "time" "github.com/godbus/dbus" ) const ( cleanIgnoreInterval = int64(10 * time.Second) ignoreInterval = int64(30 * time.Millisecond) ) // Subscribe sets up this connection to subscribe to all systemd dbus events. // This is required before calling SubscribeUnits. When the connection closes // 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, "type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'") c.sysconn.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() if err != nil { return err } return nil } // Unsubscribe this connection from systemd dbus events. func (c *Conn) Unsubscribe() error { err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store() if err != nil { return err } return nil } func (c *Conn) initSubscription() { c.subscriber.ignore = make(map[dbus.ObjectPath]int64) } func (c *Conn) initDispatch() { ch := make(chan *dbus.Signal, signalBuffer) c.sysconn.Signal(ch) go func() { for { signal, ok := <-ch if !ok { return } 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)) 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) } } } }() } // Returns two unbuffered channels which will receive all changed units every // interval. Deleted units are sent as nil. func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) { return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil) } // 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) { old := make(map[string]*UnitStatus) statusChan := make(chan map[string]*UnitStatus, buffer) errChan := make(chan error, buffer) go func() { for { timerChan := time.After(interval) units, err := c.ListUnits() if err == nil { cur := make(map[string]*UnitStatus) for i := range units { if filterUnit != nil && filterUnit(units[i].Name) { continue } cur[units[i].Name] = &units[i] } // add all new or changed units changed := make(map[string]*UnitStatus) for n, u := range cur { if oldU, ok := old[n]; !ok || isChanged(oldU, u) { changed[n] = u } delete(old, n) } // add all deleted units for oldN := range old { changed[oldN] = nil } old = cur if len(changed) != 0 { statusChan <- changed } } else { errChan <- err } <-timerChan } }() return statusChan, errChan } type SubStateUpdate struct { UnitName string SubState string } // SetSubStateSubscriber writes to updateCh when any unit's substate changes. // Although this writes to updateCh on every state change, the reported state // may be more recent than the change that generated it (due to an unavoidable // race in the systemd dbus interface). That is, this method provides a good // way to keep a current view of all units' states, but is not guaranteed to // show every state transition they go through. Furthermore, state changes // will only be written to the channel with non-blocking writes. If updateCh // is full, it attempts to write an error to errCh; if errCh is full, the error // passes silently. func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) { c.subscriber.Lock() defer c.subscriber.Unlock() c.subscriber.updateCh = updateCh c.subscriber.errCh = errCh } 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 } info, err := c.GetUnitProperties(string(path)) if err != nil { select { case c.subscriber.errCh <- err: default: } } name := info["Id"].(string) substate := info["SubState"].(string) update := &SubStateUpdate{name, substate} select { case c.subscriber.updateCh <- update: default: select { case c.subscriber.errCh <- errors.New("update channel full!"): default: } } c.updateIgnore(path, info) } // The ignore functions work around a wart in the systemd dbus interface. // Requesting the properties of an unloaded unit will cause systemd to send a // pair of UnitNew/UnitRemoved signals. Because we need to get a unit's // properties on UnitNew (as that's the only indication of a new unit coming up // for the first time), we would enter an infinite loop if we did not attempt // to detect and ignore these spurious signals. The signal themselves are // indistinguishable from relevant ones, so we (somewhat hackishly) ignore an // unloaded unit's signals for a short time after requesting its properties. // This means that we will miss e.g. a transient unit being restarted // *immediately* upon failure and also a transient unit being started // immediately after requesting its status (with systemctl status, for example, // because this causes a UnitNew signal to be sent which then causes us to fetch // the properties). func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool { t, ok := c.subscriber.ignore[path] return ok && t >= time.Now().UnixNano() } func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) { c.cleanIgnore() // unit is unloaded - it will trigger bad systemd dbus behavior if info["LoadState"].(string) == "not-found" { c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval } } // without this, ignore would grow unboundedly over time func (c *Conn) cleanIgnore() { now := time.Now().UnixNano() if c.subscriber.cleanIgnore < now { c.subscriber.cleanIgnore = now + cleanIgnoreInterval for p, t := range c.subscriber.ignore { if t < now { delete(c.subscriber.ignore, p) } } } } go-systemd-2/dbus/subscription_set.go000066400000000000000000000016351233124260300201740ustar00rootroot00000000000000package dbus import ( "time" ) // SubscriptionSet returns a subscription set which is like conn.Subscribe but // can filter to only return events for a set of units. type SubscriptionSet struct { *set conn *Conn } func (s *SubscriptionSet) filter(unit string) bool { return !s.Contains(unit) } // Subscribe starts listening for dbus events for all of the units in the set. // Returns channels identical to conn.SubscribeUnits. 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 }, func(unit string) bool { return s.filter(unit) }, ) } // NewSubscriptionSet returns a new subscription set. func (conn *Conn) NewSubscriptionSet() (*SubscriptionSet) { return &SubscriptionSet{newSet(), conn} } go-systemd-2/dbus/subscription_set_test.go000066400000000000000000000020371233124260300212300ustar00rootroot00000000000000package dbus import ( "testing" "time" ) // TestSubscribeUnit exercises the basics of subscription of a particular unit. func TestSubscriptionSetUnit(t *testing.T) { target := "subscribe-events-set.service" conn, err := New() if err != nil { t.Fatal(err) } err = conn.Subscribe() if err != nil { t.Fatal(err) } subSet := conn.NewSubscriptionSet() evChan, errChan := subSet.Subscribe() subSet.Add(target) setupUnit(target, conn, t) linkUnit(target, conn, t) job, err := conn.StartUnit(target, "replace") if err != nil { t.Fatal(err) } if job != "done" { t.Fatal("Couldn't start", target) } timeout := make(chan bool, 1) go func() { time.Sleep(3 * time.Second) close(timeout) }() for { select { case changes := <-evChan: tCh, ok := changes[target] if !ok { t.Fatal("Unexpected event:", changes) } if tCh.ActiveState == "active" && tCh.Name == target { goto success } case err = <-errChan: t.Fatal(err) case <-timeout: t.Fatal("Reached timeout") } } success: return } go-systemd-2/dbus/subscription_test.go000066400000000000000000000025071233124260300203570ustar00rootroot00000000000000package dbus import ( "testing" "time" ) // TestSubscribe exercises the basics of subscription func TestSubscribe(t *testing.T) { conn, err := New() if err != nil { t.Fatal(err) } err = conn.Subscribe() if err != nil { t.Fatal(err) } err = conn.Unsubscribe() if err != nil { t.Fatal(err) } } // TestSubscribeUnit exercises the basics of subscription of a particular unit. func TestSubscribeUnit(t *testing.T) { target := "subscribe-events.service" conn, err := New() if err != nil { t.Fatal(err) } err = conn.Subscribe() if err != nil { t.Fatal(err) } err = conn.Unsubscribe() if err != nil { t.Fatal(err) } evChan, errChan := conn.SubscribeUnits(time.Second) setupUnit(target, conn, t) linkUnit(target, conn, t) job, err := conn.StartUnit(target, "replace") if err != nil { t.Fatal(err) } if job != "done" { t.Fatal("Couldn't start", target) } timeout := make(chan bool, 1) go func() { time.Sleep(3 * time.Second) close(timeout) }() for { select { case changes := <-evChan: tCh, ok := changes[target] // Just continue until we see our event. if !ok { continue } if tCh.ActiveState == "active" && tCh.Name == target { goto success } case err = <-errChan: t.Fatal(err) case <-timeout: t.Fatal("Reached timeout") } } success: return } go-systemd-2/examples/000077500000000000000000000000001233124260300151225ustar00rootroot00000000000000go-systemd-2/examples/activation/000077500000000000000000000000001233124260300172635ustar00rootroot00000000000000go-systemd-2/examples/activation/activation.go000066400000000000000000000016361233124260300217610ustar00rootroot00000000000000// Activation example used by the activation unit tests. package main import ( "fmt" "os" "github.com/coreos/go-systemd/activation" ) func fixListenPid() { if os.Getenv("FIX_LISTEN_PID") != "" { // HACK: real systemd would set LISTEN_PID before exec'ing but // this is too difficult in golang for the purpose of a test. // Do not do this in real code. os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) } } func main() { fixListenPid() files := activation.Files(false) if len(files) == 0 { panic("No files") } if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { panic("Should not unset envs") } files = activation.Files(true) if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { panic("Can not unset envs") } // Write out the expected strings to the two pipes files[0].Write([]byte("Hello world")) files[1].Write([]byte("Goodbye world")) return } go-systemd-2/examples/activation/httpserver/000077500000000000000000000000001233124260300214715ustar00rootroot00000000000000go-systemd-2/examples/activation/httpserver/README.md000066400000000000000000000010141233124260300227440ustar00rootroot00000000000000## socket activated http server This is a simple example of using socket activation with systemd to serve a simple HTTP server on http://127.0.0.1:8076 To try it out `go get` the httpserver and run it under the systemd-activate helper ``` export GOPATH=`pwd` go get github.com/coreos/go-systemd/examples/activation/httpserver sudo /usr/lib/systemd/systemd-activate -l 127.0.0.1:8076 ./bin/httpserver ``` Then curl the URL and you will notice that it starts up: ``` curl 127.0.0.1:8076 hello socket activated world! ``` go-systemd-2/examples/activation/httpserver/hello.service000066400000000000000000000002651233124260300241610ustar00rootroot00000000000000[Unit] Description=Hello World HTTP Requires=network.target After=multi-user.target [Service] Type=simple ExecStart=/usr/local/bin/httpserver [Install] WantedBy=multi-user.target go-systemd-2/examples/activation/httpserver/hello.socket000066400000000000000000000001101233124260300237760ustar00rootroot00000000000000[Socket] ListenStream=127.0.0.1:8076 [Install] WantedBy=sockets.target go-systemd-2/examples/activation/httpserver/httpserver.go000066400000000000000000000007041233124260300242270ustar00rootroot00000000000000package main import ( "io" "net/http" "github.com/coreos/go-systemd/activation" ) func HelloServer(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "hello socket activated world!\n") } func main() { listeners, err := activation.Listeners(true) if err != nil { panic(err) } if len(listeners) != 1 { panic("Unexpected number of socket activation fds") } http.HandleFunc("/", HelloServer) http.Serve(listeners[0], nil) } go-systemd-2/examples/activation/listen.go000066400000000000000000000020251233124260300211070ustar00rootroot00000000000000// Activation example used by the activation unit tests. package main import ( "fmt" "os" "github.com/coreos/go-systemd/activation" ) func fixListenPid() { if os.Getenv("FIX_LISTEN_PID") != "" { // HACK: real systemd would set LISTEN_PID before exec'ing but // this is too difficult in golang for the purpose of a test. // Do not do this in real code. os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) } } func main() { fixListenPid() listeners, _ := activation.Listeners(false) if len(listeners) == 0 { panic("No listeners") } if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { panic("Should not unset envs") } listeners, err := activation.Listeners(true) if err != nil { panic(err) } if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { panic("Can not unset envs") } c0, _ := listeners[0].Accept() c1, _ := listeners[1].Accept() // Write out the expected strings to the two pipes c0.Write([]byte("Hello world")) c1.Write([]byte("Goodbye world")) return } go-systemd-2/fixtures/000077500000000000000000000000001233124260300151555ustar00rootroot00000000000000go-systemd-2/fixtures/enable-disable.service000066400000000000000000000001131233124260300213610ustar00rootroot00000000000000[Unit] Description=enable disable test [Service] ExecStart=/bin/sleep 400 go-systemd-2/fixtures/start-stop.service000066400000000000000000000001071233124260300206550ustar00rootroot00000000000000[Unit] Description=start stop test [Service] ExecStart=/bin/sleep 400 go-systemd-2/fixtures/subscribe-events-set.service000066400000000000000000000001071233124260300226110ustar00rootroot00000000000000[Unit] Description=start stop test [Service] ExecStart=/bin/sleep 400 go-systemd-2/fixtures/subscribe-events.service000066400000000000000000000001071233124260300220200ustar00rootroot00000000000000[Unit] Description=start stop test [Service] ExecStart=/bin/sleep 400 go-systemd-2/journal/000077500000000000000000000000001233124260300147565ustar00rootroot00000000000000go-systemd-2/journal/send.go000066400000000000000000000101101233124260300162270ustar00rootroot00000000000000/* 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. */ // Package journal provides write bindings to the systemd journal package journal import ( "bytes" "encoding/binary" "errors" "fmt" "io" "io/ioutil" "net" "os" "strconv" "strings" "syscall" ) // Priority of a journal message type Priority int const ( PriEmerg Priority = iota PriAlert PriCrit PriErr PriWarning PriNotice PriInfo PriDebug ) var conn net.Conn func init() { var err error conn, err = net.Dial("unixgram", "/run/systemd/journal/socket") if err != nil { conn = nil } } // Enabled returns true iff the systemd journal is available for logging func Enabled() bool { return conn != nil } // Send a message to the systemd journal. vars is a map of journald fields to // values. Fields must be composed of uppercase letters, numbers, and // underscores, but must not start with an underscore. Within these // restrictions, any arbitrary field name may be used. Some names have special // significance: see the journalctl documentation // (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html) // for more details. vars may be nil. func Send(message string, priority Priority, vars map[string]string) error { if conn == nil { return journalError("could not connect to journald socket") } data := new(bytes.Buffer) appendVariable(data, "PRIORITY", strconv.Itoa(int(priority))) appendVariable(data, "MESSAGE", message) for k, v := range vars { appendVariable(data, k, v) } _, err := io.Copy(conn, data) if err != nil && isSocketSpaceError(err) { file, err := tempFd() if err != nil { return journalError(err.Error()) } _, err = io.Copy(file, data) if err != nil { return journalError(err.Error()) } rights := syscall.UnixRights(int(file.Fd())) /* this connection should always be a UnixConn, but better safe than sorry */ unixConn, ok := conn.(*net.UnixConn) if !ok { return journalError("can't send file through non-Unix connection") } unixConn.WriteMsgUnix([]byte{}, rights, nil) } else if err != nil { return journalError(err.Error()) } return nil } func appendVariable(w io.Writer, name, value string) { if !validVarName(name) { journalError("variable name contains invalid character, ignoring") } if strings.ContainsRune(value, '\n') { /* When the value contains a newline, we write: * - the variable name, followed by a newline * - the size (in 64bit little endian format) * - the data, followed by a newline */ fmt.Fprintln(w, name) binary.Write(w, binary.LittleEndian, uint64(len(value))) fmt.Fprintln(w, value) } else { /* just write the variable and value all on one line */ fmt.Fprintf(w, "%s=%s\n", name, value) } } func validVarName(name string) bool { /* The variable name must be in uppercase and consist only of characters, * numbers and underscores, and may not begin with an underscore. (from the docs) */ valid := name[0] != '_' for _, c := range name { valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_' } return valid } func isSocketSpaceError(err error) bool { opErr, ok := err.(*net.OpError) if !ok { return false } sysErr, ok := opErr.Err.(syscall.Errno) if !ok { return false } return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS } func tempFd() (*os.File, error) { file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX") if err != nil { return nil, err } syscall.Unlink(file.Name()) if err != nil { return nil, err } return file, nil } func journalError(s string) error { s = "journal error: " + s fmt.Fprintln(os.Stderr, s) return errors.New(s) } go-systemd-2/login1/000077500000000000000000000000001233124260300144755ustar00rootroot00000000000000go-systemd-2/login1/dbus.go000066400000000000000000000035621233124260300157670ustar00rootroot00000000000000/* Copyright 2014 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 logind API. See http://www.freedesktop.org/wiki/Software/systemd/logind/ package login1 import ( "os" "strconv" "github.com/godbus/dbus" ) const ( dbusInterface = "org.freedesktop.login1.Manager" dbusPath = "/org/freedesktop/login1" ) // Conn is a connection to systemds dbus endpoint. type Conn struct { conn *dbus.Conn object *dbus.Object } // New() establishes a connection to the system bus and authenticates. func New() (*Conn, error) { c := new(Conn) if err := c.initConnection(); err != nil { return nil, err } return c, nil } func (c *Conn) initConnection() error { var err error c.conn, err = dbus.SystemBusPrivate() if err != nil { return err } // Only use EXTERNAL method, and hardcode the uid (not username) // to avoid a username lookup (which requires a dynamically linked // libc) methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} err = c.conn.Auth(methods) if err != nil { c.conn.Close() return err } err = c.conn.Hello() if err != nil { c.conn.Close() return err } c.object = c.conn.Object("org.freedesktop.login1", dbus.ObjectPath(dbusPath)) return nil } // Reboot asks logind for a reboot optionally asking for auth. func (c *Conn) Reboot(askForAuth bool) { c.object.Call(dbusInterface+".Reboot", 0, askForAuth) } go-systemd-2/login1/dbus_test.go000066400000000000000000000013371233124260300170240ustar00rootroot00000000000000/* Copyright 2014 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 login1 import ( "testing" ) // TestNew ensures that New() works without errors. func TestNew(t *testing.T) { _, err := New() if err != nil { t.Fatal(err) } } go-systemd-2/test000077500000000000000000000000371233124260300142110ustar00rootroot00000000000000#!/bin/sh -e go test -v ./...