Browse code

Vendor github.com/godbus/dbus and github.com/coreos/go-systemd

We need this to do systemd API calls.

We also add the static_build tag to make godbus not use
os/user which is problematic for static builds.

Docker-DCO-1.1-Signed-off-by: Alexander Larsson <alexl@redhat.com> (github: alexlarsson)

Alexander Larsson authored on 2014/02/21 18:34:06
Showing 74 changed files
... ...
@@ -89,7 +89,7 @@ LDFLAGS='
89 89
 '
90 90
 LDFLAGS_STATIC='-linkmode external'
91 91
 EXTLDFLAGS_STATIC='-static'
92
-BUILDFLAGS=( -a -tags "netgo $DOCKER_BUILDTAGS" )
92
+BUILDFLAGS=( -a -tags "netgo static_build $DOCKER_BUILDTAGS" )
93 93
 
94 94
 # A few more flags that are specific just to building a completely-static binary (see hack/make/binary)
95 95
 # PLEASE do not use these anywhere else.
... ...
@@ -58,3 +58,6 @@ mv src/code.google.com/p/go/src/pkg/archive/tar tmp-tar
58 58
 rm -rf src/code.google.com/p/go
59 59
 mkdir -p src/code.google.com/p/go/src/pkg/archive
60 60
 mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar
61
+
62
+clone git github.com/godbus/dbus cb98efbb933d8389ab549a060e880ea3c375d213
63
+clone git github.com/coreos/go-systemd 4c14ed39b8a643ac44b4f95b5a53c00e94261475
61 64
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+language: go
1
+go: 1.2
2
+
3
+install:
4
+ - echo "Skip install"
5
+
6
+script:
7
+ - ./test
0 8
new file mode 100644
... ...
@@ -0,0 +1,191 @@
0
+Apache License
1
+Version 2.0, January 2004
2
+http://www.apache.org/licenses/
3
+
4
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
5
+
6
+1. Definitions.
7
+
8
+"License" shall mean the terms and conditions for use, reproduction, and
9
+distribution as defined by Sections 1 through 9 of this document.
10
+
11
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
12
+owner that is granting the License.
13
+
14
+"Legal Entity" shall mean the union of the acting entity and all other entities
15
+that control, are controlled by, or are under common control with that entity.
16
+For the purposes of this definition, "control" means (i) the power, direct or
17
+indirect, to cause the direction or management of such entity, whether by
18
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
19
+outstanding shares, or (iii) beneficial ownership of such entity.
20
+
21
+"You" (or "Your") shall mean an individual or Legal Entity exercising
22
+permissions granted by this License.
23
+
24
+"Source" form shall mean the preferred form for making modifications, including
25
+but not limited to software source code, documentation source, and configuration
26
+files.
27
+
28
+"Object" form shall mean any form resulting from mechanical transformation or
29
+translation of a Source form, including but not limited to compiled object code,
30
+generated documentation, and conversions to other media types.
31
+
32
+"Work" shall mean the work of authorship, whether in Source or Object form, made
33
+available under the License, as indicated by a copyright notice that is included
34
+in or attached to the work (an example is provided in the Appendix below).
35
+
36
+"Derivative Works" shall mean any work, whether in Source or Object form, that
37
+is based on (or derived from) the Work and for which the editorial revisions,
38
+annotations, elaborations, or other modifications represent, as a whole, an
39
+original work of authorship. For the purposes of this License, Derivative Works
40
+shall not include works that remain separable from, or merely link (or bind by
41
+name) to the interfaces of, the Work and Derivative Works thereof.
42
+
43
+"Contribution" shall mean any work of authorship, including the original version
44
+of the Work and any modifications or additions to that Work or Derivative Works
45
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
46
+by the copyright owner or by an individual or Legal Entity authorized to submit
47
+on behalf of the copyright owner. For the purposes of this definition,
48
+"submitted" means any form of electronic, verbal, or written communication sent
49
+to the Licensor or its representatives, including but not limited to
50
+communication on electronic mailing lists, source code control systems, and
51
+issue tracking systems that are managed by, or on behalf of, the Licensor for
52
+the purpose of discussing and improving the Work, but excluding communication
53
+that is conspicuously marked or otherwise designated in writing by the copyright
54
+owner as "Not a Contribution."
55
+
56
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
57
+of whom a Contribution has been received by Licensor and subsequently
58
+incorporated within the Work.
59
+
60
+2. Grant of Copyright License.
61
+
62
+Subject to the terms and conditions of this License, each Contributor hereby
63
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
64
+irrevocable copyright license to reproduce, prepare Derivative Works of,
65
+publicly display, publicly perform, sublicense, and distribute the Work and such
66
+Derivative Works in Source or Object form.
67
+
68
+3. Grant of Patent License.
69
+
70
+Subject to the terms and conditions of this License, each Contributor hereby
71
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
72
+irrevocable (except as stated in this section) patent license to make, have
73
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
74
+such license applies only to those patent claims licensable by such Contributor
75
+that are necessarily infringed by their Contribution(s) alone or by combination
76
+of their Contribution(s) with the Work to which such Contribution(s) was
77
+submitted. If You institute patent litigation against any entity (including a
78
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
79
+Contribution incorporated within the Work constitutes direct or contributory
80
+patent infringement, then any patent licenses granted to You under this License
81
+for that Work shall terminate as of the date such litigation is filed.
82
+
83
+4. Redistribution.
84
+
85
+You may reproduce and distribute copies of the Work or Derivative Works thereof
86
+in any medium, with or without modifications, and in Source or Object form,
87
+provided that You meet the following conditions:
88
+
89
+You must give any other recipients of the Work or Derivative Works a copy of
90
+this License; and
91
+You must cause any modified files to carry prominent notices stating that You
92
+changed the files; and
93
+You must retain, in the Source form of any Derivative Works that You distribute,
94
+all copyright, patent, trademark, and attribution notices from the Source form
95
+of the Work, excluding those notices that do not pertain to any part of the
96
+Derivative Works; and
97
+If the Work includes a "NOTICE" text file as part of its distribution, then any
98
+Derivative Works that You distribute must include a readable copy of the
99
+attribution notices contained within such NOTICE file, excluding those notices
100
+that do not pertain to any part of the Derivative Works, in at least one of the
101
+following places: within a NOTICE text file distributed as part of the
102
+Derivative Works; within the Source form or documentation, if provided along
103
+with the Derivative Works; or, within a display generated by the Derivative
104
+Works, if and wherever such third-party notices normally appear. The contents of
105
+the NOTICE file are for informational purposes only and do not modify the
106
+License. You may add Your own attribution notices within Derivative Works that
107
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
108
+provided that such additional attribution notices cannot be construed as
109
+modifying the License.
110
+You may add Your own copyright statement to Your modifications and may provide
111
+additional or different license terms and conditions for use, reproduction, or
112
+distribution of Your modifications, or for any such Derivative Works as a whole,
113
+provided Your use, reproduction, and distribution of the Work otherwise complies
114
+with the conditions stated in this License.
115
+
116
+5. Submission of Contributions.
117
+
118
+Unless You explicitly state otherwise, any Contribution intentionally submitted
119
+for inclusion in the Work by You to the Licensor shall be under the terms and
120
+conditions of this License, without any additional terms or conditions.
121
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
122
+any separate license agreement you may have executed with Licensor regarding
123
+such Contributions.
124
+
125
+6. Trademarks.
126
+
127
+This License does not grant permission to use the trade names, trademarks,
128
+service marks, or product names of the Licensor, except as required for
129
+reasonable and customary use in describing the origin of the Work and
130
+reproducing the content of the NOTICE file.
131
+
132
+7. Disclaimer of Warranty.
133
+
134
+Unless required by applicable law or agreed to in writing, Licensor provides the
135
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
136
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
137
+including, without limitation, any warranties or conditions of TITLE,
138
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
139
+solely responsible for determining the appropriateness of using or
140
+redistributing the Work and assume any risks associated with Your exercise of
141
+permissions under this License.
142
+
143
+8. Limitation of Liability.
144
+
145
+In no event and under no legal theory, whether in tort (including negligence),
146
+contract, or otherwise, unless required by applicable law (such as deliberate
147
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
148
+liable to You for damages, including any direct, indirect, special, incidental,
149
+or consequential damages of any character arising as a result of this License or
150
+out of the use or inability to use the Work (including but not limited to
151
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
152
+any and all other commercial damages or losses), even if such Contributor has
153
+been advised of the possibility of such damages.
154
+
155
+9. Accepting Warranty or Additional Liability.
156
+
157
+While redistributing the Work or Derivative Works thereof, You may choose to
158
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
159
+other liability obligations and/or rights consistent with this License. However,
160
+in accepting such obligations, You may act only on Your own behalf and on Your
161
+sole responsibility, not on behalf of any other Contributor, and only if You
162
+agree to indemnify, defend, and hold each Contributor harmless for any liability
163
+incurred by, or claims asserted against, such Contributor by reason of your
164
+accepting any such warranty or additional liability.
165
+
166
+END OF TERMS AND CONDITIONS
167
+
168
+APPENDIX: How to apply the Apache License to your work
169
+
170
+To apply the Apache License to your work, attach the following boilerplate
171
+notice, with the fields enclosed by brackets "[]" replaced with your own
172
+identifying information. (Don't include the brackets!) The text should be
173
+enclosed in the appropriate comment syntax for the file format. We also
174
+recommend that a file or class name and description of purpose be included on
175
+the same "printed page" as the copyright notice for easier identification within
176
+third-party archives.
177
+
178
+   Copyright [yyyy] [name of copyright owner]
179
+
180
+   Licensed under the Apache License, Version 2.0 (the "License");
181
+   you may not use this file except in compliance with the License.
182
+   You may obtain a copy of the License at
183
+
184
+     http://www.apache.org/licenses/LICENSE-2.0
185
+
186
+   Unless required by applicable law or agreed to in writing, software
187
+   distributed under the License is distributed on an "AS IS" BASIS,
188
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
189
+   See the License for the specific language governing permissions and
190
+   limitations under the License.
0 191
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+# go-systemd
1
+
2
+Go bindings to systemd. The project has three packages:
3
+
4
+- activation - for writing and using socket activation from Go
5
+- journal - for writing to systemd's logging service, journal
6
+- dbus - for starting/stopping/inspecting running services and units
7
+
8
+Go docs for the entire project are here:
9
+
10
+http://godoc.org/github.com/coreos/go-systemd
11
+
12
+## Socket Activation
13
+
14
+An example HTTP server using socket activation can be quickly setup by
15
+following this README on a Linux machine running systemd:
16
+
17
+https://github.com/coreos/go-systemd/tree/master/examples/activation/httpserver
18
+
19
+## Journal
20
+
21
+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.
22
+
23
+## D-Bus
24
+
25
+The D-Bus API lets you start, stop and introspect systemd units. The API docs are here:
26
+
27
+http://godoc.org/github.com/coreos/go-systemd/dbus
28
+
29
+### Debugging
30
+
31
+Create `/etc/dbus-1/system-local.conf` that looks like this:
32
+
33
+```
34
+<!DOCTYPE busconfig PUBLIC
35
+"-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
36
+"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
37
+<busconfig>
38
+    <policy user="root">
39
+        <allow eavesdrop="true"/>
40
+        <allow eavesdrop="true" send_destination="*"/>
41
+    </policy>
42
+</busconfig>
43
+```
0 44
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+/*
1
+Copyright 2013 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+// Package activation implements primitives for systemd socket activation.
17
+package activation
18
+
19
+import (
20
+	"os"
21
+	"strconv"
22
+	"syscall"
23
+)
24
+
25
+// based on: https://gist.github.com/alberts/4640792
26
+const (
27
+	listenFdsStart = 3
28
+)
29
+
30
+func Files(unsetEnv bool) []*os.File {
31
+	if unsetEnv {
32
+		// there is no way to unset env in golang os package for now
33
+		// https://code.google.com/p/go/issues/detail?id=6423
34
+		defer os.Setenv("LISTEN_PID", "")
35
+		defer os.Setenv("LISTEN_FDS", "")
36
+	}
37
+
38
+	pid, err := strconv.Atoi(os.Getenv("LISTEN_PID"))
39
+	if err != nil || pid != os.Getpid() {
40
+		return nil
41
+	}
42
+
43
+	nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS"))
44
+	if err != nil || nfds == 0 {
45
+		return nil
46
+	}
47
+
48
+	var files []*os.File
49
+	for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ {
50
+		syscall.CloseOnExec(fd)
51
+		files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd)))
52
+	}
53
+
54
+	return files
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,84 @@
0
+/*
1
+Copyright 2013 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package activation
17
+
18
+import (
19
+	"bytes"
20
+	"io"
21
+	"os"
22
+	"os/exec"
23
+	"testing"
24
+)
25
+
26
+// correctStringWritten fails the text if the correct string wasn't written
27
+// to the other side of the pipe.
28
+func correctStringWritten(t *testing.T, r *os.File, expected string) bool {
29
+	bytes := make([]byte, len(expected))
30
+	io.ReadAtLeast(r, bytes, len(expected))
31
+
32
+	if string(bytes) != expected {
33
+		t.Fatalf("Unexpected string %s", string(bytes))
34
+	}
35
+
36
+	return true
37
+}
38
+
39
+// TestActivation forks out a copy of activation.go example and reads back two
40
+// strings from the pipes that are passed in.
41
+func TestActivation(t *testing.T) {
42
+	cmd := exec.Command("go", "run", "../examples/activation/activation.go")
43
+
44
+	r1, w1, _ := os.Pipe()
45
+	r2, w2, _ := os.Pipe()
46
+	cmd.ExtraFiles = []*os.File{
47
+		w1,
48
+		w2,
49
+	}
50
+
51
+	cmd.Env = os.Environ()
52
+	cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1")
53
+
54
+	err := cmd.Run()
55
+	if err != nil {
56
+		t.Fatalf(err.Error())
57
+	}
58
+
59
+	correctStringWritten(t, r1, "Hello world")
60
+	correctStringWritten(t, r2, "Goodbye world")
61
+}
62
+
63
+func TestActivationNoFix(t *testing.T) {
64
+	cmd := exec.Command("go", "run", "../examples/activation/activation.go")
65
+	cmd.Env = os.Environ()
66
+	cmd.Env = append(cmd.Env, "LISTEN_FDS=2")
67
+
68
+	out, _ := cmd.CombinedOutput()
69
+	if bytes.Contains(out, []byte("No files")) == false {
70
+		t.Fatalf("Child didn't error out as expected")
71
+	}
72
+}
73
+
74
+func TestActivationNoFiles(t *testing.T) {
75
+	cmd := exec.Command("go", "run", "../examples/activation/activation.go")
76
+	cmd.Env = os.Environ()
77
+	cmd.Env = append(cmd.Env, "LISTEN_FDS=0", "FIX_LISTEN_PID=1")
78
+
79
+	out, _ := cmd.CombinedOutput()
80
+	if bytes.Contains(out, []byte("No files")) == false {
81
+		t.Fatalf("Child didn't error out as expected")
82
+	}
83
+}
0 84
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+/*
1
+Copyright 2014 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package activation
17
+
18
+import (
19
+	"fmt"
20
+	"net"
21
+)
22
+
23
+// Listeners returns net.Listeners for all socket activated fds passed to this process.
24
+func Listeners(unsetEnv bool) ([]net.Listener, error) {
25
+	files := Files(unsetEnv)
26
+	listeners := make([]net.Listener, len(files))
27
+
28
+	for i, f := range files {
29
+		var err error
30
+		listeners[i], err = net.FileListener(f)
31
+		if err != nil {
32
+			return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error())
33
+		}
34
+	}
35
+
36
+	return listeners, nil
37
+}
0 38
new file mode 100644
... ...
@@ -0,0 +1,88 @@
0
+/*
1
+Copyright 2014 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package activation
17
+
18
+import (
19
+	"io"
20
+	"net"
21
+	"os"
22
+	"os/exec"
23
+	"testing"
24
+)
25
+
26
+// correctStringWritten fails the text if the correct string wasn't written
27
+// to the other side of the pipe.
28
+func correctStringWrittenNet(t *testing.T, r net.Conn, expected string) bool {
29
+	bytes := make([]byte, len(expected))
30
+	io.ReadAtLeast(r, bytes, len(expected))
31
+
32
+	if string(bytes) != expected {
33
+		t.Fatalf("Unexpected string %s", string(bytes))
34
+	}
35
+
36
+	return true
37
+}
38
+
39
+// TestActivation forks out a copy of activation.go example and reads back two
40
+// strings from the pipes that are passed in.
41
+func TestListeners(t *testing.T) {
42
+	cmd := exec.Command("go", "run", "../examples/activation/listen.go")
43
+
44
+	l1, err := net.Listen("tcp", ":9999")
45
+	if err != nil {
46
+		t.Fatalf(err.Error())
47
+	}
48
+	l2, err := net.Listen("tcp", ":1234")
49
+	if err != nil {
50
+		t.Fatalf(err.Error())
51
+	}
52
+
53
+	t1 := l1.(*net.TCPListener)
54
+	t2 := l2.(*net.TCPListener)
55
+
56
+	f1, _ := t1.File()
57
+	f2, _  := t2.File()
58
+
59
+	cmd.ExtraFiles = []*os.File{
60
+		f1,
61
+		f2,
62
+	}
63
+
64
+	r1, err := net.Dial("tcp", "127.0.0.1:9999")
65
+	if err != nil {
66
+		t.Fatalf(err.Error())
67
+	}
68
+	r1.Write([]byte("Hi"))
69
+
70
+	r2, err := net.Dial("tcp", "127.0.0.1:1234")
71
+	if err != nil {
72
+		t.Fatalf(err.Error())
73
+	}
74
+	r2.Write([]byte("Hi"))
75
+
76
+	cmd.Env = os.Environ()
77
+	cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1")
78
+
79
+	out, err := cmd.Output()
80
+	if err != nil {
81
+		println(string(out))
82
+		t.Fatalf(err.Error())
83
+	}
84
+
85
+	correctStringWrittenNet(t, r1, "Hello world")
86
+	correctStringWrittenNet(t, r2, "Goodbye world")
87
+}
0 88
new file mode 100644
... ...
@@ -0,0 +1,104 @@
0
+/*
1
+Copyright 2013 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+// Integration with the systemd D-Bus API.  See http://www.freedesktop.org/wiki/Software/systemd/dbus/
17
+package dbus
18
+
19
+import (
20
+	"os"
21
+	"strconv"
22
+	"strings"
23
+	"sync"
24
+
25
+	"github.com/godbus/dbus"
26
+)
27
+
28
+const signalBuffer = 100
29
+
30
+// ObjectPath creates a dbus.ObjectPath using the rules that systemd uses for
31
+// serializing special characters.
32
+func ObjectPath(path string) dbus.ObjectPath {
33
+	path = strings.Replace(path, ".", "_2e", -1)
34
+	path = strings.Replace(path, "-", "_2d", -1)
35
+	path = strings.Replace(path, "@", "_40", -1)
36
+
37
+	return dbus.ObjectPath(path)
38
+}
39
+
40
+// Conn is a connection to systemds dbus endpoint.
41
+type Conn struct {
42
+	sysconn     *dbus.Conn
43
+	sysobj      *dbus.Object
44
+	jobListener struct {
45
+		jobs map[dbus.ObjectPath]chan string
46
+		sync.Mutex
47
+	}
48
+	subscriber struct {
49
+		updateCh chan<- *SubStateUpdate
50
+		errCh    chan<- error
51
+		sync.Mutex
52
+		ignore      map[dbus.ObjectPath]int64
53
+		cleanIgnore int64
54
+	}
55
+	dispatch map[string]func(dbus.Signal)
56
+}
57
+
58
+// New() establishes a connection to the system bus and authenticates.
59
+func New() (*Conn, error) {
60
+	c := new(Conn)
61
+
62
+	if err := c.initConnection(); err != nil {
63
+		return nil, err
64
+	}
65
+
66
+	c.initJobs()
67
+	return c, nil
68
+}
69
+
70
+func (c *Conn) initConnection() error {
71
+	var err error
72
+	c.sysconn, err = dbus.SystemBusPrivate()
73
+	if err != nil {
74
+		return err
75
+	}
76
+
77
+	// Only use EXTERNAL method, and hardcode the uid (not username)
78
+	// to avoid a username lookup (which requires a dynamically linked
79
+	// libc)
80
+	methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))}
81
+
82
+	err = c.sysconn.Auth(methods)
83
+	if err != nil {
84
+		c.sysconn.Close()
85
+		return err
86
+	}
87
+
88
+	err = c.sysconn.Hello()
89
+	if err != nil {
90
+		c.sysconn.Close()
91
+		return err
92
+	}
93
+
94
+	c.sysobj = c.sysconn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
95
+
96
+	// Setup the listeners on jobs so that we can get completions
97
+	c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
98
+		"type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
99
+	c.initSubscription()
100
+	c.initDispatch()
101
+
102
+	return nil
103
+}
0 104
new file mode 100644
... ...
@@ -0,0 +1,41 @@
0
+/*
1
+Copyright 2013 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package dbus
17
+
18
+import (
19
+	"testing"
20
+)
21
+
22
+// TestObjectPath ensures path encoding of the systemd rules works.
23
+func TestObjectPath(t *testing.T) {
24
+	input := "/silly-path/to@a/unit..service"
25
+	output := ObjectPath(input)
26
+	expected := "/silly_2dpath/to_40a/unit_2e_2eservice"
27
+
28
+	if string(output) != expected {
29
+		t.Fatalf("Output '%s' did not match expected '%s'", output, expected)
30
+	}
31
+}
32
+
33
+// TestNew ensures that New() works without errors.
34
+func TestNew(t *testing.T) {
35
+	_, err := New()
36
+
37
+	if err != nil {
38
+		t.Fatal(err)
39
+	}
40
+}
0 41
new file mode 100644
... ...
@@ -0,0 +1,354 @@
0
+/*
1
+Copyright 2013 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package dbus
17
+
18
+import (
19
+	"errors"
20
+	"github.com/godbus/dbus"
21
+)
22
+
23
+func (c *Conn) initJobs() {
24
+	c.jobListener.jobs = make(map[dbus.ObjectPath]chan string)
25
+}
26
+
27
+func (c *Conn) jobComplete(signal *dbus.Signal) {
28
+	var id uint32
29
+	var job dbus.ObjectPath
30
+	var unit string
31
+	var result string
32
+	dbus.Store(signal.Body, &id, &job, &unit, &result)
33
+	c.jobListener.Lock()
34
+	out, ok := c.jobListener.jobs[job]
35
+	if ok {
36
+		out <- result
37
+		delete(c.jobListener.jobs, job)
38
+	}
39
+	c.jobListener.Unlock()
40
+}
41
+
42
+func (c *Conn) startJob(job string, args ...interface{}) (<-chan string, error) {
43
+	c.jobListener.Lock()
44
+	defer c.jobListener.Unlock()
45
+
46
+	ch := make(chan string, 1)
47
+	var path dbus.ObjectPath
48
+	err := c.sysobj.Call(job, 0, args...).Store(&path)
49
+	if err != nil {
50
+		return nil, err
51
+	}
52
+	c.jobListener.jobs[path] = ch
53
+	return ch, nil
54
+}
55
+
56
+func (c *Conn) runJob(job string, args ...interface{}) (string, error) {
57
+	respCh, err := c.startJob(job, args...)
58
+	if err != nil {
59
+		return "", err
60
+	}
61
+	return <-respCh, nil
62
+}
63
+
64
+// StartUnit enqeues a start job and depending jobs, if any (unless otherwise
65
+// specified by the mode string).
66
+//
67
+// Takes the unit to activate, plus a mode string. The mode needs to be one of
68
+// replace, fail, isolate, ignore-dependencies, ignore-requirements. If
69
+// "replace" the call will start the unit and its dependencies, possibly
70
+// replacing already queued jobs that conflict with this. If "fail" the call
71
+// will start the unit and its dependencies, but will fail if this would change
72
+// an already queued job. If "isolate" the call will start the unit in question
73
+// and terminate all units that aren't dependencies of it. If
74
+// "ignore-dependencies" it will start a unit but ignore all its dependencies.
75
+// If "ignore-requirements" it will start a unit but only ignore the
76
+// requirement dependencies. It is not recommended to make use of the latter
77
+// two options.
78
+//
79
+// Result string: one of done, canceled, timeout, failed, dependency, skipped.
80
+// done indicates successful execution of a job. canceled indicates that a job
81
+// has been canceled  before it finished execution. timeout indicates that the
82
+// job timeout was reached. failed indicates that the job failed. dependency
83
+// indicates that a job this job has been depending on failed and the job hence
84
+// has been removed too. skipped indicates that a job was skipped because it
85
+// didn't apply to the units current state.
86
+func (c *Conn) StartUnit(name string, mode string) (string, error) {
87
+	return c.runJob("org.freedesktop.systemd1.Manager.StartUnit", name, mode)
88
+}
89
+
90
+// StopUnit is similar to StartUnit but stops the specified unit rather
91
+// than starting it.
92
+func (c *Conn) StopUnit(name string, mode string) (string, error) {
93
+	return c.runJob("org.freedesktop.systemd1.Manager.StopUnit", name, mode)
94
+}
95
+
96
+// ReloadUnit reloads a unit.  Reloading is done only if the unit is already running and fails otherwise.
97
+func (c *Conn) ReloadUnit(name string, mode string) (string, error) {
98
+	return c.runJob("org.freedesktop.systemd1.Manager.ReloadUnit", name, mode)
99
+}
100
+
101
+// RestartUnit restarts a service.  If a service is restarted that isn't
102
+// running it will be started.
103
+func (c *Conn) RestartUnit(name string, mode string) (string, error) {
104
+	return c.runJob("org.freedesktop.systemd1.Manager.RestartUnit", name, mode)
105
+}
106
+
107
+// TryRestartUnit is like RestartUnit, except that a service that isn't running
108
+// is not affected by the restart.
109
+func (c *Conn) TryRestartUnit(name string, mode string) (string, error) {
110
+	return c.runJob("org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode)
111
+}
112
+
113
+// ReloadOrRestart attempts a reload if the unit supports it and use a restart
114
+// otherwise.
115
+func (c *Conn) ReloadOrRestartUnit(name string, mode string) (string, error) {
116
+	return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode)
117
+}
118
+
119
+// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try"
120
+// flavored restart otherwise.
121
+func (c *Conn) ReloadOrTryRestartUnit(name string, mode string) (string, error) {
122
+	return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode)
123
+}
124
+
125
+// StartTransientUnit() may be used to create and start a transient unit, which
126
+// will be released as soon as it is not running or referenced anymore or the
127
+// system is rebooted. name is the unit name including suffix, and must be
128
+// unique. mode is the same as in StartUnit(), properties contains properties
129
+// of the unit.
130
+func (c *Conn) StartTransientUnit(name string, mode string, properties ...Property) (string, error) {
131
+	return c.runJob("org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0))
132
+}
133
+
134
+// KillUnit takes the unit name and a UNIX signal number to send.  All of the unit's
135
+// processes are killed.
136
+func (c *Conn) KillUnit(name string, signal int32) {
137
+	c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
138
+}
139
+
140
+// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
141
+func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
142
+	var err error
143
+	var props map[string]dbus.Variant
144
+
145
+	path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit)
146
+	if !path.IsValid() {
147
+		return nil, errors.New("invalid unit name: " + unit)
148
+	}
149
+
150
+	obj := c.sysconn.Object("org.freedesktop.systemd1", path)
151
+	err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props)
152
+	if err != nil {
153
+		return nil, err
154
+	}
155
+
156
+	out := make(map[string]interface{}, len(props))
157
+	for k, v := range props {
158
+		out[k] = v.Value()
159
+	}
160
+
161
+	return out, nil
162
+}
163
+
164
+// GetUnitProperties takes the unit name and returns all of its dbus object properties.
165
+func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) {
166
+	return c.getProperties(unit, "org.freedesktop.systemd1.Unit")
167
+}
168
+
169
+func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) {
170
+	var err error
171
+	var prop dbus.Variant
172
+
173
+	path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit)
174
+	if !path.IsValid() {
175
+		return nil, errors.New("invalid unit name: " + unit)
176
+	}
177
+
178
+	obj := c.sysconn.Object("org.freedesktop.systemd1", path)
179
+	err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop)
180
+	if err != nil {
181
+		return nil, err
182
+	}
183
+
184
+	return &Property{Name: propertyName, Value: prop}, nil
185
+}
186
+
187
+func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) {
188
+	return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName)
189
+}
190
+
191
+// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type.
192
+// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope
193
+// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit
194
+func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
195
+	return c.getProperties(unit, "org.freedesktop.systemd1."+unitType)
196
+}
197
+
198
+// SetUnitProperties() may be used to modify certain unit properties at runtime.
199
+// Not all properties may be changed at runtime, but many resource management
200
+// settings (primarily those in systemd.cgroup(5)) may. The changes are applied
201
+// instantly, and stored on disk for future boots, unless runtime is true, in which
202
+// case the settings only apply until the next reboot. name is the name of the unit
203
+// to modify. properties are the settings to set, encoded as an array of property
204
+// name and value pairs.
205
+func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error {
206
+	return c.sysobj.Call("SetUnitProperties", 0, name, runtime, properties).Store()
207
+}
208
+
209
+func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
210
+	return c.getProperty(unit, "org.freedesktop.systemd1." + unitType, propertyName)
211
+}
212
+
213
+// ListUnits returns an array with all currently loaded units. Note that
214
+// units may be known by multiple names at the same time, and hence there might
215
+// be more unit names loaded than actual units behind them.
216
+func (c *Conn) ListUnits() ([]UnitStatus, error) {
217
+	result := make([][]interface{}, 0)
218
+	err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store(&result)
219
+	if err != nil {
220
+		return nil, err
221
+	}
222
+
223
+	resultInterface := make([]interface{}, len(result))
224
+	for i := range result {
225
+		resultInterface[i] = result[i]
226
+	}
227
+
228
+	status := make([]UnitStatus, len(result))
229
+	statusInterface := make([]interface{}, len(status))
230
+	for i := range status {
231
+		statusInterface[i] = &status[i]
232
+	}
233
+
234
+	err = dbus.Store(resultInterface, statusInterface...)
235
+	if err != nil {
236
+		return nil, err
237
+	}
238
+
239
+	return status, nil
240
+}
241
+
242
+type UnitStatus struct {
243
+	Name        string          // The primary unit name as string
244
+	Description string          // The human readable description string
245
+	LoadState   string          // The load state (i.e. whether the unit file has been loaded successfully)
246
+	ActiveState string          // The active state (i.e. whether the unit is currently started or not)
247
+	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)
248
+	Followed    string          // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string.
249
+	Path        dbus.ObjectPath // The unit object path
250
+	JobId       uint32          // If there is a job queued for the job unit the numeric job id, 0 otherwise
251
+	JobType     string          // The job type as string
252
+	JobPath     dbus.ObjectPath // The job object path
253
+}
254
+
255
+// EnableUnitFiles() may be used to enable one or more units in the system (by
256
+// creating symlinks to them in /etc or /run).
257
+//
258
+// It takes a list of unit files to enable (either just file names or full
259
+// absolute paths if the unit files are residing outside the usual unit
260
+// search paths), and two booleans: the first controls whether the unit shall
261
+// be enabled for runtime only (true, /run), or persistently (false, /etc).
262
+// The second one controls whether symlinks pointing to other units shall
263
+// be replaced if necessary.
264
+//
265
+// This call returns one boolean and an array with the changes made. The
266
+// boolean signals whether the unit files contained any enablement
267
+// information (i.e. an [Install]) section. The changes list consists of
268
+// structures with three strings: the type of the change (one of symlink
269
+// or unlink), the file name of the symlink and the destination of the
270
+// symlink.
271
+func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) {
272
+	var carries_install_info bool
273
+
274
+	result := make([][]interface{}, 0)
275
+	err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result)
276
+	if err != nil {
277
+		return false, nil, err
278
+	}
279
+
280
+	resultInterface := make([]interface{}, len(result))
281
+	for i := range result {
282
+		resultInterface[i] = result[i]
283
+	}
284
+
285
+	changes := make([]EnableUnitFileChange, len(result))
286
+	changesInterface := make([]interface{}, len(changes))
287
+	for i := range changes {
288
+		changesInterface[i] = &changes[i]
289
+	}
290
+
291
+	err = dbus.Store(resultInterface, changesInterface...)
292
+	if err != nil {
293
+		return false, nil, err
294
+	}
295
+
296
+	return carries_install_info, changes, nil
297
+}
298
+
299
+type EnableUnitFileChange struct {
300
+	Type        string // Type of the change (one of symlink or unlink)
301
+	Filename    string // File name of the symlink
302
+	Destination string // Destination of the symlink
303
+}
304
+
305
+// DisableUnitFiles() may be used to disable one or more units in the system (by
306
+// removing symlinks to them from /etc or /run).
307
+//
308
+// It takes a list of unit files to disable (either just file names or full
309
+// absolute paths if the unit files are residing outside the usual unit
310
+// search paths), and one boolean: whether the unit was enabled for runtime
311
+// only (true, /run), or persistently (false, /etc).
312
+//
313
+// This call returns an array with the changes made. The changes list
314
+// consists of structures with three strings: the type of the change (one of
315
+// symlink or unlink), the file name of the symlink and the destination of the
316
+// symlink.
317
+func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) {
318
+	result := make([][]interface{}, 0)
319
+	err := c.sysobj.Call("DisableUnitFiles", 0, files, runtime).Store(&result)
320
+	if err != nil {
321
+		return nil, err
322
+	}
323
+
324
+	resultInterface := make([]interface{}, len(result))
325
+	for i := range result {
326
+		resultInterface[i] = result[i]
327
+	}
328
+
329
+	changes := make([]DisableUnitFileChange, len(result))
330
+	changesInterface := make([]interface{}, len(changes))
331
+	for i := range changes {
332
+		changesInterface[i] = &changes[i]
333
+	}
334
+
335
+	err = dbus.Store(resultInterface, changesInterface...)
336
+	if err != nil {
337
+		return nil, err
338
+	}
339
+
340
+	return changes, nil
341
+}
342
+
343
+type DisableUnitFileChange struct {
344
+	Type        string // Type of the change (one of symlink or unlink)
345
+	Filename    string // File name of the symlink
346
+	Destination string // Destination of the symlink
347
+}
348
+
349
+// Reload instructs systemd to scan for and reload unit files. This is
350
+// equivalent to a 'systemctl daemon-reload'.
351
+func (c *Conn) Reload() error {
352
+	return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
353
+}
0 354
new file mode 100644
... ...
@@ -0,0 +1,314 @@
0
+/*
1
+Copyright 2013 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package dbus
17
+
18
+import (
19
+	"fmt"
20
+	"github.com/guelfey/go.dbus"
21
+	"math/rand"
22
+	"os"
23
+	"path/filepath"
24
+	"reflect"
25
+	"testing"
26
+)
27
+
28
+func setupConn(t *testing.T) *Conn {
29
+	conn, err := New()
30
+	if err != nil {
31
+		t.Fatal(err)
32
+	}
33
+
34
+	return conn
35
+}
36
+
37
+func setupUnit(target string, conn *Conn, t *testing.T) {
38
+	// Blindly stop the unit in case it is running
39
+	conn.StopUnit(target, "replace")
40
+
41
+	// Blindly remove the symlink in case it exists
42
+	targetRun := filepath.Join("/run/systemd/system/", target)
43
+	err := os.Remove(targetRun)
44
+
45
+	// 1. Enable the unit
46
+	abs, err := filepath.Abs("../fixtures/" + target)
47
+	if err != nil {
48
+		t.Fatal(err)
49
+	}
50
+
51
+	fixture := []string{abs}
52
+
53
+	install, changes, err := conn.EnableUnitFiles(fixture, true, true)
54
+	if err != nil {
55
+		t.Fatal(err)
56
+	}
57
+
58
+	if install != false {
59
+		t.Fatal("Install was true")
60
+	}
61
+
62
+	if len(changes) < 1 {
63
+		t.Fatalf("Expected one change, got %v", changes)
64
+	}
65
+
66
+	if changes[0].Filename != targetRun {
67
+		t.Fatal("Unexpected target filename")
68
+	}
69
+}
70
+
71
+// Ensure that basic unit starting and stopping works.
72
+func TestStartStopUnit(t *testing.T) {
73
+	target := "start-stop.service"
74
+	conn := setupConn(t)
75
+
76
+	setupUnit(target, conn, t)
77
+
78
+	// 2. Start the unit
79
+	job, err := conn.StartUnit(target, "replace")
80
+	if err != nil {
81
+		t.Fatal(err)
82
+	}
83
+
84
+	if job != "done" {
85
+		t.Fatal("Job is not done, %v", job)
86
+	}
87
+
88
+	units, err := conn.ListUnits()
89
+
90
+	var unit *UnitStatus
91
+	for _, u := range units {
92
+		if u.Name == target {
93
+			unit = &u
94
+		}
95
+	}
96
+
97
+	if unit == nil {
98
+		t.Fatalf("Test unit not found in list")
99
+	}
100
+
101
+	if unit.ActiveState != "active" {
102
+		t.Fatalf("Test unit not active")
103
+	}
104
+
105
+	// 3. Stop the unit
106
+	job, err = conn.StopUnit(target, "replace")
107
+	if err != nil {
108
+		t.Fatal(err)
109
+	}
110
+
111
+	units, err = conn.ListUnits()
112
+
113
+	unit = nil
114
+	for _, u := range units {
115
+		if u.Name == target {
116
+			unit = &u
117
+		}
118
+	}
119
+
120
+	if unit != nil {
121
+		t.Fatalf("Test unit found in list, should be stopped")
122
+	}
123
+}
124
+
125
+// Enables a unit and then immediately tears it down
126
+func TestEnableDisableUnit(t *testing.T) {
127
+	target := "enable-disable.service"
128
+	conn := setupConn(t)
129
+
130
+	setupUnit(target, conn, t)
131
+
132
+	abs, err := filepath.Abs("../fixtures/" + target)
133
+	if err != nil {
134
+		t.Fatal(err)
135
+	}
136
+
137
+	path := filepath.Join("/run/systemd/system/", target)
138
+
139
+	// 2. Disable the unit
140
+	changes, err := conn.DisableUnitFiles([]string{abs}, true)
141
+	if err != nil {
142
+		t.Fatal(err)
143
+	}
144
+
145
+	if len(changes) != 1 {
146
+		t.Fatalf("Changes should include the path, %v", changes)
147
+	}
148
+	if changes[0].Filename != path {
149
+		t.Fatalf("Change should include correct filename, %+v", changes[0])
150
+	}
151
+	if changes[0].Destination != "" {
152
+		t.Fatalf("Change destination should be empty, %+v", changes[0])
153
+	}
154
+}
155
+
156
+// TestGetUnitProperties reads the `-.mount` which should exist on all systemd
157
+// systems and ensures that one of its properties is valid.
158
+func TestGetUnitProperties(t *testing.T) {
159
+	conn := setupConn(t)
160
+
161
+	unit := "-.mount"
162
+
163
+	info, err := conn.GetUnitProperties(unit)
164
+	if err != nil {
165
+		t.Fatal(err)
166
+	}
167
+
168
+	names := info["Wants"].([]string)
169
+
170
+	if len(names) < 1 {
171
+		t.Fatal("/ is unwanted")
172
+	}
173
+
174
+	if names[0] != "system.slice" {
175
+		t.Fatal("unexpected wants for /")
176
+	}
177
+
178
+	prop, err := conn.GetUnitProperty(unit, "Wants")
179
+	if err != nil {
180
+		t.Fatal(err)
181
+	}
182
+
183
+	if prop.Name != "Wants" {
184
+		t.Fatal("unexpected property name")
185
+	}
186
+
187
+	val := prop.Value.Value().([]string)
188
+	if !reflect.DeepEqual(val, names) {
189
+		t.Fatal("unexpected property value")
190
+	}
191
+}
192
+
193
+// TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a
194
+// unit with an invalid name. This test should be run with --test.timeout set,
195
+// as a fail will manifest as GetUnitProperties hanging indefinitely.
196
+func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) {
197
+	conn := setupConn(t)
198
+
199
+	unit := "//invalid#$^/"
200
+
201
+	_, err := conn.GetUnitProperties(unit)
202
+	if err == nil {
203
+		t.Fatal("Expected an error, got nil")
204
+	}
205
+
206
+	_, err = conn.GetUnitProperty(unit, "Wants")
207
+	if err == nil {
208
+		t.Fatal("Expected an error, got nil")
209
+	}
210
+}
211
+
212
+// TestSetUnitProperties changes a cgroup setting on the `tmp.mount`
213
+// which should exist on all systemd systems and ensures that the
214
+// property was set.
215
+func TestSetUnitProperties(t *testing.T) {
216
+	conn := setupConn(t)
217
+
218
+	unit := "tmp.mount"
219
+
220
+	if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil {
221
+		t.Fatal(err)
222
+	}
223
+
224
+	info, err := conn.GetUnitTypeProperties(unit, "Mount")
225
+	if err != nil {
226
+		t.Fatal(err)
227
+	}
228
+
229
+	value := info["CPUShares"].(uint64)
230
+	if value != 1023 {
231
+		t.Fatal("CPUShares of unit is not 1023, %s", value)
232
+	}
233
+}
234
+
235
+// Ensure that basic transient unit starting and stopping works.
236
+func TestStartStopTransientUnit(t *testing.T) {
237
+	conn := setupConn(t)
238
+
239
+	props := []Property{
240
+		PropExecStart([]string{"/bin/sleep", "400"}, false),
241
+	}
242
+	target := fmt.Sprintf("testing-transient-%d.service", rand.Int())
243
+
244
+	// Start the unit
245
+	job, err := conn.StartTransientUnit(target, "replace", props...)
246
+	if err != nil {
247
+		t.Fatal(err)
248
+	}
249
+
250
+	if job != "done" {
251
+		t.Fatal("Job is not done, %v", job)
252
+	}
253
+
254
+	units, err := conn.ListUnits()
255
+
256
+	var unit *UnitStatus
257
+	for _, u := range units {
258
+		if u.Name == target {
259
+			unit = &u
260
+		}
261
+	}
262
+
263
+	if unit == nil {
264
+		t.Fatalf("Test unit not found in list")
265
+	}
266
+
267
+	if unit.ActiveState != "active" {
268
+		t.Fatalf("Test unit not active")
269
+	}
270
+
271
+	// 3. Stop the unit
272
+	job, err = conn.StopUnit(target, "replace")
273
+	if err != nil {
274
+		t.Fatal(err)
275
+	}
276
+
277
+	units, err = conn.ListUnits()
278
+
279
+	unit = nil
280
+	for _, u := range units {
281
+		if u.Name == target {
282
+			unit = &u
283
+		}
284
+	}
285
+
286
+	if unit != nil {
287
+		t.Fatalf("Test unit found in list, should be stopped")
288
+	}
289
+}
290
+
291
+func TestConnJobListener(t *testing.T) {
292
+	target := "start-stop.service"
293
+	conn := setupConn(t)
294
+
295
+	setupUnit(target, conn, t)
296
+
297
+	jobSize := len(conn.jobListener.jobs)
298
+
299
+	_, err := conn.StartUnit(target, "replace")
300
+	if err != nil {
301
+		t.Fatal(err)
302
+	}
303
+
304
+	_, err = conn.StopUnit(target, "replace")
305
+	if err != nil {
306
+		t.Fatal(err)
307
+	}
308
+
309
+	currentJobSize := len(conn.jobListener.jobs)
310
+	if jobSize != currentJobSize {
311
+		t.Fatal("JobListener jobs leaked")
312
+	}
313
+}
0 314
new file mode 100644
... ...
@@ -0,0 +1,220 @@
0
+/*
1
+Copyright 2013 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package dbus
17
+
18
+import (
19
+	"github.com/godbus/dbus"
20
+)
21
+
22
+// From the systemd docs:
23
+//
24
+// The properties array of StartTransientUnit() may take many of the settings
25
+// that may also be configured in unit files. Not all parameters are currently
26
+// accepted though, but we plan to cover more properties with future release.
27
+// Currently you may set the Description, Slice and all dependency types of
28
+// units, as well as RemainAfterExit, ExecStart for service units,
29
+// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares,
30
+// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth,
31
+// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit,
32
+// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map
33
+// directly to their counterparts in unit files and as normal D-Bus object
34
+// properties. The exception here is the PIDs field of scope units which is
35
+// used for construction of the scope only and specifies the initial PIDs to
36
+// add to the scope object.
37
+
38
+type Property struct {
39
+	Name  string
40
+	Value dbus.Variant
41
+}
42
+
43
+type PropertyCollection struct {
44
+	Name       string
45
+	Properties []Property
46
+}
47
+
48
+type execStart struct {
49
+	Path             string   // the binary path to execute
50
+	Args             []string // an array with all arguments to pass to the executed command, starting with argument 0
51
+	UncleanIsFailure bool     // a boolean whether it should be considered a failure if the process exits uncleanly
52
+}
53
+
54
+// PropExecStart sets the ExecStart service property.  The first argument is a
55
+// slice with the binary path to execute followed by the arguments to pass to
56
+// the executed command. See
57
+// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart=
58
+func PropExecStart(command []string, uncleanIsFailure bool) Property {
59
+	execStarts := []execStart{
60
+		execStart{
61
+			Path:             command[0],
62
+			Args:             command,
63
+			UncleanIsFailure: uncleanIsFailure,
64
+		},
65
+	}
66
+
67
+	return Property{
68
+		Name:  "ExecStart",
69
+		Value: dbus.MakeVariant(execStarts),
70
+	}
71
+}
72
+
73
+// PropRemainAfterExit sets the RemainAfterExit service property. See
74
+// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit=
75
+func PropRemainAfterExit(b bool) Property {
76
+	return Property{
77
+		Name:  "RemainAfterExit",
78
+		Value: dbus.MakeVariant(b),
79
+	}
80
+}
81
+
82
+// PropDescription sets the Description unit property. See
83
+// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description=
84
+func PropDescription(desc string) Property {
85
+	return Property{
86
+		Name:  "Description",
87
+		Value: dbus.MakeVariant(desc),
88
+	}
89
+}
90
+
91
+func propDependency(name string, units []string) Property {
92
+	return Property{
93
+		Name:  name,
94
+		Value: dbus.MakeVariant(units),
95
+	}
96
+}
97
+
98
+// PropRequires sets the Requires unit property.  See
99
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires=
100
+func PropRequires(units ...string) Property {
101
+	return propDependency("Requires", units)
102
+}
103
+
104
+// PropRequiresOverridable sets the RequiresOverridable unit property.  See
105
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable=
106
+func PropRequiresOverridable(units ...string) Property {
107
+	return propDependency("RequiresOverridable", units)
108
+}
109
+
110
+// PropRequisite sets the Requisite unit property.  See
111
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite=
112
+func PropRequisite(units ...string) Property {
113
+	return propDependency("Requisite", units)
114
+}
115
+
116
+// PropRequisiteOverridable sets the RequisiteOverridable unit property.  See
117
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable=
118
+func PropRequisiteOverridable(units ...string) Property {
119
+	return propDependency("RequisiteOverridable", units)
120
+}
121
+
122
+// PropWants sets the Wants unit property.  See
123
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants=
124
+func PropWants(units ...string) Property {
125
+	return propDependency("Wants", units)
126
+}
127
+
128
+// PropBindsTo sets the BindsTo unit property.  See
129
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo=
130
+func PropBindsTo(units ...string) Property {
131
+	return propDependency("BindsTo", units)
132
+}
133
+
134
+// PropRequiredBy sets the RequiredBy unit property.  See
135
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy=
136
+func PropRequiredBy(units ...string) Property {
137
+	return propDependency("RequiredBy", units)
138
+}
139
+
140
+// PropRequiredByOverridable sets the RequiredByOverridable unit property.  See
141
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable=
142
+func PropRequiredByOverridable(units ...string) Property {
143
+	return propDependency("RequiredByOverridable", units)
144
+}
145
+
146
+// PropWantedBy sets the WantedBy unit property.  See
147
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy=
148
+func PropWantedBy(units ...string) Property {
149
+	return propDependency("WantedBy", units)
150
+}
151
+
152
+// PropBoundBy sets the BoundBy unit property.  See
153
+// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy=
154
+func PropBoundBy(units ...string) Property {
155
+	return propDependency("BoundBy", units)
156
+}
157
+
158
+// PropConflicts sets the Conflicts unit property.  See
159
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts=
160
+func PropConflicts(units ...string) Property {
161
+	return propDependency("Conflicts", units)
162
+}
163
+
164
+// PropConflictedBy sets the ConflictedBy unit property.  See
165
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy=
166
+func PropConflictedBy(units ...string) Property {
167
+	return propDependency("ConflictedBy", units)
168
+}
169
+
170
+// PropBefore sets the Before unit property.  See
171
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before=
172
+func PropBefore(units ...string) Property {
173
+	return propDependency("Before", units)
174
+}
175
+
176
+// PropAfter sets the After unit property.  See
177
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After=
178
+func PropAfter(units ...string) Property {
179
+	return propDependency("After", units)
180
+}
181
+
182
+// PropOnFailure sets the OnFailure unit property.  See
183
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure=
184
+func PropOnFailure(units ...string) Property {
185
+	return propDependency("OnFailure", units)
186
+}
187
+
188
+// PropTriggers sets the Triggers unit property.  See
189
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers=
190
+func PropTriggers(units ...string) Property {
191
+	return propDependency("Triggers", units)
192
+}
193
+
194
+// PropTriggeredBy sets the TriggeredBy unit property.  See
195
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy=
196
+func PropTriggeredBy(units ...string) Property {
197
+	return propDependency("TriggeredBy", units)
198
+}
199
+
200
+// PropPropagatesReloadTo sets the PropagatesReloadTo unit property.  See
201
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo=
202
+func PropPropagatesReloadTo(units ...string) Property {
203
+	return propDependency("PropagatesReloadTo", units)
204
+}
205
+
206
+// PropRequiresMountsFor sets the RequiresMountsFor unit property.  See
207
+// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor=
208
+func PropRequiresMountsFor(units ...string) Property {
209
+	return propDependency("RequiresMountsFor", units)
210
+}
211
+
212
+// PropSlice sets the Slice unit property.  See
213
+// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice=
214
+func PropSlice(slice string) Property {
215
+	return Property{
216
+		Name:  "Slice",
217
+		Value: dbus.MakeVariant(slice),
218
+	}
219
+}
0 220
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package dbus
1
+
2
+type set struct {
3
+	data map[string]bool
4
+}
5
+
6
+func (s *set) Add(value string) {
7
+	s.data[value] = true
8
+}
9
+
10
+func (s *set) Remove(value string) {
11
+	delete(s.data, value)
12
+}
13
+
14
+func (s *set) Contains(value string) (exists bool) {
15
+	_, exists = s.data[value]
16
+	return
17
+}
18
+
19
+func (s *set) Length() (int) {
20
+	return len(s.data)
21
+}
22
+
23
+func newSet() (*set) {
24
+	return &set{make(map[string] bool)}
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package dbus
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+// TestBasicSetActions asserts that Add & Remove behavior is correct
7
+func TestBasicSetActions(t *testing.T) {
8
+	s := newSet()
9
+
10
+	if s.Contains("foo") {
11
+		t.Fatal("set should not contain 'foo'")
12
+	}
13
+
14
+	s.Add("foo")
15
+
16
+	if !s.Contains("foo") {
17
+		t.Fatal("set should contain 'foo'")
18
+	}
19
+
20
+	s.Remove("foo")
21
+
22
+	if s.Contains("foo") {
23
+		t.Fatal("set should not contain 'foo'")
24
+	}
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,249 @@
0
+/*
1
+Copyright 2013 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+package dbus
17
+
18
+import (
19
+	"errors"
20
+	"time"
21
+
22
+	"github.com/godbus/dbus"
23
+)
24
+
25
+const (
26
+	cleanIgnoreInterval = int64(10 * time.Second)
27
+	ignoreInterval      = int64(30 * time.Millisecond)
28
+)
29
+
30
+// Subscribe sets up this connection to subscribe to all systemd dbus events.
31
+// This is required before calling SubscribeUnits. When the connection closes
32
+// systemd will automatically stop sending signals so there is no need to
33
+// explicitly call Unsubscribe().
34
+func (c *Conn) Subscribe() error {
35
+	c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
36
+		"type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'")
37
+	c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
38
+		"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'")
39
+
40
+	err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
41
+	if err != nil {
42
+		c.sysconn.Close()
43
+		return err
44
+	}
45
+
46
+	return nil
47
+}
48
+
49
+// Unsubscribe this connection from systemd dbus events.
50
+func (c *Conn) Unsubscribe() error {
51
+	err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
52
+	if err != nil {
53
+		c.sysconn.Close()
54
+		return err
55
+	}
56
+
57
+	return nil
58
+}
59
+
60
+func (c *Conn) initSubscription() {
61
+	c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
62
+}
63
+
64
+func (c *Conn) initDispatch() {
65
+	ch := make(chan *dbus.Signal, signalBuffer)
66
+
67
+	c.sysconn.Signal(ch)
68
+
69
+	go func() {
70
+		for {
71
+			signal := <-ch
72
+			switch signal.Name {
73
+			case "org.freedesktop.systemd1.Manager.JobRemoved":
74
+				c.jobComplete(signal)
75
+
76
+				unitName := signal.Body[2].(string)
77
+				var unitPath dbus.ObjectPath
78
+				c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath)
79
+				if unitPath != dbus.ObjectPath("") {
80
+					c.sendSubStateUpdate(unitPath)
81
+				}
82
+			case "org.freedesktop.systemd1.Manager.UnitNew":
83
+				c.sendSubStateUpdate(signal.Body[1].(dbus.ObjectPath))
84
+			case "org.freedesktop.DBus.Properties.PropertiesChanged":
85
+				if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
86
+					// we only care about SubState updates, which are a Unit property
87
+					c.sendSubStateUpdate(signal.Path)
88
+				}
89
+			}
90
+		}
91
+	}()
92
+}
93
+
94
+// Returns two unbuffered channels which will receive all changed units every
95
+// interval.  Deleted units are sent as nil.
96
+func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) {
97
+	return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil)
98
+}
99
+
100
+// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
101
+// size of the channels, the comparison function for detecting changes and a filter
102
+// function for cutting down on the noise that your channel receives.
103
+func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func (string) bool) (<-chan map[string]*UnitStatus, <-chan error) {
104
+	old := make(map[string]*UnitStatus)
105
+	statusChan := make(chan map[string]*UnitStatus, buffer)
106
+	errChan := make(chan error, buffer)
107
+
108
+	go func() {
109
+		for {
110
+			timerChan := time.After(interval)
111
+
112
+			units, err := c.ListUnits()
113
+			if err == nil {
114
+				cur := make(map[string]*UnitStatus)
115
+				for i := range units {
116
+					if filterUnit != nil && filterUnit(units[i].Name) {
117
+						continue
118
+					}
119
+					cur[units[i].Name] = &units[i]
120
+				}
121
+
122
+				// add all new or changed units
123
+				changed := make(map[string]*UnitStatus)
124
+				for n, u := range cur {
125
+					if oldU, ok := old[n]; !ok || isChanged(oldU, u) {
126
+						changed[n] = u
127
+					}
128
+					delete(old, n)
129
+				}
130
+
131
+				// add all deleted units
132
+				for oldN := range old {
133
+					changed[oldN] = nil
134
+				}
135
+
136
+				old = cur
137
+
138
+				if len(changed) != 0 {
139
+					statusChan <- changed
140
+				}
141
+			} else {
142
+				errChan <- err
143
+			}
144
+
145
+			<-timerChan
146
+		}
147
+	}()
148
+
149
+	return statusChan, errChan
150
+}
151
+
152
+type SubStateUpdate struct {
153
+	UnitName string
154
+	SubState string
155
+}
156
+
157
+// SetSubStateSubscriber writes to updateCh when any unit's substate changes.
158
+// Although this writes to updateCh on every state change, the reported state
159
+// may be more recent than the change that generated it (due to an unavoidable
160
+// race in the systemd dbus interface).  That is, this method provides a good
161
+// way to keep a current view of all units' states, but is not guaranteed to
162
+// show every state transition they go through.  Furthermore, state changes
163
+// will only be written to the channel with non-blocking writes.  If updateCh
164
+// is full, it attempts to write an error to errCh; if errCh is full, the error
165
+// passes silently.
166
+func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) {
167
+	c.subscriber.Lock()
168
+	defer c.subscriber.Unlock()
169
+	c.subscriber.updateCh = updateCh
170
+	c.subscriber.errCh = errCh
171
+}
172
+
173
+func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) {
174
+	c.subscriber.Lock()
175
+	defer c.subscriber.Unlock()
176
+	if c.subscriber.updateCh == nil {
177
+		return
178
+	}
179
+
180
+	if c.shouldIgnore(path) {
181
+		return
182
+	}
183
+
184
+	info, err := c.GetUnitProperties(string(path))
185
+	if err != nil {
186
+		select {
187
+		case c.subscriber.errCh <- err:
188
+		default:
189
+		}
190
+	}
191
+
192
+	name := info["Id"].(string)
193
+	substate := info["SubState"].(string)
194
+
195
+	update := &SubStateUpdate{name, substate}
196
+	select {
197
+	case c.subscriber.updateCh <- update:
198
+	default:
199
+		select {
200
+		case c.subscriber.errCh <- errors.New("update channel full!"):
201
+		default:
202
+		}
203
+	}
204
+
205
+	c.updateIgnore(path, info)
206
+}
207
+
208
+// The ignore functions work around a wart in the systemd dbus interface.
209
+// Requesting the properties of an unloaded unit will cause systemd to send a
210
+// pair of UnitNew/UnitRemoved signals.  Because we need to get a unit's
211
+// properties on UnitNew (as that's the only indication of a new unit coming up
212
+// for the first time), we would enter an infinite loop if we did not attempt
213
+// to detect and ignore these spurious signals.  The signal themselves are
214
+// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an
215
+// unloaded unit's signals for a short time after requesting its properties.
216
+// This means that we will miss e.g. a transient unit being restarted
217
+// *immediately* upon failure and also a transient unit being started
218
+// immediately after requesting its status (with systemctl status, for example,
219
+// because this causes a UnitNew signal to be sent which then causes us to fetch
220
+// the properties).
221
+
222
+func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool {
223
+	t, ok := c.subscriber.ignore[path]
224
+	return ok && t >= time.Now().UnixNano()
225
+}
226
+
227
+func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) {
228
+	c.cleanIgnore()
229
+
230
+	// unit is unloaded - it will trigger bad systemd dbus behavior
231
+	if info["LoadState"].(string) == "not-found" {
232
+		c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval
233
+	}
234
+}
235
+
236
+// without this, ignore would grow unboundedly over time
237
+func (c *Conn) cleanIgnore() {
238
+	now := time.Now().UnixNano()
239
+	if c.subscriber.cleanIgnore < now {
240
+		c.subscriber.cleanIgnore = now + cleanIgnoreInterval
241
+
242
+		for p, t := range c.subscriber.ignore {
243
+			if t < now {
244
+				delete(c.subscriber.ignore, p)
245
+			}
246
+		}
247
+	}
248
+}
0 249
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package dbus
1
+
2
+import (
3
+	"time"
4
+)
5
+
6
+// SubscriptionSet returns a subscription set which is like conn.Subscribe but
7
+// can filter to only return events for a set of units.
8
+type SubscriptionSet struct {
9
+	*set
10
+	conn *Conn
11
+}
12
+
13
+
14
+func (s *SubscriptionSet) filter(unit string) bool {
15
+	return !s.Contains(unit)
16
+}
17
+
18
+// Subscribe starts listening for dbus events for all of the units in the set.
19
+// Returns channels identical to conn.SubscribeUnits.
20
+func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) {
21
+	// TODO: Make fully evented by using systemd 209 with properties changed values
22
+	return s.conn.SubscribeUnitsCustom(time.Second, 0,
23
+		func(u1, u2 *UnitStatus) bool { return *u1 != *u2 },
24
+		func(unit string) bool { return s.filter(unit) },
25
+	)
26
+}
27
+
28
+// NewSubscriptionSet returns a new subscription set.
29
+func (conn *Conn) NewSubscriptionSet() (*SubscriptionSet) {
30
+	return &SubscriptionSet{newSet(), conn}
31
+}
0 32
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package dbus
1
+
2
+import (
3
+	"testing"
4
+	"time"
5
+)
6
+
7
+// TestSubscribeUnit exercises the basics of subscription of a particular unit.
8
+func TestSubscriptionSetUnit(t *testing.T) {
9
+	target := "subscribe-events-set.service"
10
+
11
+	conn, err := New()
12
+
13
+	if err != nil {
14
+		t.Fatal(err)
15
+	}
16
+
17
+	err = conn.Subscribe()
18
+	if err != nil {
19
+		t.Fatal(err)
20
+	}
21
+
22
+	subSet := conn.NewSubscriptionSet()
23
+	evChan, errChan := subSet.Subscribe()
24
+
25
+	subSet.Add(target)
26
+	setupUnit(target, conn, t)
27
+
28
+	job, err := conn.StartUnit(target, "replace")
29
+	if err != nil {
30
+		t.Fatal(err)
31
+	}
32
+
33
+	if job != "done" {
34
+		t.Fatal("Couldn't start", target)
35
+	}
36
+
37
+	timeout := make(chan bool, 1)
38
+	go func() {
39
+		time.Sleep(3 * time.Second)
40
+		close(timeout)
41
+	}()
42
+
43
+	for {
44
+		select {
45
+		case changes := <-evChan:
46
+			tCh, ok := changes[target]
47
+
48
+			if !ok {
49
+				t.Fatal("Unexpected event %v", changes)
50
+			}
51
+
52
+			if tCh.ActiveState == "active" && tCh.Name == target {
53
+				goto success
54
+			}
55
+		case err = <-errChan:
56
+			t.Fatal(err)
57
+		case <-timeout:
58
+			t.Fatal("Reached timeout")
59
+		}
60
+	}
61
+
62
+success:
63
+	return
64
+}
65
+
66
+
0 67
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+package dbus
1
+
2
+import (
3
+	"testing"
4
+	"time"
5
+)
6
+
7
+// TestSubscribe exercises the basics of subscription
8
+func TestSubscribe(t *testing.T) {
9
+	conn, err := New()
10
+
11
+	if err != nil {
12
+		t.Fatal(err)
13
+	}
14
+
15
+	err = conn.Subscribe()
16
+	if err != nil {
17
+		t.Fatal(err)
18
+	}
19
+
20
+	err = conn.Unsubscribe()
21
+	if err != nil {
22
+		t.Fatal(err)
23
+	}
24
+}
25
+
26
+// TestSubscribeUnit exercises the basics of subscription of a particular unit.
27
+func TestSubscribeUnit(t *testing.T) {
28
+	target := "subscribe-events.service"
29
+
30
+	conn, err := New()
31
+
32
+	if err != nil {
33
+		t.Fatal(err)
34
+	}
35
+
36
+	err = conn.Subscribe()
37
+	if err != nil {
38
+		t.Fatal(err)
39
+	}
40
+
41
+	err = conn.Unsubscribe()
42
+	if err != nil {
43
+		t.Fatal(err)
44
+	}
45
+
46
+	evChan, errChan := conn.SubscribeUnits(time.Second)
47
+
48
+	setupUnit(target, conn, t)
49
+
50
+	job, err := conn.StartUnit(target, "replace")
51
+	if err != nil {
52
+		t.Fatal(err)
53
+	}
54
+
55
+	if job != "done" {
56
+		t.Fatal("Couldn't start", target)
57
+	}
58
+
59
+	timeout := make(chan bool, 1)
60
+	go func() {
61
+		time.Sleep(3 * time.Second)
62
+		close(timeout)
63
+	}()
64
+
65
+	for {
66
+		select {
67
+		case changes := <-evChan:
68
+			tCh, ok := changes[target]
69
+
70
+			// Just continue until we see our event.
71
+			if !ok {
72
+				continue
73
+			}
74
+
75
+			if tCh.ActiveState == "active" && tCh.Name == target {
76
+				goto success
77
+			}
78
+		case err = <-errChan:
79
+			t.Fatal(err)
80
+		case <-timeout:
81
+			t.Fatal("Reached timeout")
82
+		}
83
+	}
84
+
85
+success:
86
+	return
87
+}
88
+
89
+
0 90
new file mode 100644
... ...
@@ -0,0 +1,44 @@
0
+// Activation example used by the activation unit tests.
1
+package main
2
+
3
+import (
4
+	"fmt"
5
+	"os"
6
+
7
+	"github.com/coreos/go-systemd/activation"
8
+)
9
+
10
+func fixListenPid() {
11
+	if os.Getenv("FIX_LISTEN_PID") != "" {
12
+		// HACK: real systemd would set LISTEN_PID before exec'ing but
13
+		// this is too difficult in golang for the purpose of a test.
14
+		// Do not do this in real code.
15
+		os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid()))
16
+	}
17
+}
18
+
19
+func main() {
20
+	fixListenPid()
21
+
22
+	files := activation.Files(false)
23
+
24
+	if len(files) == 0 {
25
+		panic("No files")
26
+	}
27
+
28
+	if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" {
29
+		panic("Should not unset envs")
30
+	}
31
+
32
+	files = activation.Files(true)
33
+
34
+	if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" {
35
+		panic("Can not unset envs")
36
+	}
37
+
38
+	// Write out the expected strings to the two pipes
39
+	files[0].Write([]byte("Hello world"))
40
+	files[1].Write([]byte("Goodbye world"))
41
+
42
+	return
43
+}
0 44
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+## socket activated http server
1
+
2
+This is a simple example of using socket activation with systemd to serve a
3
+simple HTTP server on http://127.0.0.1:8076
4
+
5
+To try it out `go get` the httpserver and run it under the systemd-activate helper
6
+
7
+```
8
+export GOPATH=`pwd`
9
+go get github.com/coreos/go-systemd/examples/activation/httpserver
10
+sudo /usr/lib/systemd/systemd-activate -l 127.0.0.1:8076 ./bin/httpserver
11
+```
12
+
13
+Then curl the URL and you will notice that it starts up:
14
+
15
+```
16
+curl 127.0.0.1:8076
17
+hello socket activated world!
18
+```
0 19
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+[Unit]
1
+Description=Hello World HTTP
2
+Requires=network.target
3
+After=multi-user.target
4
+
5
+[Service]
6
+Type=simple
7
+ExecStart=/usr/local/bin/httpserver
8
+
9
+[Install]
10
+WantedBy=multi-user.target
0 11
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+[Socket]
1
+ListenStream=127.0.0.1:8076
2
+
3
+[Install]
4
+WantedBy=sockets.target
0 5
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package main
1
+
2
+import (
3
+	"io"
4
+	"net/http"
5
+
6
+	"github.com/coreos/go-systemd/activation"
7
+)
8
+
9
+func HelloServer(w http.ResponseWriter, req *http.Request) {
10
+	io.WriteString(w, "hello socket activated world!\n")
11
+}
12
+
13
+func main() {
14
+	listeners, err := activation.Listeners(true)
15
+	if err != nil {
16
+		panic(err)
17
+	}
18
+
19
+	if len(listeners) != 1 {
20
+		panic("Unexpected number of socket activation fds")
21
+	}
22
+
23
+	http.HandleFunc("/", HelloServer)
24
+	http.Serve(listeners[0], nil)
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,50 @@
0
+// Activation example used by the activation unit tests.
1
+package main
2
+
3
+import (
4
+	"fmt"
5
+	"os"
6
+
7
+	"github.com/coreos/go-systemd/activation"
8
+)
9
+
10
+func fixListenPid() {
11
+	if os.Getenv("FIX_LISTEN_PID") != "" {
12
+		// HACK: real systemd would set LISTEN_PID before exec'ing but
13
+		// this is too difficult in golang for the purpose of a test.
14
+		// Do not do this in real code.
15
+		os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid()))
16
+	}
17
+}
18
+
19
+func main() {
20
+	fixListenPid()
21
+
22
+	listeners, _ := activation.Listeners(false)
23
+
24
+	if len(listeners) == 0 {
25
+		panic("No listeners")
26
+	}
27
+
28
+	if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" {
29
+		panic("Should not unset envs")
30
+	}
31
+
32
+	listeners, err := activation.Listeners(true)
33
+	if err != nil {
34
+		panic(err)
35
+	}
36
+
37
+	if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" {
38
+		panic("Can not unset envs")
39
+	}
40
+
41
+	c0, _ := listeners[0].Accept()
42
+	c1, _ := listeners[1].Accept()
43
+
44
+	// Write out the expected strings to the two pipes
45
+	c0.Write([]byte("Hello world"))
46
+	c1.Write([]byte("Goodbye world"))
47
+
48
+	return
49
+}
0 50
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+[Unit]
1
+Description=start stop test
2
+
3
+[Service]
4
+ExecStart=/bin/sleep 400
0 5
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+[Unit]
1
+Description=start stop test
2
+
3
+[Service]
4
+ExecStart=/bin/sleep 400
0 5
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+[Unit]
1
+Description=start stop test
2
+
3
+[Service]
4
+ExecStart=/bin/sleep 400
0 5
new file mode 100644
... ...
@@ -0,0 +1,168 @@
0
+/*
1
+Copyright 2013 CoreOS Inc.
2
+
3
+Licensed under the Apache License, Version 2.0 (the "License");
4
+you may not use this file except in compliance with the License.
5
+You may obtain a copy of the License at
6
+
7
+     http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+Unless required by applicable law or agreed to in writing, software
10
+distributed under the License is distributed on an "AS IS" BASIS,
11
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+See the License for the specific language governing permissions and
13
+limitations under the License.
14
+*/
15
+
16
+// Package journal provides write bindings to the systemd journal
17
+package journal
18
+
19
+import (
20
+	"bytes"
21
+	"encoding/binary"
22
+	"errors"
23
+	"fmt"
24
+	"io"
25
+	"io/ioutil"
26
+	"net"
27
+	"os"
28
+	"strconv"
29
+	"strings"
30
+	"syscall"
31
+)
32
+
33
+// Priority of a journal message
34
+type Priority int
35
+
36
+const (
37
+	PriEmerg Priority = iota
38
+	PriAlert
39
+	PriCrit
40
+	PriErr
41
+	PriWarning
42
+	PriNotice
43
+	PriInfo
44
+	PriDebug
45
+)
46
+
47
+var conn net.Conn
48
+
49
+func init() {
50
+	var err error
51
+	conn, err = net.Dial("unixgram", "/run/systemd/journal/socket")
52
+	if err != nil {
53
+		conn = nil
54
+	}
55
+}
56
+
57
+// Enabled returns true iff the systemd journal is available for logging
58
+func Enabled() bool {
59
+	return conn != nil
60
+}
61
+
62
+// Send a message to the systemd journal. vars is a map of journald fields to
63
+// values.  Fields must be composed of uppercase letters, numbers, and
64
+// underscores, but must not start with an underscore. Within these
65
+// restrictions, any arbitrary field name may be used.  Some names have special
66
+// significance: see the journalctl documentation
67
+// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
68
+// for more details.  vars may be nil.
69
+func Send(message string, priority Priority, vars map[string]string) error {
70
+	if conn == nil {
71
+		return journalError("could not connect to journald socket")
72
+	}
73
+
74
+	data := new(bytes.Buffer)
75
+	appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
76
+	appendVariable(data, "MESSAGE", message)
77
+	for k, v := range vars {
78
+		appendVariable(data, k, v)
79
+	}
80
+
81
+	_, err := io.Copy(conn, data)
82
+	if err != nil && isSocketSpaceError(err) {
83
+		file, err := tempFd()
84
+		if err != nil {
85
+			return journalError(err.Error())
86
+		}
87
+		_, err = io.Copy(file, data)
88
+		if err != nil {
89
+			return journalError(err.Error())
90
+		}
91
+
92
+		rights := syscall.UnixRights(int(file.Fd()))
93
+
94
+		/* this connection should always be a UnixConn, but better safe than sorry */
95
+		unixConn, ok := conn.(*net.UnixConn)
96
+		if !ok {
97
+			return journalError("can't send file through non-Unix connection")
98
+		}
99
+		unixConn.WriteMsgUnix([]byte{}, rights, nil)
100
+	} else if err != nil {
101
+		return journalError(err.Error())
102
+	}
103
+	return nil
104
+}
105
+
106
+func appendVariable(w io.Writer, name, value string) {
107
+	if !validVarName(name) {
108
+		journalError("variable name contains invalid character, ignoring")
109
+	}
110
+	if strings.ContainsRune(value, '\n') {
111
+		/* When the value contains a newline, we write:
112
+		 * - the variable name, followed by a newline
113
+		 * - the size (in 64bit little endian format)
114
+		 * - the data, followed by a newline
115
+		 */
116
+		fmt.Fprintln(w, name)
117
+		binary.Write(w, binary.LittleEndian, uint64(len(value)))
118
+		fmt.Fprintln(w, value)
119
+	} else {
120
+		/* just write the variable and value all on one line */
121
+		fmt.Fprintln(w, "%s=%s", name, value)
122
+	}
123
+}
124
+
125
+func validVarName(name string) bool {
126
+	/* The variable name must be in uppercase and consist only of characters,
127
+	 * numbers and underscores, and may not begin with an underscore. (from the docs)
128
+	 */
129
+
130
+	valid := name[0] != '_'
131
+	for _, c := range name {
132
+		valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_'
133
+	}
134
+	return valid
135
+}
136
+
137
+func isSocketSpaceError(err error) bool {
138
+	opErr, ok := err.(*net.OpError)
139
+	if !ok {
140
+		return false
141
+	}
142
+
143
+	sysErr, ok := opErr.Err.(syscall.Errno)
144
+	if !ok {
145
+		return false
146
+	}
147
+
148
+	return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
149
+}
150
+
151
+func tempFd() (*os.File, error) {
152
+	file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
153
+	if err != nil {
154
+		return nil, err
155
+	}
156
+	syscall.Unlink(file.Name())
157
+	if err != nil {
158
+		return nil, err
159
+	}
160
+	return file, nil
161
+}
162
+
163
+func journalError(s string) error {
164
+	s = "journal error: " + s
165
+	fmt.Fprintln(os.Stderr, s)
166
+	return errors.New(s)
167
+}
0 168
new file mode 100755
... ...
@@ -0,0 +1,3 @@
0
+#!/bin/sh -e
1
+
2
+go test -v ./...
0 3
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>)
1
+All rights reserved.
2
+
3
+Redistribution and use in source and binary forms, with or without
4
+modification, are permitted provided that the following conditions
5
+are met:
6
+
7
+1. Redistributions of source code must retain the above copyright notice,
8
+this list of conditions and the following disclaimer.
9
+
10
+2. Redistributions in binary form must reproduce the above copyright
11
+notice, this list of conditions and the following disclaimer in the
12
+documentation and/or other materials provided with the distribution.
13
+
14
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
20
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
22
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
23
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 25
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+dbus
1
+----
2
+
3
+dbus is a simple library that implements native Go client bindings for the
4
+D-Bus message bus system.
5
+
6
+### Features
7
+
8
+* Complete native implementation of the D-Bus message protocol
9
+* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections)
10
+* Subpackages that help with the introspection / property interfaces
11
+
12
+### Installation
13
+
14
+This packages requires Go 1.1. If you installed it and set up your GOPATH, just run:
15
+
16
+```
17
+go get github.com/godbus/dbus
18
+```
19
+
20
+If you want to use the subpackages, you can install them the same way.
21
+
22
+### Usage
23
+
24
+The complete package documentation and some simple examples are available at
25
+[godoc.org](http://godoc.org/github.com/godbus/dbus). Also, the
26
+[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory
27
+gives a short overview over the basic usage. 
28
+
29
+Please note that the API is considered unstable for now and may change without
30
+further notice.
31
+
32
+### License
33
+
34
+go.dbus is available under the Simplified BSD License; see LICENSE for the full
35
+text.
36
+
37
+Nearly all of the credit for this library goes to github.com/guelfey/go.dbus.
0 38
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+package main
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/godbus/dbus"
5
+	"os"
6
+)
7
+
8
+func main() {
9
+	conn, err := dbus.SessionBus()
10
+	if err != nil {
11
+		fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err)
12
+		os.Exit(1)
13
+	}
14
+
15
+	for _, v := range []string{"method_call", "method_return", "error", "signal"} {
16
+		call := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
17
+			"eavesdrop='true',type='"+v+"'")
18
+		if call.Err != nil {
19
+			fmt.Fprintln(os.Stderr, "Failed to add match:", call.Err)
20
+			os.Exit(1)
21
+		}
22
+	}
23
+	c := make(chan *dbus.Message, 10)
24
+	conn.Eavesdrop(c)
25
+	fmt.Println("Listening for everything")
26
+	for v := range c {
27
+		fmt.Println(v)
28
+	}
29
+}
0 30
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package main
1
+
2
+import (
3
+	"encoding/json"
4
+	"github.com/godbus/dbus"
5
+	"github.com/godbus/dbus/introspect"
6
+	"os"
7
+)
8
+
9
+func main() {
10
+	conn, err := dbus.SessionBus()
11
+	if err != nil {
12
+		panic(err)
13
+	}
14
+	node, err := introspect.Call(conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus"))
15
+	if err != nil {
16
+		panic(err)
17
+	}
18
+	data, _ := json.MarshalIndent(node, "", "    ")
19
+	os.Stdout.Write(data)
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package main
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/godbus/dbus"
5
+	"os"
6
+)
7
+
8
+func main() {
9
+	conn, err := dbus.SessionBus()
10
+	if err != nil {
11
+		fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err)
12
+		os.Exit(1)
13
+	}
14
+
15
+	var s []string
16
+	err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&s)
17
+	if err != nil {
18
+		fmt.Fprintln(os.Stderr, "Failed to get list of owned names:", err)
19
+		os.Exit(1)
20
+	}
21
+
22
+	fmt.Println("Currently owned names on the session bus:")
23
+	for _, v := range s {
24
+		fmt.Println(v)
25
+	}
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+package main
1
+
2
+import "github.com/godbus/dbus"
3
+
4
+func main() {
5
+	conn, err := dbus.SessionBus()
6
+	if err != nil {
7
+		panic(err)
8
+	}
9
+	obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
10
+	call := obj.Call("org.freedesktop.Notifications.Notify", 0, "", uint32(0),
11
+		"", "Test", "This is a test of the DBus bindings for go.", []string{},
12
+		map[string]dbus.Variant{}, int32(5000))
13
+	if call.Err != nil {
14
+		panic(call.Err)
15
+	}
16
+}
0 17
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+package main
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/godbus/dbus"
5
+	"github.com/godbus/dbus/introspect"
6
+	"github.com/godbus/dbus/prop"
7
+	"os"
8
+)
9
+
10
+type foo string
11
+
12
+func (f foo) Foo() (string, *dbus.Error) {
13
+	fmt.Println(f)
14
+	return string(f), nil
15
+}
16
+
17
+func main() {
18
+	conn, err := dbus.SessionBus()
19
+	if err != nil {
20
+		panic(err)
21
+	}
22
+	reply, err := conn.RequestName("com.github.guelfey.Demo",
23
+		dbus.NameFlagDoNotQueue)
24
+	if err != nil {
25
+		panic(err)
26
+	}
27
+	if reply != dbus.RequestNameReplyPrimaryOwner {
28
+		fmt.Fprintln(os.Stderr, "name already taken")
29
+		os.Exit(1)
30
+	}
31
+	propsSpec := map[string]map[string]*prop.Prop{
32
+		"com.github.guelfey.Demo": {
33
+			"SomeInt": {
34
+				int32(0),
35
+				true,
36
+				prop.EmitTrue,
37
+				func(c *prop.Change) *dbus.Error {
38
+					fmt.Println(c.Name, "changed to", c.Value)
39
+					return nil
40
+				},
41
+			},
42
+		},
43
+	}
44
+	f := foo("Bar")
45
+	conn.Export(f, "/com/github/guelfey/Demo", "com.github.guelfey.Demo")
46
+	props := prop.New(conn, "/com/github/guelfey/Demo", propsSpec)
47
+	n := &introspect.Node{
48
+		Name: "/com/github/guelfey/Demo",
49
+		Interfaces: []introspect.Interface{
50
+			introspect.IntrospectData,
51
+			prop.IntrospectData,
52
+			{
53
+				Name:       "com.github.guelfey.Demo",
54
+				Methods:    introspect.Methods(f),
55
+				Properties: props.Introspection("com.github.guelfey.Demo"),
56
+			},
57
+		},
58
+	}
59
+	conn.Export(introspect.NewIntrospectable(n), "/com/github/guelfey/Demo",
60
+		"org.freedesktop.DBus.Introspectable")
61
+	fmt.Println("Listening on com.github.guelfey.Demo / /com/github/guelfey/Demo ...")
62
+
63
+	c := make(chan *dbus.Signal)
64
+	conn.Signal(c)
65
+	for _ = range c {
66
+	}
67
+}
0 68
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+package main
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/godbus/dbus"
5
+	"github.com/godbus/dbus/introspect"
6
+	"os"
7
+)
8
+
9
+const intro = `
10
+<node>
11
+	<interface name="com.github.guelfey.Demo">
12
+		<method name="Foo">
13
+			<arg direction="out" type="s"/>
14
+		</method>
15
+	</interface>` + introspect.IntrospectDataString + `</node> `
16
+
17
+type foo string
18
+
19
+func (f foo) Foo() (string, *dbus.Error) {
20
+	fmt.Println(f)
21
+	return string(f), nil
22
+}
23
+
24
+func main() {
25
+	conn, err := dbus.SessionBus()
26
+	if err != nil {
27
+		panic(err)
28
+	}
29
+	reply, err := conn.RequestName("com.github.guelfey.Demo",
30
+		dbus.NameFlagDoNotQueue)
31
+	if err != nil {
32
+		panic(err)
33
+	}
34
+	if reply != dbus.RequestNameReplyPrimaryOwner {
35
+		fmt.Fprintln(os.Stderr, "name already taken")
36
+		os.Exit(1)
37
+	}
38
+	f := foo("Bar!")
39
+	conn.Export(f, "/com/github/guelfey/Demo", "com.github.guelfey.Demo")
40
+	conn.Export(introspect.Introspectable(intro), "/com/github/guelfey/Demo",
41
+		"org.freedesktop.DBus.Introspectable")
42
+	fmt.Println("Listening on com.github.guelfey.Demo / /com/github/guelfey/Demo ...")
43
+	select {}
44
+}
0 45
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package main
1
+
2
+import (
3
+	"fmt"
4
+	"github.com/godbus/dbus"
5
+	"os"
6
+)
7
+
8
+func main() {
9
+	conn, err := dbus.SessionBus()
10
+	if err != nil {
11
+		fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err)
12
+		os.Exit(1)
13
+	}
14
+
15
+	conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
16
+		"type='signal',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',sender='org.freedesktop.DBus'")
17
+
18
+	c := make(chan *dbus.Signal, 10)
19
+	conn.Signal(c)
20
+	for v := range c {
21
+		fmt.Println(v)
22
+	}
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,253 @@
0
+package dbus
1
+
2
+import (
3
+	"bufio"
4
+	"bytes"
5
+	"errors"
6
+	"io"
7
+	"os"
8
+	"strconv"
9
+)
10
+
11
+// AuthStatus represents the Status of an authentication mechanism.
12
+type AuthStatus byte
13
+
14
+const (
15
+	// AuthOk signals that authentication is finished; the next command
16
+	// from the server should be an OK.
17
+	AuthOk AuthStatus = iota
18
+
19
+	// AuthContinue signals that additional data is needed; the next command
20
+	// from the server should be a DATA.
21
+	AuthContinue
22
+
23
+	// AuthError signals an error; the server sent invalid data or some
24
+	// other unexpected thing happened and the current authentication
25
+	// process should be aborted.
26
+	AuthError
27
+)
28
+
29
+type authState byte
30
+
31
+const (
32
+	waitingForData authState = iota
33
+	waitingForOk
34
+	waitingForReject
35
+)
36
+
37
+// Auth defines the behaviour of an authentication mechanism.
38
+type Auth interface {
39
+	// Return the name of the mechnism, the argument to the first AUTH command
40
+	// and the next status.
41
+	FirstData() (name, resp []byte, status AuthStatus)
42
+
43
+	// Process the given DATA command, and return the argument to the DATA
44
+	// command and the next status. If len(resp) == 0, no DATA command is sent.
45
+	HandleData(data []byte) (resp []byte, status AuthStatus)
46
+}
47
+
48
+// Auth authenticates the connection, trying the given list of authentication
49
+// mechanisms (in that order). If nil is passed, the EXTERNAL and
50
+// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private
51
+// connections, this method must be called before sending any messages to the
52
+// bus. Auth must not be called on shared connections.
53
+func (conn *Conn) Auth(methods []Auth) error {
54
+	if methods == nil {
55
+		uid := strconv.Itoa(os.Getuid())
56
+		methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())}
57
+	}
58
+	in := bufio.NewReader(conn.transport)
59
+	err := conn.transport.SendNullByte()
60
+	if err != nil {
61
+		return err
62
+	}
63
+	err = authWriteLine(conn.transport, []byte("AUTH"))
64
+	if err != nil {
65
+		return err
66
+	}
67
+	s, err := authReadLine(in)
68
+	if err != nil {
69
+		return err
70
+	}
71
+	if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {
72
+		return errors.New("dbus: authentication protocol error")
73
+	}
74
+	s = s[1:]
75
+	for _, v := range s {
76
+		for _, m := range methods {
77
+			if name, data, status := m.FirstData(); bytes.Equal(v, name) {
78
+				var ok bool
79
+				err = authWriteLine(conn.transport, []byte("AUTH"), []byte(v), data)
80
+				if err != nil {
81
+					return err
82
+				}
83
+				switch status {
84
+				case AuthOk:
85
+					err, ok = conn.tryAuth(m, waitingForOk, in)
86
+				case AuthContinue:
87
+					err, ok = conn.tryAuth(m, waitingForData, in)
88
+				default:
89
+					panic("dbus: invalid authentication status")
90
+				}
91
+				if err != nil {
92
+					return err
93
+				}
94
+				if ok {
95
+					if conn.transport.SupportsUnixFDs() {
96
+						err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))
97
+						if err != nil {
98
+							return err
99
+						}
100
+						line, err := authReadLine(in)
101
+						if err != nil {
102
+							return err
103
+						}
104
+						switch {
105
+						case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):
106
+							conn.EnableUnixFDs()
107
+							conn.unixFD = true
108
+						case bytes.Equal(line[0], []byte("ERROR")):
109
+						default:
110
+							return errors.New("dbus: authentication protocol error")
111
+						}
112
+					}
113
+					err = authWriteLine(conn.transport, []byte("BEGIN"))
114
+					if err != nil {
115
+						return err
116
+					}
117
+					go conn.inWorker()
118
+					go conn.outWorker()
119
+					return nil
120
+				}
121
+			}
122
+		}
123
+	}
124
+	return errors.New("dbus: authentication failed")
125
+}
126
+
127
+// tryAuth tries to authenticate with m as the mechanism, using state as the
128
+// initial authState and in for reading input. It returns (nil, true) on
129
+// success, (nil, false) on a REJECTED and (someErr, false) if some other
130
+// error occured.
131
+func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {
132
+	for {
133
+		s, err := authReadLine(in)
134
+		if err != nil {
135
+			return err, false
136
+		}
137
+		switch {
138
+		case state == waitingForData && string(s[0]) == "DATA":
139
+			if len(s) != 2 {
140
+				err = authWriteLine(conn.transport, []byte("ERROR"))
141
+				if err != nil {
142
+					return err, false
143
+				}
144
+				continue
145
+			}
146
+			data, status := m.HandleData(s[1])
147
+			switch status {
148
+			case AuthOk, AuthContinue:
149
+				if len(data) != 0 {
150
+					err = authWriteLine(conn.transport, []byte("DATA"), data)
151
+					if err != nil {
152
+						return err, false
153
+					}
154
+				}
155
+				if status == AuthOk {
156
+					state = waitingForOk
157
+				}
158
+			case AuthError:
159
+				err = authWriteLine(conn.transport, []byte("ERROR"))
160
+				if err != nil {
161
+					return err, false
162
+				}
163
+			}
164
+		case state == waitingForData && string(s[0]) == "REJECTED":
165
+			return nil, false
166
+		case state == waitingForData && string(s[0]) == "ERROR":
167
+			err = authWriteLine(conn.transport, []byte("CANCEL"))
168
+			if err != nil {
169
+				return err, false
170
+			}
171
+			state = waitingForReject
172
+		case state == waitingForData && string(s[0]) == "OK":
173
+			if len(s) != 2 {
174
+				err = authWriteLine(conn.transport, []byte("CANCEL"))
175
+				if err != nil {
176
+					return err, false
177
+				}
178
+				state = waitingForReject
179
+			}
180
+			conn.uuid = string(s[1])
181
+			return nil, true
182
+		case state == waitingForData:
183
+			err = authWriteLine(conn.transport, []byte("ERROR"))
184
+			if err != nil {
185
+				return err, false
186
+			}
187
+		case state == waitingForOk && string(s[0]) == "OK":
188
+			if len(s) != 2 {
189
+				err = authWriteLine(conn.transport, []byte("CANCEL"))
190
+				if err != nil {
191
+					return err, false
192
+				}
193
+				state = waitingForReject
194
+			}
195
+			conn.uuid = string(s[1])
196
+			return nil, true
197
+		case state == waitingForOk && string(s[0]) == "REJECTED":
198
+			return nil, false
199
+		case state == waitingForOk && (string(s[0]) == "DATA" ||
200
+			string(s[0]) == "ERROR"):
201
+
202
+			err = authWriteLine(conn.transport, []byte("CANCEL"))
203
+			if err != nil {
204
+				return err, false
205
+			}
206
+			state = waitingForReject
207
+		case state == waitingForOk:
208
+			err = authWriteLine(conn.transport, []byte("ERROR"))
209
+			if err != nil {
210
+				return err, false
211
+			}
212
+		case state == waitingForReject && string(s[0]) == "REJECTED":
213
+			return nil, false
214
+		case state == waitingForReject:
215
+			return errors.New("dbus: authentication protocol error"), false
216
+		default:
217
+			panic("dbus: invalid auth state")
218
+		}
219
+	}
220
+}
221
+
222
+// authReadLine reads a line and separates it into its fields.
223
+func authReadLine(in *bufio.Reader) ([][]byte, error) {
224
+	data, err := in.ReadBytes('\n')
225
+	if err != nil {
226
+		return nil, err
227
+	}
228
+	data = bytes.TrimSuffix(data, []byte("\r\n"))
229
+	return bytes.Split(data, []byte{' '}), nil
230
+}
231
+
232
+// authWriteLine writes the given line in the authentication protocol format
233
+// (elements of data separated by a " " and terminated by "\r\n").
234
+func authWriteLine(out io.Writer, data ...[]byte) error {
235
+	buf := make([]byte, 0)
236
+	for i, v := range data {
237
+		buf = append(buf, v...)
238
+		if i != len(data)-1 {
239
+			buf = append(buf, ' ')
240
+		}
241
+	}
242
+	buf = append(buf, '\r')
243
+	buf = append(buf, '\n')
244
+	n, err := out.Write(buf)
245
+	if err != nil {
246
+		return err
247
+	}
248
+	if n != len(buf) {
249
+		return io.ErrUnexpectedEOF
250
+	}
251
+	return nil
252
+}
0 253
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package dbus
1
+
2
+import (
3
+	"encoding/hex"
4
+)
5
+
6
+// AuthExternal returns an Auth that authenticates as the given user with the
7
+// EXTERNAL mechanism.
8
+func AuthExternal(user string) Auth {
9
+	return authExternal{user}
10
+}
11
+
12
+// AuthExternal implements the EXTERNAL authentication mechanism.
13
+type authExternal struct {
14
+	user string
15
+}
16
+
17
+func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) {
18
+	b := make([]byte, 2*len(a.user))
19
+	hex.Encode(b, []byte(a.user))
20
+	return []byte("EXTERNAL"), b, AuthOk
21
+}
22
+
23
+func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) {
24
+	return nil, AuthError
25
+}
0 26
new file mode 100644
... ...
@@ -0,0 +1,102 @@
0
+package dbus
1
+
2
+import (
3
+	"bufio"
4
+	"bytes"
5
+	"crypto/rand"
6
+	"crypto/sha1"
7
+	"encoding/hex"
8
+	"os"
9
+)
10
+
11
+// AuthCookieSha1 returns an Auth that authenticates as the given user with the
12
+// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home
13
+// directory of the user.
14
+func AuthCookieSha1(user, home string) Auth {
15
+	return authCookieSha1{user, home}
16
+}
17
+
18
+type authCookieSha1 struct {
19
+	user, home string
20
+}
21
+
22
+func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) {
23
+	b := make([]byte, 2*len(a.user))
24
+	hex.Encode(b, []byte(a.user))
25
+	return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue
26
+}
27
+
28
+func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) {
29
+	challenge := make([]byte, len(data)/2)
30
+	_, err := hex.Decode(challenge, data)
31
+	if err != nil {
32
+		return nil, AuthError
33
+	}
34
+	b := bytes.Split(challenge, []byte{' '})
35
+	if len(b) != 3 {
36
+		return nil, AuthError
37
+	}
38
+	context := b[0]
39
+	id := b[1]
40
+	svchallenge := b[2]
41
+	cookie := a.getCookie(context, id)
42
+	if cookie == nil {
43
+		return nil, AuthError
44
+	}
45
+	clchallenge := a.generateChallenge()
46
+	if clchallenge == nil {
47
+		return nil, AuthError
48
+	}
49
+	hash := sha1.New()
50
+	hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'}))
51
+	hexhash := make([]byte, 2*hash.Size())
52
+	hex.Encode(hexhash, hash.Sum(nil))
53
+	data = append(clchallenge, ' ')
54
+	data = append(data, hexhash...)
55
+	resp := make([]byte, 2*len(data))
56
+	hex.Encode(resp, data)
57
+	return resp, AuthOk
58
+}
59
+
60
+// getCookie searches for the cookie identified by id in context and returns
61
+// the cookie content or nil. (Since HandleData can't return a specific error,
62
+// but only whether an error occured, this function also doesn't bother to
63
+// return an error.)
64
+func (a authCookieSha1) getCookie(context, id []byte) []byte {
65
+	file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context))
66
+	if err != nil {
67
+		return nil
68
+	}
69
+	defer file.Close()
70
+	rd := bufio.NewReader(file)
71
+	for {
72
+		line, err := rd.ReadBytes('\n')
73
+		if err != nil {
74
+			return nil
75
+		}
76
+		line = line[:len(line)-1]
77
+		b := bytes.Split(line, []byte{' '})
78
+		if len(b) != 3 {
79
+			return nil
80
+		}
81
+		if bytes.Equal(b[0], id) {
82
+			return b[2]
83
+		}
84
+	}
85
+}
86
+
87
+// generateChallenge returns a random, hex-encoded challenge, or nil on error
88
+// (see above).
89
+func (a authCookieSha1) generateChallenge() []byte {
90
+	b := make([]byte, 16)
91
+	n, err := rand.Read(b)
92
+	if err != nil {
93
+		return nil
94
+	}
95
+	if n != 16 {
96
+		return nil
97
+	}
98
+	enc := make([]byte, 32)
99
+	hex.Encode(enc, b)
100
+	return enc
101
+}
0 102
new file mode 100644
... ...
@@ -0,0 +1,147 @@
0
+package dbus
1
+
2
+import (
3
+	"errors"
4
+	"strings"
5
+)
6
+
7
+// Call represents a pending or completed method call.
8
+type Call struct {
9
+	Destination string
10
+	Path        ObjectPath
11
+	Method      string
12
+	Args        []interface{}
13
+
14
+	// Strobes when the call is complete.
15
+	Done chan *Call
16
+
17
+	// After completion, the error status. If this is non-nil, it may be an
18
+	// error message from the peer (with Error as its type) or some other error.
19
+	Err error
20
+
21
+	// Holds the response once the call is done.
22
+	Body []interface{}
23
+}
24
+
25
+var errSignature = errors.New("dbus: mismatched signature")
26
+
27
+// Store stores the body of the reply into the provided pointers. It returns
28
+// an error if the signatures of the body and retvalues don't match, or if
29
+// the error status is not nil.
30
+func (c *Call) Store(retvalues ...interface{}) error {
31
+	if c.Err != nil {
32
+		return c.Err
33
+	}
34
+
35
+	return Store(c.Body, retvalues...)
36
+}
37
+
38
+// Object represents a remote object on which methods can be invoked.
39
+type Object struct {
40
+	conn *Conn
41
+	dest string
42
+	path ObjectPath
43
+}
44
+
45
+// Call calls a method with (*Object).Go and waits for its reply.
46
+func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
47
+	return <-o.Go(method, flags, make(chan *Call, 1), args...).Done
48
+}
49
+
50
+// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given
51
+// object. The property name must be given in interface.member notation.
52
+func (o *Object) GetProperty(p string) (Variant, error) {
53
+	idx := strings.LastIndex(p, ".")
54
+	if idx == -1 || idx+1 == len(p) {
55
+		return Variant{}, errors.New("dbus: invalid property " + p)
56
+	}
57
+
58
+	iface := p[:idx]
59
+	prop := p[idx+1:]
60
+
61
+	result := Variant{}
62
+	err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result)
63
+
64
+	if err != nil {
65
+		return Variant{}, err
66
+	}
67
+
68
+	return result, nil
69
+}
70
+
71
+// Go calls a method with the given arguments asynchronously. It returns a
72
+// Call structure representing this method call. The passed channel will
73
+// return the same value once the call is done. If ch is nil, a new channel
74
+// will be allocated. Otherwise, ch has to be buffered or Go will panic.
75
+//
76
+// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
77
+// is returned of which only the Err member is valid.
78
+//
79
+// If the method parameter contains a dot ('.'), the part before the last dot
80
+// specifies the interface on which the method is called.
81
+func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
82
+	iface := ""
83
+	i := strings.LastIndex(method, ".")
84
+	if i != -1 {
85
+		iface = method[:i]
86
+	}
87
+	method = method[i+1:]
88
+	msg := new(Message)
89
+	msg.Type = TypeMethodCall
90
+	msg.serial = o.conn.getSerial()
91
+	msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
92
+	msg.Headers = make(map[HeaderField]Variant)
93
+	msg.Headers[FieldPath] = MakeVariant(o.path)
94
+	msg.Headers[FieldDestination] = MakeVariant(o.dest)
95
+	msg.Headers[FieldMember] = MakeVariant(method)
96
+	if iface != "" {
97
+		msg.Headers[FieldInterface] = MakeVariant(iface)
98
+	}
99
+	msg.Body = args
100
+	if len(args) > 0 {
101
+		msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
102
+	}
103
+	if msg.Flags&FlagNoReplyExpected == 0 {
104
+		if ch == nil {
105
+			ch = make(chan *Call, 10)
106
+		} else if cap(ch) == 0 {
107
+			panic("dbus: unbuffered channel passed to (*Object).Go")
108
+		}
109
+		call := &Call{
110
+			Destination: o.dest,
111
+			Path:        o.path,
112
+			Method:      method,
113
+			Args:        args,
114
+			Done:        ch,
115
+		}
116
+		o.conn.callsLck.Lock()
117
+		o.conn.calls[msg.serial] = call
118
+		o.conn.callsLck.Unlock()
119
+		o.conn.outLck.RLock()
120
+		if o.conn.closed {
121
+			call.Err = ErrClosed
122
+			call.Done <- call
123
+		} else {
124
+			o.conn.out <- msg
125
+		}
126
+		o.conn.outLck.RUnlock()
127
+		return call
128
+	}
129
+	o.conn.outLck.RLock()
130
+	defer o.conn.outLck.RUnlock()
131
+	if o.conn.closed {
132
+		return &Call{Err: ErrClosed}
133
+	}
134
+	o.conn.out <- msg
135
+	return &Call{Err: nil}
136
+}
137
+
138
+// Destination returns the destination that calls on o are sent to.
139
+func (o *Object) Destination() string {
140
+	return o.dest
141
+}
142
+
143
+// Path returns the path that calls on o are sent to.
144
+func (o *Object) Path() ObjectPath {
145
+	return o.path
146
+}
0 147
new file mode 100644
... ...
@@ -0,0 +1,601 @@
0
+package dbus
1
+
2
+import (
3
+	"errors"
4
+	"io"
5
+	"os"
6
+	"reflect"
7
+	"strings"
8
+	"sync"
9
+)
10
+
11
+const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
12
+
13
+var (
14
+	systemBus     *Conn
15
+	systemBusLck  sync.Mutex
16
+	sessionBus    *Conn
17
+	sessionBusLck sync.Mutex
18
+)
19
+
20
+// ErrClosed is the error returned by calls on a closed connection.
21
+var ErrClosed = errors.New("dbus: connection closed by user")
22
+
23
+// Conn represents a connection to a message bus (usually, the system or
24
+// session bus).
25
+//
26
+// Connections are either shared or private. Shared connections
27
+// are shared between calls to the functions that return them. As a result,
28
+// the methods Close, Auth and Hello must not be called on them.
29
+//
30
+// Multiple goroutines may invoke methods on a connection simultaneously.
31
+type Conn struct {
32
+	transport
33
+
34
+	busObj *Object
35
+	unixFD bool
36
+	uuid   string
37
+
38
+	names    []string
39
+	namesLck sync.RWMutex
40
+
41
+	serialLck  sync.Mutex
42
+	nextSerial uint32
43
+	serialUsed map[uint32]bool
44
+
45
+	calls    map[uint32]*Call
46
+	callsLck sync.RWMutex
47
+
48
+	handlers    map[ObjectPath]map[string]interface{}
49
+	handlersLck sync.RWMutex
50
+
51
+	out    chan *Message
52
+	closed bool
53
+	outLck sync.RWMutex
54
+
55
+	signals    []chan<- *Signal
56
+	signalsLck sync.Mutex
57
+
58
+	eavesdropped    chan<- *Message
59
+	eavesdroppedLck sync.Mutex
60
+}
61
+
62
+// SessionBus returns a shared connection to the session bus, connecting to it
63
+// if not already done.
64
+func SessionBus() (conn *Conn, err error) {
65
+	sessionBusLck.Lock()
66
+	defer sessionBusLck.Unlock()
67
+	if sessionBus != nil {
68
+		return sessionBus, nil
69
+	}
70
+	defer func() {
71
+		if conn != nil {
72
+			sessionBus = conn
73
+		}
74
+	}()
75
+	conn, err = SessionBusPrivate()
76
+	if err != nil {
77
+		return
78
+	}
79
+	if err = conn.Auth(nil); err != nil {
80
+		conn.Close()
81
+		conn = nil
82
+		return
83
+	}
84
+	if err = conn.Hello(); err != nil {
85
+		conn.Close()
86
+		conn = nil
87
+	}
88
+	return
89
+}
90
+
91
+// SessionBusPrivate returns a new private connection to the session bus.
92
+func SessionBusPrivate() (*Conn, error) {
93
+	address := os.Getenv("DBUS_SESSION_BUS_ADDRESS")
94
+	if address != "" && address != "autolaunch:" {
95
+		return Dial(address)
96
+	}
97
+
98
+	return sessionBusPlatform()
99
+}
100
+
101
+// SystemBus returns a shared connection to the system bus, connecting to it if
102
+// not already done.
103
+func SystemBus() (conn *Conn, err error) {
104
+	systemBusLck.Lock()
105
+	defer systemBusLck.Unlock()
106
+	if systemBus != nil {
107
+		return systemBus, nil
108
+	}
109
+	defer func() {
110
+		if conn != nil {
111
+			systemBus = conn
112
+		}
113
+	}()
114
+	conn, err = SystemBusPrivate()
115
+	if err != nil {
116
+		return
117
+	}
118
+	if err = conn.Auth(nil); err != nil {
119
+		conn.Close()
120
+		conn = nil
121
+		return
122
+	}
123
+	if err = conn.Hello(); err != nil {
124
+		conn.Close()
125
+		conn = nil
126
+	}
127
+	return
128
+}
129
+
130
+// SystemBusPrivate returns a new private connection to the system bus.
131
+func SystemBusPrivate() (*Conn, error) {
132
+	address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
133
+	if address != "" {
134
+		return Dial(address)
135
+	}
136
+	return Dial(defaultSystemBusAddress)
137
+}
138
+
139
+// Dial establishes a new private connection to the message bus specified by address.
140
+func Dial(address string) (*Conn, error) {
141
+	tr, err := getTransport(address)
142
+	if err != nil {
143
+		return nil, err
144
+	}
145
+	return newConn(tr)
146
+}
147
+
148
+// NewConn creates a new private *Conn from an already established connection.
149
+func NewConn(conn io.ReadWriteCloser) (*Conn, error) {
150
+	return newConn(genericTransport{conn})
151
+}
152
+
153
+// newConn creates a new *Conn from a transport.
154
+func newConn(tr transport) (*Conn, error) {
155
+	conn := new(Conn)
156
+	conn.transport = tr
157
+	conn.calls = make(map[uint32]*Call)
158
+	conn.out = make(chan *Message, 10)
159
+	conn.handlers = make(map[ObjectPath]map[string]interface{})
160
+	conn.nextSerial = 1
161
+	conn.serialUsed = map[uint32]bool{0: true}
162
+	conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
163
+	return conn, nil
164
+}
165
+
166
+// BusObject returns the object owned by the bus daemon which handles
167
+// administrative requests.
168
+func (conn *Conn) BusObject() *Object {
169
+	return conn.busObj
170
+}
171
+
172
+// Close closes the connection. Any blocked operations will return with errors
173
+// and the channels passed to Eavesdrop and Signal are closed. This method must
174
+// not be called on shared connections.
175
+func (conn *Conn) Close() error {
176
+	conn.outLck.Lock()
177
+	close(conn.out)
178
+	conn.closed = true
179
+	conn.outLck.Unlock()
180
+	conn.signalsLck.Lock()
181
+	for _, ch := range conn.signals {
182
+		close(ch)
183
+	}
184
+	conn.signalsLck.Unlock()
185
+	conn.eavesdroppedLck.Lock()
186
+	if conn.eavesdropped != nil {
187
+		close(conn.eavesdropped)
188
+	}
189
+	conn.eavesdroppedLck.Unlock()
190
+	return conn.transport.Close()
191
+}
192
+
193
+// Eavesdrop causes conn to send all incoming messages to the given channel
194
+// without further processing. Method replies, errors and signals will not be
195
+// sent to the appropiate channels and method calls will not be handled. If nil
196
+// is passed, the normal behaviour is restored.
197
+//
198
+// The caller has to make sure that ch is sufficiently buffered;
199
+// if a message arrives when a write to ch is not possible, the message is
200
+// discarded.
201
+func (conn *Conn) Eavesdrop(ch chan<- *Message) {
202
+	conn.eavesdroppedLck.Lock()
203
+	conn.eavesdropped = ch
204
+	conn.eavesdroppedLck.Unlock()
205
+}
206
+
207
+// getSerial returns an unused serial.
208
+func (conn *Conn) getSerial() uint32 {
209
+	conn.serialLck.Lock()
210
+	defer conn.serialLck.Unlock()
211
+	n := conn.nextSerial
212
+	for conn.serialUsed[n] {
213
+		n++
214
+	}
215
+	conn.serialUsed[n] = true
216
+	conn.nextSerial = n + 1
217
+	return n
218
+}
219
+
220
+// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be
221
+// called after authentication, but before sending any other messages to the
222
+// bus. Hello must not be called for shared connections.
223
+func (conn *Conn) Hello() error {
224
+	var s string
225
+	err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s)
226
+	if err != nil {
227
+		return err
228
+	}
229
+	conn.namesLck.Lock()
230
+	conn.names = make([]string, 1)
231
+	conn.names[0] = s
232
+	conn.namesLck.Unlock()
233
+	return nil
234
+}
235
+
236
+// inWorker runs in an own goroutine, reading incoming messages from the
237
+// transport and dispatching them appropiately.
238
+func (conn *Conn) inWorker() {
239
+	for {
240
+		msg, err := conn.ReadMessage()
241
+		if err == nil {
242
+			conn.eavesdroppedLck.Lock()
243
+			if conn.eavesdropped != nil {
244
+				select {
245
+				case conn.eavesdropped <- msg:
246
+				default:
247
+				}
248
+				conn.eavesdroppedLck.Unlock()
249
+				continue
250
+			}
251
+			conn.eavesdroppedLck.Unlock()
252
+			dest, _ := msg.Headers[FieldDestination].value.(string)
253
+			found := false
254
+			if dest == "" {
255
+				found = true
256
+			} else {
257
+				conn.namesLck.RLock()
258
+				if len(conn.names) == 0 {
259
+					found = true
260
+				}
261
+				for _, v := range conn.names {
262
+					if dest == v {
263
+						found = true
264
+						break
265
+					}
266
+				}
267
+				conn.namesLck.RUnlock()
268
+			}
269
+			if !found {
270
+				// Eavesdropped a message, but no channel for it is registered.
271
+				// Ignore it.
272
+				continue
273
+			}
274
+			switch msg.Type {
275
+			case TypeMethodReply, TypeError:
276
+				serial := msg.Headers[FieldReplySerial].value.(uint32)
277
+				conn.callsLck.Lock()
278
+				if c, ok := conn.calls[serial]; ok {
279
+					if msg.Type == TypeError {
280
+						name, _ := msg.Headers[FieldErrorName].value.(string)
281
+						c.Err = Error{name, msg.Body}
282
+					} else {
283
+						c.Body = msg.Body
284
+					}
285
+					c.Done <- c
286
+					conn.serialLck.Lock()
287
+					delete(conn.serialUsed, serial)
288
+					conn.serialLck.Unlock()
289
+					delete(conn.calls, serial)
290
+				}
291
+				conn.callsLck.Unlock()
292
+			case TypeSignal:
293
+				iface := msg.Headers[FieldInterface].value.(string)
294
+				member := msg.Headers[FieldMember].value.(string)
295
+				// as per http://dbus.freedesktop.org/doc/dbus-specification.html ,
296
+				// sender is optional for signals.
297
+				sender, _ := msg.Headers[FieldSender].value.(string)
298
+				if iface == "org.freedesktop.DBus" && member == "NameLost" &&
299
+					sender == "org.freedesktop.DBus" {
300
+
301
+					name, _ := msg.Body[0].(string)
302
+					conn.namesLck.Lock()
303
+					for i, v := range conn.names {
304
+						if v == name {
305
+							copy(conn.names[i:], conn.names[i+1:])
306
+							conn.names = conn.names[:len(conn.names)-1]
307
+						}
308
+					}
309
+					conn.namesLck.Unlock()
310
+				}
311
+				signal := &Signal{
312
+					Sender: sender,
313
+					Path:   msg.Headers[FieldPath].value.(ObjectPath),
314
+					Name:   iface + "." + member,
315
+					Body:   msg.Body,
316
+				}
317
+				conn.signalsLck.Lock()
318
+				for _, ch := range conn.signals {
319
+					// don't block trying to send a signal
320
+					select {
321
+					case ch <- signal:
322
+					default:
323
+					}
324
+				}
325
+				conn.signalsLck.Unlock()
326
+			case TypeMethodCall:
327
+				go conn.handleCall(msg)
328
+			}
329
+		} else if _, ok := err.(InvalidMessageError); !ok {
330
+			// Some read error occured (usually EOF); we can't really do
331
+			// anything but to shut down all stuff and returns errors to all
332
+			// pending replies.
333
+			conn.Close()
334
+			conn.callsLck.RLock()
335
+			for _, v := range conn.calls {
336
+				v.Err = err
337
+				v.Done <- v
338
+			}
339
+			conn.callsLck.RUnlock()
340
+			return
341
+		}
342
+		// invalid messages are ignored
343
+	}
344
+}
345
+
346
+// Names returns the list of all names that are currently owned by this
347
+// connection. The slice is always at least one element long, the first element
348
+// being the unique name of the connection.
349
+func (conn *Conn) Names() []string {
350
+	conn.namesLck.RLock()
351
+	// copy the slice so it can't be modified
352
+	s := make([]string, len(conn.names))
353
+	copy(s, conn.names)
354
+	conn.namesLck.RUnlock()
355
+	return s
356
+}
357
+
358
+// Object returns the object identified by the given destination name and path.
359
+func (conn *Conn) Object(dest string, path ObjectPath) *Object {
360
+	return &Object{conn, dest, path}
361
+}
362
+
363
+// outWorker runs in an own goroutine, encoding and sending messages that are
364
+// sent to conn.out.
365
+func (conn *Conn) outWorker() {
366
+	for msg := range conn.out {
367
+		err := conn.SendMessage(msg)
368
+		conn.callsLck.RLock()
369
+		if err != nil {
370
+			if c := conn.calls[msg.serial]; c != nil {
371
+				c.Err = err
372
+				c.Done <- c
373
+			}
374
+			conn.serialLck.Lock()
375
+			delete(conn.serialUsed, msg.serial)
376
+			conn.serialLck.Unlock()
377
+		} else if msg.Type != TypeMethodCall {
378
+			conn.serialLck.Lock()
379
+			delete(conn.serialUsed, msg.serial)
380
+			conn.serialLck.Unlock()
381
+		}
382
+		conn.callsLck.RUnlock()
383
+	}
384
+}
385
+
386
+// Send sends the given message to the message bus. You usually don't need to
387
+// use this; use the higher-level equivalents (Call / Go, Emit and Export)
388
+// instead. If msg is a method call and NoReplyExpected is not set, a non-nil
389
+// call is returned and the same value is sent to ch (which must be buffered)
390
+// once the call is complete. Otherwise, ch is ignored and a Call structure is
391
+// returned of which only the Err member is valid.
392
+func (conn *Conn) Send(msg *Message, ch chan *Call) *Call {
393
+	var call *Call
394
+
395
+	msg.serial = conn.getSerial()
396
+	if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 {
397
+		if ch == nil {
398
+			ch = make(chan *Call, 5)
399
+		} else if cap(ch) == 0 {
400
+			panic("dbus: unbuffered channel passed to (*Conn).Send")
401
+		}
402
+		call = new(Call)
403
+		call.Destination, _ = msg.Headers[FieldDestination].value.(string)
404
+		call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath)
405
+		iface, _ := msg.Headers[FieldInterface].value.(string)
406
+		member, _ := msg.Headers[FieldMember].value.(string)
407
+		call.Method = iface + "." + member
408
+		call.Args = msg.Body
409
+		call.Done = ch
410
+		conn.callsLck.Lock()
411
+		conn.calls[msg.serial] = call
412
+		conn.callsLck.Unlock()
413
+		conn.outLck.RLock()
414
+		if conn.closed {
415
+			call.Err = ErrClosed
416
+			call.Done <- call
417
+		} else {
418
+			conn.out <- msg
419
+		}
420
+		conn.outLck.RUnlock()
421
+	} else {
422
+		conn.outLck.RLock()
423
+		if conn.closed {
424
+			call = &Call{Err: ErrClosed}
425
+		} else {
426
+			conn.out <- msg
427
+			call = &Call{Err: nil}
428
+		}
429
+		conn.outLck.RUnlock()
430
+	}
431
+	return call
432
+}
433
+
434
+// sendError creates an error message corresponding to the parameters and sends
435
+// it to conn.out.
436
+func (conn *Conn) sendError(e Error, dest string, serial uint32) {
437
+	msg := new(Message)
438
+	msg.Type = TypeError
439
+	msg.serial = conn.getSerial()
440
+	msg.Headers = make(map[HeaderField]Variant)
441
+	if dest != "" {
442
+		msg.Headers[FieldDestination] = MakeVariant(dest)
443
+	}
444
+	msg.Headers[FieldErrorName] = MakeVariant(e.Name)
445
+	msg.Headers[FieldReplySerial] = MakeVariant(serial)
446
+	msg.Body = e.Body
447
+	if len(e.Body) > 0 {
448
+		msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...))
449
+	}
450
+	conn.outLck.RLock()
451
+	if !conn.closed {
452
+		conn.out <- msg
453
+	}
454
+	conn.outLck.RUnlock()
455
+}
456
+
457
+// sendReply creates a method reply message corresponding to the parameters and
458
+// sends it to conn.out.
459
+func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
460
+	msg := new(Message)
461
+	msg.Type = TypeMethodReply
462
+	msg.serial = conn.getSerial()
463
+	msg.Headers = make(map[HeaderField]Variant)
464
+	if dest != "" {
465
+		msg.Headers[FieldDestination] = MakeVariant(dest)
466
+	}
467
+	msg.Headers[FieldReplySerial] = MakeVariant(serial)
468
+	msg.Body = values
469
+	if len(values) > 0 {
470
+		msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
471
+	}
472
+	conn.outLck.RLock()
473
+	if !conn.closed {
474
+		conn.out <- msg
475
+	}
476
+	conn.outLck.RUnlock()
477
+}
478
+
479
+// Signal registers the given channel to be passed all received signal messages.
480
+// The caller has to make sure that ch is sufficiently buffered; if a message
481
+// arrives when a write to c is not possible, it is discarded.
482
+//
483
+// Multiple of these channels can be registered at the same time. Passing a
484
+// channel that already is registered will remove it from the list of the
485
+// registered channels.
486
+//
487
+// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a
488
+// channel for eavesdropped messages, this channel receives all signals, and
489
+// none of the channels passed to Signal will receive any signals.
490
+func (conn *Conn) Signal(ch chan<- *Signal) {
491
+	conn.signalsLck.Lock()
492
+	conn.signals = append(conn.signals, ch)
493
+	conn.signalsLck.Unlock()
494
+}
495
+
496
+// SupportsUnixFDs returns whether the underlying transport supports passing of
497
+// unix file descriptors. If this is false, method calls containing unix file
498
+// descriptors will return an error and emitted signals containing them will
499
+// not be sent.
500
+func (conn *Conn) SupportsUnixFDs() bool {
501
+	return conn.unixFD
502
+}
503
+
504
+// Error represents a D-Bus message of type Error.
505
+type Error struct {
506
+	Name string
507
+	Body []interface{}
508
+}
509
+
510
+func (e Error) Error() string {
511
+	if len(e.Body) >= 1 {
512
+		s, ok := e.Body[0].(string)
513
+		if ok {
514
+			return s
515
+		}
516
+	}
517
+	return e.Name
518
+}
519
+
520
+// Signal represents a D-Bus message of type Signal. The name member is given in
521
+// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost.
522
+type Signal struct {
523
+	Sender string
524
+	Path   ObjectPath
525
+	Name   string
526
+	Body   []interface{}
527
+}
528
+
529
+// transport is a D-Bus transport.
530
+type transport interface {
531
+	// Read and Write raw data (for example, for the authentication protocol).
532
+	io.ReadWriteCloser
533
+
534
+	// Send the initial null byte used for the EXTERNAL mechanism.
535
+	SendNullByte() error
536
+
537
+	// Returns whether this transport supports passing Unix FDs.
538
+	SupportsUnixFDs() bool
539
+
540
+	// Signal the transport that Unix FD passing is enabled for this connection.
541
+	EnableUnixFDs()
542
+
543
+	// Read / send a message, handling things like Unix FDs.
544
+	ReadMessage() (*Message, error)
545
+	SendMessage(*Message) error
546
+}
547
+
548
+func getTransport(address string) (transport, error) {
549
+	var err error
550
+	var t transport
551
+
552
+	m := map[string]func(string) (transport, error){
553
+		"unix": newUnixTransport,
554
+	}
555
+	addresses := strings.Split(address, ";")
556
+	for _, v := range addresses {
557
+		i := strings.IndexRune(v, ':')
558
+		if i == -1 {
559
+			err = errors.New("dbus: invalid bus address (no transport)")
560
+			continue
561
+		}
562
+		f := m[v[:i]]
563
+		if f == nil {
564
+			err = errors.New("dbus: invalid bus address (invalid or unsupported transport)")
565
+		}
566
+		t, err = f(v[i+1:])
567
+		if err == nil {
568
+			return t, nil
569
+		}
570
+	}
571
+	return nil, err
572
+}
573
+
574
+// dereferenceAll returns a slice that, assuming that vs is a slice of pointers
575
+// of arbitrary types, containes the values that are obtained from dereferencing
576
+// all elements in vs.
577
+func dereferenceAll(vs []interface{}) []interface{} {
578
+	for i := range vs {
579
+		v := reflect.ValueOf(vs[i])
580
+		v = v.Elem()
581
+		vs[i] = v.Interface()
582
+	}
583
+	return vs
584
+}
585
+
586
+// getKey gets a key from a the list of keys. Returns "" on error / not found...
587
+func getKey(s, key string) string {
588
+	i := strings.Index(s, key)
589
+	if i == -1 {
590
+		return ""
591
+	}
592
+	if i+len(key)+1 >= len(s) || s[i+len(key)] != '=' {
593
+		return ""
594
+	}
595
+	j := strings.Index(s, ",")
596
+	if j == -1 {
597
+		j = len(s)
598
+	}
599
+	return s[i+len(key)+1 : j]
600
+}
0 601
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package dbus
1
+
2
+import (
3
+	"errors"
4
+	"os/exec"
5
+)
6
+
7
+func sessionBusPlatform() (*Conn, error) {
8
+	cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET")
9
+	b, err := cmd.CombinedOutput()
10
+
11
+	if err != nil {
12
+		return nil, err
13
+	}
14
+
15
+	if len(b) == 0 {
16
+		return nil, errors.New("dbus: couldn't determine address of session bus")
17
+	}
18
+
19
+	return Dial("unix:path=" + string(b[:len(b)-1]))
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+// +build !darwin
1
+
2
+package dbus
3
+
4
+import (
5
+	"bytes"
6
+	"errors"
7
+	"os/exec"
8
+)
9
+
10
+func sessionBusPlatform() (*Conn, error) {
11
+	cmd := exec.Command("dbus-launch")
12
+	b, err := cmd.CombinedOutput()
13
+
14
+	if err != nil {
15
+		return nil, err
16
+	}
17
+
18
+	i := bytes.IndexByte(b, '=')
19
+	j := bytes.IndexByte(b, '\n')
20
+
21
+	if i == -1 || j == -1 {
22
+		return nil, errors.New("dbus: couldn't determine address of session bus")
23
+	}
24
+
25
+	return Dial(string(b[i+1 : j]))
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,199 @@
0
+package dbus
1
+
2
+import "testing"
3
+
4
+func TestSessionBus(t *testing.T) {
5
+	_, err := SessionBus()
6
+	if err != nil {
7
+		t.Error(err)
8
+	}
9
+}
10
+
11
+func TestSystemBus(t *testing.T) {
12
+	_, err := SystemBus()
13
+	if err != nil {
14
+		t.Error(err)
15
+	}
16
+}
17
+
18
+func TestSend(t *testing.T) {
19
+	bus, err := SessionBus()
20
+	if err != nil {
21
+		t.Error(err)
22
+	}
23
+	ch := make(chan *Call, 1)
24
+	msg := &Message{
25
+		Type:  TypeMethodCall,
26
+		Flags: 0,
27
+		Headers: map[HeaderField]Variant{
28
+			FieldDestination: MakeVariant(bus.Names()[0]),
29
+			FieldPath:        MakeVariant(ObjectPath("/org/freedesktop/DBus")),
30
+			FieldInterface:   MakeVariant("org.freedesktop.DBus.Peer"),
31
+			FieldMember:      MakeVariant("Ping"),
32
+		},
33
+	}
34
+	call := bus.Send(msg, ch)
35
+	<-ch
36
+	if call.Err != nil {
37
+		t.Error(call.Err)
38
+	}
39
+}
40
+
41
+type server struct{}
42
+
43
+func (server) Double(i int64) (int64, *Error) {
44
+	return 2 * i, nil
45
+}
46
+
47
+func BenchmarkCall(b *testing.B) {
48
+	b.StopTimer()
49
+	var s string
50
+	bus, err := SessionBus()
51
+	if err != nil {
52
+		b.Fatal(err)
53
+	}
54
+	name := bus.Names()[0]
55
+	obj := bus.BusObject()
56
+	b.StartTimer()
57
+	for i := 0; i < b.N; i++ {
58
+		err := obj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&s)
59
+		if err != nil {
60
+			b.Fatal(err)
61
+		}
62
+		if s != name {
63
+			b.Errorf("got %s, wanted %s", s, name)
64
+		}
65
+	}
66
+}
67
+
68
+func BenchmarkCallAsync(b *testing.B) {
69
+	b.StopTimer()
70
+	bus, err := SessionBus()
71
+	if err != nil {
72
+		b.Fatal(err)
73
+	}
74
+	name := bus.Names()[0]
75
+	obj := bus.BusObject()
76
+	c := make(chan *Call, 50)
77
+	done := make(chan struct{})
78
+	go func() {
79
+		for i := 0; i < b.N; i++ {
80
+			v := <-c
81
+			if v.Err != nil {
82
+				b.Error(v.Err)
83
+			}
84
+			s := v.Body[0].(string)
85
+			if s != name {
86
+				b.Errorf("got %s, wanted %s", s, name)
87
+			}
88
+		}
89
+		close(done)
90
+	}()
91
+	b.StartTimer()
92
+	for i := 0; i < b.N; i++ {
93
+		obj.Go("org.freedesktop.DBus.GetNameOwner", 0, c, name)
94
+	}
95
+	<-done
96
+}
97
+
98
+func BenchmarkServe(b *testing.B) {
99
+	b.StopTimer()
100
+	srv, err := SessionBus()
101
+	if err != nil {
102
+		b.Fatal(err)
103
+	}
104
+	cli, err := SessionBusPrivate()
105
+	if err != nil {
106
+		b.Fatal(err)
107
+	}
108
+	if err = cli.Auth(nil); err != nil {
109
+		b.Fatal(err)
110
+	}
111
+	if err = cli.Hello(); err != nil {
112
+		b.Fatal(err)
113
+	}
114
+	benchmarkServe(b, srv, cli)
115
+}
116
+
117
+func BenchmarkServeAsync(b *testing.B) {
118
+	b.StopTimer()
119
+	srv, err := SessionBus()
120
+	if err != nil {
121
+		b.Fatal(err)
122
+	}
123
+	cli, err := SessionBusPrivate()
124
+	if err != nil {
125
+		b.Fatal(err)
126
+	}
127
+	if err = cli.Auth(nil); err != nil {
128
+		b.Fatal(err)
129
+	}
130
+	if err = cli.Hello(); err != nil {
131
+		b.Fatal(err)
132
+	}
133
+	benchmarkServeAsync(b, srv, cli)
134
+}
135
+
136
+func BenchmarkServeSameConn(b *testing.B) {
137
+	b.StopTimer()
138
+	bus, err := SessionBus()
139
+	if err != nil {
140
+		b.Fatal(err)
141
+	}
142
+
143
+	benchmarkServe(b, bus, bus)
144
+}
145
+
146
+func BenchmarkServeSameConnAsync(b *testing.B) {
147
+	b.StopTimer()
148
+	bus, err := SessionBus()
149
+	if err != nil {
150
+		b.Fatal(err)
151
+	}
152
+
153
+	benchmarkServeAsync(b, bus, bus)
154
+}
155
+
156
+func benchmarkServe(b *testing.B, srv, cli *Conn) {
157
+	var r int64
158
+	var err error
159
+	dest := srv.Names()[0]
160
+	srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
161
+	obj := cli.Object(dest, "/org/guelfey/DBus/Test")
162
+	b.StartTimer()
163
+	for i := 0; i < b.N; i++ {
164
+		err = obj.Call("org.guelfey.DBus.Test.Double", 0, int64(i)).Store(&r)
165
+		if err != nil {
166
+			b.Fatal(err)
167
+		}
168
+		if r != 2*int64(i) {
169
+			b.Errorf("got %d, wanted %d", r, 2*int64(i))
170
+		}
171
+	}
172
+}
173
+
174
+func benchmarkServeAsync(b *testing.B, srv, cli *Conn) {
175
+	dest := srv.Names()[0]
176
+	srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
177
+	obj := cli.Object(dest, "/org/guelfey/DBus/Test")
178
+	c := make(chan *Call, 50)
179
+	done := make(chan struct{})
180
+	go func() {
181
+		for i := 0; i < b.N; i++ {
182
+			v := <-c
183
+			if v.Err != nil {
184
+				b.Fatal(v.Err)
185
+			}
186
+			i, r := v.Args[0].(int64), v.Body[0].(int64)
187
+			if 2*i != r {
188
+				b.Errorf("got %d, wanted %d", r, 2*i)
189
+			}
190
+		}
191
+		close(done)
192
+	}()
193
+	b.StartTimer()
194
+	for i := 0; i < b.N; i++ {
195
+		obj.Go("org.guelfey.DBus.Test.Double", 0, c, int64(i))
196
+	}
197
+	<-done
198
+}
0 199
new file mode 100644
... ...
@@ -0,0 +1,258 @@
0
+package dbus
1
+
2
+import (
3
+	"errors"
4
+	"reflect"
5
+	"strings"
6
+)
7
+
8
+var (
9
+	byteType        = reflect.TypeOf(byte(0))
10
+	boolType        = reflect.TypeOf(false)
11
+	uint8Type       = reflect.TypeOf(uint8(0))
12
+	int16Type       = reflect.TypeOf(int16(0))
13
+	uint16Type      = reflect.TypeOf(uint16(0))
14
+	int32Type       = reflect.TypeOf(int32(0))
15
+	uint32Type      = reflect.TypeOf(uint32(0))
16
+	int64Type       = reflect.TypeOf(int64(0))
17
+	uint64Type      = reflect.TypeOf(uint64(0))
18
+	float64Type     = reflect.TypeOf(float64(0))
19
+	stringType      = reflect.TypeOf("")
20
+	signatureType   = reflect.TypeOf(Signature{""})
21
+	objectPathType  = reflect.TypeOf(ObjectPath(""))
22
+	variantType     = reflect.TypeOf(Variant{Signature{""}, nil})
23
+	interfacesType  = reflect.TypeOf([]interface{}{})
24
+	unixFDType      = reflect.TypeOf(UnixFD(0))
25
+	unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
26
+)
27
+
28
+// An InvalidTypeError signals that a value which cannot be represented in the
29
+// D-Bus wire format was passed to a function.
30
+type InvalidTypeError struct {
31
+	Type reflect.Type
32
+}
33
+
34
+func (e InvalidTypeError) Error() string {
35
+	return "dbus: invalid type " + e.Type.String()
36
+}
37
+
38
+// Store copies the values contained in src to dest, which must be a slice of
39
+// pointers. It converts slices of interfaces from src to corresponding structs
40
+// in dest. An error is returned if the lengths of src and dest or the types of
41
+// their elements don't match.
42
+func Store(src []interface{}, dest ...interface{}) error {
43
+	if len(src) != len(dest) {
44
+		return errors.New("dbus.Store: length mismatch")
45
+	}
46
+
47
+	for i := range src {
48
+		if err := store(src[i], dest[i]); err != nil {
49
+			return err
50
+		}
51
+	}
52
+	return nil
53
+}
54
+
55
+func store(src, dest interface{}) error {
56
+	if reflect.TypeOf(dest).Elem() == reflect.TypeOf(src) {
57
+		reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src))
58
+		return nil
59
+	} else if hasStruct(dest) {
60
+		rv := reflect.ValueOf(dest).Elem()
61
+		switch rv.Kind() {
62
+		case reflect.Struct:
63
+			vs, ok := src.([]interface{})
64
+			if !ok {
65
+				return errors.New("dbus.Store: type mismatch")
66
+			}
67
+			t := rv.Type()
68
+			ndest := make([]interface{}, 0, rv.NumField())
69
+			for i := 0; i < rv.NumField(); i++ {
70
+				field := t.Field(i)
71
+				if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
72
+					ndest = append(ndest, rv.Field(i).Addr().Interface())
73
+				}
74
+			}
75
+			if len(vs) != len(ndest) {
76
+				return errors.New("dbus.Store: type mismatch")
77
+			}
78
+			err := Store(vs, ndest...)
79
+			if err != nil {
80
+				return errors.New("dbus.Store: type mismatch")
81
+			}
82
+		case reflect.Slice:
83
+			sv := reflect.ValueOf(src)
84
+			if sv.Kind() != reflect.Slice {
85
+				return errors.New("dbus.Store: type mismatch")
86
+			}
87
+			rv.Set(reflect.MakeSlice(rv.Type(), sv.Len(), sv.Len()))
88
+			for i := 0; i < sv.Len(); i++ {
89
+				if err := store(sv.Index(i).Interface(), rv.Index(i).Addr().Interface()); err != nil {
90
+					return err
91
+				}
92
+			}
93
+		case reflect.Map:
94
+			sv := reflect.ValueOf(src)
95
+			if sv.Kind() != reflect.Map {
96
+				return errors.New("dbus.Store: type mismatch")
97
+			}
98
+			keys := sv.MapKeys()
99
+			rv.Set(reflect.MakeMap(sv.Type()))
100
+			for _, key := range keys {
101
+				v := reflect.New(sv.Type().Elem())
102
+				if err := store(v, sv.MapIndex(key).Interface()); err != nil {
103
+					return err
104
+				}
105
+				rv.SetMapIndex(key, v.Elem())
106
+			}
107
+		default:
108
+			return errors.New("dbus.Store: type mismatch")
109
+		}
110
+		return nil
111
+	} else {
112
+		return errors.New("dbus.Store: type mismatch")
113
+	}
114
+}
115
+
116
+func hasStruct(v interface{}) bool {
117
+	t := reflect.TypeOf(v)
118
+	for {
119
+		switch t.Kind() {
120
+		case reflect.Struct:
121
+			return true
122
+		case reflect.Slice, reflect.Ptr, reflect.Map:
123
+			t = t.Elem()
124
+		default:
125
+			return false
126
+		}
127
+	}
128
+}
129
+
130
+// An ObjectPath is an object path as defined by the D-Bus spec.
131
+type ObjectPath string
132
+
133
+// IsValid returns whether the object path is valid.
134
+func (o ObjectPath) IsValid() bool {
135
+	s := string(o)
136
+	if len(s) == 0 {
137
+		return false
138
+	}
139
+	if s[0] != '/' {
140
+		return false
141
+	}
142
+	if s[len(s)-1] == '/' && len(s) != 1 {
143
+		return false
144
+	}
145
+	// probably not used, but technically possible
146
+	if s == "/" {
147
+		return true
148
+	}
149
+	split := strings.Split(s[1:], "/")
150
+	for _, v := range split {
151
+		if len(v) == 0 {
152
+			return false
153
+		}
154
+		for _, c := range v {
155
+			if !isMemberChar(c) {
156
+				return false
157
+			}
158
+		}
159
+	}
160
+	return true
161
+}
162
+
163
+// A UnixFD is a Unix file descriptor sent over the wire. See the package-level
164
+// documentation for more information about Unix file descriptor passsing.
165
+type UnixFD int32
166
+
167
+// A UnixFDIndex is the representation of a Unix file descriptor in a message.
168
+type UnixFDIndex uint32
169
+
170
+// alignment returns the alignment of values of type t.
171
+func alignment(t reflect.Type) int {
172
+	switch t {
173
+	case variantType:
174
+		return 1
175
+	case objectPathType:
176
+		return 4
177
+	case signatureType:
178
+		return 1
179
+	case interfacesType: // sometimes used for structs
180
+		return 8
181
+	}
182
+	switch t.Kind() {
183
+	case reflect.Uint8:
184
+		return 1
185
+	case reflect.Uint16, reflect.Int16:
186
+		return 2
187
+	case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
188
+		return 4
189
+	case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
190
+		return 8
191
+	case reflect.Ptr:
192
+		return alignment(t.Elem())
193
+	}
194
+	return 1
195
+}
196
+
197
+// isKeyType returns whether t is a valid type for a D-Bus dict.
198
+func isKeyType(t reflect.Type) bool {
199
+	switch t.Kind() {
200
+	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
201
+		reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
202
+		reflect.String:
203
+
204
+		return true
205
+	}
206
+	return false
207
+}
208
+
209
+// isValidInterface returns whether s is a valid name for an interface.
210
+func isValidInterface(s string) bool {
211
+	if len(s) == 0 || len(s) > 255 || s[0] == '.' {
212
+		return false
213
+	}
214
+	elem := strings.Split(s, ".")
215
+	if len(elem) < 2 {
216
+		return false
217
+	}
218
+	for _, v := range elem {
219
+		if len(v) == 0 {
220
+			return false
221
+		}
222
+		if v[0] >= '0' && v[0] <= '9' {
223
+			return false
224
+		}
225
+		for _, c := range v {
226
+			if !isMemberChar(c) {
227
+				return false
228
+			}
229
+		}
230
+	}
231
+	return true
232
+}
233
+
234
+// isValidMember returns whether s is a valid name for a member.
235
+func isValidMember(s string) bool {
236
+	if len(s) == 0 || len(s) > 255 {
237
+		return false
238
+	}
239
+	i := strings.Index(s, ".")
240
+	if i != -1 {
241
+		return false
242
+	}
243
+	if s[0] >= '0' && s[0] <= '9' {
244
+		return false
245
+	}
246
+	for _, c := range s {
247
+		if !isMemberChar(c) {
248
+			return false
249
+		}
250
+	}
251
+	return true
252
+}
253
+
254
+func isMemberChar(c rune) bool {
255
+	return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
256
+		(c >= 'a' && c <= 'z') || c == '_'
257
+}
0 258
new file mode 100644
... ...
@@ -0,0 +1,228 @@
0
+package dbus
1
+
2
+import (
3
+	"encoding/binary"
4
+	"io"
5
+	"reflect"
6
+)
7
+
8
+type decoder struct {
9
+	in    io.Reader
10
+	order binary.ByteOrder
11
+	pos   int
12
+}
13
+
14
+// newDecoder returns a new decoder that reads values from in. The input is
15
+// expected to be in the given byte order.
16
+func newDecoder(in io.Reader, order binary.ByteOrder) *decoder {
17
+	dec := new(decoder)
18
+	dec.in = in
19
+	dec.order = order
20
+	return dec
21
+}
22
+
23
+// align aligns the input to the given boundary and panics on error.
24
+func (dec *decoder) align(n int) {
25
+	if dec.pos%n != 0 {
26
+		newpos := (dec.pos + n - 1) & ^(n - 1)
27
+		empty := make([]byte, newpos-dec.pos)
28
+		if _, err := io.ReadFull(dec.in, empty); err != nil {
29
+			panic(err)
30
+		}
31
+		dec.pos = newpos
32
+	}
33
+}
34
+
35
+// Calls binary.Read(dec.in, dec.order, v) and panics on read errors.
36
+func (dec *decoder) binread(v interface{}) {
37
+	if err := binary.Read(dec.in, dec.order, v); err != nil {
38
+		panic(err)
39
+	}
40
+}
41
+
42
+func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) {
43
+	defer func() {
44
+		var ok bool
45
+		v := recover()
46
+		if err, ok = v.(error); ok {
47
+			if err == io.EOF || err == io.ErrUnexpectedEOF {
48
+				err = FormatError("unexpected EOF")
49
+			}
50
+		}
51
+	}()
52
+	vs = make([]interface{}, 0)
53
+	s := sig.str
54
+	for s != "" {
55
+		err, rem := validSingle(s, 0)
56
+		if err != nil {
57
+			return nil, err
58
+		}
59
+		v := dec.decode(s[:len(s)-len(rem)], 0)
60
+		vs = append(vs, v)
61
+		s = rem
62
+	}
63
+	return vs, nil
64
+}
65
+
66
+func (dec *decoder) decode(s string, depth int) interface{} {
67
+	dec.align(alignment(typeFor(s)))
68
+	switch s[0] {
69
+	case 'y':
70
+		var b [1]byte
71
+		if _, err := dec.in.Read(b[:]); err != nil {
72
+			panic(err)
73
+		}
74
+		dec.pos++
75
+		return b[0]
76
+	case 'b':
77
+		i := dec.decode("u", depth).(uint32)
78
+		switch {
79
+		case i == 0:
80
+			return false
81
+		case i == 1:
82
+			return true
83
+		default:
84
+			panic(FormatError("invalid value for boolean"))
85
+		}
86
+	case 'n':
87
+		var i int16
88
+		dec.binread(&i)
89
+		dec.pos += 2
90
+		return i
91
+	case 'i':
92
+		var i int32
93
+		dec.binread(&i)
94
+		dec.pos += 4
95
+		return i
96
+	case 'x':
97
+		var i int64
98
+		dec.binread(&i)
99
+		dec.pos += 8
100
+		return i
101
+	case 'q':
102
+		var i uint16
103
+		dec.binread(&i)
104
+		dec.pos += 2
105
+		return i
106
+	case 'u':
107
+		var i uint32
108
+		dec.binread(&i)
109
+		dec.pos += 4
110
+		return i
111
+	case 't':
112
+		var i uint64
113
+		dec.binread(&i)
114
+		dec.pos += 8
115
+		return i
116
+	case 'd':
117
+		var f float64
118
+		dec.binread(&f)
119
+		dec.pos += 8
120
+		return f
121
+	case 's':
122
+		length := dec.decode("u", depth).(uint32)
123
+		b := make([]byte, int(length)+1)
124
+		if _, err := io.ReadFull(dec.in, b); err != nil {
125
+			panic(err)
126
+		}
127
+		dec.pos += int(length) + 1
128
+		return string(b[:len(b)-1])
129
+	case 'o':
130
+		return ObjectPath(dec.decode("s", depth).(string))
131
+	case 'g':
132
+		length := dec.decode("y", depth).(byte)
133
+		b := make([]byte, int(length)+1)
134
+		if _, err := io.ReadFull(dec.in, b); err != nil {
135
+			panic(err)
136
+		}
137
+		dec.pos += int(length) + 1
138
+		sig, err := ParseSignature(string(b[:len(b)-1]))
139
+		if err != nil {
140
+			panic(err)
141
+		}
142
+		return sig
143
+	case 'v':
144
+		if depth >= 64 {
145
+			panic(FormatError("input exceeds container depth limit"))
146
+		}
147
+		var variant Variant
148
+		sig := dec.decode("g", depth).(Signature)
149
+		if len(sig.str) == 0 {
150
+			panic(FormatError("variant signature is empty"))
151
+		}
152
+		err, rem := validSingle(sig.str, 0)
153
+		if err != nil {
154
+			panic(err)
155
+		}
156
+		if rem != "" {
157
+			panic(FormatError("variant signature has multiple types"))
158
+		}
159
+		variant.sig = sig
160
+		variant.value = dec.decode(sig.str, depth+1)
161
+		return variant
162
+	case 'h':
163
+		return UnixFDIndex(dec.decode("u", depth).(uint32))
164
+	case 'a':
165
+		if len(s) > 1 && s[1] == '{' {
166
+			ksig := s[2:3]
167
+			vsig := s[3 : len(s)-1]
168
+			v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig)))
169
+			if depth >= 63 {
170
+				panic(FormatError("input exceeds container depth limit"))
171
+			}
172
+			length := dec.decode("u", depth).(uint32)
173
+			// Even for empty maps, the correct padding must be included
174
+			dec.align(8)
175
+			spos := dec.pos
176
+			for dec.pos < spos+int(length) {
177
+				dec.align(8)
178
+				if !isKeyType(v.Type().Key()) {
179
+					panic(InvalidTypeError{v.Type()})
180
+				}
181
+				kv := dec.decode(ksig, depth+2)
182
+				vv := dec.decode(vsig, depth+2)
183
+				v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
184
+			}
185
+			return v.Interface()
186
+		}
187
+		if depth >= 64 {
188
+			panic(FormatError("input exceeds container depth limit"))
189
+		}
190
+		length := dec.decode("u", depth).(uint32)
191
+		v := reflect.MakeSlice(reflect.SliceOf(typeFor(s[1:])), 0, int(length))
192
+		// Even for empty arrays, the correct padding must be included
193
+		dec.align(alignment(typeFor(s[1:])))
194
+		spos := dec.pos
195
+		for dec.pos < spos+int(length) {
196
+			ev := dec.decode(s[1:], depth+1)
197
+			v = reflect.Append(v, reflect.ValueOf(ev))
198
+		}
199
+		return v.Interface()
200
+	case '(':
201
+		if depth >= 64 {
202
+			panic(FormatError("input exceeds container depth limit"))
203
+		}
204
+		dec.align(8)
205
+		v := make([]interface{}, 0)
206
+		s = s[1 : len(s)-1]
207
+		for s != "" {
208
+			err, rem := validSingle(s, 0)
209
+			if err != nil {
210
+				panic(err)
211
+			}
212
+			ev := dec.decode(s[:len(s)-len(rem)], depth+1)
213
+			v = append(v, ev)
214
+			s = rem
215
+		}
216
+		return v
217
+	default:
218
+		panic(SignatureError{Sig: s})
219
+	}
220
+}
221
+
222
+// A FormatError is an error in the wire format.
223
+type FormatError string
224
+
225
+func (e FormatError) Error() string {
226
+	return "dbus: wire format error: " + string(e)
227
+}
0 228
new file mode 100644
... ...
@@ -0,0 +1,63 @@
0
+/*
1
+Package dbus implements bindings to the D-Bus message bus system.
2
+
3
+To use the message bus API, you first need to connect to a bus (usually the
4
+session or system bus). The acquired connection then can be used to call methods
5
+on remote objects and emit or receive signals. Using the Export method, you can
6
+arrange D-Bus methods calls to be directly translated to method calls on a Go
7
+value.
8
+
9
+Conversion Rules
10
+
11
+For outgoing messages, Go types are automatically converted to the
12
+corresponding D-Bus types. The following types are directly encoded as their
13
+respective D-Bus equivalents:
14
+
15
+     Go type     | D-Bus type
16
+     ------------+-----------
17
+     byte        | BYTE
18
+     bool        | BOOLEAN
19
+     int16       | INT16
20
+     uint16      | UINT16
21
+     int32       | INT32
22
+     uint32      | UINT32
23
+     int64       | INT64
24
+     uint64      | UINT64
25
+     float64     | DOUBLE
26
+     string      | STRING
27
+     ObjectPath  | OBJECT_PATH
28
+     Signature   | SIGNATURE
29
+     Variant     | VARIANT
30
+     UnixFDIndex | UNIX_FD
31
+
32
+Slices and arrays encode as ARRAYs of their element type.
33
+
34
+Maps encode as DICTs, provided that their key type can be used as a key for
35
+a DICT.
36
+
37
+Structs other than Variant and Signature encode as a STRUCT containing their
38
+exported fields. Fields whose tags contain `dbus:"-"` and unexported fields will
39
+be skipped.
40
+
41
+Pointers encode as the value they're pointed to.
42
+
43
+Trying to encode any other type or a slice, map or struct containing an
44
+unsupported type will result in an InvalidTypeError.
45
+
46
+For incoming messages, the inverse of these rules are used, with the exception
47
+of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces
48
+containing the struct fields in the correct order. The Store function can be
49
+used to convert such values to Go structs.
50
+
51
+Unix FD passing
52
+
53
+Handling Unix file descriptors deserves special mention. To use them, you should
54
+first check that they are supported on a connection by calling SupportsUnixFDs.
55
+If it returns true, all method of Connection will translate messages containing
56
+UnixFD's to messages that are accompanied by the given file descriptors with the
57
+UnixFD values being substituted by the correct indices. Similarily, the indices
58
+of incoming messages are automatically resolved. It shouldn't be necessary to use
59
+UnixFDIndex.
60
+
61
+*/
62
+package dbus
0 63
new file mode 100644
... ...
@@ -0,0 +1,179 @@
0
+package dbus
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/binary"
5
+	"io"
6
+	"reflect"
7
+)
8
+
9
+// An encoder encodes values to the D-Bus wire format.
10
+type encoder struct {
11
+	out   io.Writer
12
+	order binary.ByteOrder
13
+	pos   int
14
+}
15
+
16
+// NewEncoder returns a new encoder that writes to out in the given byte order.
17
+func newEncoder(out io.Writer, order binary.ByteOrder) *encoder {
18
+	enc := new(encoder)
19
+	enc.out = out
20
+	enc.order = order
21
+	return enc
22
+}
23
+
24
+// Aligns the next output to be on a multiple of n. Panics on write errors.
25
+func (enc *encoder) align(n int) {
26
+	if enc.pos%n != 0 {
27
+		newpos := (enc.pos + n - 1) & ^(n - 1)
28
+		empty := make([]byte, newpos-enc.pos)
29
+		if _, err := enc.out.Write(empty); err != nil {
30
+			panic(err)
31
+		}
32
+		enc.pos = newpos
33
+	}
34
+}
35
+
36
+// Calls binary.Write(enc.out, enc.order, v) and panics on write errors.
37
+func (enc *encoder) binwrite(v interface{}) {
38
+	if err := binary.Write(enc.out, enc.order, v); err != nil {
39
+		panic(err)
40
+	}
41
+}
42
+
43
+// Encode encodes the given values to the underyling reader. All written values
44
+// are aligned properly as required by the D-Bus spec.
45
+func (enc *encoder) Encode(vs ...interface{}) (err error) {
46
+	defer func() {
47
+		err, _ = recover().(error)
48
+	}()
49
+	for _, v := range vs {
50
+		enc.encode(reflect.ValueOf(v), 0)
51
+	}
52
+	return nil
53
+}
54
+
55
+// encode encodes the given value to the writer and panics on error. depth holds
56
+// the depth of the container nesting.
57
+func (enc *encoder) encode(v reflect.Value, depth int) {
58
+	enc.align(alignment(v.Type()))
59
+	switch v.Kind() {
60
+	case reflect.Uint8:
61
+		var b [1]byte
62
+		b[0] = byte(v.Uint())
63
+		if _, err := enc.out.Write(b[:]); err != nil {
64
+			panic(err)
65
+		}
66
+		enc.pos++
67
+	case reflect.Bool:
68
+		if v.Bool() {
69
+			enc.encode(reflect.ValueOf(uint32(1)), depth)
70
+		} else {
71
+			enc.encode(reflect.ValueOf(uint32(0)), depth)
72
+		}
73
+	case reflect.Int16:
74
+		enc.binwrite(int16(v.Int()))
75
+		enc.pos += 2
76
+	case reflect.Uint16:
77
+		enc.binwrite(uint16(v.Uint()))
78
+		enc.pos += 2
79
+	case reflect.Int32:
80
+		enc.binwrite(int32(v.Int()))
81
+		enc.pos += 4
82
+	case reflect.Uint32:
83
+		enc.binwrite(uint32(v.Uint()))
84
+		enc.pos += 4
85
+	case reflect.Int64:
86
+		enc.binwrite(v.Int())
87
+		enc.pos += 8
88
+	case reflect.Uint64:
89
+		enc.binwrite(v.Uint())
90
+		enc.pos += 8
91
+	case reflect.Float64:
92
+		enc.binwrite(v.Float())
93
+		enc.pos += 8
94
+	case reflect.String:
95
+		enc.encode(reflect.ValueOf(uint32(len(v.String()))), depth)
96
+		b := make([]byte, v.Len()+1)
97
+		copy(b, v.String())
98
+		b[len(b)-1] = 0
99
+		n, err := enc.out.Write(b)
100
+		if err != nil {
101
+			panic(err)
102
+		}
103
+		enc.pos += n
104
+	case reflect.Ptr:
105
+		enc.encode(v.Elem(), depth)
106
+	case reflect.Slice, reflect.Array:
107
+		if depth >= 64 {
108
+			panic(FormatError("input exceeds container depth limit"))
109
+		}
110
+		var buf bytes.Buffer
111
+		bufenc := newEncoder(&buf, enc.order)
112
+
113
+		for i := 0; i < v.Len(); i++ {
114
+			bufenc.encode(v.Index(i), depth+1)
115
+		}
116
+		enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
117
+		length := buf.Len()
118
+		enc.align(alignment(v.Type().Elem()))
119
+		if _, err := buf.WriteTo(enc.out); err != nil {
120
+			panic(err)
121
+		}
122
+		enc.pos += length
123
+	case reflect.Struct:
124
+		if depth >= 64 && v.Type() != signatureType {
125
+			panic(FormatError("input exceeds container depth limit"))
126
+		}
127
+		switch t := v.Type(); t {
128
+		case signatureType:
129
+			str := v.Field(0)
130
+			enc.encode(reflect.ValueOf(byte(str.Len())), depth+1)
131
+			b := make([]byte, str.Len()+1)
132
+			copy(b, str.String())
133
+			b[len(b)-1] = 0
134
+			n, err := enc.out.Write(b)
135
+			if err != nil {
136
+				panic(err)
137
+			}
138
+			enc.pos += n
139
+		case variantType:
140
+			variant := v.Interface().(Variant)
141
+			enc.encode(reflect.ValueOf(variant.sig), depth+1)
142
+			enc.encode(reflect.ValueOf(variant.value), depth+1)
143
+		default:
144
+			for i := 0; i < v.Type().NumField(); i++ {
145
+				field := t.Field(i)
146
+				if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
147
+					enc.encode(v.Field(i), depth+1)
148
+				}
149
+			}
150
+		}
151
+	case reflect.Map:
152
+		// Maps are arrays of structures, so they actually increase the depth by
153
+		// 2.
154
+		if depth >= 63 {
155
+			panic(FormatError("input exceeds container depth limit"))
156
+		}
157
+		if !isKeyType(v.Type().Key()) {
158
+			panic(InvalidTypeError{v.Type()})
159
+		}
160
+		keys := v.MapKeys()
161
+		var buf bytes.Buffer
162
+		bufenc := newEncoder(&buf, enc.order)
163
+		for _, k := range keys {
164
+			bufenc.align(8)
165
+			bufenc.encode(k, depth+2)
166
+			bufenc.encode(v.MapIndex(k), depth+2)
167
+		}
168
+		enc.encode(reflect.ValueOf(uint32(buf.Len())), depth)
169
+		length := buf.Len()
170
+		enc.align(8)
171
+		if _, err := buf.WriteTo(enc.out); err != nil {
172
+			panic(err)
173
+		}
174
+		enc.pos += length
175
+	default:
176
+		panic(InvalidTypeError{v.Type()})
177
+	}
178
+}
0 179
new file mode 100644
... ...
@@ -0,0 +1,50 @@
0
+package dbus
1
+
2
+import "fmt"
3
+
4
+func ExampleConn_Emit() {
5
+	conn, err := SessionBus()
6
+	if err != nil {
7
+		panic(err)
8
+	}
9
+
10
+	conn.Emit("/foo/bar", "foo.bar.Baz", uint32(0xDAEDBEEF))
11
+}
12
+
13
+func ExampleObject_Call() {
14
+	var list []string
15
+
16
+	conn, err := SessionBus()
17
+	if err != nil {
18
+		panic(err)
19
+	}
20
+
21
+	err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&list)
22
+	if err != nil {
23
+		panic(err)
24
+	}
25
+	for _, v := range list {
26
+		fmt.Println(v)
27
+	}
28
+}
29
+
30
+func ExampleObject_Go() {
31
+	conn, err := SessionBus()
32
+	if err != nil {
33
+		panic(err)
34
+	}
35
+
36
+	ch := make(chan *Call, 10)
37
+	conn.BusObject().Go("org.freedesktop.DBus.ListActivatableNames", 0, ch)
38
+	select {
39
+	case call := <-ch:
40
+		if call.Err != nil {
41
+			panic(err)
42
+		}
43
+		list := call.Body[0].([]string)
44
+		for _, v := range list {
45
+			fmt.Println(v)
46
+		}
47
+		// put some other cases here
48
+	}
49
+}
0 50
new file mode 100644
... ...
@@ -0,0 +1,302 @@
0
+package dbus
1
+
2
+import (
3
+	"errors"
4
+	"reflect"
5
+	"strings"
6
+	"unicode"
7
+)
8
+
9
+var (
10
+	errmsgInvalidArg = Error{
11
+		"org.freedesktop.DBus.Error.InvalidArgs",
12
+		[]interface{}{"Invalid type / number of args"},
13
+	}
14
+	errmsgNoObject = Error{
15
+		"org.freedesktop.DBus.Error.NoSuchObject",
16
+		[]interface{}{"No such object"},
17
+	}
18
+	errmsgUnknownMethod = Error{
19
+		"org.freedesktop.DBus.Error.UnknownMethod",
20
+		[]interface{}{"Unknown / invalid method"},
21
+	}
22
+)
23
+
24
+// Sender is a type which can be used in exported methods to receive the message
25
+// sender.
26
+type Sender string
27
+
28
+func exportedMethod(v interface{}, name string) reflect.Value {
29
+	if v == nil {
30
+		return reflect.Value{}
31
+	}
32
+	m := reflect.ValueOf(v).MethodByName(name)
33
+	if !m.IsValid() {
34
+		return reflect.Value{}
35
+	}
36
+	t := m.Type()
37
+	if t.NumOut() == 0 ||
38
+		t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) {
39
+
40
+		return reflect.Value{}
41
+	}
42
+	return m
43
+}
44
+
45
+// handleCall handles the given method call (i.e. looks if it's one of the
46
+// pre-implemented ones and searches for a corresponding handler if not).
47
+func (conn *Conn) handleCall(msg *Message) {
48
+	name := msg.Headers[FieldMember].value.(string)
49
+	path := msg.Headers[FieldPath].value.(ObjectPath)
50
+	ifaceName, hasIface := msg.Headers[FieldInterface].value.(string)
51
+	sender, hasSender := msg.Headers[FieldSender].value.(string)
52
+	serial := msg.serial
53
+	if ifaceName == "org.freedesktop.DBus.Peer" {
54
+		switch name {
55
+		case "Ping":
56
+			conn.sendReply(sender, serial)
57
+		case "GetMachineId":
58
+			conn.sendReply(sender, serial, conn.uuid)
59
+		default:
60
+			conn.sendError(errmsgUnknownMethod, sender, serial)
61
+		}
62
+		return
63
+	}
64
+	if len(name) == 0 || unicode.IsLower([]rune(name)[0]) {
65
+		conn.sendError(errmsgUnknownMethod, sender, serial)
66
+	}
67
+	var m reflect.Value
68
+	if hasIface {
69
+		conn.handlersLck.RLock()
70
+		obj, ok := conn.handlers[path]
71
+		if !ok {
72
+			conn.sendError(errmsgNoObject, sender, serial)
73
+			conn.handlersLck.RUnlock()
74
+			return
75
+		}
76
+		iface := obj[ifaceName]
77
+		conn.handlersLck.RUnlock()
78
+		m = exportedMethod(iface, name)
79
+	} else {
80
+		conn.handlersLck.RLock()
81
+		if _, ok := conn.handlers[path]; !ok {
82
+			conn.sendError(errmsgNoObject, sender, serial)
83
+			conn.handlersLck.RUnlock()
84
+			return
85
+		}
86
+		for _, v := range conn.handlers[path] {
87
+			m = exportedMethod(v, name)
88
+			if m.IsValid() {
89
+				break
90
+			}
91
+		}
92
+		conn.handlersLck.RUnlock()
93
+	}
94
+	if !m.IsValid() {
95
+		conn.sendError(errmsgUnknownMethod, sender, serial)
96
+		return
97
+	}
98
+	t := m.Type()
99
+	vs := msg.Body
100
+	pointers := make([]interface{}, t.NumIn())
101
+	decode := make([]interface{}, 0, len(vs))
102
+	for i := 0; i < t.NumIn(); i++ {
103
+		tp := t.In(i)
104
+		val := reflect.New(tp)
105
+		pointers[i] = val.Interface()
106
+		if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
107
+			val.Elem().SetString(sender)
108
+		} else {
109
+			decode = append(decode, pointers[i])
110
+		}
111
+	}
112
+	if len(decode) != len(vs) {
113
+		conn.sendError(errmsgInvalidArg, sender, serial)
114
+		return
115
+	}
116
+	if err := Store(vs, decode...); err != nil {
117
+		conn.sendError(errmsgInvalidArg, sender, serial)
118
+		return
119
+	}
120
+	params := make([]reflect.Value, len(pointers))
121
+	for i := 0; i < len(pointers); i++ {
122
+		params[i] = reflect.ValueOf(pointers[i]).Elem()
123
+	}
124
+	ret := m.Call(params)
125
+	if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
126
+		conn.sendError(*em, sender, serial)
127
+		return
128
+	}
129
+	if msg.Flags&FlagNoReplyExpected == 0 {
130
+		reply := new(Message)
131
+		reply.Type = TypeMethodReply
132
+		reply.serial = conn.getSerial()
133
+		reply.Headers = make(map[HeaderField]Variant)
134
+		if hasSender {
135
+			reply.Headers[FieldDestination] = msg.Headers[FieldSender]
136
+		}
137
+		reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
138
+		reply.Body = make([]interface{}, len(ret)-1)
139
+		for i := 0; i < len(ret)-1; i++ {
140
+			reply.Body[i] = ret[i].Interface()
141
+		}
142
+		if len(ret) != 1 {
143
+			reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
144
+		}
145
+		conn.outLck.RLock()
146
+		if !conn.closed {
147
+			conn.out <- reply
148
+		}
149
+		conn.outLck.RUnlock()
150
+	}
151
+}
152
+
153
+// Emit emits the given signal on the message bus. The name parameter must be
154
+// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
155
+func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
156
+	if !path.IsValid() {
157
+		return errors.New("dbus: invalid object path")
158
+	}
159
+	i := strings.LastIndex(name, ".")
160
+	if i == -1 {
161
+		return errors.New("dbus: invalid method name")
162
+	}
163
+	iface := name[:i]
164
+	member := name[i+1:]
165
+	if !isValidMember(member) {
166
+		return errors.New("dbus: invalid method name")
167
+	}
168
+	if !isValidInterface(iface) {
169
+		return errors.New("dbus: invalid interface name")
170
+	}
171
+	msg := new(Message)
172
+	msg.Type = TypeSignal
173
+	msg.serial = conn.getSerial()
174
+	msg.Headers = make(map[HeaderField]Variant)
175
+	msg.Headers[FieldInterface] = MakeVariant(iface)
176
+	msg.Headers[FieldMember] = MakeVariant(member)
177
+	msg.Headers[FieldPath] = MakeVariant(path)
178
+	msg.Body = values
179
+	if len(values) > 0 {
180
+		msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
181
+	}
182
+	conn.outLck.RLock()
183
+	defer conn.outLck.RUnlock()
184
+	if conn.closed {
185
+		return ErrClosed
186
+	}
187
+	conn.out <- msg
188
+	return nil
189
+}
190
+
191
+// Export registers the given value to be exported as an object on the
192
+// message bus.
193
+//
194
+// If a method call on the given path and interface is received, an exported
195
+// method with the same name is called with v as the receiver if the
196
+// parameters match and the last return value is of type *Error. If this
197
+// *Error is not nil, it is sent back to the caller as an error.
198
+// Otherwise, a method reply is sent with the other return values as its body.
199
+//
200
+// Any parameters with the special type Sender are set to the sender of the
201
+// dbus message when the method is called. Parameters of this type do not
202
+// contribute to the dbus signature of the method (i.e. the method is exposed
203
+// as if the parameters of type Sender were not there).
204
+//
205
+// Every method call is executed in a new goroutine, so the method may be called
206
+// in multiple goroutines at once.
207
+//
208
+// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
209
+// handled for every object.
210
+//
211
+// Passing nil as the first parameter will cause conn to cease handling calls on
212
+// the given combination of path and interface.
213
+//
214
+// Export returns an error if path is not a valid path name.
215
+func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
216
+	if !path.IsValid() {
217
+		return errors.New("dbus: invalid path name")
218
+	}
219
+	conn.handlersLck.Lock()
220
+	if v == nil {
221
+		if _, ok := conn.handlers[path]; ok {
222
+			delete(conn.handlers[path], iface)
223
+			if len(conn.handlers[path]) == 0 {
224
+				delete(conn.handlers, path)
225
+			}
226
+		}
227
+		return nil
228
+	}
229
+	if _, ok := conn.handlers[path]; !ok {
230
+		conn.handlers[path] = make(map[string]interface{})
231
+	}
232
+	conn.handlers[path][iface] = v
233
+	conn.handlersLck.Unlock()
234
+	return nil
235
+}
236
+
237
+// ReleaseName calls org.freedesktop.DBus.ReleaseName. You should use only this
238
+// method to release a name (see below).
239
+func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
240
+	var r uint32
241
+	err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
242
+	if err != nil {
243
+		return 0, err
244
+	}
245
+	if r == uint32(ReleaseNameReplyReleased) {
246
+		conn.namesLck.Lock()
247
+		for i, v := range conn.names {
248
+			if v == name {
249
+				copy(conn.names[i:], conn.names[i+1:])
250
+				conn.names = conn.names[:len(conn.names)-1]
251
+			}
252
+		}
253
+		conn.namesLck.Unlock()
254
+	}
255
+	return ReleaseNameReply(r), nil
256
+}
257
+
258
+// RequestName calls org.freedesktop.DBus.RequestName. You should use only this
259
+// method to request a name because package dbus needs to keep track of all
260
+// names that the connection has.
261
+func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
262
+	var r uint32
263
+	err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
264
+	if err != nil {
265
+		return 0, err
266
+	}
267
+	if r == uint32(RequestNameReplyPrimaryOwner) {
268
+		conn.namesLck.Lock()
269
+		conn.names = append(conn.names, name)
270
+		conn.namesLck.Unlock()
271
+	}
272
+	return RequestNameReply(r), nil
273
+}
274
+
275
+// ReleaseNameReply is the reply to a ReleaseName call.
276
+type ReleaseNameReply uint32
277
+
278
+const (
279
+	ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
280
+	ReleaseNameReplyNonExistent
281
+	ReleaseNameReplyNotOwner
282
+)
283
+
284
+// RequestNameFlags represents the possible flags for a RequestName call.
285
+type RequestNameFlags uint32
286
+
287
+const (
288
+	NameFlagAllowReplacement RequestNameFlags = 1 << iota
289
+	NameFlagReplaceExisting
290
+	NameFlagDoNotQueue
291
+)
292
+
293
+// RequestNameReply is the reply to a RequestName call.
294
+type RequestNameReply uint32
295
+
296
+const (
297
+	RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
298
+	RequestNameReplyInQueue
299
+	RequestNameReplyExists
300
+	RequestNameReplyAlreadyOwner
301
+)
0 302
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+package dbus
1
+
2
+import (
3
+	"os"
4
+	"sync"
5
+)
6
+
7
+var (
8
+	homeDir     string
9
+	homeDirLock sync.Mutex
10
+)
11
+
12
+func getHomeDir() string {
13
+	homeDirLock.Lock()
14
+	defer homeDirLock.Unlock()
15
+
16
+	if homeDir != "" {
17
+		return homeDir
18
+	}
19
+
20
+	homeDir = os.Getenv("HOME")
21
+	if homeDir != "" {
22
+		return homeDir
23
+	}
24
+
25
+	homeDir = lookupHomeDir()
26
+	return homeDir
27
+}
0 28
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+// +build !static_build
1
+
2
+package dbus
3
+
4
+import (
5
+	"os/user"
6
+)
7
+
8
+func lookupHomeDir() string {
9
+	u, err := user.Current()
10
+	if err != nil {
11
+		return "/"
12
+	}
13
+	return u.HomeDir
14
+}
0 15
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+// +build static_build
1
+
2
+package dbus
3
+
4
+import (
5
+	"bufio"
6
+	"os"
7
+	"strconv"
8
+	"strings"
9
+)
10
+
11
+func lookupHomeDir() string {
12
+	myUid := os.Getuid()
13
+
14
+	f, err := os.Open("/etc/passwd")
15
+	if err != nil {
16
+		return "/"
17
+	}
18
+	defer f.Close()
19
+
20
+	s := bufio.NewScanner(f)
21
+
22
+	for s.Scan() {
23
+		if err := s.Err(); err != nil {
24
+			break
25
+		}
26
+
27
+		line := strings.TrimSpace(s.Text())
28
+		if line == "" {
29
+			continue
30
+		}
31
+
32
+		parts := strings.Split(line, ":")
33
+
34
+		if len(parts) >= 6 {
35
+			uid, err := strconv.Atoi(parts[2])
36
+			if err == nil && uid == myUid {
37
+				return parts[5]
38
+			}
39
+		}
40
+	}
41
+
42
+	// Default to / if we can't get a better value
43
+	return "/"
44
+}
0 45
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package introspect
1
+
2
+import (
3
+	"encoding/xml"
4
+	"github.com/godbus/dbus"
5
+	"strings"
6
+)
7
+
8
+// Call calls org.freedesktop.Introspectable.Introspect on a remote object
9
+// and returns the introspection data.
10
+func Call(o *dbus.Object) (*Node, error) {
11
+	var xmldata string
12
+	var node Node
13
+
14
+	err := o.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xmldata)
15
+	if err != nil {
16
+		return nil, err
17
+	}
18
+	err = xml.NewDecoder(strings.NewReader(xmldata)).Decode(&node)
19
+	if err != nil {
20
+		return nil, err
21
+	}
22
+	if node.Name == "" {
23
+		node.Name = string(o.Path())
24
+	}
25
+	return &node, nil
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,80 @@
0
+// Package introspect provides some utilities for dealing with the DBus
1
+// introspection format.
2
+package introspect
3
+
4
+import "encoding/xml"
5
+
6
+// The introspection data for the org.freedesktop.DBus.Introspectable interface.
7
+var IntrospectData = Interface{
8
+	Name: "org.freedesktop.DBus.Introspectable",
9
+	Methods: []Method{
10
+		{
11
+			Name: "Introspect",
12
+			Args: []Arg{
13
+				{"out", "s", "out"},
14
+			},
15
+		},
16
+	},
17
+}
18
+
19
+// The introspection data for the org.freedesktop.DBus.Introspectable interface,
20
+// as a string.
21
+const IntrospectDataString = `
22
+	<interface name="org.freedesktop.DBus.Introspectable">
23
+		<method name="Introspect">
24
+			<arg name="out" direction="out" type="s"/>
25
+		</method>
26
+	</interface>
27
+`
28
+
29
+// Node is the root element of an introspection.
30
+type Node struct {
31
+	XMLName    xml.Name    `xml:"node"`
32
+	Name       string      `xml:"name,attr,omitempty"`
33
+	Interfaces []Interface `xml:"interface"`
34
+	Children   []Node      `xml:"node,omitempty"`
35
+}
36
+
37
+// Interface describes a DBus interface that is available on the message bus.
38
+type Interface struct {
39
+	Name        string       `xml:"name,attr"`
40
+	Methods     []Method     `xml:"method"`
41
+	Signals     []Signal     `xml:"signal"`
42
+	Properties  []Property   `xml:"property"`
43
+	Annotations []Annotation `xml:"annotation"`
44
+}
45
+
46
+// Method describes a Method on an Interface as retured by an introspection.
47
+type Method struct {
48
+	Name        string       `xml:"name,attr"`
49
+	Args        []Arg        `xml:"arg"`
50
+	Annotations []Annotation `xml:"annotation"`
51
+}
52
+
53
+// Signal describes a Signal emitted on an Interface.
54
+type Signal struct {
55
+	Name        string       `xml:"name,attr"`
56
+	Args        []Arg        `xml:"arg"`
57
+	Annotations []Annotation `xml:"annotation"`
58
+}
59
+
60
+// Property describes a property of an Interface.
61
+type Property struct {
62
+	Name        string       `xml:"name,attr"`
63
+	Type        string       `xml:"type,attr"`
64
+	Access      string       `xml:"access,attr"`
65
+	Annotations []Annotation `xml:"annotation"`
66
+}
67
+
68
+// Arg represents an argument of a method or a signal.
69
+type Arg struct {
70
+	Name      string `xml:"name,attr,omitempty"`
71
+	Type      string `xml:"type,attr"`
72
+	Direction string `xml:"direction,attr,omitempty"`
73
+}
74
+
75
+// Annotation is an annotation in the introspection format.
76
+type Annotation struct {
77
+	Name  string `xml:"name,attr"`
78
+	Value string `xml:"value,attr"`
79
+}
0 80
new file mode 100644
... ...
@@ -0,0 +1,74 @@
0
+package introspect
1
+
2
+import (
3
+	"encoding/xml"
4
+	"github.com/godbus/dbus"
5
+	"reflect"
6
+)
7
+
8
+// Introspectable implements org.freedesktop.Introspectable.
9
+//
10
+// You can create it by converting the XML-formatted introspection data from a
11
+// string to an Introspectable or call NewIntrospectable with a Node. Then,
12
+// export it as org.freedesktop.Introspectable on you object.
13
+type Introspectable string
14
+
15
+// NewIntrospectable returns an Introspectable that returns the introspection
16
+// data that corresponds to the given Node. If n.Interfaces doesn't contain the
17
+// data for org.freedesktop.DBus.Introspectable, it is added automatically.
18
+func NewIntrospectable(n *Node) Introspectable {
19
+	found := false
20
+	for _, v := range n.Interfaces {
21
+		if v.Name == "org.freedesktop.DBus.Introspectable" {
22
+			found = true
23
+			break
24
+		}
25
+	}
26
+	if !found {
27
+		n.Interfaces = append(n.Interfaces, IntrospectData)
28
+	}
29
+	b, err := xml.Marshal(n)
30
+	if err != nil {
31
+		panic(err)
32
+	}
33
+	return Introspectable(b)
34
+}
35
+
36
+// Introspect implements org.freedesktop.Introspectable.Introspect.
37
+func (i Introspectable) Introspect() (string, *dbus.Error) {
38
+	return string(i), nil
39
+}
40
+
41
+// Methods returns the description of the methods of v. This can be used to
42
+// create a Node which can be passed to NewIntrospectable.
43
+func Methods(v interface{}) []Method {
44
+	t := reflect.TypeOf(v)
45
+	ms := make([]Method, 0, t.NumMethod())
46
+	for i := 0; i < t.NumMethod(); i++ {
47
+		if t.Method(i).PkgPath != "" {
48
+			continue
49
+		}
50
+		mt := t.Method(i).Type
51
+		if mt.NumOut() == 0 ||
52
+			mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{"", nil}) {
53
+
54
+			continue
55
+		}
56
+		var m Method
57
+		m.Name = t.Method(i).Name
58
+		m.Args = make([]Arg, 0, mt.NumIn()+mt.NumOut()-2)
59
+		for j := 1; j < mt.NumIn(); j++ {
60
+			if mt.In(j) != reflect.TypeOf((*dbus.Sender)(nil)).Elem() {
61
+				arg := Arg{"", dbus.SignatureOfType(mt.In(j)).String(), "in"}
62
+				m.Args = append(m.Args, arg)
63
+			}
64
+		}
65
+		for j := 0; j < mt.NumOut()-1; j++ {
66
+			arg := Arg{"", dbus.SignatureOfType(mt.Out(j)).String(), "out"}
67
+			m.Args = append(m.Args, arg)
68
+		}
69
+		m.Annotations = make([]Annotation, 0)
70
+		ms = append(ms, m)
71
+	}
72
+	return ms
73
+}
0 74
new file mode 100644
... ...
@@ -0,0 +1,346 @@
0
+package dbus
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/binary"
5
+	"errors"
6
+	"io"
7
+	"reflect"
8
+	"strconv"
9
+)
10
+
11
+const protoVersion byte = 1
12
+
13
+// Flags represents the possible flags of a D-Bus message.
14
+type Flags byte
15
+
16
+const (
17
+	// FlagNoReplyExpected signals that the message is not expected to generate
18
+	// a reply. If this flag is set on outgoing messages, any possible reply
19
+	// will be discarded.
20
+	FlagNoReplyExpected Flags = 1 << iota
21
+	// FlagNoAutoStart signals that the message bus should not automatically
22
+	// start an application when handling this message.
23
+	FlagNoAutoStart
24
+)
25
+
26
+// Type represents the possible types of a D-Bus message.
27
+type Type byte
28
+
29
+const (
30
+	TypeMethodCall Type = 1 + iota
31
+	TypeMethodReply
32
+	TypeError
33
+	TypeSignal
34
+	typeMax
35
+)
36
+
37
+func (t Type) String() string {
38
+	switch t {
39
+	case TypeMethodCall:
40
+		return "method call"
41
+	case TypeMethodReply:
42
+		return "reply"
43
+	case TypeError:
44
+		return "error"
45
+	case TypeSignal:
46
+		return "signal"
47
+	}
48
+	return "invalid"
49
+}
50
+
51
+// HeaderField represents the possible byte codes for the headers
52
+// of a D-Bus message.
53
+type HeaderField byte
54
+
55
+const (
56
+	FieldPath HeaderField = 1 + iota
57
+	FieldInterface
58
+	FieldMember
59
+	FieldErrorName
60
+	FieldReplySerial
61
+	FieldDestination
62
+	FieldSender
63
+	FieldSignature
64
+	FieldUnixFDs
65
+	fieldMax
66
+)
67
+
68
+// An InvalidMessageError describes the reason why a D-Bus message is regarded as
69
+// invalid.
70
+type InvalidMessageError string
71
+
72
+func (e InvalidMessageError) Error() string {
73
+	return "dbus: invalid message: " + string(e)
74
+}
75
+
76
+// fieldType are the types of the various header fields.
77
+var fieldTypes = [fieldMax]reflect.Type{
78
+	FieldPath:        objectPathType,
79
+	FieldInterface:   stringType,
80
+	FieldMember:      stringType,
81
+	FieldErrorName:   stringType,
82
+	FieldReplySerial: uint32Type,
83
+	FieldDestination: stringType,
84
+	FieldSender:      stringType,
85
+	FieldSignature:   signatureType,
86
+	FieldUnixFDs:     uint32Type,
87
+}
88
+
89
+// requiredFields lists the header fields that are required by the different
90
+// message types.
91
+var requiredFields = [typeMax][]HeaderField{
92
+	TypeMethodCall:  {FieldPath, FieldMember},
93
+	TypeMethodReply: {FieldReplySerial},
94
+	TypeError:       {FieldErrorName, FieldReplySerial},
95
+	TypeSignal:      {FieldPath, FieldInterface, FieldMember},
96
+}
97
+
98
+// Message represents a single D-Bus message.
99
+type Message struct {
100
+	Type
101
+	Flags
102
+	Headers map[HeaderField]Variant
103
+	Body    []interface{}
104
+
105
+	serial uint32
106
+}
107
+
108
+type header struct {
109
+	Field byte
110
+	Variant
111
+}
112
+
113
+// DecodeMessage tries to decode a single message in the D-Bus wire format
114
+// from the given reader. The byte order is figured out from the first byte.
115
+// The possibly returned error can be an error of the underlying reader, an
116
+// InvalidMessageError or a FormatError.
117
+func DecodeMessage(rd io.Reader) (msg *Message, err error) {
118
+	var order binary.ByteOrder
119
+	var hlength, length uint32
120
+	var typ, flags, proto byte
121
+	var headers []header
122
+
123
+	b := make([]byte, 1)
124
+	_, err = rd.Read(b)
125
+	if err != nil {
126
+		return
127
+	}
128
+	switch b[0] {
129
+	case 'l':
130
+		order = binary.LittleEndian
131
+	case 'B':
132
+		order = binary.BigEndian
133
+	default:
134
+		return nil, InvalidMessageError("invalid byte order")
135
+	}
136
+
137
+	dec := newDecoder(rd, order)
138
+	dec.pos = 1
139
+
140
+	msg = new(Message)
141
+	vs, err := dec.Decode(Signature{"yyyuu"})
142
+	if err != nil {
143
+		return nil, err
144
+	}
145
+	if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil {
146
+		return nil, err
147
+	}
148
+	msg.Type = Type(typ)
149
+	msg.Flags = Flags(flags)
150
+
151
+	// get the header length separately because we need it later
152
+	b = make([]byte, 4)
153
+	_, err = io.ReadFull(rd, b)
154
+	if err != nil {
155
+		return nil, err
156
+	}
157
+	binary.Read(bytes.NewBuffer(b), order, &hlength)
158
+	if hlength+length+16 > 1<<27 {
159
+		return nil, InvalidMessageError("message is too long")
160
+	}
161
+	dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order)
162
+	dec.pos = 12
163
+	vs, err = dec.Decode(Signature{"a(yv)"})
164
+	if err != nil {
165
+		return nil, err
166
+	}
167
+	if err = Store(vs, &headers); err != nil {
168
+		return nil, err
169
+	}
170
+
171
+	msg.Headers = make(map[HeaderField]Variant)
172
+	for _, v := range headers {
173
+		msg.Headers[HeaderField(v.Field)] = v.Variant
174
+	}
175
+
176
+	dec.align(8)
177
+	body := make([]byte, int(length))
178
+	if length != 0 {
179
+		_, err := io.ReadFull(rd, body)
180
+		if err != nil {
181
+			return nil, err
182
+		}
183
+	}
184
+
185
+	if err = msg.IsValid(); err != nil {
186
+		return nil, err
187
+	}
188
+	sig, _ := msg.Headers[FieldSignature].value.(Signature)
189
+	if sig.str != "" {
190
+		buf := bytes.NewBuffer(body)
191
+		dec = newDecoder(buf, order)
192
+		vs, err := dec.Decode(sig)
193
+		if err != nil {
194
+			return nil, err
195
+		}
196
+		msg.Body = vs
197
+	}
198
+
199
+	return
200
+}
201
+
202
+// EncodeTo encodes and sends a message to the given writer. The byte order must
203
+// be either binary.LittleEndian or binary.BigEndian. If the message is not
204
+// valid or an error occurs when writing, an error is returned.
205
+func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error {
206
+	if err := msg.IsValid(); err != nil {
207
+		return err
208
+	}
209
+	var vs [7]interface{}
210
+	switch order {
211
+	case binary.LittleEndian:
212
+		vs[0] = byte('l')
213
+	case binary.BigEndian:
214
+		vs[0] = byte('B')
215
+	default:
216
+		return errors.New("dbus: invalid byte order")
217
+	}
218
+	body := new(bytes.Buffer)
219
+	enc := newEncoder(body, order)
220
+	if len(msg.Body) != 0 {
221
+		enc.Encode(msg.Body...)
222
+	}
223
+	vs[1] = msg.Type
224
+	vs[2] = msg.Flags
225
+	vs[3] = protoVersion
226
+	vs[4] = uint32(len(body.Bytes()))
227
+	vs[5] = msg.serial
228
+	headers := make([]header, 0, len(msg.Headers))
229
+	for k, v := range msg.Headers {
230
+		headers = append(headers, header{byte(k), v})
231
+	}
232
+	vs[6] = headers
233
+	var buf bytes.Buffer
234
+	enc = newEncoder(&buf, order)
235
+	enc.Encode(vs[:]...)
236
+	enc.align(8)
237
+	body.WriteTo(&buf)
238
+	if buf.Len() > 1<<27 {
239
+		return InvalidMessageError("message is too long")
240
+	}
241
+	if _, err := buf.WriteTo(out); err != nil {
242
+		return err
243
+	}
244
+	return nil
245
+}
246
+
247
+// IsValid checks whether msg is a valid message and returns an
248
+// InvalidMessageError if it is not.
249
+func (msg *Message) IsValid() error {
250
+	if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected) != 0 {
251
+		return InvalidMessageError("invalid flags")
252
+	}
253
+	if msg.Type == 0 || msg.Type >= typeMax {
254
+		return InvalidMessageError("invalid message type")
255
+	}
256
+	for k, v := range msg.Headers {
257
+		if k == 0 || k >= fieldMax {
258
+			return InvalidMessageError("invalid header")
259
+		}
260
+		if reflect.TypeOf(v.value) != fieldTypes[k] {
261
+			return InvalidMessageError("invalid type of header field")
262
+		}
263
+	}
264
+	for _, v := range requiredFields[msg.Type] {
265
+		if _, ok := msg.Headers[v]; !ok {
266
+			return InvalidMessageError("missing required header")
267
+		}
268
+	}
269
+	if path, ok := msg.Headers[FieldPath]; ok {
270
+		if !path.value.(ObjectPath).IsValid() {
271
+			return InvalidMessageError("invalid path name")
272
+		}
273
+	}
274
+	if iface, ok := msg.Headers[FieldInterface]; ok {
275
+		if !isValidInterface(iface.value.(string)) {
276
+			return InvalidMessageError("invalid interface name")
277
+		}
278
+	}
279
+	if member, ok := msg.Headers[FieldMember]; ok {
280
+		if !isValidMember(member.value.(string)) {
281
+			return InvalidMessageError("invalid member name")
282
+		}
283
+	}
284
+	if errname, ok := msg.Headers[FieldErrorName]; ok {
285
+		if !isValidInterface(errname.value.(string)) {
286
+			return InvalidMessageError("invalid error name")
287
+		}
288
+	}
289
+	if len(msg.Body) != 0 {
290
+		if _, ok := msg.Headers[FieldSignature]; !ok {
291
+			return InvalidMessageError("missing signature")
292
+		}
293
+	}
294
+	return nil
295
+}
296
+
297
+// Serial returns the message's serial number. The returned value is only valid
298
+// for messages received by eavesdropping.
299
+func (msg *Message) Serial() uint32 {
300
+	return msg.serial
301
+}
302
+
303
+// String returns a string representation of a message similar to the format of
304
+// dbus-monitor.
305
+func (msg *Message) String() string {
306
+	if err := msg.IsValid(); err != nil {
307
+		return "<invalid>"
308
+	}
309
+	s := msg.Type.String()
310
+	if v, ok := msg.Headers[FieldSender]; ok {
311
+		s += " from " + v.value.(string)
312
+	}
313
+	if v, ok := msg.Headers[FieldDestination]; ok {
314
+		s += " to " + v.value.(string)
315
+	}
316
+	s += " serial " + strconv.FormatUint(uint64(msg.serial), 10)
317
+	if v, ok := msg.Headers[FieldReplySerial]; ok {
318
+		s += " reply_serial " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
319
+	}
320
+	if v, ok := msg.Headers[FieldUnixFDs]; ok {
321
+		s += " unixfds " + strconv.FormatUint(uint64(v.value.(uint32)), 10)
322
+	}
323
+	if v, ok := msg.Headers[FieldPath]; ok {
324
+		s += " path " + string(v.value.(ObjectPath))
325
+	}
326
+	if v, ok := msg.Headers[FieldInterface]; ok {
327
+		s += " interface " + v.value.(string)
328
+	}
329
+	if v, ok := msg.Headers[FieldErrorName]; ok {
330
+		s += " error " + v.value.(string)
331
+	}
332
+	if v, ok := msg.Headers[FieldMember]; ok {
333
+		s += " member " + v.value.(string)
334
+	}
335
+	if len(msg.Body) != 0 {
336
+		s += "\n"
337
+	}
338
+	for i, v := range msg.Body {
339
+		s += "  " + MakeVariant(v).String()
340
+		if i != len(msg.Body)-1 {
341
+			s += "\n"
342
+		}
343
+	}
344
+	return s
345
+}
0 346
new file mode 100644
... ...
@@ -0,0 +1,264 @@
0
+// Package prop provides the Properties struct which can be used to implement
1
+// org.freedesktop.DBus.Properties.
2
+package prop
3
+
4
+import (
5
+	"github.com/godbus/dbus"
6
+	"github.com/godbus/dbus/introspect"
7
+	"sync"
8
+)
9
+
10
+// EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is
11
+// emitted for a property. If it is EmitTrue, the signal is emitted. If it is
12
+// EmitInvalidates, the signal is also emitted, but the new value of the property
13
+// is not disclosed.
14
+type EmitType byte
15
+
16
+const (
17
+	EmitFalse EmitType = iota
18
+	EmitTrue
19
+	EmitInvalidates
20
+)
21
+
22
+// ErrIfaceNotFound is the error returned to peers who try to access properties
23
+// on interfaces that aren't found.
24
+var ErrIfaceNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil}
25
+
26
+// ErrPropNotFound is the error returned to peers trying to access properties
27
+// that aren't found.
28
+var ErrPropNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil}
29
+
30
+// ErrReadOnly is the error returned to peers trying to set a read-only
31
+// property.
32
+var ErrReadOnly = &dbus.Error{"org.freedesktop.DBus.Properties.Error.ReadOnly", nil}
33
+
34
+// ErrInvalidArg is returned to peers if the type of the property that is being
35
+// changed and the argument don't match.
36
+var ErrInvalidArg = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InvalidArg", nil}
37
+
38
+// The introspection data for the org.freedesktop.DBus.Properties interface.
39
+var IntrospectData = introspect.Interface{
40
+	Name: "org.freedesktop.DBus.Properties",
41
+	Methods: []introspect.Method{
42
+		{
43
+			Name: "Get",
44
+			Args: []introspect.Arg{
45
+				{"interface", "s", "in"},
46
+				{"property", "s", "in"},
47
+				{"value", "v", "out"},
48
+			},
49
+		},
50
+		{
51
+			Name: "GetAll",
52
+			Args: []introspect.Arg{
53
+				{"interface", "s", "in"},
54
+				{"props", "a{sv}", "out"},
55
+			},
56
+		},
57
+		{
58
+			Name: "Set",
59
+			Args: []introspect.Arg{
60
+				{"interface", "s", "in"},
61
+				{"property", "s", "in"},
62
+				{"value", "v", "in"},
63
+			},
64
+		},
65
+	},
66
+	Signals: []introspect.Signal{
67
+		{
68
+			Name: "PropertiesChanged",
69
+			Args: []introspect.Arg{
70
+				{"interface", "s", "out"},
71
+				{"changed_properties", "a{sv}", "out"},
72
+				{"invalidates_properties", "as", "out"},
73
+			},
74
+		},
75
+	},
76
+}
77
+
78
+// The introspection data for the org.freedesktop.DBus.Properties interface, as
79
+// a string.
80
+const IntrospectDataString = `
81
+	<interface name="org.freedesktop.DBus.Introspectable">
82
+		<method name="Get">
83
+			<arg name="interface" direction="in" type="s"/>
84
+			<arg name="property" direction="in" type="s"/>
85
+			<arg name="value" direction="out" type="v"/>
86
+		</method>
87
+		<method name="GetAll">
88
+			<arg name="interface" direction="in" type="s"/>
89
+			<arg name="props" direction="out" type="a{sv}"/>
90
+		</method>
91
+		<method name="Set">
92
+			<arg name="interface" direction="in" type="s"/>
93
+			<arg name="property" direction="in" type="s"/>
94
+			<arg name="value" direction="in" type="v"/>
95
+		</method>
96
+		<signal name="PropertiesChanged">
97
+			<arg name="interface" type="s"/>
98
+			<arg name="changed_properties" type="a{sv}"/>
99
+			<arg name="invalidates_properties" type="as"/>
100
+		</signal>
101
+	</interface>
102
+`
103
+
104
+// Prop represents a single property. It is used for creating a Properties
105
+// value.
106
+type Prop struct {
107
+	// Initial value. Must be a DBus-representable type.
108
+	Value interface{}
109
+
110
+	// If true, the value can be modified by calls to Set.
111
+	Writable bool
112
+
113
+	// Controls how org.freedesktop.DBus.Properties.PropertiesChanged is
114
+	// emitted if this property changes.
115
+	Emit EmitType
116
+
117
+	// If not nil, anytime this property is changed by Set, this function is
118
+	// called with an appropiate Change as its argument. If the returned error
119
+	// is not nil, it is sent back to the caller of Set and the property is not
120
+	// changed.
121
+	Callback func(*Change) *dbus.Error
122
+}
123
+
124
+// Change represents a change of a property by a call to Set.
125
+type Change struct {
126
+	Props *Properties
127
+	Iface string
128
+	Name  string
129
+	Value interface{}
130
+}
131
+
132
+// Properties is a set of values that can be made available to the message bus
133
+// using the org.freedesktop.DBus.Properties interface. It is safe for
134
+// concurrent use by multiple goroutines.
135
+type Properties struct {
136
+	m    map[string]map[string]*Prop
137
+	mut  sync.RWMutex
138
+	conn *dbus.Conn
139
+	path dbus.ObjectPath
140
+}
141
+
142
+// New returns a new Properties structure that manages the given properties.
143
+// The key for the first-level map of props is the name of the interface; the
144
+// second-level key is the name of the property. The returned structure will be
145
+// exported as org.freedesktop.DBus.Properties on path.
146
+func New(conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Prop) *Properties {
147
+	p := &Properties{m: props, conn: conn, path: path}
148
+	conn.Export(p, path, "org.freedesktop.DBus.Properties")
149
+	return p
150
+}
151
+
152
+// Get implements org.freedesktop.DBus.Properties.Get.
153
+func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) {
154
+	p.mut.RLock()
155
+	defer p.mut.RUnlock()
156
+	m, ok := p.m[iface]
157
+	if !ok {
158
+		return dbus.Variant{}, ErrIfaceNotFound
159
+	}
160
+	prop, ok := m[property]
161
+	if !ok {
162
+		return dbus.Variant{}, ErrPropNotFound
163
+	}
164
+	return dbus.MakeVariant(prop.Value), nil
165
+}
166
+
167
+// GetAll implements org.freedesktop.DBus.Properties.GetAll.
168
+func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
169
+	p.mut.RLock()
170
+	defer p.mut.RUnlock()
171
+	m, ok := p.m[iface]
172
+	if !ok {
173
+		return nil, ErrIfaceNotFound
174
+	}
175
+	rm := make(map[string]dbus.Variant, len(m))
176
+	for k, v := range m {
177
+		rm[k] = dbus.MakeVariant(v.Value)
178
+	}
179
+	return rm, nil
180
+}
181
+
182
+// GetMust returns the value of the given property and panics if either the
183
+// interface or the property name are invalid.
184
+func (p *Properties) GetMust(iface, property string) interface{} {
185
+	p.mut.RLock()
186
+	defer p.mut.RUnlock()
187
+	return p.m[iface][property].Value
188
+}
189
+
190
+// Introspection returns the introspection data that represents the properties
191
+// of iface.
192
+func (p *Properties) Introspection(iface string) []introspect.Property {
193
+	p.mut.RLock()
194
+	defer p.mut.RUnlock()
195
+	m := p.m[iface]
196
+	s := make([]introspect.Property, 0, len(m))
197
+	for k, v := range m {
198
+		p := introspect.Property{Name: k, Type: dbus.SignatureOf(v.Value).String()}
199
+		if v.Writable {
200
+			p.Access = "readwrite"
201
+		} else {
202
+			p.Access = "read"
203
+		}
204
+		s = append(s, p)
205
+	}
206
+	return s
207
+}
208
+
209
+// set sets the given property and emits PropertyChanged if appropiate. p.mut
210
+// must already be locked.
211
+func (p *Properties) set(iface, property string, v interface{}) {
212
+	prop := p.m[iface][property]
213
+	prop.Value = v
214
+	switch prop.Emit {
215
+	case EmitFalse:
216
+		// do nothing
217
+	case EmitInvalidates:
218
+		p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
219
+			iface, map[string]dbus.Variant{}, []string{property})
220
+	case EmitTrue:
221
+		p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
222
+			iface, map[string]dbus.Variant{property: dbus.MakeVariant(v)},
223
+			[]string{})
224
+	default:
225
+		panic("invalid value for EmitType")
226
+	}
227
+}
228
+
229
+// Set implements org.freedesktop.Properties.Set.
230
+func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error {
231
+	p.mut.Lock()
232
+	defer p.mut.Unlock()
233
+	m, ok := p.m[iface]
234
+	if !ok {
235
+		return ErrIfaceNotFound
236
+	}
237
+	prop, ok := m[property]
238
+	if !ok {
239
+		return ErrPropNotFound
240
+	}
241
+	if !prop.Writable {
242
+		return ErrReadOnly
243
+	}
244
+	if newv.Signature() != dbus.SignatureOf(prop.Value) {
245
+		return ErrInvalidArg
246
+	}
247
+	if prop.Callback != nil {
248
+		err := prop.Callback(&Change{p, iface, property, newv.Value()})
249
+		if err != nil {
250
+			return err
251
+		}
252
+	}
253
+	p.set(iface, property, newv.Value())
254
+	return nil
255
+}
256
+
257
+// SetMust sets the value of the given property and panics if the interface or
258
+// the property name are invalid.
259
+func (p *Properties) SetMust(iface, property string, v interface{}) {
260
+	p.mut.Lock()
261
+	p.set(iface, property, v)
262
+	p.mut.Unlock()
263
+}
0 264
new file mode 100644
... ...
@@ -0,0 +1,369 @@
0
+package dbus
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/binary"
5
+	"io/ioutil"
6
+	"math"
7
+	"reflect"
8
+	"testing"
9
+)
10
+
11
+var protoTests = []struct {
12
+	vs           []interface{}
13
+	bigEndian    []byte
14
+	littleEndian []byte
15
+}{
16
+	{
17
+		[]interface{}{int32(0)},
18
+		[]byte{0, 0, 0, 0},
19
+		[]byte{0, 0, 0, 0},
20
+	},
21
+	{
22
+		[]interface{}{true, false},
23
+		[]byte{0, 0, 0, 1, 0, 0, 0, 0},
24
+		[]byte{1, 0, 0, 0, 0, 0, 0, 0},
25
+	},
26
+	{
27
+		[]interface{}{byte(0), uint16(12), int16(32), uint32(43)},
28
+		[]byte{0, 0, 0, 12, 0, 32, 0, 0, 0, 0, 0, 43},
29
+		[]byte{0, 0, 12, 0, 32, 0, 0, 0, 43, 0, 0, 0},
30
+	},
31
+	{
32
+		[]interface{}{int64(-1), uint64(1<<64 - 1)},
33
+		bytes.Repeat([]byte{255}, 16),
34
+		bytes.Repeat([]byte{255}, 16),
35
+	},
36
+	{
37
+		[]interface{}{math.Inf(+1)},
38
+		[]byte{0x7f, 0xf0, 0, 0, 0, 0, 0, 0},
39
+		[]byte{0, 0, 0, 0, 0, 0, 0xf0, 0x7f},
40
+	},
41
+	{
42
+		[]interface{}{"foo"},
43
+		[]byte{0, 0, 0, 3, 'f', 'o', 'o', 0},
44
+		[]byte{3, 0, 0, 0, 'f', 'o', 'o', 0},
45
+	},
46
+	{
47
+		[]interface{}{Signature{"ai"}},
48
+		[]byte{2, 'a', 'i', 0},
49
+		[]byte{2, 'a', 'i', 0},
50
+	},
51
+	{
52
+		[]interface{}{[]int16{42, 256}},
53
+		[]byte{0, 0, 0, 4, 0, 42, 1, 0},
54
+		[]byte{4, 0, 0, 0, 42, 0, 0, 1},
55
+	},
56
+	{
57
+		[]interface{}{MakeVariant("foo")},
58
+		[]byte{1, 's', 0, 0, 0, 0, 0, 3, 'f', 'o', 'o', 0},
59
+		[]byte{1, 's', 0, 0, 3, 0, 0, 0, 'f', 'o', 'o', 0},
60
+	},
61
+	{
62
+		[]interface{}{MakeVariant(MakeVariant(Signature{"v"}))},
63
+		[]byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0},
64
+		[]byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0},
65
+	},
66
+	{
67
+		[]interface{}{map[int32]bool{42: true}},
68
+		[]byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1},
69
+		[]byte{8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1, 0, 0, 0},
70
+	},
71
+	{
72
+		[]interface{}{map[string]Variant{}, byte(42)},
73
+		[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
74
+		[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
75
+	},
76
+	{
77
+		[]interface{}{[]uint64{}, byte(42)},
78
+		[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
79
+		[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
80
+	},
81
+}
82
+
83
+func TestProto(t *testing.T) {
84
+	for i, v := range protoTests {
85
+		buf := new(bytes.Buffer)
86
+		bigEnc := newEncoder(buf, binary.BigEndian)
87
+		bigEnc.Encode(v.vs...)
88
+		marshalled := buf.Bytes()
89
+		if bytes.Compare(marshalled, v.bigEndian) != 0 {
90
+			t.Errorf("test %d (marshal be): got '%v', but expected '%v'\n", i+1, marshalled,
91
+				v.bigEndian)
92
+		}
93
+		buf.Reset()
94
+		litEnc := newEncoder(buf, binary.LittleEndian)
95
+		litEnc.Encode(v.vs...)
96
+		marshalled = buf.Bytes()
97
+		if bytes.Compare(marshalled, v.littleEndian) != 0 {
98
+			t.Errorf("test %d (marshal le): got '%v', but expected '%v'\n", i+1, marshalled,
99
+				v.littleEndian)
100
+		}
101
+		unmarshalled := reflect.MakeSlice(reflect.TypeOf(v.vs),
102
+			0, 0)
103
+		for i := range v.vs {
104
+			unmarshalled = reflect.Append(unmarshalled,
105
+				reflect.New(reflect.TypeOf(v.vs[i])))
106
+		}
107
+		bigDec := newDecoder(bytes.NewReader(v.bigEndian), binary.BigEndian)
108
+		vs, err := bigDec.Decode(SignatureOf(v.vs...))
109
+		if err != nil {
110
+			t.Errorf("test %d (unmarshal be): %s\n", i+1, err)
111
+			continue
112
+		}
113
+		if !reflect.DeepEqual(vs, v.vs) {
114
+			t.Errorf("test %d (unmarshal be): got %#v, but expected %#v\n", i+1, vs, v.vs)
115
+		}
116
+		litDec := newDecoder(bytes.NewReader(v.littleEndian), binary.LittleEndian)
117
+		vs, err = litDec.Decode(SignatureOf(v.vs...))
118
+		if err != nil {
119
+			t.Errorf("test %d (unmarshal le): %s\n", i+1, err)
120
+			continue
121
+		}
122
+		if !reflect.DeepEqual(vs, v.vs) {
123
+			t.Errorf("test %d (unmarshal le): got %#v, but expected %#v\n", i+1, vs, v.vs)
124
+		}
125
+
126
+	}
127
+}
128
+
129
+func TestProtoMap(t *testing.T) {
130
+	m := map[string]uint8{
131
+		"foo": 23,
132
+		"bar": 2,
133
+	}
134
+	var n map[string]uint8
135
+	buf := new(bytes.Buffer)
136
+	enc := newEncoder(buf, binary.LittleEndian)
137
+	enc.Encode(m)
138
+	dec := newDecoder(buf, binary.LittleEndian)
139
+	vs, err := dec.Decode(Signature{"a{sy}"})
140
+	if err != nil {
141
+		t.Fatal(err)
142
+	}
143
+	if err = Store(vs, &n); err != nil {
144
+		t.Fatal(err)
145
+	}
146
+	if len(n) != 2 || n["foo"] != 23 || n["bar"] != 2 {
147
+		t.Error("got", n)
148
+	}
149
+}
150
+
151
+func TestProtoVariantStruct(t *testing.T) {
152
+	var variant Variant
153
+	v := MakeVariant(struct {
154
+		A int32
155
+		B int16
156
+	}{1, 2})
157
+	buf := new(bytes.Buffer)
158
+	enc := newEncoder(buf, binary.LittleEndian)
159
+	enc.Encode(v)
160
+	dec := newDecoder(buf, binary.LittleEndian)
161
+	vs, err := dec.Decode(Signature{"v"})
162
+	if err != nil {
163
+		t.Fatal(err)
164
+	}
165
+	if err = Store(vs, &variant); err != nil {
166
+		t.Fatal(err)
167
+	}
168
+	sl := variant.Value().([]interface{})
169
+	v1, v2 := sl[0].(int32), sl[1].(int16)
170
+	if v1 != int32(1) {
171
+		t.Error("got", v1, "as first int")
172
+	}
173
+	if v2 != int16(2) {
174
+		t.Error("got", v2, "as second int")
175
+	}
176
+}
177
+
178
+func TestProtoStructTag(t *testing.T) {
179
+	type Bar struct {
180
+		A int32
181
+		B chan interface{} `dbus:"-"`
182
+		C int32
183
+	}
184
+	var bar1, bar2 Bar
185
+	bar1.A = 234
186
+	bar2.C = 345
187
+	buf := new(bytes.Buffer)
188
+	enc := newEncoder(buf, binary.LittleEndian)
189
+	enc.Encode(bar1)
190
+	dec := newDecoder(buf, binary.LittleEndian)
191
+	vs, err := dec.Decode(Signature{"(ii)"})
192
+	if err != nil {
193
+		t.Fatal(err)
194
+	}
195
+	if err = Store(vs, &bar2); err != nil {
196
+		t.Fatal(err)
197
+	}
198
+	if bar1 != bar2 {
199
+		t.Error("struct tag test: got", bar2)
200
+	}
201
+}
202
+
203
+func TestProtoStoreStruct(t *testing.T) {
204
+	var foo struct {
205
+		A int32
206
+		B string
207
+		c chan interface{}
208
+		D interface{} `dbus:"-"`
209
+	}
210
+	src := []interface{}{[]interface{}{int32(42), "foo"}}
211
+	err := Store(src, &foo)
212
+	if err != nil {
213
+		t.Fatal(err)
214
+	}
215
+}
216
+
217
+func TestProtoStoreNestedStruct(t *testing.T) {
218
+	var foo struct {
219
+		A int32
220
+		B struct {
221
+			C string
222
+			D float64
223
+		}
224
+	}
225
+	src := []interface{}{
226
+		[]interface{}{
227
+			int32(42),
228
+			[]interface{}{
229
+				"foo",
230
+				3.14,
231
+			},
232
+		},
233
+	}
234
+	err := Store(src, &foo)
235
+	if err != nil {
236
+		t.Fatal(err)
237
+	}
238
+}
239
+
240
+func TestMessage(t *testing.T) {
241
+	buf := new(bytes.Buffer)
242
+	message := new(Message)
243
+	message.Type = TypeMethodCall
244
+	message.serial = 32
245
+	message.Headers = map[HeaderField]Variant{
246
+		FieldPath:   MakeVariant(ObjectPath("/org/foo/bar")),
247
+		FieldMember: MakeVariant("baz"),
248
+	}
249
+	message.Body = make([]interface{}, 0)
250
+	err := message.EncodeTo(buf, binary.LittleEndian)
251
+	if err != nil {
252
+		t.Error(err)
253
+	}
254
+	_, err = DecodeMessage(buf)
255
+	if err != nil {
256
+		t.Error(err)
257
+	}
258
+}
259
+
260
+func TestProtoStructInterfaces(t *testing.T) {
261
+	b := []byte{42}
262
+	vs, err := newDecoder(bytes.NewReader(b), binary.LittleEndian).Decode(Signature{"(y)"})
263
+	if err != nil {
264
+		t.Fatal(err)
265
+	}
266
+	if vs[0].([]interface{})[0].(byte) != 42 {
267
+		t.Errorf("wrongs results (got %v)", vs)
268
+	}
269
+}
270
+
271
+// ordinary org.freedesktop.DBus.Hello call
272
+var smallMessage = &Message{
273
+	Type:   TypeMethodCall,
274
+	serial: 1,
275
+	Headers: map[HeaderField]Variant{
276
+		FieldDestination: MakeVariant("org.freedesktop.DBus"),
277
+		FieldPath:        MakeVariant(ObjectPath("/org/freedesktop/DBus")),
278
+		FieldInterface:   MakeVariant("org.freedesktop.DBus"),
279
+		FieldMember:      MakeVariant("Hello"),
280
+	},
281
+}
282
+
283
+// org.freedesktop.Notifications.Notify
284
+var bigMessage = &Message{
285
+	Type:   TypeMethodCall,
286
+	serial: 2,
287
+	Headers: map[HeaderField]Variant{
288
+		FieldDestination: MakeVariant("org.freedesktop.Notifications"),
289
+		FieldPath:        MakeVariant(ObjectPath("/org/freedesktop/Notifications")),
290
+		FieldInterface:   MakeVariant("org.freedesktop.Notifications"),
291
+		FieldMember:      MakeVariant("Notify"),
292
+		FieldSignature:   MakeVariant(Signature{"susssasa{sv}i"}),
293
+	},
294
+	Body: []interface{}{
295
+		"app_name",
296
+		uint32(0),
297
+		"dialog-information",
298
+		"Notification",
299
+		"This is the body of a notification",
300
+		[]string{"ok", "Ok"},
301
+		map[string]Variant{
302
+			"sound-name": MakeVariant("dialog-information"),
303
+		},
304
+		int32(-1),
305
+	},
306
+}
307
+
308
+func BenchmarkDecodeMessageSmall(b *testing.B) {
309
+	var err error
310
+	var rd *bytes.Reader
311
+
312
+	b.StopTimer()
313
+	buf := new(bytes.Buffer)
314
+	err = smallMessage.EncodeTo(buf, binary.LittleEndian)
315
+	if err != nil {
316
+		b.Fatal(err)
317
+	}
318
+	decoded := buf.Bytes()
319
+	b.StartTimer()
320
+	for i := 0; i < b.N; i++ {
321
+		rd = bytes.NewReader(decoded)
322
+		_, err = DecodeMessage(rd)
323
+		if err != nil {
324
+			b.Fatal(err)
325
+		}
326
+	}
327
+}
328
+
329
+func BenchmarkDecodeMessageBig(b *testing.B) {
330
+	var err error
331
+	var rd *bytes.Reader
332
+
333
+	b.StopTimer()
334
+	buf := new(bytes.Buffer)
335
+	err = bigMessage.EncodeTo(buf, binary.LittleEndian)
336
+	if err != nil {
337
+		b.Fatal(err)
338
+	}
339
+	decoded := buf.Bytes()
340
+	b.StartTimer()
341
+	for i := 0; i < b.N; i++ {
342
+		rd = bytes.NewReader(decoded)
343
+		_, err = DecodeMessage(rd)
344
+		if err != nil {
345
+			b.Fatal(err)
346
+		}
347
+	}
348
+}
349
+
350
+func BenchmarkEncodeMessageSmall(b *testing.B) {
351
+	var err error
352
+	for i := 0; i < b.N; i++ {
353
+		err = smallMessage.EncodeTo(ioutil.Discard, binary.LittleEndian)
354
+		if err != nil {
355
+			b.Fatal(err)
356
+		}
357
+	}
358
+}
359
+
360
+func BenchmarkEncodeMessageBig(b *testing.B) {
361
+	var err error
362
+	for i := 0; i < b.N; i++ {
363
+		err = bigMessage.EncodeTo(ioutil.Discard, binary.LittleEndian)
364
+		if err != nil {
365
+			b.Fatal(err)
366
+		}
367
+	}
368
+}
0 369
new file mode 100644
... ...
@@ -0,0 +1,257 @@
0
+package dbus
1
+
2
+import (
3
+	"fmt"
4
+	"reflect"
5
+	"strings"
6
+)
7
+
8
+var sigToType = map[byte]reflect.Type{
9
+	'y': byteType,
10
+	'b': boolType,
11
+	'n': int16Type,
12
+	'q': uint16Type,
13
+	'i': int32Type,
14
+	'u': uint32Type,
15
+	'x': int64Type,
16
+	't': uint64Type,
17
+	'd': float64Type,
18
+	's': stringType,
19
+	'g': signatureType,
20
+	'o': objectPathType,
21
+	'v': variantType,
22
+	'h': unixFDIndexType,
23
+}
24
+
25
+// Signature represents a correct type signature as specified by the D-Bus
26
+// specification. The zero value represents the empty signature, "".
27
+type Signature struct {
28
+	str string
29
+}
30
+
31
+// SignatureOf returns the concatenation of all the signatures of the given
32
+// values. It panics if one of them is not representable in D-Bus.
33
+func SignatureOf(vs ...interface{}) Signature {
34
+	var s string
35
+	for _, v := range vs {
36
+		s += getSignature(reflect.TypeOf(v))
37
+	}
38
+	return Signature{s}
39
+}
40
+
41
+// SignatureOfType returns the signature of the given type. It panics if the
42
+// type is not representable in D-Bus.
43
+func SignatureOfType(t reflect.Type) Signature {
44
+	return Signature{getSignature(t)}
45
+}
46
+
47
+// getSignature returns the signature of the given type and panics on unknown types.
48
+func getSignature(t reflect.Type) string {
49
+	// handle simple types first
50
+	switch t.Kind() {
51
+	case reflect.Uint8:
52
+		return "y"
53
+	case reflect.Bool:
54
+		return "b"
55
+	case reflect.Int16:
56
+		return "n"
57
+	case reflect.Uint16:
58
+		return "q"
59
+	case reflect.Int32:
60
+		if t == unixFDType {
61
+			return "h"
62
+		}
63
+		return "i"
64
+	case reflect.Uint32:
65
+		if t == unixFDIndexType {
66
+			return "h"
67
+		}
68
+		return "u"
69
+	case reflect.Int64:
70
+		return "x"
71
+	case reflect.Uint64:
72
+		return "t"
73
+	case reflect.Float64:
74
+		return "d"
75
+	case reflect.Ptr:
76
+		return getSignature(t.Elem())
77
+	case reflect.String:
78
+		if t == objectPathType {
79
+			return "o"
80
+		}
81
+		return "s"
82
+	case reflect.Struct:
83
+		if t == variantType {
84
+			return "v"
85
+		} else if t == signatureType {
86
+			return "g"
87
+		}
88
+		var s string
89
+		for i := 0; i < t.NumField(); i++ {
90
+			field := t.Field(i)
91
+			if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
92
+				s += getSignature(t.Field(i).Type)
93
+			}
94
+		}
95
+		return "(" + s + ")"
96
+	case reflect.Array, reflect.Slice:
97
+		return "a" + getSignature(t.Elem())
98
+	case reflect.Map:
99
+		if !isKeyType(t.Key()) {
100
+			panic(InvalidTypeError{t})
101
+		}
102
+		return "a{" + getSignature(t.Key()) + getSignature(t.Elem()) + "}"
103
+	}
104
+	panic(InvalidTypeError{t})
105
+}
106
+
107
+// ParseSignature returns the signature represented by this string, or a
108
+// SignatureError if the string is not a valid signature.
109
+func ParseSignature(s string) (sig Signature, err error) {
110
+	if len(s) == 0 {
111
+		return
112
+	}
113
+	if len(s) > 255 {
114
+		return Signature{""}, SignatureError{s, "too long"}
115
+	}
116
+	sig.str = s
117
+	for err == nil && len(s) != 0 {
118
+		err, s = validSingle(s, 0)
119
+	}
120
+	if err != nil {
121
+		sig = Signature{""}
122
+	}
123
+
124
+	return
125
+}
126
+
127
+// ParseSignatureMust behaves like ParseSignature, except that it panics if s
128
+// is not valid.
129
+func ParseSignatureMust(s string) Signature {
130
+	sig, err := ParseSignature(s)
131
+	if err != nil {
132
+		panic(err)
133
+	}
134
+	return sig
135
+}
136
+
137
+// Empty retruns whether the signature is the empty signature.
138
+func (s Signature) Empty() bool {
139
+	return s.str == ""
140
+}
141
+
142
+// Single returns whether the signature represents a single, complete type.
143
+func (s Signature) Single() bool {
144
+	err, r := validSingle(s.str, 0)
145
+	return err != nil && r == ""
146
+}
147
+
148
+// String returns the signature's string representation.
149
+func (s Signature) String() string {
150
+	return s.str
151
+}
152
+
153
+// A SignatureError indicates that a signature passed to a function or received
154
+// on a connection is not a valid signature.
155
+type SignatureError struct {
156
+	Sig    string
157
+	Reason string
158
+}
159
+
160
+func (e SignatureError) Error() string {
161
+	return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
162
+}
163
+
164
+// Try to read a single type from this string. If it was successfull, err is nil
165
+// and rem is the remaining unparsed part. Otherwise, err is a non-nil
166
+// SignatureError and rem is "". depth is the current recursion depth which may
167
+// not be greater than 64 and should be given as 0 on the first call.
168
+func validSingle(s string, depth int) (err error, rem string) {
169
+	if s == "" {
170
+		return SignatureError{Sig: s, Reason: "empty signature"}, ""
171
+	}
172
+	if depth > 64 {
173
+		return SignatureError{Sig: s, Reason: "container nesting too deep"}, ""
174
+	}
175
+	switch s[0] {
176
+	case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h':
177
+		return nil, s[1:]
178
+	case 'a':
179
+		if len(s) > 1 && s[1] == '{' {
180
+			i := findMatching(s[1:], '{', '}')
181
+			if i == -1 {
182
+				return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
183
+			}
184
+			i++
185
+			rem = s[i+1:]
186
+			s = s[2:i]
187
+			if err, _ = validSingle(s[:1], depth+1); err != nil {
188
+				return err, ""
189
+			}
190
+			err, nr := validSingle(s[1:], depth+1)
191
+			if err != nil {
192
+				return err, ""
193
+			}
194
+			if nr != "" {
195
+				return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
196
+			}
197
+			return nil, rem
198
+		}
199
+		return validSingle(s[1:], depth+1)
200
+	case '(':
201
+		i := findMatching(s, '(', ')')
202
+		if i == -1 {
203
+			return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
204
+		}
205
+		rem = s[i+1:]
206
+		s = s[1:i]
207
+		for err == nil && s != "" {
208
+			err, s = validSingle(s, depth+1)
209
+		}
210
+		if err != nil {
211
+			rem = ""
212
+		}
213
+		return
214
+	}
215
+	return SignatureError{Sig: s, Reason: "invalid type character"}, ""
216
+}
217
+
218
+func findMatching(s string, left, right rune) int {
219
+	n := 0
220
+	for i, v := range s {
221
+		if v == left {
222
+			n++
223
+		} else if v == right {
224
+			n--
225
+		}
226
+		if n == 0 {
227
+			return i
228
+		}
229
+	}
230
+	return -1
231
+}
232
+
233
+// typeFor returns the type of the given signature. It ignores any left over
234
+// characters and panics if s doesn't start with a valid type signature.
235
+func typeFor(s string) (t reflect.Type) {
236
+	err, _ := validSingle(s, 0)
237
+	if err != nil {
238
+		panic(err)
239
+	}
240
+
241
+	if t, ok := sigToType[s[0]]; ok {
242
+		return t
243
+	}
244
+	switch s[0] {
245
+	case 'a':
246
+		if s[1] == '{' {
247
+			i := strings.LastIndex(s, "}")
248
+			t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i]))
249
+		} else {
250
+			t = reflect.SliceOf(typeFor(s[1:]))
251
+		}
252
+	case '(':
253
+		t = interfacesType
254
+	}
255
+	return
256
+}
0 257
new file mode 100644
... ...
@@ -0,0 +1,70 @@
0
+package dbus
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+var sigTests = []struct {
7
+	vs  []interface{}
8
+	sig Signature
9
+}{
10
+	{
11
+		[]interface{}{new(int32)},
12
+		Signature{"i"},
13
+	},
14
+	{
15
+		[]interface{}{new(string)},
16
+		Signature{"s"},
17
+	},
18
+	{
19
+		[]interface{}{new(Signature)},
20
+		Signature{"g"},
21
+	},
22
+	{
23
+		[]interface{}{new([]int16)},
24
+		Signature{"an"},
25
+	},
26
+	{
27
+		[]interface{}{new(int16), new(uint32)},
28
+		Signature{"nu"},
29
+	},
30
+	{
31
+		[]interface{}{new(map[byte]Variant)},
32
+		Signature{"a{yv}"},
33
+	},
34
+	{
35
+		[]interface{}{new(Variant), new([]map[int32]string)},
36
+		Signature{"vaa{is}"},
37
+	},
38
+}
39
+
40
+func TestSig(t *testing.T) {
41
+	for i, v := range sigTests {
42
+		sig := SignatureOf(v.vs...)
43
+		if sig != v.sig {
44
+			t.Errorf("test %d: got %q, expected %q", i+1, sig.str, v.sig.str)
45
+		}
46
+	}
47
+}
48
+
49
+var getSigTest = []interface{}{
50
+	[]struct {
51
+		b byte
52
+		i int32
53
+		t uint64
54
+		s string
55
+	}{},
56
+	map[string]Variant{},
57
+}
58
+
59
+func BenchmarkGetSignatureSimple(b *testing.B) {
60
+	for i := 0; i < b.N; i++ {
61
+		SignatureOf("", int32(0))
62
+	}
63
+}
64
+
65
+func BenchmarkGetSignatureLong(b *testing.B) {
66
+	for i := 0; i < b.N; i++ {
67
+		SignatureOf(getSigTest...)
68
+	}
69
+}
0 70
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+package dbus
1
+
2
+func (t *unixTransport) SendNullByte() error {
3
+	_, err := t.Write([]byte{0})
4
+	return err
5
+}
0 6
new file mode 100644
... ...
@@ -0,0 +1,35 @@
0
+package dbus
1
+
2
+import (
3
+	"encoding/binary"
4
+	"errors"
5
+	"io"
6
+)
7
+
8
+type genericTransport struct {
9
+	io.ReadWriteCloser
10
+}
11
+
12
+func (t genericTransport) SendNullByte() error {
13
+	_, err := t.Write([]byte{0})
14
+	return err
15
+}
16
+
17
+func (t genericTransport) SupportsUnixFDs() bool {
18
+	return false
19
+}
20
+
21
+func (t genericTransport) EnableUnixFDs() {}
22
+
23
+func (t genericTransport) ReadMessage() (*Message, error) {
24
+	return DecodeMessage(t)
25
+}
26
+
27
+func (t genericTransport) SendMessage(msg *Message) error {
28
+	for _, v := range msg.Body {
29
+		if _, ok := v.(UnixFD); ok {
30
+			return errors.New("dbus: unix fd passing not enabled")
31
+		}
32
+	}
33
+	return msg.EncodeTo(t, binary.LittleEndian)
34
+}
0 35
new file mode 100644
... ...
@@ -0,0 +1,190 @@
0
+package dbus
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/binary"
5
+	"errors"
6
+	"io"
7
+	"net"
8
+	"syscall"
9
+)
10
+
11
+type oobReader struct {
12
+	conn *net.UnixConn
13
+	oob  []byte
14
+	buf  [4096]byte
15
+}
16
+
17
+func (o *oobReader) Read(b []byte) (n int, err error) {
18
+	n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:])
19
+	if err != nil {
20
+		return n, err
21
+	}
22
+	if flags&syscall.MSG_CTRUNC != 0 {
23
+		return n, errors.New("dbus: control data truncated (too many fds received)")
24
+	}
25
+	o.oob = append(o.oob, o.buf[:oobn]...)
26
+	return n, nil
27
+}
28
+
29
+type unixTransport struct {
30
+	*net.UnixConn
31
+	hasUnixFDs bool
32
+}
33
+
34
+func newUnixTransport(keys string) (transport, error) {
35
+	var err error
36
+
37
+	t := new(unixTransport)
38
+	abstract := getKey(keys, "abstract")
39
+	path := getKey(keys, "path")
40
+	switch {
41
+	case abstract == "" && path == "":
42
+		return nil, errors.New("dbus: invalid address (neither path nor abstract set)")
43
+	case abstract != "" && path == "":
44
+		t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"})
45
+		if err != nil {
46
+			return nil, err
47
+		}
48
+		return t, nil
49
+	case abstract == "" && path != "":
50
+		t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"})
51
+		if err != nil {
52
+			return nil, err
53
+		}
54
+		return t, nil
55
+	default:
56
+		return nil, errors.New("dbus: invalid address (both path and abstract set)")
57
+	}
58
+}
59
+
60
+func (t *unixTransport) EnableUnixFDs() {
61
+	t.hasUnixFDs = true
62
+}
63
+
64
+func (t *unixTransport) ReadMessage() (*Message, error) {
65
+	var (
66
+		blen, hlen uint32
67
+		csheader   [16]byte
68
+		headers    []header
69
+		order      binary.ByteOrder
70
+		unixfds    uint32
71
+	)
72
+	// To be sure that all bytes of out-of-band data are read, we use a special
73
+	// reader that uses ReadUnix on the underlying connection instead of Read
74
+	// and gathers the out-of-band data in a buffer.
75
+	rd := &oobReader{conn: t.UnixConn}
76
+	// read the first 16 bytes (the part of the header that has a constant size),
77
+	// from which we can figure out the length of the rest of the message
78
+	if _, err := io.ReadFull(rd, csheader[:]); err != nil {
79
+		return nil, err
80
+	}
81
+	switch csheader[0] {
82
+	case 'l':
83
+		order = binary.LittleEndian
84
+	case 'B':
85
+		order = binary.BigEndian
86
+	default:
87
+		return nil, InvalidMessageError("invalid byte order")
88
+	}
89
+	// csheader[4:8] -> length of message body, csheader[12:16] -> length of
90
+	// header fields (without alignment)
91
+	binary.Read(bytes.NewBuffer(csheader[4:8]), order, &blen)
92
+	binary.Read(bytes.NewBuffer(csheader[12:]), order, &hlen)
93
+	if hlen%8 != 0 {
94
+		hlen += 8 - (hlen % 8)
95
+	}
96
+
97
+	// decode headers and look for unix fds
98
+	headerdata := make([]byte, hlen+4)
99
+	copy(headerdata, csheader[12:])
100
+	if _, err := io.ReadFull(t, headerdata[4:]); err != nil {
101
+		return nil, err
102
+	}
103
+	dec := newDecoder(bytes.NewBuffer(headerdata), order)
104
+	dec.pos = 12
105
+	vs, err := dec.Decode(Signature{"a(yv)"})
106
+	if err != nil {
107
+		return nil, err
108
+	}
109
+	Store(vs, &headers)
110
+	for _, v := range headers {
111
+		if v.Field == byte(FieldUnixFDs) {
112
+			unixfds, _ = v.Variant.value.(uint32)
113
+		}
114
+	}
115
+	all := make([]byte, 16+hlen+blen)
116
+	copy(all, csheader[:])
117
+	copy(all[16:], headerdata[4:])
118
+	if _, err := io.ReadFull(rd, all[16+hlen:]); err != nil {
119
+		return nil, err
120
+	}
121
+	if unixfds != 0 {
122
+		if !t.hasUnixFDs {
123
+			return nil, errors.New("dbus: got unix fds on unsupported transport")
124
+		}
125
+		// read the fds from the OOB data
126
+		scms, err := syscall.ParseSocketControlMessage(rd.oob)
127
+		if err != nil {
128
+			return nil, err
129
+		}
130
+		if len(scms) != 1 {
131
+			return nil, errors.New("dbus: received more than one socket control message")
132
+		}
133
+		fds, err := syscall.ParseUnixRights(&scms[0])
134
+		if err != nil {
135
+			return nil, err
136
+		}
137
+		msg, err := DecodeMessage(bytes.NewBuffer(all))
138
+		if err != nil {
139
+			return nil, err
140
+		}
141
+		// substitute the values in the message body (which are indices for the
142
+		// array receiver via OOB) with the actual values
143
+		for i, v := range msg.Body {
144
+			if j, ok := v.(UnixFDIndex); ok {
145
+				if uint32(j) >= unixfds {
146
+					return nil, InvalidMessageError("invalid index for unix fd")
147
+				}
148
+				msg.Body[i] = UnixFD(fds[j])
149
+			}
150
+		}
151
+		return msg, nil
152
+	}
153
+	return DecodeMessage(bytes.NewBuffer(all))
154
+}
155
+
156
+func (t *unixTransport) SendMessage(msg *Message) error {
157
+	fds := make([]int, 0)
158
+	for i, v := range msg.Body {
159
+		if fd, ok := v.(UnixFD); ok {
160
+			msg.Body[i] = UnixFDIndex(len(fds))
161
+			fds = append(fds, int(fd))
162
+		}
163
+	}
164
+	if len(fds) != 0 {
165
+		if !t.hasUnixFDs {
166
+			return errors.New("dbus: unix fd passing not enabled")
167
+		}
168
+		msg.Headers[FieldUnixFDs] = MakeVariant(uint32(len(fds)))
169
+		oob := syscall.UnixRights(fds...)
170
+		buf := new(bytes.Buffer)
171
+		msg.EncodeTo(buf, binary.LittleEndian)
172
+		n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil)
173
+		if err != nil {
174
+			return err
175
+		}
176
+		if n != buf.Len() || oobn != len(oob) {
177
+			return io.ErrShortWrite
178
+		}
179
+	} else {
180
+		if err := msg.EncodeTo(t, binary.LittleEndian); err != nil {
181
+			return nil
182
+		}
183
+	}
184
+	return nil
185
+}
186
+
187
+func (t *unixTransport) SupportsUnixFDs() bool {
188
+	return true
189
+}
0 190
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package dbus
1
+
2
+import (
3
+	"os"
4
+	"testing"
5
+)
6
+
7
+const testString = `This is a test!
8
+This text should be read from the file that is created by this test.`
9
+
10
+type unixFDTest struct{}
11
+
12
+func (t unixFDTest) Test(fd UnixFD) (string, *Error) {
13
+	var b [4096]byte
14
+	file := os.NewFile(uintptr(fd), "testfile")
15
+	defer file.Close()
16
+	n, err := file.Read(b[:])
17
+	if err != nil {
18
+		return "", &Error{"com.github.guelfey.test.Error", nil}
19
+	}
20
+	return string(b[:n]), nil
21
+}
22
+
23
+func TestUnixFDs(t *testing.T) {
24
+	conn, err := SessionBus()
25
+	if err != nil {
26
+		t.Fatal(err)
27
+	}
28
+	r, w, err := os.Pipe()
29
+	if err != nil {
30
+		t.Fatal(err)
31
+	}
32
+	defer w.Close()
33
+	if _, err := w.Write([]byte(testString)); err != nil {
34
+		t.Fatal(err)
35
+	}
36
+	name := conn.Names()[0]
37
+	test := unixFDTest{}
38
+	conn.Export(test, "/com/github/guelfey/test", "com.github.guelfey.test")
39
+	var s string
40
+	obj := conn.Object(name, "/com/github/guelfey/test")
41
+	err = obj.Call("com.github.guelfey.test.Test", 0, UnixFD(r.Fd())).Store(&s)
42
+	if err != nil {
43
+		t.Fatal(err)
44
+	}
45
+	if s != testString {
46
+		t.Fatal("got", s, "wanted", testString)
47
+	}
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,22 @@
0
+// +build !darwin
1
+
2
+package dbus
3
+
4
+import (
5
+	"io"
6
+	"os"
7
+	"syscall"
8
+)
9
+
10
+func (t *unixTransport) SendNullByte() error {
11
+	ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
12
+	b := syscall.UnixCredentials(ucred)
13
+	_, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil)
14
+	if err != nil {
15
+		return err
16
+	}
17
+	if oobn != len(b) {
18
+		return io.ErrShortWrite
19
+	}
20
+	return nil
21
+}
0 22
new file mode 100644
... ...
@@ -0,0 +1,129 @@
0
+package dbus
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"reflect"
6
+	"strconv"
7
+)
8
+
9
+// Variant represents the D-Bus variant type.
10
+type Variant struct {
11
+	sig   Signature
12
+	value interface{}
13
+}
14
+
15
+// MakeVariant converts the given value to a Variant. It panics if v cannot be
16
+// represented as a D-Bus type.
17
+func MakeVariant(v interface{}) Variant {
18
+	return Variant{SignatureOf(v), v}
19
+}
20
+
21
+// ParseVariant parses the given string as a variant as described at
22
+// https://developer.gnome.org/glib/unstable/gvariant-text.html. If sig is not
23
+// empty, it is taken to be the expected signature for the variant.
24
+func ParseVariant(s string, sig Signature) (Variant, error) {
25
+	tokens := varLex(s)
26
+	p := &varParser{tokens: tokens}
27
+	n, err := varMakeNode(p)
28
+	if err != nil {
29
+		return Variant{}, err
30
+	}
31
+	if sig.str == "" {
32
+		sig, err = varInfer(n)
33
+		if err != nil {
34
+			return Variant{}, err
35
+		}
36
+	}
37
+	v, err := n.Value(sig)
38
+	if err != nil {
39
+		return Variant{}, err
40
+	}
41
+	return MakeVariant(v), nil
42
+}
43
+
44
+// format returns a formatted version of v and whether this string can be parsed
45
+// unambigously.
46
+func (v Variant) format() (string, bool) {
47
+	switch v.sig.str[0] {
48
+	case 'b', 'i':
49
+		return fmt.Sprint(v.value), true
50
+	case 'n', 'q', 'u', 'x', 't', 'd', 'h':
51
+		return fmt.Sprint(v.value), false
52
+	case 's':
53
+		return strconv.Quote(v.value.(string)), true
54
+	case 'o':
55
+		return strconv.Quote(string(v.value.(ObjectPath))), false
56
+	case 'g':
57
+		return strconv.Quote(v.value.(Signature).str), false
58
+	case 'v':
59
+		s, unamb := v.value.(Variant).format()
60
+		if !unamb {
61
+			return "<@" + v.value.(Variant).sig.str + " " + s + ">", true
62
+		}
63
+		return "<" + s + ">", true
64
+	case 'y':
65
+		return fmt.Sprintf("%#x", v.value.(byte)), false
66
+	}
67
+	rv := reflect.ValueOf(v.value)
68
+	switch rv.Kind() {
69
+	case reflect.Slice:
70
+		if rv.Len() == 0 {
71
+			return "[]", false
72
+		}
73
+		unamb := true
74
+		buf := bytes.NewBuffer([]byte("["))
75
+		for i := 0; i < rv.Len(); i++ {
76
+			// TODO: slooow
77
+			s, b := MakeVariant(rv.Index(i).Interface()).format()
78
+			unamb = unamb && b
79
+			buf.WriteString(s)
80
+			if i != rv.Len()-1 {
81
+				buf.WriteString(", ")
82
+			}
83
+		}
84
+		buf.WriteByte(']')
85
+		return buf.String(), unamb
86
+	case reflect.Map:
87
+		if rv.Len() == 0 {
88
+			return "{}", false
89
+		}
90
+		unamb := true
91
+		buf := bytes.NewBuffer([]byte("{"))
92
+		for i, k := range rv.MapKeys() {
93
+			s, b := MakeVariant(k.Interface()).format()
94
+			unamb = unamb && b
95
+			buf.WriteString(s)
96
+			buf.WriteString(": ")
97
+			s, b = MakeVariant(rv.MapIndex(k).Interface()).format()
98
+			unamb = unamb && b
99
+			buf.WriteString(s)
100
+			if i != rv.Len()-1 {
101
+				buf.WriteString(", ")
102
+			}
103
+		}
104
+		buf.WriteByte('}')
105
+		return buf.String(), unamb
106
+	}
107
+	return `"INVALID"`, true
108
+}
109
+
110
+// Signature returns the D-Bus signature of the underlying value of v.
111
+func (v Variant) Signature() Signature {
112
+	return v.sig
113
+}
114
+
115
+// String returns the string representation of the underlying value of v as
116
+// described at https://developer.gnome.org/glib/unstable/gvariant-text.html.
117
+func (v Variant) String() string {
118
+	s, unamb := v.format()
119
+	if !unamb {
120
+		return "@" + v.sig.str + " " + s
121
+	}
122
+	return s
123
+}
124
+
125
+// Value returns the underlying value of v.
126
+func (v Variant) Value() interface{} {
127
+	return v.value
128
+}
0 129
new file mode 100644
... ...
@@ -0,0 +1,284 @@
0
+package dbus
1
+
2
+import (
3
+	"fmt"
4
+	"strings"
5
+	"unicode"
6
+	"unicode/utf8"
7
+)
8
+
9
+// Heavily inspired by the lexer from text/template.
10
+
11
+type varToken struct {
12
+	typ varTokenType
13
+	val string
14
+}
15
+
16
+type varTokenType byte
17
+
18
+const (
19
+	tokEOF varTokenType = iota
20
+	tokError
21
+	tokNumber
22
+	tokString
23
+	tokBool
24
+	tokArrayStart
25
+	tokArrayEnd
26
+	tokDictStart
27
+	tokDictEnd
28
+	tokVariantStart
29
+	tokVariantEnd
30
+	tokComma
31
+	tokColon
32
+	tokType
33
+	tokByteString
34
+)
35
+
36
+type varLexer struct {
37
+	input  string
38
+	start  int
39
+	pos    int
40
+	width  int
41
+	tokens []varToken
42
+}
43
+
44
+type lexState func(*varLexer) lexState
45
+
46
+func varLex(s string) []varToken {
47
+	l := &varLexer{input: s}
48
+	l.run()
49
+	return l.tokens
50
+}
51
+
52
+func (l *varLexer) accept(valid string) bool {
53
+	if strings.IndexRune(valid, l.next()) >= 0 {
54
+		return true
55
+	}
56
+	l.backup()
57
+	return false
58
+}
59
+
60
+func (l *varLexer) backup() {
61
+	l.pos -= l.width
62
+}
63
+
64
+func (l *varLexer) emit(t varTokenType) {
65
+	l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]})
66
+	l.start = l.pos
67
+}
68
+
69
+func (l *varLexer) errorf(format string, v ...interface{}) lexState {
70
+	l.tokens = append(l.tokens, varToken{
71
+		tokError,
72
+		fmt.Sprintf(format, v...),
73
+	})
74
+	return nil
75
+}
76
+
77
+func (l *varLexer) ignore() {
78
+	l.start = l.pos
79
+}
80
+
81
+func (l *varLexer) next() rune {
82
+	var r rune
83
+
84
+	if l.pos >= len(l.input) {
85
+		l.width = 0
86
+		return -1
87
+	}
88
+	r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
89
+	l.pos += l.width
90
+	return r
91
+}
92
+
93
+func (l *varLexer) run() {
94
+	for state := varLexNormal; state != nil; {
95
+		state = state(l)
96
+	}
97
+}
98
+
99
+func (l *varLexer) peek() rune {
100
+	r := l.next()
101
+	l.backup()
102
+	return r
103
+}
104
+
105
+func varLexNormal(l *varLexer) lexState {
106
+	for {
107
+		r := l.next()
108
+		switch {
109
+		case r == -1:
110
+			l.emit(tokEOF)
111
+			return nil
112
+		case r == '[':
113
+			l.emit(tokArrayStart)
114
+		case r == ']':
115
+			l.emit(tokArrayEnd)
116
+		case r == '{':
117
+			l.emit(tokDictStart)
118
+		case r == '}':
119
+			l.emit(tokDictEnd)
120
+		case r == '<':
121
+			l.emit(tokVariantStart)
122
+		case r == '>':
123
+			l.emit(tokVariantEnd)
124
+		case r == ':':
125
+			l.emit(tokColon)
126
+		case r == ',':
127
+			l.emit(tokComma)
128
+		case r == '\'' || r == '"':
129
+			l.backup()
130
+			return varLexString
131
+		case r == '@':
132
+			l.backup()
133
+			return varLexType
134
+		case unicode.IsSpace(r):
135
+			l.ignore()
136
+		case unicode.IsNumber(r) || r == '+' || r == '-':
137
+			l.backup()
138
+			return varLexNumber
139
+		case r == 'b':
140
+			pos := l.start
141
+			if n := l.peek(); n == '"' || n == '\'' {
142
+				return varLexByteString
143
+			}
144
+			// not a byte string; try to parse it as a type or bool below
145
+			l.pos = pos + 1
146
+			l.width = 1
147
+			fallthrough
148
+		default:
149
+			// either a bool or a type. Try bools first.
150
+			l.backup()
151
+			if l.pos+4 <= len(l.input) {
152
+				if l.input[l.pos:l.pos+4] == "true" {
153
+					l.pos += 4
154
+					l.emit(tokBool)
155
+					continue
156
+				}
157
+			}
158
+			if l.pos+5 <= len(l.input) {
159
+				if l.input[l.pos:l.pos+5] == "false" {
160
+					l.pos += 5
161
+					l.emit(tokBool)
162
+					continue
163
+				}
164
+			}
165
+			// must be a type.
166
+			return varLexType
167
+		}
168
+	}
169
+}
170
+
171
+var varTypeMap = map[string]string{
172
+	"boolean":    "b",
173
+	"byte":       "y",
174
+	"int16":      "n",
175
+	"uint16":     "q",
176
+	"int32":      "i",
177
+	"uint32":     "u",
178
+	"int64":      "x",
179
+	"uint64":     "t",
180
+	"double":     "f",
181
+	"string":     "s",
182
+	"objectpath": "o",
183
+	"signature":  "g",
184
+}
185
+
186
+func varLexByteString(l *varLexer) lexState {
187
+	q := l.next()
188
+Loop:
189
+	for {
190
+		switch l.next() {
191
+		case '\\':
192
+			if r := l.next(); r != -1 {
193
+				break
194
+			}
195
+			fallthrough
196
+		case -1:
197
+			return l.errorf("unterminated bytestring")
198
+		case q:
199
+			break Loop
200
+		}
201
+	}
202
+	l.emit(tokByteString)
203
+	return varLexNormal
204
+}
205
+
206
+func varLexNumber(l *varLexer) lexState {
207
+	l.accept("+-")
208
+	digits := "0123456789"
209
+	if l.accept("0") {
210
+		if l.accept("x") {
211
+			digits = "0123456789abcdefABCDEF"
212
+		} else {
213
+			digits = "01234567"
214
+		}
215
+	}
216
+	for strings.IndexRune(digits, l.next()) >= 0 {
217
+	}
218
+	l.backup()
219
+	if l.accept(".") {
220
+		for strings.IndexRune(digits, l.next()) >= 0 {
221
+		}
222
+		l.backup()
223
+	}
224
+	if l.accept("eE") {
225
+		l.accept("+-")
226
+		for strings.IndexRune("0123456789", l.next()) >= 0 {
227
+		}
228
+		l.backup()
229
+	}
230
+	if r := l.peek(); unicode.IsLetter(r) {
231
+		l.next()
232
+		return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
233
+	}
234
+	l.emit(tokNumber)
235
+	return varLexNormal
236
+}
237
+
238
+func varLexString(l *varLexer) lexState {
239
+	q := l.next()
240
+Loop:
241
+	for {
242
+		switch l.next() {
243
+		case '\\':
244
+			if r := l.next(); r != -1 {
245
+				break
246
+			}
247
+			fallthrough
248
+		case -1:
249
+			return l.errorf("unterminated string")
250
+		case q:
251
+			break Loop
252
+		}
253
+	}
254
+	l.emit(tokString)
255
+	return varLexNormal
256
+}
257
+
258
+func varLexType(l *varLexer) lexState {
259
+	at := l.accept("@")
260
+	for {
261
+		r := l.next()
262
+		if r == -1 {
263
+			break
264
+		}
265
+		if unicode.IsSpace(r) {
266
+			l.backup()
267
+			break
268
+		}
269
+	}
270
+	if at {
271
+		if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil {
272
+			return l.errorf("%s", err)
273
+		}
274
+	} else {
275
+		if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok {
276
+			l.emit(tokType)
277
+			return varLexNormal
278
+		}
279
+		return l.errorf("unrecognized type %q", l.input[l.start:l.pos])
280
+	}
281
+	l.emit(tokType)
282
+	return varLexNormal
283
+}
0 284
new file mode 100644
... ...
@@ -0,0 +1,817 @@
0
+package dbus
1
+
2
+import (
3
+	"bytes"
4
+	"errors"
5
+	"fmt"
6
+	"io"
7
+	"reflect"
8
+	"strconv"
9
+	"strings"
10
+	"unicode/utf8"
11
+)
12
+
13
+type varParser struct {
14
+	tokens []varToken
15
+	i      int
16
+}
17
+
18
+func (p *varParser) backup() {
19
+	p.i--
20
+}
21
+
22
+func (p *varParser) next() varToken {
23
+	if p.i < len(p.tokens) {
24
+		t := p.tokens[p.i]
25
+		p.i++
26
+		return t
27
+	}
28
+	return varToken{typ: tokEOF}
29
+}
30
+
31
+type varNode interface {
32
+	Infer() (Signature, error)
33
+	String() string
34
+	Sigs() sigSet
35
+	Value(Signature) (interface{}, error)
36
+}
37
+
38
+func varMakeNode(p *varParser) (varNode, error) {
39
+	var sig Signature
40
+
41
+	for {
42
+		t := p.next()
43
+		switch t.typ {
44
+		case tokEOF:
45
+			return nil, io.ErrUnexpectedEOF
46
+		case tokError:
47
+			return nil, errors.New(t.val)
48
+		case tokNumber:
49
+			return varMakeNumNode(t, sig)
50
+		case tokString:
51
+			return varMakeStringNode(t, sig)
52
+		case tokBool:
53
+			if sig.str != "" && sig.str != "b" {
54
+				return nil, varTypeError{t.val, sig}
55
+			}
56
+			b, err := strconv.ParseBool(t.val)
57
+			if err != nil {
58
+				return nil, err
59
+			}
60
+			return boolNode(b), nil
61
+		case tokArrayStart:
62
+			return varMakeArrayNode(p, sig)
63
+		case tokVariantStart:
64
+			return varMakeVariantNode(p, sig)
65
+		case tokDictStart:
66
+			return varMakeDictNode(p, sig)
67
+		case tokType:
68
+			if sig.str != "" {
69
+				return nil, errors.New("unexpected type annotation")
70
+			}
71
+			if t.val[0] == '@' {
72
+				sig.str = t.val[1:]
73
+			} else {
74
+				sig.str = varTypeMap[t.val]
75
+			}
76
+		case tokByteString:
77
+			if sig.str != "" && sig.str != "ay" {
78
+				return nil, varTypeError{t.val, sig}
79
+			}
80
+			b, err := varParseByteString(t.val)
81
+			if err != nil {
82
+				return nil, err
83
+			}
84
+			return byteStringNode(b), nil
85
+		default:
86
+			return nil, fmt.Errorf("unexpected %q", t.val)
87
+		}
88
+	}
89
+}
90
+
91
+type varTypeError struct {
92
+	val string
93
+	sig Signature
94
+}
95
+
96
+func (e varTypeError) Error() string {
97
+	return fmt.Sprintf("dbus: can't parse %q as type %q", e.val, e.sig.str)
98
+}
99
+
100
+type sigSet map[Signature]bool
101
+
102
+func (s sigSet) Empty() bool {
103
+	return len(s) == 0
104
+}
105
+
106
+func (s sigSet) Intersect(s2 sigSet) sigSet {
107
+	r := make(sigSet)
108
+	for k := range s {
109
+		if s2[k] {
110
+			r[k] = true
111
+		}
112
+	}
113
+	return r
114
+}
115
+
116
+func (s sigSet) Single() (Signature, bool) {
117
+	if len(s) == 1 {
118
+		for k := range s {
119
+			return k, true
120
+		}
121
+	}
122
+	return Signature{}, false
123
+}
124
+
125
+func (s sigSet) ToArray() sigSet {
126
+	r := make(sigSet, len(s))
127
+	for k := range s {
128
+		r[Signature{"a" + k.str}] = true
129
+	}
130
+	return r
131
+}
132
+
133
+type numNode struct {
134
+	sig Signature
135
+	str string
136
+	val interface{}
137
+}
138
+
139
+var numSigSet = sigSet{
140
+	Signature{"y"}: true,
141
+	Signature{"n"}: true,
142
+	Signature{"q"}: true,
143
+	Signature{"i"}: true,
144
+	Signature{"u"}: true,
145
+	Signature{"x"}: true,
146
+	Signature{"t"}: true,
147
+	Signature{"d"}: true,
148
+}
149
+
150
+func (n numNode) Infer() (Signature, error) {
151
+	if strings.ContainsAny(n.str, ".e") {
152
+		return Signature{"d"}, nil
153
+	}
154
+	return Signature{"i"}, nil
155
+}
156
+
157
+func (n numNode) String() string {
158
+	return n.str
159
+}
160
+
161
+func (n numNode) Sigs() sigSet {
162
+	if n.sig.str != "" {
163
+		return sigSet{n.sig: true}
164
+	}
165
+	if strings.ContainsAny(n.str, ".e") {
166
+		return sigSet{Signature{"d"}: true}
167
+	}
168
+	return numSigSet
169
+}
170
+
171
+func (n numNode) Value(sig Signature) (interface{}, error) {
172
+	if n.sig.str != "" && n.sig != sig {
173
+		return nil, varTypeError{n.str, sig}
174
+	}
175
+	if n.val != nil {
176
+		return n.val, nil
177
+	}
178
+	return varNumAs(n.str, sig)
179
+}
180
+
181
+func varMakeNumNode(tok varToken, sig Signature) (varNode, error) {
182
+	if sig.str == "" {
183
+		return numNode{str: tok.val}, nil
184
+	}
185
+	num, err := varNumAs(tok.val, sig)
186
+	if err != nil {
187
+		return nil, err
188
+	}
189
+	return numNode{sig: sig, val: num}, nil
190
+}
191
+
192
+func varNumAs(s string, sig Signature) (interface{}, error) {
193
+	isUnsigned := false
194
+	size := 32
195
+	switch sig.str {
196
+	case "n":
197
+		size = 16
198
+	case "i":
199
+	case "x":
200
+		size = 64
201
+	case "y":
202
+		size = 8
203
+		isUnsigned = true
204
+	case "q":
205
+		size = 16
206
+		isUnsigned = true
207
+	case "u":
208
+		isUnsigned = true
209
+	case "t":
210
+		size = 64
211
+		isUnsigned = true
212
+	case "d":
213
+		d, err := strconv.ParseFloat(s, 64)
214
+		if err != nil {
215
+			return nil, err
216
+		}
217
+		return d, nil
218
+	default:
219
+		return nil, varTypeError{s, sig}
220
+	}
221
+	base := 10
222
+	if strings.HasPrefix(s, "0x") {
223
+		base = 16
224
+		s = s[2:]
225
+	}
226
+	if strings.HasPrefix(s, "0") && len(s) != 1 {
227
+		base = 8
228
+		s = s[1:]
229
+	}
230
+	if isUnsigned {
231
+		i, err := strconv.ParseUint(s, base, size)
232
+		if err != nil {
233
+			return nil, err
234
+		}
235
+		var v interface{} = i
236
+		switch sig.str {
237
+		case "y":
238
+			v = byte(i)
239
+		case "q":
240
+			v = uint16(i)
241
+		case "u":
242
+			v = uint32(i)
243
+		}
244
+		return v, nil
245
+	}
246
+	i, err := strconv.ParseInt(s, base, size)
247
+	if err != nil {
248
+		return nil, err
249
+	}
250
+	var v interface{} = i
251
+	switch sig.str {
252
+	case "n":
253
+		v = int16(i)
254
+	case "i":
255
+		v = int32(i)
256
+	}
257
+	return v, nil
258
+}
259
+
260
+type stringNode struct {
261
+	sig Signature
262
+	str string      // parsed
263
+	val interface{} // has correct type
264
+}
265
+
266
+var stringSigSet = sigSet{
267
+	Signature{"s"}: true,
268
+	Signature{"g"}: true,
269
+	Signature{"o"}: true,
270
+}
271
+
272
+func (n stringNode) Infer() (Signature, error) {
273
+	return Signature{"s"}, nil
274
+}
275
+
276
+func (n stringNode) String() string {
277
+	return n.str
278
+}
279
+
280
+func (n stringNode) Sigs() sigSet {
281
+	if n.sig.str != "" {
282
+		return sigSet{n.sig: true}
283
+	}
284
+	return stringSigSet
285
+}
286
+
287
+func (n stringNode) Value(sig Signature) (interface{}, error) {
288
+	if n.sig.str != "" && n.sig != sig {
289
+		return nil, varTypeError{n.str, sig}
290
+	}
291
+	if n.val != nil {
292
+		return n.val, nil
293
+	}
294
+	switch {
295
+	case sig.str == "g":
296
+		return Signature{n.str}, nil
297
+	case sig.str == "o":
298
+		return ObjectPath(n.str), nil
299
+	case sig.str == "s":
300
+		return n.str, nil
301
+	default:
302
+		return nil, varTypeError{n.str, sig}
303
+	}
304
+}
305
+
306
+func varMakeStringNode(tok varToken, sig Signature) (varNode, error) {
307
+	if sig.str != "" && sig.str != "s" && sig.str != "g" && sig.str != "o" {
308
+		return nil, fmt.Errorf("invalid type %q for string", sig.str)
309
+	}
310
+	s, err := varParseString(tok.val)
311
+	if err != nil {
312
+		return nil, err
313
+	}
314
+	n := stringNode{str: s}
315
+	if sig.str == "" {
316
+		return stringNode{str: s}, nil
317
+	}
318
+	n.sig = sig
319
+	switch sig.str {
320
+	case "o":
321
+		n.val = ObjectPath(s)
322
+	case "g":
323
+		n.val = Signature{s}
324
+	case "s":
325
+		n.val = s
326
+	}
327
+	return n, nil
328
+}
329
+
330
+func varParseString(s string) (string, error) {
331
+	// quotes are guaranteed to be there
332
+	s = s[1 : len(s)-1]
333
+	buf := new(bytes.Buffer)
334
+	for len(s) != 0 {
335
+		r, size := utf8.DecodeRuneInString(s)
336
+		if r == utf8.RuneError && size == 1 {
337
+			return "", errors.New("invalid UTF-8")
338
+		}
339
+		s = s[size:]
340
+		if r != '\\' {
341
+			buf.WriteRune(r)
342
+			continue
343
+		}
344
+		r, size = utf8.DecodeRuneInString(s)
345
+		if r == utf8.RuneError && size == 1 {
346
+			return "", errors.New("invalid UTF-8")
347
+		}
348
+		s = s[size:]
349
+		switch r {
350
+		case 'a':
351
+			buf.WriteRune(0x7)
352
+		case 'b':
353
+			buf.WriteRune(0x8)
354
+		case 'f':
355
+			buf.WriteRune(0xc)
356
+		case 'n':
357
+			buf.WriteRune('\n')
358
+		case 'r':
359
+			buf.WriteRune('\r')
360
+		case 't':
361
+			buf.WriteRune('\t')
362
+		case '\n':
363
+		case 'u':
364
+			if len(s) < 4 {
365
+				return "", errors.New("short unicode escape")
366
+			}
367
+			r, err := strconv.ParseUint(s[:4], 16, 32)
368
+			if err != nil {
369
+				return "", err
370
+			}
371
+			buf.WriteRune(rune(r))
372
+			s = s[4:]
373
+		case 'U':
374
+			if len(s) < 8 {
375
+				return "", errors.New("short unicode escape")
376
+			}
377
+			r, err := strconv.ParseUint(s[:8], 16, 32)
378
+			if err != nil {
379
+				return "", err
380
+			}
381
+			buf.WriteRune(rune(r))
382
+			s = s[8:]
383
+		default:
384
+			buf.WriteRune(r)
385
+		}
386
+	}
387
+	return buf.String(), nil
388
+}
389
+
390
+var boolSigSet = sigSet{Signature{"b"}: true}
391
+
392
+type boolNode bool
393
+
394
+func (boolNode) Infer() (Signature, error) {
395
+	return Signature{"b"}, nil
396
+}
397
+
398
+func (b boolNode) String() string {
399
+	if b {
400
+		return "true"
401
+	}
402
+	return "false"
403
+}
404
+
405
+func (boolNode) Sigs() sigSet {
406
+	return boolSigSet
407
+}
408
+
409
+func (b boolNode) Value(sig Signature) (interface{}, error) {
410
+	if sig.str != "b" {
411
+		return nil, varTypeError{b.String(), sig}
412
+	}
413
+	return bool(b), nil
414
+}
415
+
416
+type arrayNode struct {
417
+	set      sigSet
418
+	children []varNode
419
+	val      interface{}
420
+}
421
+
422
+func (n arrayNode) Infer() (Signature, error) {
423
+	for _, v := range n.children {
424
+		csig, err := varInfer(v)
425
+		if err != nil {
426
+			continue
427
+		}
428
+		return Signature{"a" + csig.str}, nil
429
+	}
430
+	return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
431
+}
432
+
433
+func (n arrayNode) String() string {
434
+	s := "["
435
+	for i, v := range n.children {
436
+		s += v.String()
437
+		if i != len(n.children)-1 {
438
+			s += ", "
439
+		}
440
+	}
441
+	return s + "]"
442
+}
443
+
444
+func (n arrayNode) Sigs() sigSet {
445
+	return n.set
446
+}
447
+
448
+func (n arrayNode) Value(sig Signature) (interface{}, error) {
449
+	if n.set.Empty() {
450
+		// no type information whatsoever, so this must be an empty slice
451
+		return reflect.MakeSlice(typeFor(sig.str), 0, 0).Interface(), nil
452
+	}
453
+	if !n.set[sig] {
454
+		return nil, varTypeError{n.String(), sig}
455
+	}
456
+	s := reflect.MakeSlice(typeFor(sig.str), len(n.children), len(n.children))
457
+	for i, v := range n.children {
458
+		rv, err := v.Value(Signature{sig.str[1:]})
459
+		if err != nil {
460
+			return nil, err
461
+		}
462
+		s.Index(i).Set(reflect.ValueOf(rv))
463
+	}
464
+	return s.Interface(), nil
465
+}
466
+
467
+func varMakeArrayNode(p *varParser, sig Signature) (varNode, error) {
468
+	var n arrayNode
469
+	if sig.str != "" {
470
+		n.set = sigSet{sig: true}
471
+	}
472
+	if t := p.next(); t.typ == tokArrayEnd {
473
+		return n, nil
474
+	} else {
475
+		p.backup()
476
+	}
477
+Loop:
478
+	for {
479
+		t := p.next()
480
+		switch t.typ {
481
+		case tokEOF:
482
+			return nil, io.ErrUnexpectedEOF
483
+		case tokError:
484
+			return nil, errors.New(t.val)
485
+		}
486
+		p.backup()
487
+		cn, err := varMakeNode(p)
488
+		if err != nil {
489
+			return nil, err
490
+		}
491
+		if cset := cn.Sigs(); !cset.Empty() {
492
+			if n.set.Empty() {
493
+				n.set = cset.ToArray()
494
+			} else {
495
+				nset := cset.ToArray().Intersect(n.set)
496
+				if nset.Empty() {
497
+					return nil, fmt.Errorf("can't parse %q with given type information", cn.String())
498
+				}
499
+				n.set = nset
500
+			}
501
+		}
502
+		n.children = append(n.children, cn)
503
+		switch t := p.next(); t.typ {
504
+		case tokEOF:
505
+			return nil, io.ErrUnexpectedEOF
506
+		case tokError:
507
+			return nil, errors.New(t.val)
508
+		case tokArrayEnd:
509
+			break Loop
510
+		case tokComma:
511
+			continue
512
+		default:
513
+			return nil, fmt.Errorf("unexpected %q", t.val)
514
+		}
515
+	}
516
+	return n, nil
517
+}
518
+
519
+type variantNode struct {
520
+	n varNode
521
+}
522
+
523
+var variantSet = sigSet{
524
+	Signature{"v"}: true,
525
+}
526
+
527
+func (variantNode) Infer() (Signature, error) {
528
+	return Signature{"v"}, nil
529
+}
530
+
531
+func (n variantNode) String() string {
532
+	return "<" + n.n.String() + ">"
533
+}
534
+
535
+func (variantNode) Sigs() sigSet {
536
+	return variantSet
537
+}
538
+
539
+func (n variantNode) Value(sig Signature) (interface{}, error) {
540
+	if sig.str != "v" {
541
+		return nil, varTypeError{n.String(), sig}
542
+	}
543
+	sig, err := varInfer(n.n)
544
+	if err != nil {
545
+		return nil, err
546
+	}
547
+	v, err := n.n.Value(sig)
548
+	if err != nil {
549
+		return nil, err
550
+	}
551
+	return MakeVariant(v), nil
552
+}
553
+
554
+func varMakeVariantNode(p *varParser, sig Signature) (varNode, error) {
555
+	n, err := varMakeNode(p)
556
+	if err != nil {
557
+		return nil, err
558
+	}
559
+	if t := p.next(); t.typ != tokVariantEnd {
560
+		return nil, fmt.Errorf("unexpected %q", t.val)
561
+	}
562
+	vn := variantNode{n}
563
+	if sig.str != "" && sig.str != "v" {
564
+		return nil, varTypeError{vn.String(), sig}
565
+	}
566
+	return variantNode{n}, nil
567
+}
568
+
569
+type dictEntry struct {
570
+	key, val varNode
571
+}
572
+
573
+type dictNode struct {
574
+	kset, vset sigSet
575
+	children   []dictEntry
576
+	val        interface{}
577
+}
578
+
579
+func (n dictNode) Infer() (Signature, error) {
580
+	for _, v := range n.children {
581
+		ksig, err := varInfer(v.key)
582
+		if err != nil {
583
+			continue
584
+		}
585
+		vsig, err := varInfer(v.val)
586
+		if err != nil {
587
+			continue
588
+		}
589
+		return Signature{"a{" + ksig.str + vsig.str + "}"}, nil
590
+	}
591
+	return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
592
+}
593
+
594
+func (n dictNode) String() string {
595
+	s := "{"
596
+	for i, v := range n.children {
597
+		s += v.key.String() + ": " + v.val.String()
598
+		if i != len(n.children)-1 {
599
+			s += ", "
600
+		}
601
+	}
602
+	return s + "}"
603
+}
604
+
605
+func (n dictNode) Sigs() sigSet {
606
+	r := sigSet{}
607
+	for k := range n.kset {
608
+		for v := range n.vset {
609
+			sig := "a{" + k.str + v.str + "}"
610
+			r[Signature{sig}] = true
611
+		}
612
+	}
613
+	return r
614
+}
615
+
616
+func (n dictNode) Value(sig Signature) (interface{}, error) {
617
+	set := n.Sigs()
618
+	if set.Empty() {
619
+		// no type information -> empty dict
620
+		return reflect.MakeMap(typeFor(sig.str)).Interface(), nil
621
+	}
622
+	if !set[sig] {
623
+		return nil, varTypeError{n.String(), sig}
624
+	}
625
+	m := reflect.MakeMap(typeFor(sig.str))
626
+	ksig := Signature{sig.str[2:3]}
627
+	vsig := Signature{sig.str[3 : len(sig.str)-1]}
628
+	for _, v := range n.children {
629
+		kv, err := v.key.Value(ksig)
630
+		if err != nil {
631
+			return nil, err
632
+		}
633
+		vv, err := v.val.Value(vsig)
634
+		if err != nil {
635
+			return nil, err
636
+		}
637
+		m.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
638
+	}
639
+	return m.Interface(), nil
640
+}
641
+
642
+func varMakeDictNode(p *varParser, sig Signature) (varNode, error) {
643
+	var n dictNode
644
+
645
+	if sig.str != "" {
646
+		if len(sig.str) < 5 {
647
+			return nil, fmt.Errorf("invalid signature %q for dict type", sig)
648
+		}
649
+		ksig := Signature{string(sig.str[2])}
650
+		vsig := Signature{sig.str[3 : len(sig.str)-1]}
651
+		n.kset = sigSet{ksig: true}
652
+		n.vset = sigSet{vsig: true}
653
+	}
654
+	if t := p.next(); t.typ == tokDictEnd {
655
+		return n, nil
656
+	} else {
657
+		p.backup()
658
+	}
659
+Loop:
660
+	for {
661
+		t := p.next()
662
+		switch t.typ {
663
+		case tokEOF:
664
+			return nil, io.ErrUnexpectedEOF
665
+		case tokError:
666
+			return nil, errors.New(t.val)
667
+		}
668
+		p.backup()
669
+		kn, err := varMakeNode(p)
670
+		if err != nil {
671
+			return nil, err
672
+		}
673
+		if kset := kn.Sigs(); !kset.Empty() {
674
+			if n.kset.Empty() {
675
+				n.kset = kset
676
+			} else {
677
+				n.kset = kset.Intersect(n.kset)
678
+				if n.kset.Empty() {
679
+					return nil, fmt.Errorf("can't parse %q with given type information", kn.String())
680
+				}
681
+			}
682
+		}
683
+		t = p.next()
684
+		switch t.typ {
685
+		case tokEOF:
686
+			return nil, io.ErrUnexpectedEOF
687
+		case tokError:
688
+			return nil, errors.New(t.val)
689
+		case tokColon:
690
+		default:
691
+			return nil, fmt.Errorf("unexpected %q", t.val)
692
+		}
693
+		t = p.next()
694
+		switch t.typ {
695
+		case tokEOF:
696
+			return nil, io.ErrUnexpectedEOF
697
+		case tokError:
698
+			return nil, errors.New(t.val)
699
+		}
700
+		p.backup()
701
+		vn, err := varMakeNode(p)
702
+		if err != nil {
703
+			return nil, err
704
+		}
705
+		if vset := vn.Sigs(); !vset.Empty() {
706
+			if n.vset.Empty() {
707
+				n.vset = vset
708
+			} else {
709
+				n.vset = n.vset.Intersect(vset)
710
+				if n.vset.Empty() {
711
+					return nil, fmt.Errorf("can't parse %q with given type information", vn.String())
712
+				}
713
+			}
714
+		}
715
+		n.children = append(n.children, dictEntry{kn, vn})
716
+		t = p.next()
717
+		switch t.typ {
718
+		case tokEOF:
719
+			return nil, io.ErrUnexpectedEOF
720
+		case tokError:
721
+			return nil, errors.New(t.val)
722
+		case tokDictEnd:
723
+			break Loop
724
+		case tokComma:
725
+			continue
726
+		default:
727
+			return nil, fmt.Errorf("unexpected %q", t.val)
728
+		}
729
+	}
730
+	return n, nil
731
+}
732
+
733
+type byteStringNode []byte
734
+
735
+var byteStringSet = sigSet{
736
+	Signature{"ay"}: true,
737
+}
738
+
739
+func (byteStringNode) Infer() (Signature, error) {
740
+	return Signature{"ay"}, nil
741
+}
742
+
743
+func (b byteStringNode) String() string {
744
+	return string(b)
745
+}
746
+
747
+func (b byteStringNode) Sigs() sigSet {
748
+	return byteStringSet
749
+}
750
+
751
+func (b byteStringNode) Value(sig Signature) (interface{}, error) {
752
+	if sig.str != "ay" {
753
+		return nil, varTypeError{b.String(), sig}
754
+	}
755
+	return []byte(b), nil
756
+}
757
+
758
+func varParseByteString(s string) ([]byte, error) {
759
+	// quotes and b at start are guaranteed to be there
760
+	b := make([]byte, 0, 1)
761
+	s = s[2 : len(s)-1]
762
+	for len(s) != 0 {
763
+		c := s[0]
764
+		s = s[1:]
765
+		if c != '\\' {
766
+			b = append(b, c)
767
+			continue
768
+		}
769
+		c = s[0]
770
+		s = s[1:]
771
+		switch c {
772
+		case 'a':
773
+			b = append(b, 0x7)
774
+		case 'b':
775
+			b = append(b, 0x8)
776
+		case 'f':
777
+			b = append(b, 0xc)
778
+		case 'n':
779
+			b = append(b, '\n')
780
+		case 'r':
781
+			b = append(b, '\r')
782
+		case 't':
783
+			b = append(b, '\t')
784
+		case 'x':
785
+			if len(s) < 2 {
786
+				return nil, errors.New("short escape")
787
+			}
788
+			n, err := strconv.ParseUint(s[:2], 16, 8)
789
+			if err != nil {
790
+				return nil, err
791
+			}
792
+			b = append(b, byte(n))
793
+			s = s[2:]
794
+		case '0':
795
+			if len(s) < 3 {
796
+				return nil, errors.New("short escape")
797
+			}
798
+			n, err := strconv.ParseUint(s[:3], 8, 8)
799
+			if err != nil {
800
+				return nil, err
801
+			}
802
+			b = append(b, byte(n))
803
+			s = s[3:]
804
+		default:
805
+			b = append(b, c)
806
+		}
807
+	}
808
+	return append(b, 0), nil
809
+}
810
+
811
+func varInfer(n varNode) (Signature, error) {
812
+	if sig, ok := n.Sigs().Single(); ok {
813
+		return sig, nil
814
+	}
815
+	return n.Infer()
816
+}
0 817
new file mode 100644
... ...
@@ -0,0 +1,78 @@
0
+package dbus
1
+
2
+import "reflect"
3
+import "testing"
4
+
5
+var variantFormatTests = []struct {
6
+	v interface{}
7
+	s string
8
+}{
9
+	{int32(1), `1`},
10
+	{"foo", `"foo"`},
11
+	{ObjectPath("/org/foo"), `@o "/org/foo"`},
12
+	{Signature{"i"}, `@g "i"`},
13
+	{[]byte{}, `@ay []`},
14
+	{[]int32{1, 2}, `[1, 2]`},
15
+	{[]int64{1, 2}, `@ax [1, 2]`},
16
+	{[][]int32{{3, 4}, {5, 6}}, `[[3, 4], [5, 6]]`},
17
+	{[]Variant{MakeVariant(int32(1)), MakeVariant(1.0)}, `[<1>, <@d 1>]`},
18
+	{map[string]int32{"one": 1, "two": 2}, `{"one": 1, "two": 2}`},
19
+	{map[int32]ObjectPath{1: "/org/foo"}, `@a{io} {1: "/org/foo"}`},
20
+	{map[string]Variant{}, `@a{sv} {}`},
21
+}
22
+
23
+func TestFormatVariant(t *testing.T) {
24
+	for i, v := range variantFormatTests {
25
+		if s := MakeVariant(v.v).String(); s != v.s {
26
+			t.Errorf("test %d: got %q, wanted %q", i+1, s, v.s)
27
+		}
28
+	}
29
+}
30
+
31
+var variantParseTests = []struct {
32
+	s string
33
+	v interface{}
34
+}{
35
+	{"1", int32(1)},
36
+	{"true", true},
37
+	{"false", false},
38
+	{"1.0", float64(1.0)},
39
+	{"0x10", int32(16)},
40
+	{"1e1", float64(10)},
41
+	{`"foo"`, "foo"},
42
+	{`"\a\b\f\n\r\t"`, "\x07\x08\x0c\n\r\t"},
43
+	{`"\u00e4\U0001f603"`, "\u00e4\U0001f603"},
44
+	{"[1]", []int32{1}},
45
+	{"[1, 2, 3]", []int32{1, 2, 3}},
46
+	{"@ai []", []int32{}},
47
+	{"[1, 5.0]", []float64{1, 5.0}},
48
+	{"[[1, 2], [3, 4.0]]", [][]float64{{1, 2}, {3, 4}}},
49
+	{`[@o "/org/foo", "/org/bar"]`, []ObjectPath{"/org/foo", "/org/bar"}},
50
+	{"<1>", MakeVariant(int32(1))},
51
+	{"[<1>, <2.0>]", []Variant{MakeVariant(int32(1)), MakeVariant(2.0)}},
52
+	{`[[], [""]]`, [][]string{{}, {""}}},
53
+	{`@a{ss} {}`, map[string]string{}},
54
+	{`{"foo": 1}`, map[string]int32{"foo": 1}},
55
+	{`[{}, {"foo": "bar"}]`, []map[string]string{{}, {"foo": "bar"}}},
56
+	{`{"a": <1>, "b": <"foo">}`,
57
+		map[string]Variant{"a": MakeVariant(int32(1)), "b": MakeVariant("foo")}},
58
+	{`b''`, []byte{0}},
59
+	{`b"abc"`, []byte{'a', 'b', 'c', 0}},
60
+	{`b"\x01\0002\a\b\f\n\r\t"`, []byte{1, 2, 0x7, 0x8, 0xc, '\n', '\r', '\t', 0}},
61
+	{`[[0], b""]`, [][]byte{{0}, {0}}},
62
+	{"int16 0", int16(0)},
63
+	{"byte 0", byte(0)},
64
+}
65
+
66
+func TestParseVariant(t *testing.T) {
67
+	for i, v := range variantParseTests {
68
+		nv, err := ParseVariant(v.s, Signature{})
69
+		if err != nil {
70
+			t.Errorf("test %d: parsing failed: %s", i+1, err)
71
+			continue
72
+		}
73
+		if !reflect.DeepEqual(nv.value, v.v) {
74
+			t.Errorf("test %d: got %q, wanted %q", i+1, nv, v.v)
75
+		}
76
+	}
77
+}