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)
| ... | ... |
@@ -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 |
| 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 | 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 | 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 | 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 | 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 | 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 |
+} |