From b39d02b611f1cc0af283f417b73bf0d36f26277a Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Mon, 3 Mar 2014 21:53:57 -0700 Subject: [PATCH 01/50] Support hairpin NAT without going through docker server Hairpin NAT is currently done by passing through the docker server. If two containers on the same box try to access each other through exposed ports and using the host IP the current iptables rules will not match the DNAT and thus the traffic goes to 'docker -d' This change drops the restriction that DNAT traffic must not originate from docker0. It should be safe to drop this restriction because the DOCKER chain is already gated by jumps that check for the destination address to be a local address. Docker-DCO-1.1-Signed-off-by: Darren Shepherd (github: ibuildthecloud) --- pkg/iptables/iptables.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 4cdd67ef7c..1f25952bd9 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -66,7 +66,6 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr str "-p", proto, "-d", daddr, "--dport", strconv.Itoa(port), - "!", "-i", c.Bridge, "-j", "DNAT", "--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))); err != nil { return err From aceb10b1e51d1d9016b25fa5275e5a4f02772c57 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 6 Mar 2014 22:26:47 -0700 Subject: [PATCH 02/50] Resync the DCO text with upstream at http://developercertificate.org/ ``` Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. ``` Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- CONTRIBUTING.md | 53 ++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c03c5d0d9c..0e8b98122f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,33 +126,46 @@ For more details see [MAINTAINERS.md](hack/MAINTAINERS.md) The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you -can certify the below: +can certify the below (from +[developercertificate.org](http://developercertificate.org/)): ``` -Docker Developer Certificate of Origin 1.1 +Developer Certificate of Origin +Version 1.1 -By making a contribution to the Docker Project ("Project"), I represent and -warrant that: +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA -a. The contribution was created in whole or in part by me and I have the right -to submit the contribution on my own behalf or on behalf of a third party who -has authorized me to submit this contribution to the Project; or +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. -b. The contribution is based upon previous work that, to the best of my -knowledge, is covered under an appropriate open source license and I have the -right and authorization to submit that work with modifications, whether -created in whole or in part by me, under the same open source license (unless -I am permitted to submit under a different license) that I have identified in -the contribution; or -c. The contribution was provided directly to me by some other person who -represented and warranted (a) or (b) and I have not modified it. +Developer's Certificate of Origin 1.1 -d. I understand and agree that this Project and the contribution are publicly -known and that a record of the contribution (including all personal -information I submit with it, including my sign-off record) is maintained -indefinitely and may be redistributed consistent with this Project or the open -source license(s) involved. +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. ``` then you just add a line to every git commit message: From f7b6fbbd7664634481c8519e58844d572423f3e1 Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Wed, 12 Mar 2014 17:22:57 -0400 Subject: [PATCH 03/50] Prevent dynamic allocation of previously allocated ports Docker-DCO-1.1-Signed-off-by: Andy Kipp (github: kippandrew) --- .../portallocator/portallocator.go | 32 +++++++++++++++---- .../portallocator/portallocator_test.go | 7 ++++ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/runtime/networkdriver/portallocator/portallocator.go b/runtime/networkdriver/portallocator/portallocator.go index 71cac82703..18ae9469e5 100644 --- a/runtime/networkdriver/portallocator/portallocator.go +++ b/runtime/networkdriver/portallocator/portallocator.go @@ -100,22 +100,30 @@ func ReleaseAll() error { } func registerDynamicPort(ip net.IP, proto string) (int, error) { - allocated := defaultAllocatedPorts[proto] - - port := nextPort(proto) - if port > EndPortRange { - return 0, ErrPortExceedsRange - } if !equalsDefault(ip) { registerIP(ip) ipAllocated := otherAllocatedPorts[ip.String()][proto] + + port, err := findNextPort(proto, ipAllocated) + if err != nil { + return 0, err + } ipAllocated.Push(port) + return port, nil + } else { + + allocated := defaultAllocatedPorts[proto] + + port, err := findNextPort(proto, allocated) + if err != nil { + return 0, err + } allocated.Push(port) + return port, nil } - return port, nil } func registerSetPort(ip net.IP, proto string, port int) error { @@ -142,6 +150,16 @@ func equalsDefault(ip net.IP) bool { return ip == nil || ip.Equal(defaultIP) } +func findNextPort(proto string, allocated *collections.OrderedIntSet) (int, error) { + port := 0 + for port = nextPort(proto); allocated.Exists(port); port = nextPort(proto) { + } + if port > EndPortRange { + return 0, ErrPortExceedsRange + } + return port, nil +} + func nextPort(proto string) int { c := currentDynamicPort[proto] + 1 currentDynamicPort[proto] = c diff --git a/runtime/networkdriver/portallocator/portallocator_test.go b/runtime/networkdriver/portallocator/portallocator_test.go index 603bd03bd7..3f3afa657b 100644 --- a/runtime/networkdriver/portallocator/portallocator_test.go +++ b/runtime/networkdriver/portallocator/portallocator_test.go @@ -181,4 +181,11 @@ func TestPortAllocation(t *testing.T) { if _, err := RequestPort(ip, "tcp", 80); err != nil { t.Fatal(err) } + + port, err = RequestPort(ip, "tcp", 0) + port2, err := RequestPort(ip, "tcp", port+1) + port3, err := RequestPort(ip, "tcp", 0) + if port3 == port2 { + t.Fatal("A dynamic port should never allocate a used port") + } } From 7a1db291fcedd50ce99649e95109187c76da255c Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Wed, 12 Mar 2014 17:26:17 -0400 Subject: [PATCH 04/50] Better test error message Docker-DCO-1.1-Signed-off-by: Andy Kipp (github: kippandrew) --- runtime/networkdriver/portallocator/portallocator_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/networkdriver/portallocator/portallocator_test.go b/runtime/networkdriver/portallocator/portallocator_test.go index 3f3afa657b..356da855b6 100644 --- a/runtime/networkdriver/portallocator/portallocator_test.go +++ b/runtime/networkdriver/portallocator/portallocator_test.go @@ -186,6 +186,6 @@ func TestPortAllocation(t *testing.T) { port2, err := RequestPort(ip, "tcp", port+1) port3, err := RequestPort(ip, "tcp", 0) if port3 == port2 { - t.Fatal("A dynamic port should never allocate a used port") + t.Fatal("Requesting a dynamic port should never allocate a used port") } } From 73c416a20db8fe48302a6cf0db4c1c0585ed0739 Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Thu, 13 Mar 2014 13:30:07 -0400 Subject: [PATCH 05/50] Be more explicit in finding next port to allocate Docker-DCO-1.1-Signed-off-by: Andy Kipp (github: kippandrew) --- runtime/networkdriver/portallocator/portallocator.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/networkdriver/portallocator/portallocator.go b/runtime/networkdriver/portallocator/portallocator.go index 18ae9469e5..4d698f2de2 100644 --- a/runtime/networkdriver/portallocator/portallocator.go +++ b/runtime/networkdriver/portallocator/portallocator.go @@ -151,8 +151,9 @@ func equalsDefault(ip net.IP) bool { } func findNextPort(proto string, allocated *collections.OrderedIntSet) (int, error) { - port := 0 - for port = nextPort(proto); allocated.Exists(port); port = nextPort(proto) { + port := nextPort(proto) + for allocated.Exists(port) { + port = nextPort(proto) } if port > EndPortRange { return 0, ErrPortExceedsRange From 555416fd02b9e062385dcdaf0c4b9f5de61df388 Mon Sep 17 00:00:00 2001 From: Andy Kipp Date: Tue, 18 Mar 2014 13:29:24 -0400 Subject: [PATCH 06/50] Add err checks for port allocator tests Docker-DCO-1.1-Signed-off-by: Andy Kipp (github: kippandrew) --- .../networkdriver/portallocator/portallocator_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/runtime/networkdriver/portallocator/portallocator_test.go b/runtime/networkdriver/portallocator/portallocator_test.go index 356da855b6..f01bcfc99e 100644 --- a/runtime/networkdriver/portallocator/portallocator_test.go +++ b/runtime/networkdriver/portallocator/portallocator_test.go @@ -183,8 +183,17 @@ func TestPortAllocation(t *testing.T) { } port, err = RequestPort(ip, "tcp", 0) + if err != nil { + t.Fatal(err) + } port2, err := RequestPort(ip, "tcp", port+1) + if err != nil { + t.Fatal(err) + } port3, err := RequestPort(ip, "tcp", 0) + if err != nil { + t.Fatal(err) + } if port3 == port2 { t.Fatal("Requesting a dynamic port should never allocate a used port") } From 5127732c7911988c81eda7bb31ac77fc1dd36ac2 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 19 Mar 2014 14:30:13 -0400 Subject: [PATCH 07/50] docker save: --output flag for those that do not care to redirect stdout Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/api/client.go b/api/client.go index 8f515639a7..343a24078c 100644 --- a/api/client.go +++ b/api/client.go @@ -2044,6 +2044,8 @@ func (cli *DockerCli) CmdCp(args ...string) error { func (cli *DockerCli) CmdSave(args ...string) error { cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout)") + outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") + if err := cmd.Parse(args); err != nil { return err } @@ -2053,8 +2055,16 @@ func (cli *DockerCli) CmdSave(args ...string) error { return nil } + var output io.Writer = cli.out + var err error + if *outfile != "" { + output, err = os.Create(*outfile) + if err != nil { + return err + } + } image := cmd.Arg(0) - if err := cli.stream("GET", "/images/"+image+"/get", nil, cli.out, nil); err != nil { + if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil { return err } return nil From e93a16ab48f75311aab155548f32776cbd21dfe6 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 19 Mar 2014 14:47:20 -0400 Subject: [PATCH 08/50] docker save: add and improve docs add usage examples for `docker save ...` Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client.go | 2 +- docs/sources/reference/commandline/cli.rst | 23 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/api/client.go b/api/client.go index 343a24078c..5a69700704 100644 --- a/api/client.go +++ b/api/client.go @@ -2043,7 +2043,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { } func (cli *DockerCli) CmdSave(args ...string) error { - cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout)") + cmd := cli.Subcmd("save", "IMAGE", "Save an image to a tar archive (streamed to stdout by default)") outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") if err := cmd.Parse(args); err != nil { diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 0f33b05ec4..294e1d0544 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1307,10 +1307,27 @@ This example shows 5 containers that might be set up to test a web application c :: - Usage: docker save image > repository.tar + Usage: docker save IMAGE + + Save an image to a tar archive (streamed to stdout by default) + + -o, --output="": Write to an file, instead of STDOUT + + +Produces a tarred repository to the standard output stream. +Contains all parent layers, and all tags + versions, or specified repo:tag. + +.. code-block:: bash + + $ sudo docker save busybox > busybox.tar + $ ls -sh b.tar + 2.7M b.tar + $ sudo docker save --output busybox.tar busybox + $ ls -sh b.tar + 2.7M b.tar + $ sudo docker save -o fedora-all.tar fedora + $ sudo docker save -o fedora-latest.tar fedora:latest - Streams a tarred repository to the standard output stream. - Contains all parent layers, and all tags + versions. .. _cli_search: From 48cb2f03177732823b4091fd3ddd44b2bef2c58e Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Wed, 19 Mar 2014 21:01:20 +0400 Subject: [PATCH 09/50] Remove duplication of Dns in config merging. Fixes #4714 Docker-DCO-1.1-Signed-off-by: Alexandr Morozov (github: LK4D4) --- runconfig/config_test.go | 2 +- runconfig/merge.go | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/runconfig/config_test.go b/runconfig/config_test.go index 46e4691b93..8bbad9effa 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -231,7 +231,7 @@ func TestMerge(t *testing.T) { volumesUser := make(map[string]struct{}) volumesUser["/test3"] = struct{}{} configUser := &Config{ - Dns: []string{"3.3.3.3"}, + Dns: []string{"2.2.2.2", "3.3.3.3"}, PortSpecs: []string{"3333:2222", "3333:3333"}, Env: []string{"VAR2=3", "VAR3=3"}, Volumes: volumesUser, diff --git a/runconfig/merge.go b/runconfig/merge.go index 3b91aa2af0..79e3951271 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -97,8 +97,15 @@ func Merge(userConf, imageConf *Config) error { if userConf.Dns == nil || len(userConf.Dns) == 0 { userConf.Dns = imageConf.Dns } else { - //duplicates aren't an issue here - userConf.Dns = append(userConf.Dns, imageConf.Dns...) + dnsSet := make(map[string]struct{}, len(userConf.Dns)) + for _, dns := range userConf.Dns { + dnsSet[dns] = struct{}{} + } + for _, dns := range imageConf.Dns { + if _, exists := dnsSet[dns]; !exists { + userConf.Dns = append(userConf.Dns, dns) + } + } } if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 { userConf.Entrypoint = imageConf.Entrypoint From 78a0105eaf80ed85e2ee236632a2cc16998228f9 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 19 Mar 2014 17:09:12 -0400 Subject: [PATCH 10/50] api/client: var style tweak Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/client.go b/api/client.go index 5a69700704..e7abf04cb5 100644 --- a/api/client.go +++ b/api/client.go @@ -2055,8 +2055,10 @@ func (cli *DockerCli) CmdSave(args ...string) error { return nil } - var output io.Writer = cli.out - var err error + var ( + output io.Writer = cli.out + err error + ) if *outfile != "" { output, err = os.Create(*outfile) if err != nil { From e38e977a0410b754b6f318ff973dc15e6d756023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djibril=20Kon=C3=A9?= Date: Tue, 18 Mar 2014 21:18:36 +0100 Subject: [PATCH 11/50] Harmonize / across all name-related commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Djibril Koné (github: enokd) Harmonize / across all name-related commands Docker-DCO-1.1-Signed-off-by: Djibril Koné (github: enokd) Harmonize / across all name-related commands:Return an error when repeated / Docker-DCO-1.1-Signed-off-by: Djibril Koné (github: enokd) --- api/client.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/client.go b/api/client.go index 10dd9406dc..07a3d25e5c 100644 --- a/api/client.go +++ b/api/client.go @@ -1452,6 +1452,11 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return nil } + re := regexp.MustCompile("/{2}") + if re.MatchString(repository) { + return fmt.Errorf("Error: Bad image name. Please rename your image in the format /") + } + v := url.Values{} v.Set("container", name) v.Set("repo", repository) From a8cc6ebb181abf58b12ed6ee037711f0b2f1eff2 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 20 Mar 2014 09:59:54 +1000 Subject: [PATCH 12/50] I'm not looking forward to documenting cli arguments that may or may not show depending on what plugins / drviers you choose Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.rst | 2 +- docs/sources/reference/run.rst | 15 ++++++++------- runconfig/parse.go | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index d398b16e53..5f228f55b4 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1145,7 +1145,7 @@ image is removed. --volumes-from="": Mount all volumes from the given container(s) --entrypoint="": Overwrite the default entrypoint set by the image -w, --workdir="": Working directory inside the container - --lxc-conf=[]: Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" + --lxc-conf=[]: (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" --sig-proxy=true: Proxify all received signal to the process (even in non-tty mode) --expose=[]: Expose a port from the container without publishing it to your host --link="": Add link to another container (name:alias) diff --git a/docs/sources/reference/run.rst b/docs/sources/reference/run.rst index 0b4f7eebf4..d2fe449c22 100644 --- a/docs/sources/reference/run.rst +++ b/docs/sources/reference/run.rst @@ -194,7 +194,7 @@ Runtime Privilege and LXC Configuration :: --privileged=false: Give extended privileges to this container - --lxc-conf=[]: Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" + --lxc-conf=[]: (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" By default, Docker containers are "unprivileged" and cannot, for example, run a Docker daemon inside a Docker container. This is @@ -211,12 +211,13 @@ host. Additional information about running with ``--privileged`` is available on the `Docker Blog `_. -An operator can also specify LXC options using one or more -``--lxc-conf`` parameters. These can be new parameters or override -existing parameters from the lxc-template.go_. Note that in the -future, a given host's Docker daemon may not use LXC, so this is an -implementation-specific configuration meant for operators already -familiar with using LXC directly. +If the Docker daemon was started using the ``lxc`` exec-driver +(``docker -d --exec-driver=lxc``) then the operator can also specify +LXC options using one or more ``--lxc-conf`` parameters. These can be +new parameters or override existing parameters from the lxc-template.go_. +Note that in the future, a given host's Docker daemon may not use LXC, +so this is an implementation-specific configuration meant for operators +already familiar with using LXC directly. .. _lxc-template.go: https://github.com/dotcloud/docker/blob/master/execdriver/lxc/lxc_template.go diff --git a/runconfig/parse.go b/runconfig/parse.go index cc33188ad5..c2591722d5 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -76,7 +76,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers") cmd.Var(&flDnsSearch, []string{"-dns-search"}, "Set custom dns search domains") cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") - cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err From a9fa1a13c3b0a654a96be01ff7ec19e8009b2094 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 20 Mar 2014 17:32:59 +0100 Subject: [PATCH 13/50] devicemapper: Better/faster shutdown Right now shutdown is looping over *all* devicemapper devices and actively deactivating them, this is pretty slow if you have a lot of non-active containers. We instead only deactivate the devices that are mounted. We also do the shutdown unmount using MNT_DETACH which forces the unmount in the global namespace, even if it is busy because of some container having it mounted. This means the device will be freed when that container exits. Also, we move the call to waitClose to deactivateDevice because all callers of any of them call both anyway. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime/graphdriver/devmapper/deviceset.go | 36 ++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index 4d33e243e0..dfdb180bb2 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" "sync" + "syscall" "time" ) @@ -677,6 +678,12 @@ func (devices *DeviceSet) deactivateDevice(hash string) error { utils.Debugf("[devmapper] deactivateDevice(%s)", hash) defer utils.Debugf("[devmapper] deactivateDevice END") + // Wait for the unmount to be effective, + // by watching the value of Info.OpenCount for the device + if err := devices.waitClose(hash); err != nil { + utils.Errorf("Warning: error waiting for device %s to close: %s\n", hash, err) + } + info := devices.Devices[hash] if info == nil { return fmt.Errorf("Unknown device %s", hash) @@ -799,26 +806,20 @@ func (devices *DeviceSet) Shutdown() error { for _, info := range devices.Devices { info.lock.Lock() if info.mountCount > 0 { - if err := sysUnmount(info.mountPath, 0); err != nil { + // We use MNT_DETACH here in case it is still busy in some running + // container. This means it'll go away from the global scope directly, + // and the device will be released when that container dies. + if err := sysUnmount(info.mountPath, syscall.MNT_DETACH); err != nil { utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err) } + + if err := devices.deactivateDevice(info.Hash); err != nil { + utils.Debugf("Shutdown deactivate %s , error: %s\n", info.Hash, err) + } } info.lock.Unlock() } - for _, d := range devices.Devices { - d.lock.Lock() - - if err := devices.waitClose(d.Hash); err != nil { - utils.Errorf("Warning: error waiting for device %s to unmount: %s\n", d.Hash, err) - } - if err := devices.deactivateDevice(d.Hash); err != nil { - utils.Debugf("Shutdown deactivate %s , error: %s\n", d.Hash, err) - } - - d.lock.Unlock() - } - if err := devices.deactivatePool(); err != nil { utils.Debugf("Shutdown deactivate pool , error: %s\n", err) } @@ -920,14 +921,11 @@ func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { return err } utils.Debugf("[devmapper] Unmount done") - // Wait for the unmount to be effective, - // by watching the value of Info.OpenCount for the device - if err := devices.waitClose(hash); err != nil { + + if err := devices.deactivateDevice(hash); err != nil { return err } - devices.deactivateDevice(hash) - info.mountPath = "" return nil From fbd6fee4ab9b98f477f365307a641b879badd282 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Thu, 20 Mar 2014 13:09:34 -0400 Subject: [PATCH 14/50] Fix double single dash arg issues in docs Docker-DCO-1.1-Signed-off-by: Brian Goff (github: cpuguy83) --- docs/sources/articles/runmetrics.rst | 2 +- docs/sources/examples/postgresql_service.rst | 2 +- docs/sources/use/working_with_volumes.rst | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/articles/runmetrics.rst b/docs/sources/articles/runmetrics.rst index afb7f82e39..6b705fb737 100644 --- a/docs/sources/articles/runmetrics.rst +++ b/docs/sources/articles/runmetrics.rst @@ -63,7 +63,7 @@ For Docker containers using cgroups, the container name will be the full ID or long ID of the container. If a container shows up as ae836c95b4c3 in ``docker ps``, its long ID might be something like ``ae836c95b4c3c9e9179e0e91015512da89fdec91612f63cebae57df9a5444c79``. You -can look it up with ``docker inspect`` or ``docker ps -notrunc``. +can look it up with ``docker inspect`` or ``docker ps --no-trunc``. Putting everything together to look at the memory metrics for a Docker container, take a look at ``/sys/fs/cgroup/memory/lxc//``. diff --git a/docs/sources/examples/postgresql_service.rst b/docs/sources/examples/postgresql_service.rst index 66b0fd7aa5..488e1530b2 100644 --- a/docs/sources/examples/postgresql_service.rst +++ b/docs/sources/examples/postgresql_service.rst @@ -37,7 +37,7 @@ And run the PostgreSQL server container (in the foreground): .. code-block:: bash - $ sudo docker run --rm -P -name pg_test eg_postgresql + $ sudo docker run --rm -P --name pg_test eg_postgresql There are 2 ways to connect to the PostgreSQL server. We can use :ref:`working_with_links_names`, or we can access it from our host (or the network). diff --git a/docs/sources/use/working_with_volumes.rst b/docs/sources/use/working_with_volumes.rst index 02f4e71b13..d2f035dc84 100644 --- a/docs/sources/use/working_with_volumes.rst +++ b/docs/sources/use/working_with_volumes.rst @@ -129,7 +129,7 @@ because they are external to images. Instead you can use ``--volumes-from`` to start a new container that can access the data-container's volume. For example:: - $ sudo docker run -rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data + $ sudo docker run --rm --volumes-from DATA -v $(pwd):/backup busybox tar cvf /backup/backup.tar /data * ``--rm`` - remove the container when it exits * ``--volumes-from DATA`` - attach to the volumes shared by the ``DATA`` container @@ -140,7 +140,7 @@ data-container's volume. For example:: Then to restore to the same container, or another that you've made elsewhere:: # create a new data container - $ sudo docker run -v /data -name DATA2 busybox true + $ sudo docker run -v /data --name DATA2 busybox true # untar the backup files into the new container's data volume $ sudo docker run --rm --volumes-from DATA2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar data/ From 8944fb2e9b07d5a764f8d48065b9afd73364f640 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 20 Mar 2014 21:51:28 +0000 Subject: [PATCH 15/50] rename lxc to bridge Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- builtins/builtins.go | 7 +++---- runtime/networkdriver/{lxc => bridge}/driver.go | 2 +- runtime/runtime.go | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) rename runtime/networkdriver/{lxc => bridge}/driver.go (99%) diff --git a/builtins/builtins.go b/builtins/builtins.go index 86f3973c62..10ee9b19e6 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -1,10 +1,9 @@ package builtins import ( - "github.com/dotcloud/docker/engine" - "github.com/dotcloud/docker/api" - "github.com/dotcloud/docker/runtime/networkdriver/lxc" + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/runtime/networkdriver/bridge" "github.com/dotcloud/docker/server" ) @@ -35,5 +34,5 @@ func remote(eng *engine.Engine) { // func daemon(eng *engine.Engine) { eng.Register("initserver", server.InitServer) - eng.Register("init_networkdriver", lxc.InitDriver) + eng.Register("init_networkdriver", bridge.InitDriver) } diff --git a/runtime/networkdriver/lxc/driver.go b/runtime/networkdriver/bridge/driver.go similarity index 99% rename from runtime/networkdriver/lxc/driver.go rename to runtime/networkdriver/bridge/driver.go index 827de2a609..41588b1c27 100644 --- a/runtime/networkdriver/lxc/driver.go +++ b/runtime/networkdriver/bridge/driver.go @@ -1,4 +1,4 @@ -package lxc +package bridge import ( "fmt" diff --git a/runtime/runtime.go b/runtime/runtime.go index 38a1beccd2..0d3468e350 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -17,7 +17,7 @@ import ( "github.com/dotcloud/docker/runtime/execdriver/lxc" "github.com/dotcloud/docker/runtime/graphdriver" _ "github.com/dotcloud/docker/runtime/graphdriver/vfs" - _ "github.com/dotcloud/docker/runtime/networkdriver/lxc" + _ "github.com/dotcloud/docker/runtime/networkdriver/bridge" "github.com/dotcloud/docker/runtime/networkdriver/portallocator" "github.com/dotcloud/docker/utils" "io" From 43c3ee3ba154e2480191ed3743391810f23f29af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Djibril=20Kon=C3=A9?= Date: Fri, 21 Mar 2014 00:40:58 +0100 Subject: [PATCH 16/50] Harmonize / across all name-related commands/Validate images names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Djibril Koné (github: enokd) --- api/client.go | 26 +++++++++++++++++++++++--- registry/registry_test.go | 4 ++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/api/client.go b/api/client.go index 07a3d25e5c..aed1ccabc6 100644 --- a/api/client.go +++ b/api/client.go @@ -207,6 +207,15 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } // Upload the build context v := &url.Values{} + + //Check if the given image name can be resolved + if *tag != "" { + repository, _ := utils.ParseRepositoryTag(*tag) + if _, _, err := registry.ResolveRepositoryName(repository); err != nil { + return err + } + } + v.Set("t", *tag) if *suppressOutput { @@ -1002,6 +1011,12 @@ func (cli *DockerCli) CmdImport(args ...string) error { repository, tag = utils.ParseRepositoryTag(cmd.Arg(1)) } v := url.Values{} + + //Check if the given image name can be resolved + if _, _, err := registry.ResolveRepositoryName(repository); err != nil { + return err + } + v.Set("repo", repository) v.Set("tag", tag) v.Set("fromSrc", src) @@ -1452,9 +1467,9 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return nil } - re := regexp.MustCompile("/{2}") - if re.MatchString(repository) { - return fmt.Errorf("Error: Bad image name. Please rename your image in the format /") + //Check if the given image name can be resolved + if _, _, err := registry.ResolveRepositoryName(repository); err != nil { + return err } v := url.Values{} @@ -1745,6 +1760,11 @@ func (cli *DockerCli) CmdTag(args ...string) error { } v := url.Values{} + + //Check if the given image name can be resolved + if _, _, err := registry.ResolveRepositoryName(repository); err != nil { + return err + } v.Set("repo", repository) v.Set("tag", tag) diff --git a/registry/registry_test.go b/registry/registry_test.go index ebfb99b4c3..c072da41c5 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -206,4 +206,8 @@ func TestValidRepositoryName(t *testing.T) { t.Log("Repository name should be invalid") t.Fail() } + if err := validateRepositoryName("docker///docker"); err == nil { + t.Log("Repository name should be invalid") + t.Fail() + } } From 79c11b19ecd506bd76db391b896cec0d4263183d Mon Sep 17 00:00:00 2001 From: alambike Date: Fri, 21 Mar 2014 03:13:06 +0100 Subject: [PATCH 17/50] Added Eixo::Docker to the list of libraries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Docker-DCO-1.1-Signed-off-by: Javier Gómez (github: alambike) --- docs/sources/reference/api/remote_api_client_libraries.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/api/remote_api_client_libraries.rst b/docs/sources/reference/api/remote_api_client_libraries.rst index f74dd416bc..4a445db36f 100644 --- a/docs/sources/reference/api/remote_api_client_libraries.rst +++ b/docs/sources/reference/api/remote_api_client_libraries.rst @@ -49,3 +49,5 @@ and we will add the libraries here. +----------------------+----------------+--------------------------------------------+----------+ | Perl | Net::Docker | https://metacpan.org/pod/Net::Docker | Active | +----------------------+----------------+--------------------------------------------+----------+ +| Perl | Eixo::Docker | https://github.com/alambike/eixo-docker | Active | ++----------------------+----------------+--------------------------------------------+----------+ From 4002eac8b8b4007de03e78dbd57232fac583d05b Mon Sep 17 00:00:00 2001 From: Ken ICHIKAWA Date: Wed, 19 Mar 2014 12:05:54 +0900 Subject: [PATCH 18/50] Fix since time exit display when s.FinishedAt is zero When s.FinishedAt is zero, the since time exit in docker ps doesn't display correct time. For example ``` Exited (0) 292.471209 years ago ``` This patch fixes the since time exit to display nothing if s.FinishedAt is zero. Docker-DCO-1.1-Signed-off-by: Ken ICHIKAWA (github: ichik1) --- runtime/state.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/runtime/state.go b/runtime/state.go index 1c682acd26..316b8a40f1 100644 --- a/runtime/state.go +++ b/runtime/state.go @@ -28,6 +28,9 @@ func (s *State) String() string { } return fmt.Sprintf("Up %s", utils.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) } + if s.FinishedAt.IsZero() { + return "" + } return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, utils.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) } From 708ecd7da2125a47abb9678ed382893c7b30f10f Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 12 Mar 2014 01:58:53 -0600 Subject: [PATCH 19/50] Add mention of mounting cgroupfs properly to PACKAGERS.md Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- docs/sources/installation/binaries.rst | 6 ++++++ hack/PACKAGERS.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index bfdfbe211f..a367a1a94c 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -29,6 +29,12 @@ To run properly, docker needs the following software to be installed at runtime: - iptables version 1.4 or later - Git version 1.7 or later - XZ Utils 4.9 or later +- a `properly mounted + `_ + cgroupfs hierarchy (having a single, all-encompassing "cgroup" mount point `is + `_ `not + `_ `sufficient + `_) Check kernel dependencies diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 5dcb120689..e525a838a1 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -239,6 +239,12 @@ installed and available at runtime: * iptables version 1.4 or later * XZ Utils version 4.9 or later +* a [properly + mounted](https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount) + cgroupfs hierarchy (having a single, all-encompassing "cgroup" mount point + [is](https://github.com/dotcloud/docker/issues/2683) + [not](https://github.com/dotcloud/docker/issues/3485) + [sufficient](https://github.com/dotcloud/docker/issues/4568)) Additionally, the Docker client needs the following software to be installed and available at runtime: From f1bd79ec97c125148c690d66ebd3ac5ab3f388b2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 12:03:41 +0000 Subject: [PATCH 20/50] Revert "fix failing test to use kill instead of stop" This reverts commit 4434dcee89f7d0d0239f6b492b24e940cdbafb21. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration/server_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integration/server_test.go b/integration/server_test.go index 617f81fa4d..a401f1306e 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -416,7 +416,7 @@ func TestRestartKillWait(t *testing.T) { }) } -func TestCreateStartRestartKillStartKillRm(t *testing.T) { +func TestCreateStartRestartStopStartKillRm(t *testing.T) { eng := NewTestEngine(t) srv := mkServerFromEngine(eng, t) defer mkRuntimeFromEngine(eng, t).Nuke() @@ -456,7 +456,8 @@ func TestCreateStartRestartKillStartKillRm(t *testing.T) { t.Fatal(err) } - job = eng.Job("kill", id) + job = eng.Job("stop", id) + job.SetenvInt("t", 15) if err := job.Run(); err != nil { t.Fatal(err) } From d503714285143013d9fa6932ee5775fd155d26d2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 12:03:56 +0000 Subject: [PATCH 21/50] Revert "Disable automatic killing of containers when docker stop fails" This reverts commit 8b5cf51d600dc4f3611cf063c52cf3448e7b01e5. Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/client.go | 6 +++--- .../sources/reference/api/docker_remote_api_v1.9.rst | 4 ++-- docs/sources/reference/commandline/cli.rst | 6 +++--- runtime/container.go | 12 ++++++++++-- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/api/client.go b/api/client.go index ae9e7cef19..b39b102330 100644 --- a/api/client.go +++ b/api/client.go @@ -498,8 +498,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error { } func (cli *DockerCli) CmdStop(args ...string) error { - cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM)") - nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop.") + cmd := cli.Subcmd("stop", "[OPTIONS] CONTAINER [CONTAINER...]", "Stop a running container (Send SIGTERM, and then SIGKILL after grace period)") + nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop before killing it.") if err := cmd.Parse(args); err != nil { return nil } @@ -526,7 +526,7 @@ func (cli *DockerCli) CmdStop(args ...string) error { func (cli *DockerCli) CmdRestart(args ...string) error { cmd := cli.Subcmd("restart", "[OPTIONS] CONTAINER [CONTAINER...]", "Restart a running container") - nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to wait for the container to stop. Default=10") + nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Number of seconds to try to stop for before killing the container. Once killed it will then be restarted. Default=10") if err := cmd.Parse(args); err != nil { return nil } diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.rst b/docs/sources/reference/api/docker_remote_api_v1.9.rst index def38edd55..27812457bb 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.rst +++ b/docs/sources/reference/api/docker_remote_api_v1.9.rst @@ -432,7 +432,7 @@ Stop a container HTTP/1.1 204 OK - :query t: number of seconds to wait for the container to stop + :query t: number of seconds to wait before killing the container :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error @@ -457,7 +457,7 @@ Restart a container HTTP/1.1 204 OK - :query t: number of seconds to wait for the container to stop + :query t: number of seconds to wait before killing the container :statuscode 204: no error :statuscode 404: no such container :statuscode 500: server error diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index d398b16e53..757f3b239b 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1360,11 +1360,11 @@ This example shows 5 containers that might be set up to test a web application c Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] - Stop a running container (Send SIGTERM) + Stop a running container (Send SIGTERM, and then SIGKILL after grace period) - -t, --time=10: Number of seconds to wait for the container to stop. + -t, --time=10: Number of seconds to wait for the container to stop before killing it. -The main process inside the container will receive SIGTERM. +The main process inside the container will receive SIGTERM, and after a grace period, SIGKILL .. _cli_tag: diff --git a/runtime/container.go b/runtime/container.go index 6194a19c8c..bff9aea968 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -903,12 +903,20 @@ func (container *Container) Stop(seconds int) error { // 1. Send a SIGTERM if err := container.KillSig(15); err != nil { - return err + utils.Debugf("Error sending kill SIGTERM: %s", err) + log.Print("Failed to send SIGTERM to the process, force killing") + if err := container.KillSig(9); err != nil { + return err + } } // 2. Wait for the process to exit on its own if err := container.WaitTimeout(time.Duration(seconds) * time.Second); err != nil { - return err + log.Printf("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds) + // 3. If it doesn't, then send SIGKILL + if err := container.Kill(); err != nil { + return err + } } return nil } From f41135bc11b9a4da896e5054a73afa112b2e835f Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 12:39:56 +0000 Subject: [PATCH 22/50] As far as I know this code is not used or maintained Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- hack/infrastructure/docker-ci/Dockerfile | 29 --- hack/infrastructure/docker-ci/MAINTAINERS | 1 - hack/infrastructure/docker-ci/README.rst | 65 ------- hack/infrastructure/docker-ci/VERSION | 1 - .../docker-ci/buildbot/github.py | 176 ------------------ .../docker-ci/buildbot/master.cfg | 161 ---------------- .../docker-ci/dcr/prod/docker-ci.yml | 22 --- .../docker-ci/dcr/prod/settings.yml | 5 - .../docker-ci/dcr/stage/docker-ci.yml | 22 --- .../docker-ci/dcr/stage/settings.yml | 5 - .../docker-ci/docker-coverage/gocoverage.sh | 52 ------ .../docker-ci/dockertest/docker | 1 - .../docker-ci/dockertest/docker-registry | 1 - .../docker-ci/dockertest/nightlyrelease | 13 -- .../docker-ci/dockertest/project | 8 - .../docker-ci/functionaltests/test_index.py | 61 ------ .../functionaltests/test_registry.sh | 27 --- .../infrastructure/docker-ci/nginx/nginx.conf | 12 -- .../docker-ci/report/Dockerfile | 28 --- .../docker-ci/report/deployment.py | 130 ------------- .../infrastructure/docker-ci/report/report.py | 145 --------------- hack/infrastructure/docker-ci/setup.sh | 54 ------ .../docker-ci/testbuilder/Dockerfile | 12 -- .../docker-ci/testbuilder/docker-registry.sh | 12 -- .../docker-ci/testbuilder/docker.sh | 18 -- .../docker-ci/testbuilder/testbuilder.sh | 40 ---- hack/infrastructure/docker-ci/tool/backup.py | 47 ----- 27 files changed, 1148 deletions(-) delete mode 100644 hack/infrastructure/docker-ci/Dockerfile delete mode 100644 hack/infrastructure/docker-ci/MAINTAINERS delete mode 100644 hack/infrastructure/docker-ci/README.rst delete mode 100644 hack/infrastructure/docker-ci/VERSION delete mode 100644 hack/infrastructure/docker-ci/buildbot/github.py delete mode 100644 hack/infrastructure/docker-ci/buildbot/master.cfg delete mode 100644 hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml delete mode 100644 hack/infrastructure/docker-ci/dcr/prod/settings.yml delete mode 100644 hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml delete mode 100644 hack/infrastructure/docker-ci/dcr/stage/settings.yml delete mode 100755 hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh delete mode 120000 hack/infrastructure/docker-ci/dockertest/docker delete mode 120000 hack/infrastructure/docker-ci/dockertest/docker-registry delete mode 100755 hack/infrastructure/docker-ci/dockertest/nightlyrelease delete mode 100755 hack/infrastructure/docker-ci/dockertest/project delete mode 100755 hack/infrastructure/docker-ci/functionaltests/test_index.py delete mode 100755 hack/infrastructure/docker-ci/functionaltests/test_registry.sh delete mode 100644 hack/infrastructure/docker-ci/nginx/nginx.conf delete mode 100644 hack/infrastructure/docker-ci/report/Dockerfile delete mode 100755 hack/infrastructure/docker-ci/report/deployment.py delete mode 100755 hack/infrastructure/docker-ci/report/report.py delete mode 100755 hack/infrastructure/docker-ci/setup.sh delete mode 100644 hack/infrastructure/docker-ci/testbuilder/Dockerfile delete mode 100755 hack/infrastructure/docker-ci/testbuilder/docker-registry.sh delete mode 100755 hack/infrastructure/docker-ci/testbuilder/docker.sh delete mode 100755 hack/infrastructure/docker-ci/testbuilder/testbuilder.sh delete mode 100755 hack/infrastructure/docker-ci/tool/backup.py diff --git a/hack/infrastructure/docker-ci/Dockerfile b/hack/infrastructure/docker-ci/Dockerfile deleted file mode 100644 index 5c6eec9663..0000000000 --- a/hack/infrastructure/docker-ci/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -# DOCKER-VERSION: 0.7.6 -# AUTHOR: Daniel Mizyrycki -# DESCRIPTION: docker-ci continuous integration service -# TO_BUILD: docker build -t docker-ci/docker-ci . -# TO_RUN: docker run --rm -i -t -p 8000:80 -p 2222:22 -v /run:/var/socket \ -# -v /data/docker-ci:/data/docker-ci docker-ci/docker-ci - -from ubuntu:12.04 -maintainer Daniel Mizyrycki - -ENV DEBIAN_FRONTEND noninteractive -RUN echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > \ - /etc/apt/sources.list; apt-get update -RUN apt-get install -y --no-install-recommends python2.7 python-dev \ - libevent-dev git supervisor ssh rsync less vim sudo gcc wget nginx -RUN cd /tmp; wget http://python-distribute.org/distribute_setup.py -RUN cd /tmp; python distribute_setup.py; easy_install pip; rm distribute_setup.py - -RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9 -RUN echo 'deb http://get.docker.io/ubuntu docker main' > \ - /etc/apt/sources.list.d/docker.list; apt-get update -RUN apt-get install -y lxc-docker-0.8.0 -RUN pip install SQLAlchemy==0.7.10 buildbot buildbot-slave pyopenssl boto -RUN ln -s /var/socket/docker.sock /run/docker.sock - -ADD . /docker-ci -RUN /docker-ci/setup.sh - -ENTRYPOINT ["supervisord", "-n"] diff --git a/hack/infrastructure/docker-ci/MAINTAINERS b/hack/infrastructure/docker-ci/MAINTAINERS deleted file mode 100644 index 5dfc881420..0000000000 --- a/hack/infrastructure/docker-ci/MAINTAINERS +++ /dev/null @@ -1 +0,0 @@ -Daniel Mizyrycki (@mzdaniel) diff --git a/hack/infrastructure/docker-ci/README.rst b/hack/infrastructure/docker-ci/README.rst deleted file mode 100644 index 07c1ffcec0..0000000000 --- a/hack/infrastructure/docker-ci/README.rst +++ /dev/null @@ -1,65 +0,0 @@ -========= -docker-ci -========= - -This directory contains docker-ci continuous integration system. -As expected, it is a fully dockerized and deployed using -docker-container-runner. -docker-ci is based on Buildbot, a continuous integration system designed -to automate the build/test cycle. By automatically rebuilding and testing -the tree each time something has changed, build problems are pinpointed -quickly, before other developers are inconvenienced by the failure. -We are running buildbot at Rackspace to verify docker and docker-registry -pass tests, and check for coverage code details. - -docker-ci instance is at https://docker-ci.docker.io/waterfall - -Inside docker-ci container we have the following directory structure: - -/docker-ci source code of docker-ci -/data/backup/docker-ci/ daily backup (replicated over S3) -/data/docker-ci/coverage/{docker,docker-registry}/ mapped to host volumes -/data/buildbot/{master,slave}/ main docker-ci buildbot config and database -/var/socket/{docker.sock} host volume access to docker socket - - -Production deployment -===================== - -:: - - # Clone docker-ci repository - git clone https://github.com/dotcloud/docker - cd docker/hack/infrastructure/docker-ci - - export DOCKER_PROD=[PRODUCTION_SERVER_IP] - - # Create data host volume. (only once) - docker -H $DOCKER_PROD run -v /home:/data ubuntu:12.04 \ - mkdir -p /data/docker-ci/coverage/docker - docker -H $DOCKER_PROD run -v /home:/data ubuntu:12.04 \ - mkdir -p /data/docker-ci/coverage/docker-registry - docker -H $DOCKER_PROD run -v /home:/data ubuntu:12.04 \ - chown -R 1000.1000 /data/docker-ci - - # dcr deployment. Define credentials and special environment dcr variables - # ( retrieved at /hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml ) - export WEB_USER=[DOCKER-CI-WEBSITE-USERNAME] - export WEB_IRC_PWD=[DOCKER-CI-WEBSITE-PASSWORD] - export BUILDBOT_PWD=[BUILDSLAVE_PASSWORD] - export AWS_ACCESS_KEY=[DOCKER_RELEASE_S3_ACCESS] - export AWS_SECRET_KEY=[DOCKER_RELEASE_S3_SECRET] - export GPG_PASSPHRASE=[DOCKER_RELEASE_PASSPHRASE] - export BACKUP_AWS_ID=[S3_BUCKET_CREDENTIAL_ACCESS] - export BACKUP_AWS_SECRET=[S3_BUCKET_CREDENTIAL_SECRET] - export SMTP_USER=[MAILGUN_SMTP_USERNAME] - export SMTP_PWD=[MAILGUN_SMTP_PASSWORD] - export EMAIL_RCP=[EMAIL_FOR_BUILD_ERRORS] - - # Build docker-ci and testbuilder docker images - docker -H $DOCKER_PROD build -t docker-ci/docker-ci . - (cd testbuilder; docker -H $DOCKER_PROD build --rm -t docker-ci/testbuilder .) - - # Run docker-ci container ( assuming no previous container running ) - (cd dcr/prod; dcr docker-ci.yml start) - (cd dcr/prod; dcr docker-ci.yml register docker-ci.docker.io) diff --git a/hack/infrastructure/docker-ci/VERSION b/hack/infrastructure/docker-ci/VERSION deleted file mode 100644 index b49b25336d..0000000000 --- a/hack/infrastructure/docker-ci/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.5.6 diff --git a/hack/infrastructure/docker-ci/buildbot/github.py b/hack/infrastructure/docker-ci/buildbot/github.py deleted file mode 100644 index 5316e13282..0000000000 --- a/hack/infrastructure/docker-ci/buildbot/github.py +++ /dev/null @@ -1,176 +0,0 @@ -# This file is part of Buildbot. Buildbot is free software: you can -# redistribute it and/or modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation, version 2. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., 51 -# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# Copyright Buildbot Team Members - -#!/usr/bin/env python -""" -github_buildbot.py is based on git_buildbot.py - -github_buildbot.py will determine the repository information from the JSON -HTTP POST it receives from github.com and build the appropriate repository. -If your github repository is private, you must add a ssh key to the github -repository for the user who initiated the build on the buildslave. - -""" - -import re -import datetime -from twisted.python import log -import calendar - -try: - import json - assert json -except ImportError: - import simplejson as json - -# python is silly about how it handles timezones -class fixedOffset(datetime.tzinfo): - """ - fixed offset timezone - """ - def __init__(self, minutes, hours, offsetSign = 1): - self.minutes = int(minutes) * offsetSign - self.hours = int(hours) * offsetSign - self.offset = datetime.timedelta(minutes = self.minutes, - hours = self.hours) - - def utcoffset(self, dt): - return self.offset - - def dst(self, dt): - return datetime.timedelta(0) - -def convertTime(myTestTimestamp): - #"1970-01-01T00:00:00+00:00" - # Normalize myTestTimestamp - if myTestTimestamp[-1] == 'Z': - myTestTimestamp = myTestTimestamp[:-1] + '-00:00' - matcher = re.compile(r'(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)([-+])(\d\d):(\d\d)') - result = matcher.match(myTestTimestamp) - (year, month, day, hour, minute, second, offsetsign, houroffset, minoffset) = \ - result.groups() - if offsetsign == '+': - offsetsign = 1 - else: - offsetsign = -1 - - offsetTimezone = fixedOffset( minoffset, houroffset, offsetsign ) - myDatetime = datetime.datetime( int(year), - int(month), - int(day), - int(hour), - int(minute), - int(second), - 0, - offsetTimezone) - return calendar.timegm( myDatetime.utctimetuple() ) - -def getChanges(request, options = None): - """ - Reponds only to POST events and starts the build process - - :arguments: - request - the http request object - """ - payload = json.loads(request.args['payload'][0]) - import urllib,datetime - fname = str(datetime.datetime.now()).replace(' ','_').replace(':','-')[:19] - # Github event debug - # open('github_{0}.json'.format(fname),'w').write(json.dumps(json.loads(urllib.unquote(request.args['payload'][0])), sort_keys = True, indent = 2)) - - if 'pull_request' in payload: - user = payload['pull_request']['user']['login'] - repo = payload['pull_request']['head']['repo']['name'] - repo_url = payload['pull_request']['head']['repo']['html_url'] - else: - user = payload['repository']['owner']['name'] - repo = payload['repository']['name'] - repo_url = payload['repository']['url'] - project = request.args.get('project', None) - if project: - project = project[0] - elif project is None: - project = '' - # This field is unused: - #private = payload['repository']['private'] - changes = process_change(payload, user, repo, repo_url, project) - log.msg("Received %s changes from github" % len(changes)) - return (changes, 'git') - -def process_change(payload, user, repo, repo_url, project): - """ - Consumes the JSON as a python object and actually starts the build. - - :arguments: - payload - Python Object that represents the JSON sent by GitHub Service - Hook. - """ - changes = [] - - newrev = payload['after'] if 'after' in payload else payload['pull_request']['head']['sha'] - refname = payload['ref'] if 'ref' in payload else payload['pull_request']['head']['ref'] - - # We only care about regular heads, i.e. branches - match = re.match(r"^(refs\/heads\/|)([^/]+)$", refname) - if not match: - log.msg("Ignoring refname `%s': Not a branch" % refname) - return [] - - branch = match.groups()[1] - if re.match(r"^0*$", newrev): - log.msg("Branch `%s' deleted, ignoring" % branch) - return [] - else: - if 'pull_request' in payload: - if payload['action'] == 'closed': - log.msg("PR#{} closed, ignoring".format(payload['number'])) - return [] - changes = [{ - 'category' : 'github_pullrequest', - 'who' : '{0} - PR#{1}'.format(user,payload['number']), - 'files' : [], - 'comments' : payload['pull_request']['title'], - 'revision' : newrev, - 'when' : convertTime(payload['pull_request']['updated_at']), - 'branch' : branch, - 'revlink' : '{0}/commit/{1}'.format(repo_url,newrev), - 'repository' : repo_url, - 'project' : project }] - return changes - for commit in payload['commits']: - files = [] - if 'added' in commit: - files.extend(commit['added']) - if 'modified' in commit: - files.extend(commit['modified']) - if 'removed' in commit: - files.extend(commit['removed']) - when = convertTime( commit['timestamp']) - log.msg("New revision: %s" % commit['id'][:8]) - chdict = dict( - who = commit['author']['name'] - + " <" + commit['author']['email'] + ">", - files = files, - comments = commit['message'], - revision = commit['id'], - when = when, - branch = branch, - revlink = commit['url'], - repository = repo_url, - project = project) - changes.append(chdict) - return changes diff --git a/hack/infrastructure/docker-ci/buildbot/master.cfg b/hack/infrastructure/docker-ci/buildbot/master.cfg deleted file mode 100644 index 75605da8ab..0000000000 --- a/hack/infrastructure/docker-ci/buildbot/master.cfg +++ /dev/null @@ -1,161 +0,0 @@ -import os, re -from buildbot.buildslave import BuildSlave -from buildbot.schedulers.forcesched import ForceScheduler -from buildbot.schedulers.basic import SingleBranchScheduler -from buildbot.schedulers.timed import Nightly -from buildbot.changes import filter -from buildbot.config import BuilderConfig -from buildbot.process.factory import BuildFactory -from buildbot.process.properties import Property -from buildbot.steps.shell import ShellCommand -from buildbot.status import html, words -from buildbot.status.web import authz, auth -from buildbot.status.mail import MailNotifier - - -def ENV(x): - '''Promote an environment variable for global use returning its value''' - retval = os.environ.get(x, '') - globals()[x] = retval - return retval - - -class TestCommand(ShellCommand): - '''Extend ShellCommand with optional summary logs''' - def __init__(self, *args, **kwargs): - super(TestCommand, self).__init__(*args, **kwargs) - - def createSummary(self, log): - exit_status = re.sub(r'.+\n\+ exit (\d+).+', - r'\1', log.getText()[-100:], flags=re.DOTALL) - if exit_status != '0': - return - # Infer coverage path from log - if '+ COVERAGE_PATH' in log.getText(): - path = re.sub(r'.+\+ COVERAGE_PATH=((.+?)-\d+).+', - r'\2/\1', log.getText(), flags=re.DOTALL) - url = '{}coverage/{}/index.html'.format(c['buildbotURL'], path) - self.addURL('coverage', url) - elif 'COVERAGE_FILE' in log.getText(): - path = re.sub(r'.+\+ COVERAGE_FILE=((.+?)-\d+).+', - r'\2/\1', log.getText(), flags=re.DOTALL) - url = '{}coverage/{}/index.html'.format(c['buildbotURL'], path) - self.addURL('coverage', url) - - -PORT_WEB = 8000 # Buildbot webserver port -PORT_GITHUB = 8011 # Buildbot github hook port -PORT_MASTER = 9989 # Port where buildbot master listen buildworkers - -BUILDBOT_URL = '//localhost:{}/'.format(PORT_WEB) -DOCKER_REPO = 'https://github.com/docker-test/docker' -DOCKER_TEST_ARGV = 'HEAD {}'.format(DOCKER_REPO) -REGISTRY_REPO = 'https://github.com/docker-test/docker-registry' -REGISTRY_TEST_ARGV = 'HEAD {}'.format(REGISTRY_REPO) -if ENV('DEPLOYMENT') == 'staging': - BUILDBOT_URL = "//docker-ci-stage.docker.io/" -if ENV('DEPLOYMENT') == 'production': - BUILDBOT_URL = '//docker-ci.docker.io/' - DOCKER_REPO = 'https://github.com/dotcloud/docker' - DOCKER_TEST_ARGV = '' - REGISTRY_REPO = 'https://github.com/dotcloud/docker-registry' - REGISTRY_TEST_ARGV = '' - -# Credentials set by setup.sh from deployment.py -ENV('WEB_USER') -ENV('WEB_IRC_PWD') -ENV('BUILDBOT_PWD') -ENV('SMTP_USER') -ENV('SMTP_PWD') -ENV('EMAIL_RCP') -ENV('IRC_CHANNEL') - - -c = BuildmasterConfig = {} - -c['title'] = "docker-ci" -c['titleURL'] = "waterfall" -c['buildbotURL'] = BUILDBOT_URL -c['db'] = {'db_url':"sqlite:///state.sqlite"} -c['slaves'] = [BuildSlave('buildworker', BUILDBOT_PWD)] -c['slavePortnum'] = PORT_MASTER - - -# Schedulers -c['schedulers'] = [ForceScheduler(name='trigger', builderNames=[ - 'docker', 'docker-registry', 'nightlyrelease', 'backup'])] -c['schedulers'] += [SingleBranchScheduler(name="docker", treeStableTimer=None, - change_filter=filter.ChangeFilter(branch='master', - repository=DOCKER_REPO), builderNames=['docker'])] -c['schedulers'] += [SingleBranchScheduler(name="registry", treeStableTimer=None, - change_filter=filter.ChangeFilter(branch='master', - repository=REGISTRY_REPO), builderNames=['docker-registry'])] -c['schedulers'] += [SingleBranchScheduler(name='docker-pr', treeStableTimer=None, - change_filter=filter.ChangeFilter(category='github_pullrequest', - project='docker'), builderNames=['docker-pr'])] -c['schedulers'] += [SingleBranchScheduler(name='docker-registry-pr', treeStableTimer=None, - change_filter=filter.ChangeFilter(category='github_pullrequest', - project='docker-registry'), builderNames=['docker-registry-pr'])] -c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=[ - 'nightlyrelease', 'backup'], hour=7, minute=00)] - - -# Builders - -# Backup -factory = BuildFactory() -factory.addStep(TestCommand(description='backup', logEnviron=False, - usePTY=True, command='/docker-ci/tool/backup.py')) -c['builders'] = [BuilderConfig(name='backup',slavenames=['buildworker'], - factory=factory)] - -# Docker test -factory = BuildFactory() -factory.addStep(TestCommand(description='docker', logEnviron=False, - usePTY=True, command='/docker-ci/dockertest/docker {}'.format(DOCKER_TEST_ARGV))) -c['builders'] += [BuilderConfig(name='docker',slavenames=['buildworker'], - factory=factory)] - -# Docker pull request test -factory = BuildFactory() -factory.addStep(TestCommand(description='docker-pr', logEnviron=False, - usePTY=True, command=['/docker-ci/dockertest/docker', - Property('revision'), Property('repository'), Property('branch')])) -c['builders'] += [BuilderConfig(name='docker-pr',slavenames=['buildworker'], - factory=factory)] - -# docker-registry test -factory = BuildFactory() -factory.addStep(TestCommand(description='docker-registry', logEnviron=False, - usePTY=True, command='/docker-ci/dockertest/docker-registry {}'.format(REGISTRY_TEST_ARGV))) -c['builders'] += [BuilderConfig(name='docker-registry',slavenames=['buildworker'], - factory=factory)] - -# Docker registry pull request test -factory = BuildFactory() -factory.addStep(TestCommand(description='docker-registry-pr', logEnviron=False, - usePTY=True, command=['/docker-ci/dockertest/docker-registry', - Property('revision'), Property('repository'), Property('branch')])) -c['builders'] += [BuilderConfig(name='docker-registry-pr',slavenames=['buildworker'], - factory=factory)] - -# Docker nightly release -factory = BuildFactory() -factory.addStep(ShellCommand(description='NightlyRelease',logEnviron=False, - usePTY=True, command=['/docker-ci/dockertest/nightlyrelease'])) -c['builders'] += [BuilderConfig(name='nightlyrelease',slavenames=['buildworker'], - factory=factory)] - -# Status -authz_cfg = authz.Authz(auth=auth.BasicAuth([(WEB_USER, WEB_IRC_PWD)]), - forceBuild='auth') -c['status'] = [html.WebStatus(http_port=PORT_WEB, authz=authz_cfg)] -c['status'].append(html.WebStatus(http_port=PORT_GITHUB, allowForce=True, - change_hook_dialects={ 'github': True })) -c['status'].append(MailNotifier(fromaddr='docker-test@docker.io', - sendToInterestedUsers=False, extraRecipients=[EMAIL_RCP], - mode='failing', relayhost='smtp.mailgun.org', smtpPort=587, useTls=True, - smtpUser=SMTP_USER, smtpPassword=SMTP_PWD)) -c['status'].append(words.IRC("irc.freenode.net", "dockerqabot", - channels=[IRC_CHANNEL], password=WEB_IRC_PWD, allowForce=True, - notify_events={'exception':1, 'successToFailure':1, 'failureToSuccess':1})) diff --git a/hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml b/hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml deleted file mode 100644 index 523535446a..0000000000 --- a/hack/infrastructure/docker-ci/dcr/prod/docker-ci.yml +++ /dev/null @@ -1,22 +0,0 @@ -docker-ci: - image: "docker-ci/docker-ci" - release_name: "docker-ci-0.5.6" - ports: ["80","2222:22","8011:8011"] - register: "80" - volumes: ["/run:/var/socket","/home/docker-ci:/data/docker-ci"] - command: [] - env: - - "DEPLOYMENT=production" - - "IRC_CHANNEL=docker-testing" - - "BACKUP_BUCKET=backup-ci" - - "$WEB_USER" - - "$WEB_IRC_PWD" - - "$BUILDBOT_PWD" - - "$AWS_ACCESS_KEY" - - "$AWS_SECRET_KEY" - - "$GPG_PASSPHRASE" - - "$BACKUP_AWS_ID" - - "$BACKUP_AWS_SECRET" - - "$SMTP_USER" - - "$SMTP_PWD" - - "$EMAIL_RCP" diff --git a/hack/infrastructure/docker-ci/dcr/prod/settings.yml b/hack/infrastructure/docker-ci/dcr/prod/settings.yml deleted file mode 100644 index 9831afa6dd..0000000000 --- a/hack/infrastructure/docker-ci/dcr/prod/settings.yml +++ /dev/null @@ -1,5 +0,0 @@ -default: - hipaches: ['192.168.100.67:6379'] - daemons: ['192.168.100.67:4243'] - use_ssh: False - diff --git a/hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml b/hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml deleted file mode 100644 index 8eba84825c..0000000000 --- a/hack/infrastructure/docker-ci/dcr/stage/docker-ci.yml +++ /dev/null @@ -1,22 +0,0 @@ -docker-ci: - image: "docker-ci/docker-ci" - release_name: "docker-ci-stage" - ports: ["80","2222:22","8011:8011"] - register: "80" - volumes: ["/run:/var/socket","/home/docker-ci:/data/docker-ci"] - command: [] - env: - - "DEPLOYMENT=staging" - - "IRC_CHANNEL=docker-testing-staging" - - "BACKUP_BUCKET=ci-backup-stage" - - "$BACKUP_AWS_ID" - - "$BACKUP_AWS_SECRET" - - "$WEB_USER" - - "$WEB_IRC_PWD" - - "$BUILDBOT_PWD" - - "$AWS_ACCESS_KEY" - - "$AWS_SECRET_KEY" - - "$GPG_PASSPHRASE" - - "$SMTP_USER" - - "$SMTP_PWD" - - "$EMAIL_RCP" diff --git a/hack/infrastructure/docker-ci/dcr/stage/settings.yml b/hack/infrastructure/docker-ci/dcr/stage/settings.yml deleted file mode 100644 index a7d37acff3..0000000000 --- a/hack/infrastructure/docker-ci/dcr/stage/settings.yml +++ /dev/null @@ -1,5 +0,0 @@ -default: - hipaches: ['192.168.100.65:6379'] - daemons: ['192.168.100.65:4243'] - use_ssh: False - diff --git a/hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh b/hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh deleted file mode 100755 index fdacc290b4..0000000000 --- a/hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash - -export PATH='/go/bin':$PATH -export DOCKER_PATH='/go/src/github.com/dotcloud/docker' - -# Signal coverage report name, parsed by docker-ci -set -x -COVERAGE_PATH=$(date +"docker-%Y%m%d%H%M%S") -set +x - -REPORTS="/data/$COVERAGE_PATH" -INDEX="$REPORTS/index.html" - -# Test docker -cd $DOCKER_PATH -./hack/make.sh test; exit_status=$? -PROFILE_PATH="$(ls -d $DOCKER_PATH/bundles/* | sed -n '$ p')/test/coverprofiles" - -if [ "$exit_status" -eq "0" ]; then - # Download coverage dependencies - go get github.com/axw/gocov/gocov - go get -u github.com/matm/gocov-html - - # Create coverage report - mkdir -p $REPORTS - cd $PROFILE_PATH - cat > $INDEX << "EOF" - - - - - -Docker Coverage Report - -

Docker Coverage Report

- - -EOF - for profile in *; do - gocov convert $profile | gocov-html >$REPORTS/$profile.html - echo "" >> $INDEX - done - echo "
packagepct
$profile" >> $INDEX - go tool cover -func=$profile | sed -En '$ s/.+\t(.+)/\1/p' >> $INDEX - echo "
" >> $INDEX -fi - -# Signal test and coverage result, parsed by docker-ci -set -x -exit $exit_status - diff --git a/hack/infrastructure/docker-ci/dockertest/docker b/hack/infrastructure/docker-ci/dockertest/docker deleted file mode 120000 index e3f094ee63..0000000000 --- a/hack/infrastructure/docker-ci/dockertest/docker +++ /dev/null @@ -1 +0,0 @@ -project \ No newline at end of file diff --git a/hack/infrastructure/docker-ci/dockertest/docker-registry b/hack/infrastructure/docker-ci/dockertest/docker-registry deleted file mode 120000 index e3f094ee63..0000000000 --- a/hack/infrastructure/docker-ci/dockertest/docker-registry +++ /dev/null @@ -1 +0,0 @@ -project \ No newline at end of file diff --git a/hack/infrastructure/docker-ci/dockertest/nightlyrelease b/hack/infrastructure/docker-ci/dockertest/nightlyrelease deleted file mode 100755 index cface6c125..0000000000 --- a/hack/infrastructure/docker-ci/dockertest/nightlyrelease +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -if [ "$DEPLOYMENT" == "production" ]; then - AWS_S3_BUCKET='test.docker.io' -else - AWS_S3_BUCKET='get-staging.docker.io' -fi - -docker run --rm --privileged -v /run:/var/socket \ - -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY \ - -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE \ - -e DOCKER_RELEASE=1 -e DEPLOYMENT=$DEPLOYMENT docker-ci/testbuilder docker - diff --git a/hack/infrastructure/docker-ci/dockertest/project b/hack/infrastructure/docker-ci/dockertest/project deleted file mode 100755 index 8131ab533a..0000000000 --- a/hack/infrastructure/docker-ci/dockertest/project +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash -set -x - -PROJECT_NAME=$(basename $0) - -docker run --rm -u sysadmin -e DEPLOYMENT=$DEPLOYMENT -v /run:/var/socket \ - -v /home/docker-ci/coverage/$PROJECT_NAME:/data docker-ci/testbuilder $PROJECT_NAME $1 $2 $3 - diff --git a/hack/infrastructure/docker-ci/functionaltests/test_index.py b/hack/infrastructure/docker-ci/functionaltests/test_index.py deleted file mode 100755 index fd002c81e8..0000000000 --- a/hack/infrastructure/docker-ci/functionaltests/test_index.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/python - -import os -username, password = os.environ['DOCKER_CREDS'].split(':') - -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support.ui import Select -from selenium.common.exceptions import NoSuchElementException -import unittest, time, re - -class Docker(unittest.TestCase): - def setUp(self): - self.driver = webdriver.PhantomJS() - self.driver.implicitly_wait(30) - self.base_url = "http://www.docker.io/" - self.verificationErrors = [] - self.accept_next_alert = True - - def test_docker(self): - driver = self.driver - print "Login into {0} as login user {1} ...".format(self.base_url,username) - driver.get(self.base_url + "/") - driver.find_element_by_link_text("INDEX").click() - driver.find_element_by_link_text("login").click() - driver.find_element_by_id("id_username").send_keys(username) - driver.find_element_by_id("id_password").send_keys(password) - print "Checking login user ..." - driver.find_element_by_css_selector("input[type=\"submit\"]").click() - try: self.assertEqual("test", driver.find_element_by_css_selector("h3").text) - except AssertionError as e: self.verificationErrors.append(str(e)) - print "Login user {0} found".format(username) - - def is_element_present(self, how, what): - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - def is_alert_present(self): - try: self.driver.switch_to_alert() - except NoAlertPresentException, e: return False - return True - - def close_alert_and_get_its_text(self): - try: - alert = self.driver.switch_to_alert() - alert_text = alert.text - if self.accept_next_alert: - alert.accept() - else: - alert.dismiss() - return alert_text - finally: self.accept_next_alert = True - - def tearDown(self): - self.driver.quit() - self.assertEqual([], self.verificationErrors) - -if __name__ == "__main__": - unittest.main() diff --git a/hack/infrastructure/docker-ci/functionaltests/test_registry.sh b/hack/infrastructure/docker-ci/functionaltests/test_registry.sh deleted file mode 100755 index 58642529cc..0000000000 --- a/hack/infrastructure/docker-ci/functionaltests/test_registry.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -set -x - -# Cleanup -rm -rf docker-registry - -# Setup the environment -export SETTINGS_FLAVOR=test -export DOCKER_REGISTRY_CONFIG=config_test.yml -export PYTHONPATH=$(pwd)/docker-registry/test - -# Get latest docker registry -git clone -q https://github.com/dotcloud/docker-registry.git -cd docker-registry -sed -Ei "s#(boto_bucket: ).+#\1_env:S3_BUCKET#" config_test.yml - -# Get dependencies -pip install -q -r requirements.txt -pip install -q -r test-requirements.txt -pip install -q tox - -# Run registry tests -tox || exit 1 -python -m unittest discover -p s3.py -s test || exit 1 -python -m unittest discover -p workflow.py -s test - diff --git a/hack/infrastructure/docker-ci/nginx/nginx.conf b/hack/infrastructure/docker-ci/nginx/nginx.conf deleted file mode 100644 index 6649741134..0000000000 --- a/hack/infrastructure/docker-ci/nginx/nginx.conf +++ /dev/null @@ -1,12 +0,0 @@ -server { - listen 80; - root /data/docker-ci; - - location / { - proxy_pass http://localhost:8000/; - } - - location /coverage { - root /data/docker-ci; - } -} diff --git a/hack/infrastructure/docker-ci/report/Dockerfile b/hack/infrastructure/docker-ci/report/Dockerfile deleted file mode 100644 index 32600c4c58..0000000000 --- a/hack/infrastructure/docker-ci/report/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -# VERSION: 0.22 -# DOCKER-VERSION 0.6.3 -# AUTHOR: Daniel Mizyrycki -# DESCRIPTION: Generate docker-ci daily report -# COMMENTS: The build process is initiated by deployment.py - Report configuration is passed through ./credentials.json at -# deployment time. -# TO_BUILD: docker build -t report . -# TO_DEPLOY: docker run report - -from ubuntu:12.04 -maintainer Daniel Mizyrycki - -env PYTHONPATH /report - - -# Add report dependencies -run echo 'deb http://archive.ubuntu.com/ubuntu precise main universe' > \ - /etc/apt/sources.list -run apt-get update; apt-get install -y python2.7 python-pip ssh rsync - -# Set San Francisco timezone -run echo "America/Los_Angeles" >/etc/timezone -run dpkg-reconfigure --frontend noninteractive tzdata - -# Add report code and set default container command -add . /report -cmd "/report/report.py" diff --git a/hack/infrastructure/docker-ci/report/deployment.py b/hack/infrastructure/docker-ci/report/deployment.py deleted file mode 100755 index 5b2eaf3cab..0000000000 --- a/hack/infrastructure/docker-ci/report/deployment.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python - -'''Deploy docker-ci report container on Digital Ocean. -Usage: - export CONFIG_JSON=' - { "DROPLET_NAME": "Digital_Ocean_dropplet_name", - "DO_CLIENT_ID": "Digital_Ocean_client_id", - "DO_API_KEY": "Digital_Ocean_api_key", - "DOCKER_KEY_ID": "Digital_Ocean_ssh_key_id", - "DOCKER_CI_KEY_PATH": "docker-ci_private_key_path", - "DOCKER_CI_PUB": "$(cat docker-ci_ssh_public_key.pub)", - "DOCKER_CI_ADDRESS" "user@docker-ci_fqdn_server", - "SMTP_USER": "SMTP_server_user", - "SMTP_PWD": "SMTP_server_password", - "EMAIL_SENDER": "Buildbot_mailing_sender", - "EMAIL_RCP": "Buildbot_mailing_receipient" }' - python deployment.py -''' - -import re, json, requests, base64 -from fabric import api -from fabric.api import cd, run, put, sudo -from os import environ as env -from time import sleep -from datetime import datetime - -# Populate environment variables -CONFIG = json.loads(env['CONFIG_JSON']) -for key in CONFIG: - env[key] = CONFIG[key] - -# Load DOCKER_CI_KEY -env['DOCKER_CI_KEY'] = open(env['DOCKER_CI_KEY_PATH']).read() - -DROPLET_NAME = env.get('DROPLET_NAME','report') -TIMEOUT = 120 # Seconds before timeout droplet creation -IMAGE_ID = 1004145 # Docker on Ubuntu 13.04 -REGION_ID = 4 # New York 2 -SIZE_ID = 66 # memory 512MB -DO_IMAGE_USER = 'root' # Image user on Digital Ocean -API_URL = 'https://api.digitalocean.com/' - - -class digital_ocean(): - - def __init__(self, key, client): - '''Set default API parameters''' - self.key = key - self.client = client - self.api_url = API_URL - - def api(self, cmd_path, api_arg={}): - '''Make api call''' - api_arg.update({'api_key':self.key, 'client_id':self.client}) - resp = requests.get(self.api_url + cmd_path, params=api_arg).text - resp = json.loads(resp) - if resp['status'] != 'OK': - raise Exception(resp['error_message']) - return resp - - def droplet_data(self, name): - '''Get droplet data''' - data = self.api('droplets') - data = [droplet for droplet in data['droplets'] - if droplet['name'] == name] - return data[0] if data else {} - -def json_fmt(data): - '''Format json output''' - return json.dumps(data, sort_keys = True, indent = 2) - - -do = digital_ocean(env['DO_API_KEY'], env['DO_CLIENT_ID']) - -# Get DROPLET_NAME data -data = do.droplet_data(DROPLET_NAME) - -# Stop processing if DROPLET_NAME exists on Digital Ocean -if data: - print ('Droplet: {} already deployed. Not further processing.' - .format(DROPLET_NAME)) - exit(1) - -# Create droplet -do.api('droplets/new', {'name':DROPLET_NAME, 'region_id':REGION_ID, - 'image_id':IMAGE_ID, 'size_id':SIZE_ID, - 'ssh_key_ids':[env['DOCKER_KEY_ID']]}) - -# Wait for droplet to be created. -start_time = datetime.now() -while (data.get('status','') != 'active' and ( - datetime.now()-start_time).seconds < TIMEOUT): - data = do.droplet_data(DROPLET_NAME) - print data['status'] - sleep(3) - -# Wait for the machine to boot -sleep(15) - -# Get droplet IP -ip = str(data['ip_address']) -print 'droplet: {} ip: {}'.format(DROPLET_NAME, ip) - -api.env.host_string = ip -api.env.user = DO_IMAGE_USER -api.env.key_filename = env['DOCKER_CI_KEY_PATH'] - -# Correct timezone -sudo('echo "America/Los_Angeles" >/etc/timezone') -sudo('dpkg-reconfigure --frontend noninteractive tzdata') - -# Load JSON_CONFIG environment for Dockerfile -CONFIG_JSON= base64.b64encode( - '{{"DOCKER_CI_PUB": "{DOCKER_CI_PUB}",' - ' "DOCKER_CI_KEY": "{DOCKER_CI_KEY}",' - ' "DOCKER_CI_ADDRESS": "{DOCKER_CI_ADDRESS}",' - ' "SMTP_USER": "{SMTP_USER}",' - ' "SMTP_PWD": "{SMTP_PWD}",' - ' "EMAIL_SENDER": "{EMAIL_SENDER}",' - ' "EMAIL_RCP": "{EMAIL_RCP}"}}'.format(**env)) - -run('mkdir -p /data/report') -put('./', '/data/report') -with cd('/data/report'): - run('chmod 700 report.py') - run('echo "{}" > credentials.json'.format(CONFIG_JSON)) - run('docker build -t report .') - run('rm credentials.json') - run("echo -e '30 09 * * * /usr/bin/docker run report\n' |" - " /usr/bin/crontab -") diff --git a/hack/infrastructure/docker-ci/report/report.py b/hack/infrastructure/docker-ci/report/report.py deleted file mode 100755 index 7018cabc27..0000000000 --- a/hack/infrastructure/docker-ci/report/report.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/python - -'''CONFIG_JSON is a json encoded string base64 environment variable. It is used -to clone docker-ci database, generate docker-ci report and submit it by email. -CONFIG_JSON data comes from the file /report/credentials.json inserted in this -container by deployment.py: - -{ "DOCKER_CI_PUB": "$(cat docker-ci_ssh_public_key.pub)", - "DOCKER_CI_KEY": "$(cat docker-ci_ssh_private_key.key)", - "DOCKER_CI_ADDRESS": "user@docker-ci_fqdn_server", - "SMTP_USER": "SMTP_server_user", - "SMTP_PWD": "SMTP_server_password", - "EMAIL_SENDER": "Buildbot_mailing_sender", - "EMAIL_RCP": "Buildbot_mailing_receipient" } ''' - -import os, re, json, sqlite3, datetime, base64 -import smtplib -from datetime import timedelta -from subprocess import call -from os import environ as env - -TODAY = datetime.date.today() - -# Load credentials to the environment -env['CONFIG_JSON'] = base64.b64decode(open('/report/credentials.json').read()) - -# Remove SSH private key as it needs more processing -CONFIG = json.loads(re.sub(r'("DOCKER_CI_KEY".+?"(.+?)",)','', - env['CONFIG_JSON'], flags=re.DOTALL)) - -# Populate environment variables -for key in CONFIG: - env[key] = CONFIG[key] - -# Load SSH private key -env['DOCKER_CI_KEY'] = re.sub('^.+"DOCKER_CI_KEY".+?"(.+?)".+','\\1', - env['CONFIG_JSON'],flags=re.DOTALL) - -# Prevent rsync to validate host on first connection to docker-ci -os.makedirs('/root/.ssh') -open('/root/.ssh/id_rsa','w').write(env['DOCKER_CI_KEY']) -os.chmod('/root/.ssh/id_rsa',0600) -open('/root/.ssh/config','w').write('StrictHostKeyChecking no\n') - - -# Sync buildbot database from docker-ci -call('rsync {}:/data/buildbot/master/state.sqlite .'.format( - env['DOCKER_CI_ADDRESS']), shell=True) - -class SQL: - def __init__(self, database_name): - sql = sqlite3.connect(database_name) - # Use column names as keys for fetchall rows - sql.row_factory = sqlite3.Row - sql = sql.cursor() - self.sql = sql - - def query(self,query_statement): - return self.sql.execute(query_statement).fetchall() - -sql = SQL("state.sqlite") - - -class Report(): - - def __init__(self,period='',date=''): - self.data = [] - self.period = 'date' if not period else period - self.date = str(TODAY) if not date else date - self.compute() - - def compute(self): - '''Compute report''' - if self.period == 'week': - self.week_report(self.date) - else: - self.date_report(self.date) - - - def date_report(self,date): - '''Create a date test report''' - builds = [] - # Get a queryset with all builds from date - rows = sql.query('SELECT * FROM builds JOIN buildrequests' - ' WHERE builds.brid=buildrequests.id and' - ' date(start_time, "unixepoch", "localtime") = "{0}"' - ' GROUP BY number'.format(date)) - build_names = sorted(set([row['buildername'] for row in rows])) - # Create a report build line for a given build - for build_name in build_names: - tried = len([row['buildername'] - for row in rows if row['buildername'] == build_name]) - fail_tests = [row['buildername'] for row in rows if ( - row['buildername'] == build_name and row['results'] != 0)] - fail = len(fail_tests) - fail_details = '' - fail_pct = int(100.0*fail/tried) if tried != 0 else 100 - builds.append({'name': build_name, 'tried': tried, 'fail': fail, - 'fail_pct': fail_pct, 'fail_details':fail_details}) - if builds: - self.data.append({'date': date, 'builds': builds}) - - - def week_report(self,date): - '''Add the week's date test reports to report.data''' - date = datetime.datetime.strptime(date,'%Y-%m-%d').date() - last_monday = date - datetime.timedelta(days=date.weekday()) - week_dates = [last_monday + timedelta(days=x) for x in range(7,-1,-1)] - for date in week_dates: - self.date_report(str(date)) - - def render_text(self): - '''Return rendered report in text format''' - retval = '' - fail_tests = {} - for builds in self.data: - retval += 'Test date: {0}\n'.format(builds['date'],retval) - table = '' - for build in builds['builds']: - table += ('Build {name:15} Tried: {tried:4} ' - ' Failures: {fail:4} ({fail_pct}%)\n'.format(**build)) - if build['name'] in fail_tests: - fail_tests[build['name']] += build['fail_details'] - else: - fail_tests[build['name']] = build['fail_details'] - retval += '{0}\n'.format(table) - retval += '\n Builds failing' - for fail_name in fail_tests: - retval += '\n' + fail_name + '\n' - for (fail_id,fail_url,rn_tests,nr_errors,log_errors, - tracelog_errors) in fail_tests[fail_name]: - retval += fail_url + '\n' - retval += '\n\n' - return retval - - -# Send email -smtp_from = env['EMAIL_SENDER'] -subject = '[docker-ci] Daily report for {}'.format(str(TODAY)) -msg = "From: {}\r\nTo: {}\r\nSubject: {}\r\n\r\n".format( - smtp_from, env['EMAIL_RCP'], subject) -msg = msg + Report('week').render_text() -server = smtplib.SMTP_SSL('smtp.mailgun.org') -server.login(env['SMTP_USER'], env['SMTP_PWD']) -server.sendmail(smtp_from, env['EMAIL_RCP'], msg) diff --git a/hack/infrastructure/docker-ci/setup.sh b/hack/infrastructure/docker-ci/setup.sh deleted file mode 100755 index 65a00f6dd0..0000000000 --- a/hack/infrastructure/docker-ci/setup.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash - -# Set timezone -echo "GMT" >/etc/timezone -dpkg-reconfigure --frontend noninteractive tzdata - -# Set ssh superuser -mkdir -p /data/buildbot /var/run/sshd /run -useradd -m -d /home/sysadmin -s /bin/bash -G sudo,docker -p '*' sysadmin -sed -Ei 's/(\%sudo.*) ALL/\1 NOPASSWD:ALL/' /etc/sudoers -cd /home/sysadmin -mkdir .ssh -chmod 700 .ssh -cat > .ssh/authorized_keys << 'EOF' -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC7ALVhwQ68q1SjrKaAduOuOEAcWmb8kDZf5qA7T1fM8AP07EDC7nSKRJ8PXUBGTOQfxm89coJDuSJsTAZ+1PvglXhA0Mq6+knc6ZrZY+SuZlDIDAk4TOdVPoDZnmR1YW2McxHkhcGIOKeC8MMig5NeEjtgQwXzauUSPqeh8HMlLZRMooFYyyluIpn7NaCLzyWjwAQz2s3KyI7VE7hl+ncCrW86v+dciEdwqtzNoUMFb3iDpPxaiCl3rv+SB7co/5eUDTs1FZvUcYMXKQuf8R+2ZKzXOpwr0Zs8sKQXvXavCeWykwGgXLBjVkvrDcHuDD6UXCW63UKgmRECpLZaMBVIIRWLEEgTS5OSQTcxpMVe5zUW6sDvXHTcdPwWrcn1dE9F/0vLC0HJ4ADKelLX5zyTpmXGbuZuntIf1JO67D/K/P++uV1rmVIH+zgtOf23w5rX2zKb4BSTqP0sv61pmWV7MEVoEz6yXswcTjS92tb775v7XLU9vKAkt042ORFdE4/++hejhL/Lj52IRgjt1CJZHZsR9JywJZrz3kYuf8eU2J2FYh0Cpz5gmf0f+12Rt4HztnZxGPP4KuMa66e4+hpx1jynjMZ7D5QUnNYEmuvJByopn8HSluuY/kS5MMyZCZtJLEPGX4+yECX0Di/S0vCRl2NyqfCBqS+yXXT5SA1nFw== docker-test@docker.io -EOF -chmod 600 .ssh/authorized_keys -chown -R sysadmin .ssh - -# Fix docker group id for use of host dockerd by sysadmin -sed -Ei 's/(docker:x:)[^:]+/\1999/' /etc/group - -# Create buildbot configuration -cd /data/buildbot; buildbot create-master master -cp -a /data/buildbot/master/master.cfg.sample \ - /data/buildbot/master/master.cfg -cd /data/buildbot; \ - buildslave create-slave slave localhost:9989 buildworker pass -cp /docker-ci/buildbot/master.cfg /data/buildbot/master - -# Patch github webstatus to capture pull requests -cp /docker-ci/buildbot/github.py /usr/local/lib/python2.7/dist-packages/buildbot/status/web/hooks -chown -R sysadmin.sysadmin /data - -# Create nginx configuration -rm /etc/nginx/sites-enabled/default -cp /docker-ci/nginx/nginx.conf /etc/nginx/conf.d/buildbot.conf -/bin/echo -e '\ndaemon off;\n' >> /etc/nginx/nginx.conf - -# Set supervisord buildbot, nginx and sshd processes -/bin/echo -e "\ -[program:buildmaster]\n\ -command=twistd --nodaemon --no_save -y buildbot.tac\n\ -directory=/data/buildbot/master\n\ -user=sysadmin\n\n\ -[program:buildworker]\n\ -command=twistd --nodaemon --no_save -y buildbot.tac\n\ -directory=/data/buildbot/slave\n\ -user=sysadmin\n" > \ - /etc/supervisor/conf.d/buildbot.conf -/bin/echo -e "[program:nginx]\ncommand=/usr/sbin/nginx\n" > \ - /etc/supervisor/conf.d/nginx.conf -/bin/echo -e "[program:sshd]\ncommand=/usr/sbin/sshd -D\n" > \ - /etc/supervisor/conf.d/sshd.conf diff --git a/hack/infrastructure/docker-ci/testbuilder/Dockerfile b/hack/infrastructure/docker-ci/testbuilder/Dockerfile deleted file mode 100644 index 8fa9b4c797..0000000000 --- a/hack/infrastructure/docker-ci/testbuilder/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -# TO_BUILD: docker build --no-cache -t docker-ci/testbuilder . -# TO_RUN: docker run --rm -u sysadmin \ -# -v /run:/var/socket docker-ci/testbuilder docker-registry -# - -FROM docker-ci/docker-ci -ENV HOME /home/sysadmin - -RUN mkdir /testbuilder -ADD . /testbuilder - -ENTRYPOINT ["/testbuilder/testbuilder.sh"] diff --git a/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh b/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh deleted file mode 100755 index a73704c50b..0000000000 --- a/hack/infrastructure/docker-ci/testbuilder/docker-registry.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -set -x -set -e -PROJECT_PATH=$1 - -# Build the docker project -cd /data/$PROJECT_PATH -sg docker -c "docker build -q -t registry ." -cd test; sg docker -c "docker build -q -t docker-registry-test ." - -# Run the tests -sg docker -c "docker run --rm -v /home/docker-ci/coverage/docker-registry:/data docker-registry-test" diff --git a/hack/infrastructure/docker-ci/testbuilder/docker.sh b/hack/infrastructure/docker-ci/testbuilder/docker.sh deleted file mode 100755 index c8f3c18eb9..0000000000 --- a/hack/infrastructure/docker-ci/testbuilder/docker.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -set -x -set -e -PROJECT_PATH=$1 - -# Build the docker project -cd /data/$PROJECT_PATH -sg docker -c "docker build -q -t docker ." - -if [ "$DOCKER_RELEASE" == "1" ]; then - # Do nightly release - echo sg docker -c "docker run --rm --privileged -v /run:/var/socket -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY= -e AWS_SECRET_KEY= -e GPG_PASSPHRASE= docker hack/release.sh" - set +x - sg docker -c "docker run --rm --privileged -v /run:/var/socket -e AWS_S3_BUCKET=$AWS_S3_BUCKET -e AWS_ACCESS_KEY=$AWS_ACCESS_KEY -e AWS_SECRET_KEY=$AWS_SECRET_KEY -e GPG_PASSPHRASE=$GPG_PASSPHRASE docker hack/release.sh" -else - # Run the tests - sg docker -c "docker run --rm --privileged -v /home/docker-ci/coverage/docker:/data docker ./hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh" -fi diff --git a/hack/infrastructure/docker-ci/testbuilder/testbuilder.sh b/hack/infrastructure/docker-ci/testbuilder/testbuilder.sh deleted file mode 100755 index 70701343c2..0000000000 --- a/hack/infrastructure/docker-ci/testbuilder/testbuilder.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# Download, build and run a docker project tests -# Environment variables: DEPLOYMENT - -cat $0 -set -e -set -x - -PROJECT=$1 -COMMIT=${2-HEAD} -REPO=${3-https://github.com/dotcloud/$PROJECT} -BRANCH=${4-master} -REPO_PROJ="https://github.com/docker-test/$PROJECT" -if [ "$DEPLOYMENT" == "production" ]; then - REPO_PROJ="https://github.com/dotcloud/$PROJECT" -fi -set +x - -# Generate a random string of $1 characters -function random { - cat /dev/urandom | tr -cd 'a-f0-9' | head -c $1 -} - -PROJECT_PATH="$PROJECT-tmp-$(random 12)" - -# Set docker-test git user -set -x -git config --global user.email "docker-test@docker.io" -git config --global user.name "docker-test" - -# Fetch project -git clone -q $REPO_PROJ -b master /data/$PROJECT_PATH -cd /data/$PROJECT_PATH -echo "Git commit: $(git rev-parse HEAD)" -git fetch -q $REPO $BRANCH -git merge --no-edit $COMMIT - -# Build the project dockertest -/testbuilder/$PROJECT.sh $PROJECT_PATH -rm -rf /data/$PROJECT_PATH diff --git a/hack/infrastructure/docker-ci/tool/backup.py b/hack/infrastructure/docker-ci/tool/backup.py deleted file mode 100755 index 2db633e526..0000000000 --- a/hack/infrastructure/docker-ci/tool/backup.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python - -import os,sys,json -from datetime import datetime -from filecmp import cmp -from subprocess import check_call -from boto.s3.key import Key -from boto.s3.connection import S3Connection - -def ENV(x): - '''Promote an environment variable for global use returning its value''' - retval = os.environ.get(x, '') - globals()[x] = retval - return retval - -ROOT_PATH = '/data/backup/docker-ci' -TODAY = str(datetime.today())[:10] -BACKUP_FILE = '{}/docker-ci_{}.tgz'.format(ROOT_PATH, TODAY) -BACKUP_LINK = '{}/docker-ci.tgz'.format(ROOT_PATH) -ENV('BACKUP_BUCKET') -ENV('BACKUP_AWS_ID') -ENV('BACKUP_AWS_SECRET') - -'''Create full master buildbot backup, avoiding duplicates''' -# Ensure backup path exist -if not os.path.exists(ROOT_PATH): - os.makedirs(ROOT_PATH) -# Make actual backups -check_call('/bin/tar czf {} -C /data --exclude=backup --exclude=buildbot/slave' - ' . 1>/dev/null 2>&1'.format(BACKUP_FILE),shell=True) -# remove previous dump if it is the same as the latest -if (os.path.exists(BACKUP_LINK) and cmp(BACKUP_FILE, BACKUP_LINK) and - os.path._resolve_link(BACKUP_LINK) != BACKUP_FILE): - os.unlink(os.path._resolve_link(BACKUP_LINK)) -# Recreate backup link pointing to latest backup -try: - os.unlink(BACKUP_LINK) -except: - pass -os.symlink(BACKUP_FILE, BACKUP_LINK) - -# Make backup on S3 -bucket = S3Connection(BACKUP_AWS_ID,BACKUP_AWS_SECRET).get_bucket(BACKUP_BUCKET) -k = Key(bucket) -k.key = BACKUP_FILE -k.set_contents_from_filename(BACKUP_FILE) -bucket.copy_key(os.path.basename(BACKUP_LINK),BACKUP_BUCKET,BACKUP_FILE[1:]) From 68dd722e3c54995e609b2524bad501ab1d4d15d6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 24 Mar 2014 14:15:04 +0000 Subject: [PATCH 23/50] Promote btrfs Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/graphdriver/driver.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/runtime/graphdriver/driver.go b/runtime/graphdriver/driver.go index 89fd03a624..9ea7d1c94c 100644 --- a/runtime/graphdriver/driver.go +++ b/runtime/graphdriver/driver.go @@ -39,10 +39,9 @@ var ( // Slice of drivers that should be used in an order priority = []string{ "aufs", + "btrfs", "devicemapper", "vfs", - // experimental, has to be enabled manually for now - "btrfs", } ) From c7540b3e94d7712b6b91ba80de0155f20156f3f3 Mon Sep 17 00:00:00 2001 From: LK4D4 Date: Mon, 24 Mar 2014 22:31:05 +0400 Subject: [PATCH 24/50] Workaround for hanging events. Fixes #4804 Docker-DCO-1.1-Signed-off-by: LK4D4 (github: LK4D4) --- server/server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/server.go b/server/server.go index 840a70357d..2cb3328d55 100644 --- a/server/server.go +++ b/server/server.go @@ -222,6 +222,10 @@ func (srv *Server) Events(job *engine.Job) engine.Status { listener := make(chan utils.JSONMessage) srv.Lock() + if old, ok := srv.listeners[from]; ok { + delete(srv.listeners, from) + close(old) + } srv.listeners[from] = listener srv.Unlock() job.Stdout.Write(nil) // flush From bb034c6b42c347ffdfae834960bf2386429e1980 Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 24 Mar 2014 21:38:57 -0400 Subject: [PATCH 25/50] Add Chef usage documentation Docker-DCO-1.1-Signed-off-by: Brian Flad (github: bflad) --- docs/sources/use/chef.rst | 95 ++++++++++++++++++++++++++++++++++++++ docs/sources/use/index.rst | 1 + 2 files changed, 96 insertions(+) create mode 100644 docs/sources/use/chef.rst diff --git a/docs/sources/use/chef.rst b/docs/sources/use/chef.rst new file mode 100644 index 0000000000..54755ad3b3 --- /dev/null +++ b/docs/sources/use/chef.rst @@ -0,0 +1,95 @@ +:title: Chef Usage +:description: Installating and using Docker via Chef +:keywords: chef, installation, usage, docker, documentation + +.. _install_using_chef: + +Using Chef +============= + +.. note:: + + Please note this is a community contributed installation path. The + only 'official' installation is using the :ref:`ubuntu_linux` + installation path. This version may sometimes be out of date. + +Requirements +------------ + +To use this guide you'll need a working installation of +`Chef `_. This cookbook supports a variety of +operating systems. + +Installation +------------ + +The cookbook is available on the `Chef Community Site +`_ and can be installed +using your favorite cookbook dependency manager. + +The source can be found on `GitHub +`_. + +Usage +----- + +The cookbook provides recipes for installing Docker, configuring init +for Docker, and resources for managing images and containers. +It supports almost all Docker functionality. + +Installation +~~~~~~~~~~~~ + +.. code-block:: ruby + + include_recipe 'docker' + +Images +~~~~~~ + +The next step is to pull a Docker image. For this, we have a resource: + +.. code-block:: ruby + + docker_image 'samalba/docker-registry' + +This is equivalent to running: + +.. code-block:: bash + + docker pull samalba/docker-registry + +There are attributes available to control how long the cookbook +will allow for downloading (5 minute default). + +To remove images you no longer need: + +.. code-block:: ruby + + docker_image 'samalba/docker-registry' do + action :remove + end + +Containers +~~~~~~~~~~ + +Now you have an image where you can run commands within a container +managed by Docker. + +.. code-block:: ruby + + docker_container 'samalba/docker-registry' do + detach true + port '5000:5000' + env 'SETTINGS_FLAVOR=local' + volume '/mnt/docker:/docker-storage' + end + +This is equivalent to running the following command, but under upstart: + +.. code-block:: bash + + docker run --detach=true --publish='5000:5000' --env='SETTINGS_FLAVOR=local' --volume='/mnt/docker:/docker-storage' samalba/docker-registry + +The resources will accept a single string or an array of values +for any docker flags that allow multiple values. diff --git a/docs/sources/use/index.rst b/docs/sources/use/index.rst index c1b7691cca..dcf6289b41 100644 --- a/docs/sources/use/index.rst +++ b/docs/sources/use/index.rst @@ -20,4 +20,5 @@ Contents: working_with_volumes working_with_links_names ambassador_pattern_linking + chef puppet From 84e1fdf35d9d6493d389a8e8be3ab41190004b30 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 24 Mar 2014 21:43:26 -0400 Subject: [PATCH 26/50] docker load: add --input flag for those that do not care to read from redirected stdin Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- api/client.go | 14 +++++++++++++- docs/sources/reference/commandline/cli.rst | 15 ++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/api/client.go b/api/client.go index f1a3f64f2a..f9e7445fd9 100644 --- a/api/client.go +++ b/api/client.go @@ -2075,6 +2075,8 @@ func (cli *DockerCli) CmdSave(args ...string) error { func (cli *DockerCli) CmdLoad(args ...string) error { cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN") + infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN") + if err := cmd.Parse(args); err != nil { return err } @@ -2084,7 +2086,17 @@ func (cli *DockerCli) CmdLoad(args ...string) error { return nil } - if err := cli.stream("POST", "/images/load", cli.in, cli.out, nil); err != nil { + var ( + input io.Reader = cli.in + err error + ) + if *infile != "" { + input, err = os.Open(*infile) + if err != nil { + return err + } + } + if err := cli.stream("POST", "/images/load", input, cli.out, nil); err != nil { return err } return nil diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index f4a5e0882f..2626280dd0 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -881,10 +881,19 @@ Known Issues (kill) :: - Usage: docker load < repository.tar + Usage: docker load - Loads a tarred repository from the standard input stream. - Restores both images and tags. + Load an image from a tar archive on STDIN + + -i, --input"": Read from a tar archive file, instead of STDIN + +Loads a tarred repository from the standard input stream. +Restores both images and tags. + +.. code-block:: bash + + $ sudo docker load < busybox.tar + $ sudo docker load --input busybox.tar .. _cli_login: From c84ff187c62060f20b7039a5005a44012898df7b Mon Sep 17 00:00:00 2001 From: Brian Flad Date: Mon, 24 Mar 2014 22:29:54 -0400 Subject: [PATCH 27/50] Fix typo in Using Chef documentation description Docker-DCO-1.1-Signed-off-by: Brian Flad (github: bflad) --- docs/sources/use/chef.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/chef.rst b/docs/sources/use/chef.rst index 54755ad3b3..919eba7a8f 100644 --- a/docs/sources/use/chef.rst +++ b/docs/sources/use/chef.rst @@ -1,5 +1,5 @@ :title: Chef Usage -:description: Installating and using Docker via Chef +:description: Installation and using Docker via Chef :keywords: chef, installation, usage, docker, documentation .. _install_using_chef: From c6c7c03cddc852c42b9f047fbd5c2fb6cecf39eb Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 24 Mar 2014 23:31:59 -0400 Subject: [PATCH 28/50] docker load: doc clarification Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 2626280dd0..3c7bb47113 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -885,9 +885,9 @@ Known Issues (kill) Load an image from a tar archive on STDIN - -i, --input"": Read from a tar archive file, instead of STDIN + -i, --input="": Read from a tar archive file, instead of STDIN -Loads a tarred repository from the standard input stream. +Loads a tarred repository from a file or the standard input stream. Restores both images and tags. .. code-block:: bash From 69087f2d2397b18d6dd2d7b994e24ea9814e4bcd Mon Sep 17 00:00:00 2001 From: noducks Date: Sat, 22 Mar 2014 14:12:15 +0000 Subject: [PATCH 29/50] Reminder for OSX users not to use SUDO Useful for those who haven't made it to the examples page yet. https://github.com/chadoh/docker/commit/dad4a998dc716e506c874ce0e792989b9df28748 Docker-DCO-1.1-Signed-off-by: No Ducks (github: noducks) --- docs/sources/use/basics.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index 447366f55a..4164e706f7 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -40,6 +40,8 @@ Repository to a local image cache. short form of the image ID. These short image IDs are the first 12 characters of the full image ID - which can be found using ``docker inspect`` or ``docker images --no-trunc=true`` + + **If you're using OS X** then you shouldn't use ``sudo`` Running an interactive shell ---------------------------- From 293157b8b38dd5ea5fa49d90501cc3c86717da40 Mon Sep 17 00:00:00 2001 From: viirya Date: Sun, 23 Mar 2014 23:55:20 +0800 Subject: [PATCH 30/50] check if working dir is a directory and raise corresponding errors when making dir. Docker-DCO-1.1-Signed-off-by: Liang-Chi Hsieh (github: viirya) --- integration/commands_test.go | 19 +++++++++++++++++ runtime/container.go | 41 ++++++++++++++++++++++++------------ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/integration/commands_test.go b/integration/commands_test.go index f1c5870755..7de7a227ea 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -252,6 +252,25 @@ func TestRunWorkdirExists(t *testing.T) { } } +// TestRunWorkdirExistsAndIsFile checks that if 'docker run -w' with existing file can be detected +func TestRunWorkdirExistsAndIsFile(t *testing.T) { + + cli := api.NewDockerCli(nil, nil, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) + defer cleanup(globalEngine, t) + + c := make(chan struct{}) + go func() { + defer close(c) + if err := cli.CmdRun("-w", "/bin/cat", unitTestImageID, "pwd"); err == nil { + t.Fatal("should have failed to run when using /bin/cat as working dir.") + } + }() + + setTimeout(t, "CmdRun timed out", 5*time.Second, func() { + <-c + }) +} + func TestRunExit(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() diff --git a/runtime/container.go b/runtime/container.go index 6194a19c8c..0e5e255bfc 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -535,8 +535,18 @@ func (container *Container) Start() (err error) { if container.Config.WorkingDir != "" { container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) - if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil { - return nil + + pthInfo, err := os.Stat(path.Join(container.basefs, container.Config.WorkingDir)) + if err != nil { + if !os.IsNotExist(err) { + return err + } + if err := os.MkdirAll(path.Join(container.basefs, container.Config.WorkingDir), 0755); err != nil { + return err + } + } + if pthInfo != nil && !pthInfo.IsDir() { + return fmt.Errorf("Cannot mkdir: %s is not a directory", container.Config.WorkingDir) } } @@ -950,10 +960,11 @@ func (container *Container) ExportRw() (archive.Archive, error) { return nil, err } return utils.NewReadCloserWrapper(archive, func() error { - err := archive.Close() - container.Unmount() - return err - }), nil + err := archive.Close() + container.Unmount() + return err + }), + nil } func (container *Container) Export() (archive.Archive, error) { @@ -967,10 +978,11 @@ func (container *Container) Export() (archive.Archive, error) { return nil, err } return utils.NewReadCloserWrapper(archive, func() error { - err := archive.Close() - container.Unmount() - return err - }), nil + err := archive.Close() + container.Unmount() + return err + }), + nil } func (container *Container) WaitTimeout(timeout time.Duration) error { @@ -1119,10 +1131,11 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { return nil, err } return utils.NewReadCloserWrapper(archive, func() error { - err := archive.Close() - container.Unmount() - return err - }), nil + err := archive.Close() + container.Unmount() + return err + }), + nil } // Returns true if the container exposes a certain port From 8e434c314ef74618001cc95466c2b567fa0283e2 Mon Sep 17 00:00:00 2001 From: noducks Date: Tue, 25 Mar 2014 10:26:45 +0000 Subject: [PATCH 31/50] Force flag to prevent file already exists error. Docker-DCO-1.1-Signed-off-by: No Ducks (github: noducks) --- docs/sources/examples/mongodb.rst | 2 +- docs/sources/examples/running_riak_service.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/examples/mongodb.rst b/docs/sources/examples/mongodb.rst index 3e37d74c30..930ab2ea9d 100644 --- a/docs/sources/examples/mongodb.rst +++ b/docs/sources/examples/mongodb.rst @@ -47,7 +47,7 @@ divert ``/sbin/initctl`` to ``/bin/true`` so it thinks everything is working. # Hack for initctl not being available in Ubuntu RUN dpkg-divert --local --rename --add /sbin/initctl - RUN ln -s /bin/true /sbin/initctl + RUN ln -sf /bin/true /sbin/initctl Afterwards we'll be able to update our apt repositories and install MongoDB diff --git a/docs/sources/examples/running_riak_service.rst b/docs/sources/examples/running_riak_service.rst index ae08a4b7f0..55e5e405c9 100644 --- a/docs/sources/examples/running_riak_service.rst +++ b/docs/sources/examples/running_riak_service.rst @@ -88,7 +88,7 @@ Almost there. Next, we add a hack to get us by the lack of ``initctl``: # Hack for initctl # See: https://github.com/dotcloud/docker/issues/1024 RUN dpkg-divert --local --rename --add /sbin/initctl - RUN ln -s /bin/true /sbin/initctl + RUN ln -sf /bin/true /sbin/initctl Then, we expose the Riak Protocol Buffers and HTTP interfaces, along with SSH: From 2517370088ad11765f99d75c16b58e93fe18f85a Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 25 Mar 2014 08:30:59 -0400 Subject: [PATCH 32/50] docker load: added example of a multiple tag image Docker-DCO-1.1-Signed-off-by: Vincent Batts (github: vbatts) --- docs/sources/reference/commandline/cli.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 3c7bb47113..687ce2f305 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -892,8 +892,21 @@ Restores both images and tags. .. code-block:: bash + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE $ sudo docker load < busybox.tar - $ sudo docker load --input busybox.tar + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + busybox latest 769b9341d937 7 weeks ago 2.489 MB + $ sudo docker load --input fedora.tar + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + busybox latest 769b9341d937 7 weeks ago 2.489 MB + fedora rawhide 0d20aec6529d 7 weeks ago 387 MB + fedora 20 58394af37342 7 weeks ago 385.5 MB + fedora heisenbug 58394af37342 7 weeks ago 385.5 MB + fedora latest 58394af37342 7 weeks ago 385.5 MB + .. _cli_login: From 576278102e0fa9166711f8cf23ec972fcccc085e Mon Sep 17 00:00:00 2001 From: Paul Annesley Date: Mon, 24 Mar 2014 21:21:37 -0700 Subject: [PATCH 33/50] install.sh (get.docker.io) aufs comment updated. devicemapper has landed, but the TODO hasn't been actioned presumably because aufs is still preferred over devicemapper when available[1]. Comment updated accordingly. Citation [1]: https://github.com/crosbymichael/docker/blob/267ca39921c35826ccbdb84fbbc0690bfef385d7/runtime/graphdriver/driver.go#L40-L46 Docker-DCO-1.1-Signed-off-by: Paul Annesley (github: pda) --- hack/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index 1fa8a47480..205b57ecc7 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -85,7 +85,7 @@ case "$lsb_dist" in fi } - # TODO remove this section once device-mapper lands + # aufs is preferred over devicemapper; try to ensure the driver is available. if ! grep -q aufs /proc/filesystems && ! $sh_c 'modprobe aufs'; then kern_extras="linux-image-extra-$(uname -r)" From d36176652ef8f0220a1cff5dc00933400c69a562 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 24 Mar 2014 19:43:40 +0200 Subject: [PATCH 34/50] Bump to version v0.9.1 Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) Conflicts: VERSION --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ VERSION | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40ba3d32ac..c8ea94361b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # Changelog +## 0.9.1 (2014-03-24) + +#### Builder +- Fix printing multiple messages on a single line. Fixes broken output during builds. + +#### Documentation +- Fix external link on security of containers. + +#### Contrib +- Fix init script cgroup mounting workarounds to be more similar to cgroupfs-mount and thus work properly. +- Add variable for DOCKER_LOGFILE to sysvinit and use append instead of overwrite in opening the logfile. + +#### Hack +- Generate md5 and sha256 hashes when building, and upload them via hack/release.sh. + +#### Remote API +- Fix content-type detection in `docker cp`. + +#### Runtime +- Use BSD raw mode on Darwin. Fixes nano, tmux and others. +- Only unshare the mount namespace for execin. +- Retry to retrieve the layer metadata up to 5 times for `docker pull`. +- Merge existing config when committing. +- Fix panic in monitor. +- Disable daemon startup timeout. +- Fix issue #4681: add loopback interface when networking is disabled. +- Add failing test case for issue #4681. +- Send SIGTERM to child, instead of SIGKILL. +- Show the driver and the kernel version in `docker info` even when not in debug mode. +- Always symlink /dev/ptmx for libcontainer. This fixes console related problems. +- Fix issue caused by the absence of /etc/apparmor.d. +- Don't leave empty cidFile behind when failing to create the container. +- Improve deprecation message. +- Fix attach exit on darwin. +- devicemapper: improve handling of devicemapper devices (add per device lock, increase sleep time, unlock while sleeping). +- devicemapper: succeed immediately when removing non-existing devices. +- devicemapper: increase timeout in waitClose to 10 seconds. +- Remove goroutine leak on error. +- Update parseLxcInfo to comply with new lxc1.0 format. + ## 0.9.0 (2014-03-10) #### Builder diff --git a/VERSION b/VERSION index c70836ca5c..f374f6662e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.0-dev +0.9.1 From b2721e05ce4d6026855718ad9b01eb7dec797cd2 Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 26 Mar 2014 00:18:45 +0200 Subject: [PATCH 35/50] Change version to 0.9.1-dev Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index f374f6662e..dc9bff91aa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.1 +0.9.1-dev From baba9cde9542b480162d11bd30ca3a522fa6b4d0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 26 Mar 2014 11:51:27 +0000 Subject: [PATCH 36/50] Return error when existing bridge does not match ip Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime/networkdriver/bridge/driver.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/runtime/networkdriver/bridge/driver.go b/runtime/networkdriver/bridge/driver.go index 41588b1c27..61e82dd481 100644 --- a/runtime/networkdriver/bridge/driver.go +++ b/runtime/networkdriver/bridge/driver.go @@ -93,6 +93,12 @@ func InitDriver(job *engine.Job) engine.Status { network = addr.(*net.IPNet) } else { network = addr.(*net.IPNet) + // validate that the bridge ip matches the ip specified by BridgeIP + if bridgeIP != "" { + if !network.IP.Equal(net.ParseIP(bridgeIP)) { + return job.Errorf("bridge ip (%s) does not match existing bridge configuration %s", network.IP, bridgeIP) + } + } } // Configure iptables for link support From e7f3234c1e4926c966f4c9e4cf08d9aae60d21bb Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Wed, 26 Mar 2014 09:05:21 -0700 Subject: [PATCH 37/50] Fix fish completion when having alias on awk or grep Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- contrib/completion/fish/docker.fish | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index 9c4339fe2b..ddec61cffa 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -26,20 +26,20 @@ end function __fish_print_docker_containers --description 'Print a list of docker containers' -a select switch $select case running - docker ps -a --no-trunc | awk 'NR>1' | awk 'BEGIN {FS=" +"}; $5 ~ "^Up" {print $1 "\n" $(NF-1)}' | tr ',' '\n' + docker ps -a --no-trunc | command awk 'NR>1' | command awk 'BEGIN {FS=" +"}; $5 ~ "^Up" {print $1 "\n" $(NF-1)}' | tr ',' '\n' case stopped - docker ps -a --no-trunc | awk 'NR>1' | awk 'BEGIN {FS=" +"}; $5 ~ "^Exit" {print $1 "\n" $(NF-1)}' | tr ',' '\n' + docker ps -a --no-trunc | command awk 'NR>1' | command awk 'BEGIN {FS=" +"}; $5 ~ "^Exit" {print $1 "\n" $(NF-1)}' | tr ',' '\n' case all - docker ps -a --no-trunc | awk 'NR>1' | awk 'BEGIN {FS=" +"}; {print $1 "\n" $(NF-1)}' | tr ',' '\n' + docker ps -a --no-trunc | command awk 'NR>1' | command awk 'BEGIN {FS=" +"}; {print $1 "\n" $(NF-1)}' | tr ',' '\n' end end function __fish_print_docker_images --description 'Print a list of docker images' - docker images | awk 'NR>1' | grep -v '' | awk '{print $1":"$2}' + docker images | command awk 'NR>1' | command grep -v '' | command awk '{print $1":"$2}' end function __fish_print_docker_repositories --description 'Print a list of docker repositories' - docker images | awk 'NR>1' | grep -v '' | awk '{print $1}' | sort | uniq + docker images | command awk 'NR>1' | command grep -v '' | command awk '{print $1}' | sort | uniq end # common options From 4c4356692580afb3971094e322aea64abe0e2500 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Tue, 18 Mar 2014 16:49:16 -0400 Subject: [PATCH 38/50] This patch adds SELinux labeling support. docker will run the process(es) within the container with an SELinux label and will label all of the content within the container with mount label. Any temporary file systems created within the container need to be mounted with the same mount label. The user can override the process label by specifying -Z With a string of space separated options. -Z "user=unconfined_u role=unconfined_r type=unconfined_t level=s0" Would cause the process label to run with unconfined_u:unconfined_r:unconfined_t:s0" By default the processes will run execute within the container as svirt_lxc_net_t. All of the content in the container as svirt_sandbox_file_t. The process mcs level is based of the PID of the docker process that is creating the container. If you run the container in --priv mode, the labeling will be disabled. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- Dockerfile | 2 +- graph/graph.go | 2 +- hack/PACKAGERS.md | 7 + pkg/label/label.go | 23 ++ pkg/label/label_selinux.go | 69 ++++ pkg/libcontainer/nsinit/execin.go | 11 +- pkg/libcontainer/nsinit/init.go | 8 +- pkg/libcontainer/nsinit/mount.go | 22 +- pkg/selinux/selinux.go | 387 ++++++++++++++++++ pkg/selinux/selinux_test.go | 64 +++ runconfig/config.go | 11 + runconfig/parse.go | 20 + runtime/container.go | 1 + runtime/execdriver/driver.go | 5 + runtime/execdriver/lxc/lxc_template.go | 20 +- runtime/execdriver/native/default_template.go | 2 + runtime/graphdriver/aufs/aufs.go | 2 +- runtime/graphdriver/aufs/aufs_test.go | 52 +-- runtime/graphdriver/aufs/migrate.go | 6 +- runtime/graphdriver/btrfs/btrfs.go | 6 +- runtime/graphdriver/devmapper/deviceset.go | 9 +- runtime/graphdriver/devmapper/driver.go | 15 +- runtime/graphdriver/devmapper/driver_test.go | 20 +- runtime/graphdriver/driver.go | 2 +- runtime/graphdriver/vfs/driver.go | 2 +- runtime/runtime.go | 4 +- 26 files changed, 700 insertions(+), 72 deletions(-) create mode 100644 pkg/label/label.go create mode 100644 pkg/label/label_selinux.go create mode 100644 pkg/selinux/selinux.go create mode 100644 pkg/selinux/selinux_test.go diff --git a/Dockerfile b/Dockerfile index 42438e3946..2de5b34171 100644 --- a/Dockerfile +++ b/Dockerfile @@ -87,7 +87,7 @@ RUN git config --global user.email 'docker-dummy@example.com' VOLUME /var/lib/docker WORKDIR /go/src/github.com/dotcloud/docker -ENV DOCKER_BUILDTAGS apparmor +ENV DOCKER_BUILDTAGS apparmor selinux # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] diff --git a/graph/graph.go b/graph/graph.go index 4349cac129..33aca486c6 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -189,7 +189,7 @@ func (graph *Graph) Register(jsonData []byte, layerData archive.ArchiveReader, i } // Create root filesystem in the driver - if err := graph.driver.Create(img.ID, img.Parent); err != nil { + if err := graph.driver.Create(img.ID, img.Parent, ""); err != nil { return fmt.Errorf("Driver %s failed to create image rootfs %s: %s", graph.driver, img.ID, err) } // Mount the root filesystem so we can apply the diff/layer diff --git a/hack/PACKAGERS.md b/hack/PACKAGERS.md index 47e8413bf3..dc255c57ad 100644 --- a/hack/PACKAGERS.md +++ b/hack/PACKAGERS.md @@ -177,6 +177,13 @@ export DOCKER_BUILDTAGS='exclude_graphdriver_aufs' NOTE: if you need to set more than one build tag, space separate them. +If you're building a binary that may need to be used on platforms that include +SELinux, you will need to set `DOCKER_BUILDTAGS` as follows: + +```bash +export DOCKER_BUILDTAGS='selinux' +``` + ### Static Daemon If it is feasible within the constraints of your distribution, you should diff --git a/pkg/label/label.go b/pkg/label/label.go new file mode 100644 index 0000000000..ba1e9f48ea --- /dev/null +++ b/pkg/label/label.go @@ -0,0 +1,23 @@ +// +build !selinux !linux + +package label + +func GenLabels(options string) (string, string, error) { + return "", "", nil +} + +func FormatMountLabel(src string, MountLabel string) string { + return src +} + +func SetProcessLabel(processLabel string) error { + return nil +} + +func SetFileLabel(path string, fileLabel string) error { + return nil +} + +func GetPidCon(pid int) (string, error) { + return "", nil +} diff --git a/pkg/label/label_selinux.go b/pkg/label/label_selinux.go new file mode 100644 index 0000000000..300a8b6d14 --- /dev/null +++ b/pkg/label/label_selinux.go @@ -0,0 +1,69 @@ +// +build selinux,linux + +package label + +import ( + "fmt" + "github.com/dotcloud/docker/pkg/selinux" + "strings" +) + +func GenLabels(options string) (string, string, error) { + processLabel, mountLabel := selinux.GetLxcContexts() + var err error + if processLabel == "" { // SELinux is disabled + return "", "", err + } + s := strings.Fields(options) + l := len(s) + if l > 0 { + pcon := selinux.NewContext(processLabel) + for i := 0; i < l; i++ { + o := strings.Split(s[i], "=") + pcon[o[0]] = o[1] + } + processLabel = pcon.Get() + mountLabel, err = selinux.CopyLevel(processLabel, mountLabel) + } + return processLabel, mountLabel, err +} + +func FormatMountLabel(src string, MountLabel string) string { + var mountLabel string + if src != "" { + mountLabel = src + if MountLabel != "" { + mountLabel = fmt.Sprintf("%s,context=\"%s\"", mountLabel, MountLabel) + } + } else { + if MountLabel != "" { + mountLabel = fmt.Sprintf("context=\"%s\"", MountLabel) + } + } + return mountLabel +} + +func SetProcessLabel(processLabel string) error { + if selinux.SelinuxEnabled() { + return selinux.Setexeccon(processLabel) + } + return nil +} + +func GetProcessLabel() (string, error) { + if selinux.SelinuxEnabled() { + return selinux.Getexeccon() + } + return "", nil +} + +func SetFileLabel(path string, fileLabel string) error { + if selinux.SelinuxEnabled() && fileLabel != "" { + return selinux.Setfilecon(path, fileLabel) + } + return nil +} + +func GetPidCon(pid int) (string, error) { + return selinux.Getpidcon(pid) +} diff --git a/pkg/libcontainer/nsinit/execin.go b/pkg/libcontainer/nsinit/execin.go index f8b8931390..9017af06e9 100644 --- a/pkg/libcontainer/nsinit/execin.go +++ b/pkg/libcontainer/nsinit/execin.go @@ -4,6 +4,7 @@ package nsinit import ( "fmt" + "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/system" "os" @@ -32,7 +33,11 @@ func (ns *linuxNs) ExecIn(container *libcontainer.Container, nspid int, args []s closeFds() return -1, err } - + processLabel, err := label.GetPidCon(nspid) + if err != nil { + closeFds() + return -1, err + } // foreach namespace fd, use setns to join an existing container's namespaces for _, fd := range fds { if fd > 0 { @@ -80,6 +85,10 @@ dropAndExec: if err := finalizeNamespace(container); err != nil { return -1, err } + err = label.SetProcessLabel(processLabel) + if err != nil { + return -1, err + } if err := system.Execv(args[0], args[0:], container.Env); err != nil { return -1, err } diff --git a/pkg/libcontainer/nsinit/init.go b/pkg/libcontainer/nsinit/init.go index 117ae875ed..5aa5f9f5b5 100644 --- a/pkg/libcontainer/nsinit/init.go +++ b/pkg/libcontainer/nsinit/init.go @@ -4,6 +4,7 @@ package nsinit import ( "fmt" + "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/apparmor" "github.com/dotcloud/docker/pkg/libcontainer/capabilities" @@ -12,6 +13,7 @@ import ( "github.com/dotcloud/docker/pkg/system" "github.com/dotcloud/docker/pkg/user" "os" + "runtime" "syscall" ) @@ -57,7 +59,7 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol return fmt.Errorf("parent death signal %s", err) } ns.logger.Println("setup mount namespace") - if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot); err != nil { + if err := setupNewMountNamespace(rootfs, container.Mounts, console, container.ReadonlyFs, container.NoPivotRoot, container.Context["mount_label"]); err != nil { return fmt.Errorf("setup mount namespace %s", err) } if err := setupNetwork(container, context); err != nil { @@ -76,6 +78,10 @@ func (ns *linuxNs) Init(container *libcontainer.Container, uncleanRootfs, consol return err } } + runtime.LockOSThread() + if err := label.SetProcessLabel(container.Context["process_label"]); err != nil { + return fmt.Errorf("SetProcessLabel label %s", err) + } ns.logger.Printf("execing %s\n", args[0]) return system.Execv(args[0], args[0:], container.Env) } diff --git a/pkg/libcontainer/nsinit/mount.go b/pkg/libcontainer/nsinit/mount.go index 61a90125e0..796143c68e 100644 --- a/pkg/libcontainer/nsinit/mount.go +++ b/pkg/libcontainer/nsinit/mount.go @@ -4,6 +4,7 @@ package nsinit import ( "fmt" + "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/system" "io/ioutil" @@ -20,7 +21,7 @@ const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NOD // // There is no need to unmount the new mounts because as soon as the mount namespace // is no longer in use, the mounts will be removed automatically -func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool) error { +func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, console string, readonly, noPivotRoot bool, mountLabel string) error { flag := syscall.MS_PRIVATE if noPivotRoot { flag = syscall.MS_SLAVE @@ -36,7 +37,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons return fmt.Errorf("mounting %s as readonly %s", rootfs, err) } } - if err := mountSystem(rootfs); err != nil { + if err := mountSystem(rootfs, mountLabel); err != nil { return fmt.Errorf("mount system %s", err) } @@ -64,7 +65,7 @@ func setupNewMountNamespace(rootfs string, bindMounts []libcontainer.Mount, cons if err := setupDev(rootfs); err != nil { return err } - if err := setupPtmx(rootfs, console); err != nil { + if err := setupPtmx(rootfs, console, mountLabel); err != nil { return err } if err := system.Chdir(rootfs); err != nil { @@ -196,7 +197,7 @@ func setupDev(rootfs string) error { } // setupConsole ensures that the container has a proper /dev/console setup -func setupConsole(rootfs, console string) error { +func setupConsole(rootfs, console string, mountLabel string) error { oldMask := system.Umask(0000) defer system.Umask(oldMask) @@ -220,6 +221,9 @@ func setupConsole(rootfs, console string) error { if err := system.Mknod(dest, (st.Mode&^07777)|0600, int(st.Rdev)); err != nil { return fmt.Errorf("mknod %s %s", dest, err) } + if err := label.SetFileLabel(console, mountLabel); err != nil { + return fmt.Errorf("SetFileLabel Failed %s %s", dest, err) + } if err := system.Mount(console, dest, "bind", syscall.MS_BIND, ""); err != nil { return fmt.Errorf("bind %s to %s %s", console, dest, err) } @@ -228,7 +232,7 @@ func setupConsole(rootfs, console string) error { // mountSystem sets up linux specific system mounts like sys, proc, shm, and devpts // inside the mount namespace -func mountSystem(rootfs string) error { +func mountSystem(rootfs string, mountLabel string) error { for _, m := range []struct { source string path string @@ -238,8 +242,8 @@ func mountSystem(rootfs string) error { }{ {source: "proc", path: filepath.Join(rootfs, "proc"), device: "proc", flags: defaultMountFlags}, {source: "sysfs", path: filepath.Join(rootfs, "sys"), device: "sysfs", flags: defaultMountFlags}, - {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: "mode=1777,size=65536k"}, - {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: "newinstance,ptmxmode=0666,mode=620,gid=5"}, + {source: "shm", path: filepath.Join(rootfs, "dev", "shm"), device: "tmpfs", flags: defaultMountFlags, data: label.FormatMountLabel("mode=1755,size=65536k", mountLabel)}, + {source: "devpts", path: filepath.Join(rootfs, "dev", "pts"), device: "devpts", flags: syscall.MS_NOSUID | syscall.MS_NOEXEC, data: label.FormatMountLabel("newinstance,ptmxmode=0666,mode=620,gid=5", mountLabel)}, } { if err := os.MkdirAll(m.path, 0755); err != nil && !os.IsExist(err) { return fmt.Errorf("mkdirall %s %s", m.path, err) @@ -253,7 +257,7 @@ func mountSystem(rootfs string) error { // setupPtmx adds a symlink to pts/ptmx for /dev/ptmx and // finishes setting up /dev/console -func setupPtmx(rootfs, console string) error { +func setupPtmx(rootfs, console string, mountLabel string) error { ptmx := filepath.Join(rootfs, "dev/ptmx") if err := os.Remove(ptmx); err != nil && !os.IsNotExist(err) { return err @@ -262,7 +266,7 @@ func setupPtmx(rootfs, console string) error { return fmt.Errorf("symlink dev ptmx %s", err) } if console != "" { - if err := setupConsole(rootfs, console); err != nil { + if err := setupConsole(rootfs, console, mountLabel); err != nil { return err } } diff --git a/pkg/selinux/selinux.go b/pkg/selinux/selinux.go new file mode 100644 index 0000000000..5236d3fb87 --- /dev/null +++ b/pkg/selinux/selinux.go @@ -0,0 +1,387 @@ +package selinux + +import ( + "bufio" + "crypto/rand" + "encoding/binary" + "fmt" + "github.com/dotcloud/docker/pkg/mount" + "github.com/dotcloud/docker/pkg/system" + "io" + "os" + "regexp" + "strconv" + "strings" + "syscall" +) + +const ( + Enforcing = 1 + Permissive = 0 + Disabled = -1 + selinuxDir = "/etc/selinux/" + selinuxConfig = selinuxDir + "config" + selinuxTypeTag = "SELINUXTYPE" + selinuxTag = "SELINUX" + selinuxPath = "/sys/fs/selinux" + xattrNameSelinux = "security.selinux" + stRdOnly = 0x01 +) + +var ( + assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) + spaceRegex = regexp.MustCompile(`^([^=]+) (.*)$`) + mcsList = make(map[string]bool) + selinuxfs = "unknown" + selinuxEnabled = false + selinuxEnabledChecked = false +) + +type SELinuxContext map[string]string + +func GetSelinuxMountPoint() string { + if selinuxfs != "unknown" { + return selinuxfs + } + selinuxfs = "" + + mounts, err := mount.GetMounts() + if err != nil { + return selinuxfs + } + for _, mount := range mounts { + if mount.Fstype == "selinuxfs" { + selinuxfs = mount.Mountpoint + break + } + } + if selinuxfs != "" { + var buf syscall.Statfs_t + syscall.Statfs(selinuxfs, &buf) + if (buf.Flags & stRdOnly) == 1 { + selinuxfs = "" + } + } + return selinuxfs +} + +func SelinuxEnabled() bool { + if selinuxEnabledChecked { + return selinuxEnabled + } + selinuxEnabledChecked = true + if fs := GetSelinuxMountPoint(); fs != "" { + if con, _ := Getcon(); con != "kernel" { + selinuxEnabled = true + } + } + return selinuxEnabled +} + +func ReadConfig(target string) (value string) { + var ( + val, key string + bufin *bufio.Reader + ) + + in, err := os.Open(selinuxConfig) + if err != nil { + return "" + } + defer in.Close() + + bufin = bufio.NewReader(in) + + for done := false; !done; { + var line string + if line, err = bufin.ReadString('\n'); err != nil { + if err != io.EOF { + return "" + } + done = true + } + line = strings.TrimSpace(line) + if len(line) == 0 { + // Skip blank lines + continue + } + if line[0] == ';' || line[0] == '#' { + // Skip comments + continue + } + if groups := assignRegex.FindStringSubmatch(line); groups != nil { + key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2]) + if key == target { + return strings.Trim(val, "\"") + } + } + } + return "" +} + +func GetSELinuxPolicyRoot() string { + return selinuxDir + ReadConfig(selinuxTypeTag) +} + +func readCon(name string) (string, error) { + var val string + + in, err := os.Open(name) + if err != nil { + return "", err + } + defer in.Close() + + _, err = fmt.Fscanf(in, "%s", &val) + return val, err +} + +func Setfilecon(path string, scon string) error { + return system.Lsetxattr(path, xattrNameSelinux, []byte(scon), 0) +} + +func Getfilecon(path string) (string, error) { + var scon []byte + + cnt, err := syscall.Getxattr(path, xattrNameSelinux, scon) + scon = make([]byte, cnt) + cnt, err = syscall.Getxattr(path, xattrNameSelinux, scon) + return string(scon), err +} + +func Setfscreatecon(scon string) error { + return writeCon("/proc/self/attr/fscreate", scon) +} + +func Getfscreatecon() (string, error) { + return readCon("/proc/self/attr/fscreate") +} + +func Getcon() (string, error) { + return readCon("/proc/self/attr/current") +} + +func Getpidcon(pid int) (string, error) { + return readCon(fmt.Sprintf("/proc/%d/attr/current", pid)) +} + +func Getexeccon() (string, error) { + return readCon("/proc/self/attr/exec") +} + +func writeCon(name string, val string) error { + if !SelinuxEnabled() { + return nil + } + out, err := os.OpenFile(name, os.O_WRONLY, 0) + if err != nil { + return err + } + defer out.Close() + + if val != "" { + _, err = out.Write([]byte(val)) + } else { + _, err = out.Write(nil) + } + return err +} + +func Setexeccon(scon string) error { + return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), scon) +} + +func (c SELinuxContext) Get() string { + return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"]) +} + +func NewContext(scon string) SELinuxContext { + c := make(SELinuxContext) + + if len(scon) != 0 { + con := strings.SplitN(scon, ":", 4) + c["user"] = con[0] + c["role"] = con[1] + c["type"] = con[2] + c["level"] = con[3] + } + return c +} + +func SelinuxGetEnforce() int { + var enforce int + + enforceS, err := readCon(fmt.Sprintf("%s/enforce", selinuxPath)) + if err != nil { + return -1 + } + + enforce, err = strconv.Atoi(string(enforceS)) + if err != nil { + return -1 + } + return enforce +} + +func SelinuxGetEnforceMode() int { + switch ReadConfig(selinuxTag) { + case "enforcing": + return Enforcing + case "permissive": + return Permissive + } + return Disabled +} + +func mcsAdd(mcs string) { + mcsList[mcs] = true +} + +func mcsDelete(mcs string) { + mcsList[mcs] = false +} + +func mcsExists(mcs string) bool { + return mcsList[mcs] +} + +func IntToMcs(id int, catRange uint32) string { + var ( + SETSIZE = int(catRange) + TIER = SETSIZE + ORD = id + ) + + if id < 1 || id > 523776 { + return "" + } + + for ORD > TIER { + ORD = ORD - TIER + TIER -= 1 + } + TIER = SETSIZE - TIER + ORD = ORD + TIER + return fmt.Sprintf("s0:c%d,c%d", TIER, ORD) +} + +func uniqMcs(catRange uint32) string { + var ( + n uint32 + c1, c2 uint32 + mcs string + ) + + for { + binary.Read(rand.Reader, binary.LittleEndian, &n) + c1 = n % catRange + binary.Read(rand.Reader, binary.LittleEndian, &n) + c2 = n % catRange + if c1 == c2 { + continue + } else { + if c1 > c2 { + t := c1 + c1 = c2 + c2 = t + } + } + mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2) + if mcsExists(mcs) { + continue + } + mcsAdd(mcs) + break + } + return mcs +} + +func FreeContext(con string) { + if con != "" { + scon := NewContext(con) + mcsDelete(scon["level"]) + } +} + +func GetLxcContexts() (processLabel string, fileLabel string) { + var ( + val, key string + bufin *bufio.Reader + ) + + if !SelinuxEnabled() { + return "", "" + } + lxcPath := fmt.Sprintf("%s/content/lxc_contexts", GetSELinuxPolicyRoot()) + fileLabel = "system_u:object_r:svirt_sandbox_file_t:s0" + processLabel = "system_u:system_r:svirt_lxc_net_t:s0" + + in, err := os.Open(lxcPath) + if err != nil { + goto exit + } + defer in.Close() + + bufin = bufio.NewReader(in) + + for done := false; !done; { + var line string + if line, err = bufin.ReadString('\n'); err != nil { + if err == io.EOF { + done = true + } else { + goto exit + } + } + line = strings.TrimSpace(line) + if len(line) == 0 { + // Skip blank lines + continue + } + if line[0] == ';' || line[0] == '#' { + // Skip comments + continue + } + if groups := assignRegex.FindStringSubmatch(line); groups != nil { + key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2]) + if key == "process" { + processLabel = strings.Trim(val, "\"") + } + if key == "file" { + fileLabel = strings.Trim(val, "\"") + } + } + } +exit: + mcs := IntToMcs(os.Getpid(), 1024) + scon := NewContext(processLabel) + scon["level"] = mcs + processLabel = scon.Get() + scon = NewContext(fileLabel) + scon["level"] = mcs + fileLabel = scon.Get() + return processLabel, fileLabel +} + +func SecurityCheckContext(val string) error { + return writeCon(fmt.Sprintf("%s.context", selinuxPath), val) +} + +func CopyLevel(src, dest string) (string, error) { + if !SelinuxEnabled() { + return "", nil + } + if src == "" { + return "", nil + } + if err := SecurityCheckContext(src); err != nil { + return "", err + } + if err := SecurityCheckContext(dest); err != nil { + return "", err + } + scon := NewContext(src) + tcon := NewContext(dest) + tcon["level"] = scon["level"] + return tcon.Get(), nil +} diff --git a/pkg/selinux/selinux_test.go b/pkg/selinux/selinux_test.go new file mode 100644 index 0000000000..6b59c1db11 --- /dev/null +++ b/pkg/selinux/selinux_test.go @@ -0,0 +1,64 @@ +package selinux_test + +import ( + "github.com/dotcloud/docker/pkg/selinux" + "os" + "testing" +) + +func testSetfilecon(t *testing.T) { + if selinux.SelinuxEnabled() { + tmp := "selinux_test" + out, _ := os.OpenFile(tmp, os.O_WRONLY, 0) + out.Close() + err := selinux.Setfilecon(tmp, "system_u:object_r:bin_t:s0") + if err == nil { + t.Log(selinux.Getfilecon(tmp)) + } else { + t.Log("Setfilecon failed") + t.Fatal(err) + } + os.Remove(tmp) + } +} + +func TestSELinux(t *testing.T) { + var ( + err error + plabel, flabel string + ) + + if selinux.SelinuxEnabled() { + t.Log("Enabled") + plabel, flabel = selinux.GetLxcContexts() + t.Log(plabel) + t.Log(flabel) + plabel, flabel = selinux.GetLxcContexts() + t.Log(plabel) + t.Log(flabel) + t.Log("getenforce ", selinux.SelinuxGetEnforce()) + t.Log("getenforcemode ", selinux.SelinuxGetEnforceMode()) + pid := os.Getpid() + t.Log("PID:%d MCS:%s\n", pid, selinux.IntToMcs(pid, 1023)) + t.Log(selinux.Getcon()) + t.Log(selinux.Getfilecon("/etc/passwd")) + err = selinux.Setfscreatecon("unconfined_u:unconfined_r:unconfined_t:s0") + if err == nil { + t.Log(selinux.Getfscreatecon()) + } else { + t.Log("setfscreatecon failed", err) + t.Fatal(err) + } + err = selinux.Setfscreatecon("") + if err == nil { + t.Log(selinux.Getfscreatecon()) + } else { + t.Log("setfscreatecon failed", err) + t.Fatal(err) + } + t.Log(selinux.Getpidcon(1)) + t.Log(selinux.GetSelinuxMountPoint()) + } else { + t.Log("Disabled") + } +} diff --git a/runconfig/config.go b/runconfig/config.go index e961d659d7..c3ade575c5 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -1,8 +1,10 @@ package runconfig import ( + "encoding/json" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/runtime/execdriver" ) // Note: the Config structure should hold only portable information about the container. @@ -34,9 +36,17 @@ type Config struct { Entrypoint []string NetworkDisabled bool OnBuild []string + Context execdriver.Context } func ContainerConfigFromJob(job *engine.Job) *Config { + var context execdriver.Context + val := job.Getenv("Context") + if val != "" { + if err := json.Unmarshal([]byte(val), &context); err != nil { + panic(err) + } + } config := &Config{ Hostname: job.Getenv("Hostname"), Domainname: job.Getenv("Domainname"), @@ -54,6 +64,7 @@ func ContainerConfigFromJob(job *engine.Job) *Config { VolumesFrom: job.Getenv("VolumesFrom"), WorkingDir: job.Getenv("WorkingDir"), NetworkDisabled: job.GetenvBool("NetworkDisabled"), + Context: context, } job.GetenvJson("ExposedPorts", &config.ExposedPorts) job.GetenvJson("Volumes", &config.Volumes) diff --git a/runconfig/parse.go b/runconfig/parse.go index c2591722d5..23c66cd611 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -4,8 +4,10 @@ import ( "fmt" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/opts" + "github.com/dotcloud/docker/pkg/label" flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/pkg/sysinfo" + "github.com/dotcloud/docker/runtime/execdriver" "github.com/dotcloud/docker/utils" "io/ioutil" "path" @@ -32,6 +34,10 @@ func ParseSubcommand(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) } func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) { + var ( + processLabel string + mountLabel string + ) var ( // FIXME: use utils.ListOpts for attach and volumes? flAttach = opts.NewListOpts(opts.ValidateAttach) @@ -60,6 +66,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID") flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + flLabelOptions = cmd.String([]string{"Z", "-label"}, "", "Options to pass to underlying labeling system") // For documentation purpose _ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") @@ -150,6 +157,15 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf entrypoint = []string{*flEntrypoint} } + if !*flPrivileged { + pLabel, mLabel, e := label.GenLabels(*flLabelOptions) + if e != nil { + return nil, nil, cmd, fmt.Errorf("Invalid security labels : %s", e) + } + processLabel = pLabel + mountLabel = mLabel + } + lxcConf, err := parseLxcConfOpts(flLxcOpts) if err != nil { return nil, nil, cmd, err @@ -204,6 +220,10 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","), Entrypoint: entrypoint, WorkingDir: *flWorkingDir, + Context: execdriver.Context{ + "mount_label": mountLabel, + "process_label": processLabel, + }, } hostConfig := &HostConfig{ diff --git a/runtime/container.go b/runtime/container.go index bff9aea968..53d0aa666e 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -402,6 +402,7 @@ func populateCommand(c *Container) { User: c.Config.User, Config: driverConfig, Resources: resources, + Context: c.Config.Context, } c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true} } diff --git a/runtime/execdriver/driver.go b/runtime/execdriver/driver.go index 23e31ee8d9..dca889a82d 100644 --- a/runtime/execdriver/driver.go +++ b/runtime/execdriver/driver.go @@ -7,6 +7,10 @@ import ( "os/exec" ) +// Context is a generic key value pair that allows +// arbatrary data to be sent +type Context map[string]string + var ( ErrNotRunning = errors.New("Process could not be started") ErrWaitTimeoutReached = errors.New("Wait timeout reached") @@ -121,6 +125,7 @@ type Command struct { Arguments []string `json:"arguments"` WorkingDir string `json:"working_dir"` ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver + Context Context `json:"context"` // generic context for specific options (apparmor, selinux) Tty bool `json:"tty"` Network *Network `json:"network"` Config []string `json:"config"` // generic values that specific drivers can consume diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index ce9d90469f..608fb22436 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -1,6 +1,7 @@ package lxc import ( + "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/runtime/execdriver" "strings" "text/template" @@ -29,6 +30,10 @@ lxc.pts = 1024 # disable the main console lxc.console = none +{{if getProcessLabel .Context}} +lxc.se_context = {{ getProcessLabel .Context}} +{{$MOUNTLABEL := getMountLabel .Context}} +{{end}} # no controlling tty at all lxc.tty = 1 @@ -85,8 +90,8 @@ lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noe lxc.mount.entry = {{.Console}} {{escapeFstabSpaces $ROOTFS}}/dev/console none bind,rw 0 0 {{end}} -lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0 -lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0 +lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts {{formatMountLabel "newinstance,ptmxmode=0666,nosuid,noexec" "$MOUNTLABEL"}} 0 0 +lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs {{formatMountLabel "size=65536k,nosuid,nodev,noexec" "$MOUNTLABEL"}} 0 0 {{range $value := .Mounts}} {{if $value.Writable}} @@ -142,11 +147,22 @@ func getMemorySwap(v *execdriver.Resources) int64 { return v.Memory * 2 } +func getProcessLabel(c execdriver.Context) string { + return c["process_label"] +} + +func getMountLabel(c execdriver.Context) string { + return c["mount_label"] +} + func init() { var err error funcMap := template.FuncMap{ "getMemorySwap": getMemorySwap, + "getProcessLabel": getProcessLabel, + "getMountLabel": getMountLabel, "escapeFstabSpaces": escapeFstabSpaces, + "formatMountLabel": label.FormatMountLabel, } LxcTemplateCompiled, err = template.New("lxc").Funcs(funcMap).Parse(LxcTemplate) if err != nil { diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index d744ab382f..7e1e9ed86e 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -18,6 +18,8 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.User = c.User container.WorkingDir = c.WorkingDir container.Env = c.Env + container.Context["mount_label"] = c.Context["mount_label"] + container.Context["process_label"] = c.Context["process_label"] loopbackNetwork := libcontainer.Network{ Mtu: c.Network.Mtu, diff --git a/runtime/graphdriver/aufs/aufs.go b/runtime/graphdriver/aufs/aufs.go index 6f05ddd025..401bbd8c86 100644 --- a/runtime/graphdriver/aufs/aufs.go +++ b/runtime/graphdriver/aufs/aufs.go @@ -134,7 +134,7 @@ func (a Driver) Exists(id string) bool { // Three folders are created for each id // mnt, layers, and diff -func (a *Driver) Create(id, parent string) error { +func (a *Driver) Create(id, parent string, mountLabel string) error { if err := a.createDirsFor(id); err != nil { return err } diff --git a/runtime/graphdriver/aufs/aufs_test.go b/runtime/graphdriver/aufs/aufs_test.go index cb417c3b26..9cfdebd160 100644 --- a/runtime/graphdriver/aufs/aufs_test.go +++ b/runtime/graphdriver/aufs/aufs_test.go @@ -90,7 +90,7 @@ func TestCreateNewDir(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } } @@ -99,7 +99,7 @@ func TestCreateNewDirStructure(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -120,7 +120,7 @@ func TestRemoveImage(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -145,7 +145,7 @@ func TestGetWithoutParent(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -172,7 +172,7 @@ func TestCleanupWithDir(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -185,7 +185,7 @@ func TestMountedFalseResponse(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -204,10 +204,10 @@ func TestMountedTrueReponse(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } - if err := d.Create("2", "1"); err != nil { + if err := d.Create("2", "1", ""); err != nil { t.Fatal(err) } @@ -230,10 +230,10 @@ func TestMountWithParent(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } - if err := d.Create("2", "1"); err != nil { + if err := d.Create("2", "1", ""); err != nil { t.Fatal(err) } @@ -261,10 +261,10 @@ func TestRemoveMountedDir(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } - if err := d.Create("2", "1"); err != nil { + if err := d.Create("2", "1", ""); err != nil { t.Fatal(err) } @@ -300,7 +300,7 @@ func TestCreateWithInvalidParent(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", "docker"); err == nil { + if err := d.Create("1", "docker", ""); err == nil { t.Fatalf("Error should not be nil with parent does not exist") } } @@ -309,7 +309,7 @@ func TestGetDiff(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -343,10 +343,10 @@ func TestChanges(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } - if err := d.Create("2", "1"); err != nil { + if err := d.Create("2", "1", ""); err != nil { t.Fatal(err) } @@ -392,7 +392,7 @@ func TestChanges(t *testing.T) { t.Fatalf("Change kind should be ChangeAdd got %s", change.Kind) } - if err := d.Create("3", "2"); err != nil { + if err := d.Create("3", "2", ""); err != nil { t.Fatal(err) } mntPoint, err = d.Get("3") @@ -437,7 +437,7 @@ func TestDiffSize(t *testing.T) { d := newDriver(t) defer os.RemoveAll(tmp) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -479,7 +479,7 @@ func TestChildDiffSize(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -515,7 +515,7 @@ func TestChildDiffSize(t *testing.T) { t.Fatalf("Expected size to be %d got %d", size, diffSize) } - if err := d.Create("2", "1"); err != nil { + if err := d.Create("2", "1", ""); err != nil { t.Fatal(err) } @@ -534,7 +534,7 @@ func TestExists(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -552,7 +552,7 @@ func TestStatus(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -581,7 +581,7 @@ func TestApplyDiff(t *testing.T) { defer os.RemoveAll(tmp) defer d.Cleanup() - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -607,10 +607,10 @@ func TestApplyDiff(t *testing.T) { t.Fatal(err) } - if err := d.Create("2", ""); err != nil { + if err := d.Create("2", "", ""); err != nil { t.Fatal(err) } - if err := d.Create("3", "2"); err != nil { + if err := d.Create("3", "2", ""); err != nil { t.Fatal(err) } @@ -656,7 +656,7 @@ func TestMountMoreThan42Layers(t *testing.T) { } current = hash(current) - if err := d.Create(current, parent); err != nil { + if err := d.Create(current, parent, ""); err != nil { t.Logf("Current layer %d", i) t.Fatal(err) } diff --git a/runtime/graphdriver/aufs/migrate.go b/runtime/graphdriver/aufs/migrate.go index 6018342d6c..400e260797 100644 --- a/runtime/graphdriver/aufs/migrate.go +++ b/runtime/graphdriver/aufs/migrate.go @@ -77,7 +77,7 @@ func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) e } initID := fmt.Sprintf("%s-init", id) - if err := a.Create(initID, metadata.Image); err != nil { + if err := a.Create(initID, metadata.Image, ""); err != nil { return err } @@ -90,7 +90,7 @@ func (a *Driver) migrateContainers(pth string, setupInit func(p string) error) e return err } - if err := a.Create(id, initID); err != nil { + if err := a.Create(id, initID, ""); err != nil { return err } } @@ -144,7 +144,7 @@ func (a *Driver) migrateImage(m *metadata, pth string, migrated map[string]bool) return err } if !a.Exists(m.ID) { - if err := a.Create(m.ID, m.ParentID); err != nil { + if err := a.Create(m.ID, m.ParentID, ""); err != nil { return err } } diff --git a/runtime/graphdriver/btrfs/btrfs.go b/runtime/graphdriver/btrfs/btrfs.go index b0530be92b..2a94a4089f 100644 --- a/runtime/graphdriver/btrfs/btrfs.go +++ b/runtime/graphdriver/btrfs/btrfs.go @@ -80,7 +80,7 @@ func getDirFd(dir *C.DIR) uintptr { return uintptr(C.dirfd(dir)) } -func subvolCreate(path, name string) error { +func subvolCreate(path, name string, mountLabel string) error { dir, err := openDir(path) if err != nil { return err @@ -155,13 +155,13 @@ func (d *Driver) subvolumesDirId(id string) string { return path.Join(d.subvolumesDir(), id) } -func (d *Driver) Create(id string, parent string) error { +func (d *Driver) Create(id string, parent string, mountLabel string) error { subvolumes := path.Join(d.home, "subvolumes") if err := os.MkdirAll(subvolumes, 0700); err != nil { return err } if parent == "" { - if err := subvolCreate(subvolumes, id); err != nil { + if err := subvolCreate(subvolumes, id, mountLabel); err != nil { return err } } else { diff --git a/runtime/graphdriver/devmapper/deviceset.go b/runtime/graphdriver/devmapper/deviceset.go index dfdb180bb2..762e982208 100644 --- a/runtime/graphdriver/devmapper/deviceset.go +++ b/runtime/graphdriver/devmapper/deviceset.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/dotcloud/docker/pkg/label" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -827,7 +828,7 @@ func (devices *DeviceSet) Shutdown() error { return nil } -func (devices *DeviceSet) MountDevice(hash, path string) error { +func (devices *DeviceSet) MountDevice(hash, path string, mountLabel string) error { devices.Lock() defer devices.Unlock() @@ -859,9 +860,11 @@ func (devices *DeviceSet) MountDevice(hash, path string) error { var flags uintptr = sysMsMgcVal - err := sysMount(info.DevName(), path, "ext4", flags, "discard") + mountOptions := label.FormatMountLabel("discard", mountLabel) + err := sysMount(info.DevName(), path, "ext4", flags, mountOptions) if err != nil && err == sysEInval { - err = sysMount(info.DevName(), path, "ext4", flags, "") + mountOptions = label.FormatMountLabel(mountLabel, "") + err = sysMount(info.DevName(), path, "ext4", flags, mountOptions) } if err != nil { return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) diff --git a/runtime/graphdriver/devmapper/driver.go b/runtime/graphdriver/devmapper/driver.go index 33c7a0f483..1324ddab81 100644 --- a/runtime/graphdriver/devmapper/driver.go +++ b/runtime/graphdriver/devmapper/driver.go @@ -22,7 +22,8 @@ func init() { type Driver struct { *DeviceSet - home string + home string + MountLabel string } var Init = func(home string) (graphdriver.Driver, error) { @@ -60,13 +61,13 @@ func (d *Driver) Cleanup() error { return d.DeviceSet.Shutdown() } -func (d *Driver) Create(id, parent string) error { +func (d *Driver) Create(id, parent string, mountLabel string) error { + d.MountLabel = mountLabel if err := d.DeviceSet.AddDevice(id, parent); err != nil { return err } - mp := path.Join(d.home, "mnt", id) - if err := d.mount(id, mp); err != nil { + if err := d.mount(id, mp, d.MountLabel); err != nil { return err } @@ -116,7 +117,7 @@ func (d *Driver) Remove(id string) error { func (d *Driver) Get(id string) (string, error) { mp := path.Join(d.home, "mnt", id) - if err := d.mount(id, mp); err != nil { + if err := d.mount(id, mp, d.MountLabel); err != nil { return "", err } @@ -129,13 +130,13 @@ func (d *Driver) Put(id string) { } } -func (d *Driver) mount(id, mountPoint string) error { +func (d *Driver) mount(id, mountPoint string, mountLabel string) error { // Create the target directories if they don't exist if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) { return err } // Mount the device - return d.DeviceSet.MountDevice(id, mountPoint) + return d.DeviceSet.MountDevice(id, mountPoint, mountLabel) } func (d *Driver) Exists(id string) bool { diff --git a/runtime/graphdriver/devmapper/driver_test.go b/runtime/graphdriver/devmapper/driver_test.go index 9af71a00b3..4ca72db0ca 100644 --- a/runtime/graphdriver/devmapper/driver_test.go +++ b/runtime/graphdriver/devmapper/driver_test.go @@ -494,7 +494,7 @@ func TestDriverCreate(t *testing.T) { "?ioctl.loopctlgetfree", ) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } calls.Assert(t, @@ -612,7 +612,7 @@ func TestDriverRemove(t *testing.T) { "?ioctl.loopctlgetfree", ) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -668,7 +668,7 @@ func TestCleanup(t *testing.T) { mountPoints := make([]string, 2) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } // Mount the id @@ -678,7 +678,7 @@ func TestCleanup(t *testing.T) { } mountPoints[0] = p - if err := d.Create("2", "1"); err != nil { + if err := d.Create("2", "1", ""); err != nil { t.Fatal(err) } @@ -731,7 +731,7 @@ func TestNotMounted(t *testing.T) { d := newDriver(t) defer cleanup(d) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -749,7 +749,7 @@ func TestMounted(t *testing.T) { d := newDriver(t) defer cleanup(d) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } if _, err := d.Get("1"); err != nil { @@ -769,7 +769,7 @@ func TestInitCleanedDriver(t *testing.T) { t.Skip("FIXME: not a unit test") d := newDriver(t) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } if _, err := d.Get("1"); err != nil { @@ -797,7 +797,7 @@ func TestMountMountedDriver(t *testing.T) { d := newDriver(t) defer cleanup(d) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -816,7 +816,7 @@ func TestGetReturnsValidDevice(t *testing.T) { d := newDriver(t) defer cleanup(d) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } @@ -844,7 +844,7 @@ func TestDriverGetSize(t *testing.T) { d := newDriver(t) defer cleanup(d) - if err := d.Create("1", ""); err != nil { + if err := d.Create("1", "", ""); err != nil { t.Fatal(err) } diff --git a/runtime/graphdriver/driver.go b/runtime/graphdriver/driver.go index 89fd03a624..7bea704682 100644 --- a/runtime/graphdriver/driver.go +++ b/runtime/graphdriver/driver.go @@ -13,7 +13,7 @@ type InitFunc func(root string) (Driver, error) type Driver interface { String() string - Create(id, parent string) error + Create(id, parent string, mountLabel string) error Remove(id string) error Get(id string) (dir string, err error) diff --git a/runtime/graphdriver/vfs/driver.go b/runtime/graphdriver/vfs/driver.go index 10a7b223a4..fe09560f24 100644 --- a/runtime/graphdriver/vfs/driver.go +++ b/runtime/graphdriver/vfs/driver.go @@ -42,7 +42,7 @@ func copyDir(src, dst string) error { return nil } -func (d *Driver) Create(id string, parent string) error { +func (d *Driver) Create(id string, parent string, mountLabel string) error { dir := d.dir(id) if err := os.MkdirAll(path.Dir(dir), 0700); err != nil { return err diff --git a/runtime/runtime.go b/runtime/runtime.go index 0d3468e350..35bcad9781 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -467,7 +467,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe } initID := fmt.Sprintf("%s-init", container.ID) - if err := runtime.driver.Create(initID, img.ID); err != nil { + if err := runtime.driver.Create(initID, img.ID, config.Context["mount_label"]); err != nil { return nil, nil, err } initPath, err := runtime.driver.Get(initID) @@ -480,7 +480,7 @@ func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Containe return nil, nil, err } - if err := runtime.driver.Create(container.ID, initID); err != nil { + if err := runtime.driver.Create(container.ID, initID, config.Context["mount_label"]); err != nil { return nil, nil, err } resolvConf, err := utils.GetResolvConf() From 0fb01fd8fe376a3518b1050ab62f2b3370d62535 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 26 Mar 2014 13:55:45 +0000 Subject: [PATCH 39/50] Follow symlinks inside container root for build's ADD Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration/buildfile_test.go | 18 ++++++++++++++++++ server/buildfile.go | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 95d5abb8a7..ae2282f53f 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -998,3 +998,21 @@ func TestBuildOnBuildForbiddenMaintainerTrigger(t *testing.T) { t.Fatal("Error should not be nil") } } + +// gh #2446 +func TestBuildAddToSymlinkDest(t *testing.T) { + eng := NewTestEngine(t) + defer nuke(mkRuntimeFromEngine(eng, t)) + + _, err := buildImage(testContextTemplate{` + from {IMAGE} + run mkdir /foo + run ln -s /foo /bar + add foo /bar/ + run stat /bar/foo + `, + [][2]string{{"foo", "HEYO"}}, nil}, t, eng, true) + if err != nil { + t.Fatal(err) + } +} diff --git a/server/buildfile.go b/server/buildfile.go index 5d5fda4d8e..6f95c2e593 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -395,9 +395,18 @@ func (b *buildFile) checkPathForAddition(orig string) error { func (b *buildFile) addContext(container *runtime.Container, orig, dest string, remote bool) error { var ( + err error origPath = path.Join(b.contextPath, orig) destPath = path.Join(container.RootfsPath(), dest) ) + + if destPath != container.RootfsPath() { + destPath, err = utils.FollowSymlinkInScope(destPath, container.RootfsPath()) + if err != nil { + return err + } + } + // Preserve the trailing '/' if strings.HasSuffix(dest, "/") { destPath = destPath + "/" From 67af7b3fb0b5e40a435b434c57291cb2989275ce Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 18 Mar 2014 17:50:40 -0700 Subject: [PATCH 40/50] Strip comments before parsing line continuations Fixes #3898 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- integration/buildfile_test.go | 10 ++++++++++ server/buildfile.go | 25 +++++++++++++++++++------ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 7f6e69ece3..23a1ff3d8e 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -311,6 +311,16 @@ RUN [ "$(cat /testfile)" = 'test!' ] }, nil, }, + { + ` +FROM {IMAGE} +# what \ +RUN mkdir /testing +RUN touch /testing/other +`, + nil, + nil, + }, } // FIXME: test building with 2 successive overlapping ADD commands diff --git a/server/buildfile.go b/server/buildfile.go index af6702cc1d..309b854208 100644 --- a/server/buildfile.go +++ b/server/buildfile.go @@ -729,20 +729,19 @@ func (b *buildFile) Build(context io.Reader) (string, error) { if len(fileBytes) == 0 { return "", ErrDockerfileEmpty } - dockerfile := string(fileBytes) - dockerfile = lineContinuation.ReplaceAllString(dockerfile, "") - stepN := 0 + var ( + dockerfile = lineContinuation.ReplaceAllString(stripComments(fileBytes), "") + stepN = 0 + ) for _, line := range strings.Split(dockerfile, "\n") { line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n") - // Skip comments and empty line - if len(line) == 0 || line[0] == '#' { + if len(line) == 0 { continue } if err := b.BuildStep(fmt.Sprintf("%d", stepN), line); err != nil { return "", err } stepN += 1 - } if b.image != "" { fmt.Fprintf(b.outStream, "Successfully built %s\n", utils.TruncateID(b.image)) @@ -779,6 +778,20 @@ func (b *buildFile) BuildStep(name, expression string) error { return nil } +func stripComments(raw []byte) string { + var ( + out []string + lines = strings.Split(string(raw), "\n") + ) + for _, l := range lines { + if len(l) == 0 || l[0] == '#' { + continue + } + out = append(out, l) + } + return strings.Join(out, "\n") +} + func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeCache, rm bool, outOld io.Writer, sf *utils.StreamFormatter, auth *registry.AuthConfig, authConfigFile *registry.ConfigFile) BuildFile { return &buildFile{ runtime: srv.runtime, From 097aef2ca938012a5b42e0032b30267e27a92265 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 27 Mar 2014 04:24:31 +0000 Subject: [PATCH 41/50] Fix commit and import when no repository is specified Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- api/client.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/api/client.go b/api/client.go index 1e6bf9d549..df3265a15a 100644 --- a/api/client.go +++ b/api/client.go @@ -1013,9 +1013,11 @@ func (cli *DockerCli) CmdImport(args ...string) error { } v := url.Values{} - //Check if the given image name can be resolved - if _, _, err := registry.ResolveRepositoryName(repository); err != nil { - return err + if repository != "" { + //Check if the given image name can be resolved + if _, _, err := registry.ResolveRepositoryName(repository); err != nil { + return err + } } v.Set("repo", repository) @@ -1469,8 +1471,10 @@ func (cli *DockerCli) CmdCommit(args ...string) error { } //Check if the given image name can be resolved - if _, _, err := registry.ResolveRepositoryName(repository); err != nil { - return err + if repository != "" { + if _, _, err := registry.ResolveRepositoryName(repository); err != nil { + return err + } } v := url.Values{} From ad3e71d5c7e01dca229d4077cf8b019d8085c33a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 27 Mar 2014 11:06:32 -0600 Subject: [PATCH 42/50] Adjust TestOnlyLoopbackExistsWhenUsingDisableNetworkOption to ignore "DOWN" interfaces This fixes the following, which I've been seeing on all my machines for as long as I can remember: --- FAIL: TestOnlyLoopbackExistsWhenUsingDisableNetworkOption (0.36 seconds) container_test.go:1597: Wrong interface count in test container: expected [*: lo], got [1: lo 2: sit0] Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- integration/container_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/container_test.go b/integration/container_test.go index 663b350638..8ed5525c72 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -1553,7 +1553,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { runtime := mkRuntimeFromEngine(eng, t) defer nuke(runtime) - config, hc, _, err := runconfig.Parse([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil) + config, hc, _, err := runconfig.Parse([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show", "up"}, nil) if err != nil { t.Fatal(err) } From 73ee4879afd557a3ddd0740b0a281024060f2436 Mon Sep 17 00:00:00 2001 From: Michael Gorsuch Date: Thu, 27 Mar 2014 12:44:33 -0500 Subject: [PATCH 43/50] upstart: use exec here so upstart can monitor the process and not just a shell Docker-DCO-1.1-Signed-off-by: Michael Gorsuch (github: gorsuch) --- contrib/init/upstart/docker.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index 907a536c9c..e27d77e145 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -37,5 +37,5 @@ script if [ -f /etc/default/$UPSTART_JOB ]; then . /etc/default/$UPSTART_JOB fi - "$DOCKER" -d $DOCKER_OPTS + exec "$DOCKER" -d $DOCKER_OPTS end script From 7a3070a6000963d12be9dcd2698d911b848a33b6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 13 Mar 2014 17:03:09 +0100 Subject: [PATCH 44/50] Add --opt arguments for drivers In order to handle special configuration for different drivers we make the Config field a map to string array. This lets us use it for lxc, by using the "lxc" key for those, and we can later extend it easily for other backend-specific options. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runconfig/hostconfig.go | 10 ++--- runconfig/parse.go | 45 +++++++++++++------ runconfig/parse_test.go | 3 +- runtime/container.go | 14 +++--- runtime/execdriver/driver.go | 30 ++++++------- runtime/execdriver/lxc/lxc_template.go | 4 +- .../execdriver/lxc/lxc_template_unit_test.go | 8 ++-- runtime/execdriver/native/default_template.go | 1 + runtime/execdriver/native/driver.go | 7 ++- runtime/utils.go | 20 +++++++++ utils/utils.go | 13 ++++++ 11 files changed, 104 insertions(+), 51 deletions(-) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 6c8618ee81..9a92258644 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -3,21 +3,18 @@ package runconfig import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/utils" ) type HostConfig struct { Binds []string ContainerIDFile string - LxcConf []KeyValuePair + LxcConf []utils.KeyValuePair Privileged bool PortBindings nat.PortMap Links []string PublishAllPorts bool -} - -type KeyValuePair struct { - Key string - Value string + DriverOptions map[string][]string } func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { @@ -28,6 +25,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { } job.GetenvJson("LxcConf", &hostConfig.LxcConf) job.GetenvJson("PortBindings", &hostConfig.PortBindings) + job.GetenvJson("DriverOptions", &hostConfig.DriverOptions) if Binds := job.GetenvList("Binds"); Binds != nil { hostConfig.Binds = Binds } diff --git a/runconfig/parse.go b/runconfig/parse.go index 23c66cd611..43aecdb753 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -51,6 +51,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf flDnsSearch = opts.NewListOpts(opts.ValidateDomain) flVolumesFrom opts.ListOpts flLxcOpts opts.ListOpts + flDriverOpts opts.ListOpts flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") @@ -83,7 +84,8 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers") cmd.Var(&flDnsSearch, []string{"-dns-search"}, "Set custom dns search domains") cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") - cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + cmd.Var(&flLxcOpts, []string{"#lxc-conf", "#-lxc-conf"}, "(lxc exec-driver only) Add custom lxc options --lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + cmd.Var(&flDriverOpts, []string{"o", "-opt"}, "Add custom driver options") if err := cmd.Parse(args); err != nil { return nil, nil, cmd, err @@ -166,7 +168,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf mountLabel = mLabel } - lxcConf, err := parseLxcConfOpts(flLxcOpts) + lxcConf, err := parseKeyValueOpts(flLxcOpts) if err != nil { return nil, nil, cmd, err } @@ -226,6 +228,11 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf }, } + driverOptions, err := parseDriverOpts(flDriverOpts) + if err != nil { + return nil, nil, cmd, err + } + hostConfig := &HostConfig{ Binds: binds, ContainerIDFile: *flContainerIDFile, @@ -234,6 +241,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf PortBindings: portBindings, Links: flLinks.GetAll(), PublishAllPorts: *flPublishAll, + DriverOptions: driverOptions, } if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { @@ -248,22 +256,31 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf return config, hostConfig, cmd, nil } -func parseLxcConfOpts(opts opts.ListOpts) ([]KeyValuePair, error) { - out := make([]KeyValuePair, opts.Len()) - for i, o := range opts.GetAll() { - k, v, err := parseLxcOpt(o) - if err != nil { - return nil, err +// options will come in the format of name.key=value or name.option +func parseDriverOpts(opts opts.ListOpts) (map[string][]string, error) { + out := make(map[string][]string, len(opts.GetAll())) + for _, o := range opts.GetAll() { + parts := strings.SplitN(o, ".", 2) + if len(parts) < 2 { + return nil, fmt.Errorf("invalid opt format %s", o) } - out[i] = KeyValuePair{Key: k, Value: v} + values, exists := out[parts[0]] + if !exists { + values = []string{} + } + out[parts[0]] = append(values, parts[1]) } return out, nil } -func parseLxcOpt(opt string) (string, string, error) { - parts := strings.SplitN(opt, "=", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt) +func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) { + out := make([]utils.KeyValuePair, opts.Len()) + for i, o := range opts.GetAll() { + k, v, err := utils.ParseKeyValueOpt(o) + if err != nil { + return nil, err + } + out[i] = utils.KeyValuePair{Key: k, Value: v} } - return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil + return out, nil } diff --git a/runconfig/parse_test.go b/runconfig/parse_test.go index 2b89e88ec3..fd28c4593e 100644 --- a/runconfig/parse_test.go +++ b/runconfig/parse_test.go @@ -1,6 +1,7 @@ package runconfig import ( + "github.com/dotcloud/docker/utils" "testing" ) @@ -8,7 +9,7 @@ func TestParseLxcConfOpt(t *testing.T) { opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} for _, o := range opts { - k, v, err := parseLxcOpt(o) + k, v, err := utils.ParseKeyValueOpt(o) if err != nil { t.FailNow() } diff --git a/runtime/container.go b/runtime/container.go index 4cf307d823..656e9ae587 100644 --- a/runtime/container.go +++ b/runtime/container.go @@ -361,9 +361,13 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s func populateCommand(c *Container) { var ( en *execdriver.Network - driverConfig []string + driverConfig = c.hostConfig.DriverOptions ) + if driverConfig == nil { + driverConfig = make(map[string][]string) + } + en = &execdriver.Network{ Mtu: c.runtime.config.Mtu, Interface: nil, @@ -379,11 +383,9 @@ func populateCommand(c *Container) { } } - if lxcConf := c.hostConfig.LxcConf; lxcConf != nil { - for _, pair := range lxcConf { - driverConfig = append(driverConfig, fmt.Sprintf("%s = %s", pair.Key, pair.Value)) - } - } + // TODO: this can be removed after lxc-conf is fully deprecated + mergeLxcConfIntoOptions(c.hostConfig, driverConfig) + resources := &execdriver.Resources{ Memory: c.Config.Memory, MemorySwap: c.Config.MemorySwap, diff --git a/runtime/execdriver/driver.go b/runtime/execdriver/driver.go index dca889a82d..096ea0790d 100644 --- a/runtime/execdriver/driver.go +++ b/runtime/execdriver/driver.go @@ -116,21 +116,21 @@ type Mount struct { type Command struct { exec.Cmd `json:"-"` - ID string `json:"id"` - Privileged bool `json:"privileged"` - User string `json:"user"` - Rootfs string `json:"rootfs"` // root fs of the container - InitPath string `json:"initpath"` // dockerinit - Entrypoint string `json:"entrypoint"` - Arguments []string `json:"arguments"` - WorkingDir string `json:"working_dir"` - ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver - Context Context `json:"context"` // generic context for specific options (apparmor, selinux) - Tty bool `json:"tty"` - Network *Network `json:"network"` - Config []string `json:"config"` // generic values that specific drivers can consume - Resources *Resources `json:"resources"` - Mounts []Mount `json:"mounts"` + ID string `json:"id"` + Privileged bool `json:"privileged"` + User string `json:"user"` + Rootfs string `json:"rootfs"` // root fs of the container + InitPath string `json:"initpath"` // dockerinit + Entrypoint string `json:"entrypoint"` + Arguments []string `json:"arguments"` + WorkingDir string `json:"working_dir"` + ConfigPath string `json:"config_path"` // this should be able to be removed when the lxc template is moved into the driver + Context Context `json:"context"` // generic context for specific options (apparmor, selinux) + Tty bool `json:"tty"` + Network *Network `json:"network"` + Config map[string][]string `json:"config"` // generic values that specific drivers can consume + Resources *Resources `json:"resources"` + Mounts []Mount `json:"mounts"` Terminal Terminal `json:"-"` // standard or tty terminal Console string `json:"-"` // dev/console path diff --git a/runtime/execdriver/lxc/lxc_template.go b/runtime/execdriver/lxc/lxc_template.go index 608fb22436..f325ffcaef 100644 --- a/runtime/execdriver/lxc/lxc_template.go +++ b/runtime/execdriver/lxc/lxc_template.go @@ -123,8 +123,8 @@ lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{end}} {{end}} -{{if .Config}} -{{range $value := .Config}} +{{if .Config.lxc}} +{{range $value := .Config.lxc}} {{$value}} {{end}} {{end}} diff --git a/runtime/execdriver/lxc/lxc_template_unit_test.go b/runtime/execdriver/lxc/lxc_template_unit_test.go index e613adf7a9..7f473a0502 100644 --- a/runtime/execdriver/lxc/lxc_template_unit_test.go +++ b/runtime/execdriver/lxc/lxc_template_unit_test.go @@ -75,9 +75,11 @@ func TestCustomLxcConfig(t *testing.T) { command := &execdriver.Command{ ID: "1", Privileged: false, - Config: []string{ - "lxc.utsname = docker", - "lxc.cgroup.cpuset.cpus = 0,1", + Config: map[string][]string{ + "lxc": { + "lxc.utsname = docker", + "lxc.cgroup.cpuset.cpus = 0,1", + }, }, Network: &execdriver.Network{ Mtu: 1500, diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index 7e1e9ed86e..e11f2de1cf 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -58,6 +58,7 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.Cgroups.Memory = c.Resources.Memory container.Cgroups.MemorySwap = c.Resources.MemorySwap } + // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" diff --git a/runtime/execdriver/native/driver.go b/runtime/execdriver/native/driver.go index bf7e8ccdec..db974cbf04 100644 --- a/runtime/execdriver/native/driver.go +++ b/runtime/execdriver/native/driver.go @@ -184,10 +184,9 @@ func (d *driver) removeContainerRoot(id string) error { func (d *driver) validateCommand(c *execdriver.Command) error { // we need to check the Config of the command to make sure that we // do not have any of the lxc-conf variables - for _, conf := range c.Config { - if strings.Contains(conf, "lxc") { - return fmt.Errorf("%s is not supported by the native driver", conf) - } + lxc := c.Config["lxc"] + if lxc != nil && len(lxc) > 0 { + return fmt.Errorf("lxc config options are not supported by the native driver") } return nil } diff --git a/runtime/utils.go b/runtime/utils.go index b343b5b10e..b983e67d41 100644 --- a/runtime/utils.go +++ b/runtime/utils.go @@ -1,9 +1,11 @@ package runtime import ( + "fmt" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/runconfig" + "strings" ) func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error { @@ -30,6 +32,24 @@ func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostCon return nil } +func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig, driverConfig map[string][]string) { + if hostConfig == nil { + return + } + + // merge in the lxc conf options into the generic config map + if lxcConf := hostConfig.LxcConf; lxcConf != nil { + lxc := driverConfig["lxc"] + for _, pair := range lxcConf { + // because lxc conf gets the driver name lxc.XXXX we need to trim it off + // and let the lxc driver add it back later if needed + parts := strings.SplitN(pair.Key, ".", 2) + lxc = append(lxc, fmt.Sprintf("%s=%s", parts[1], pair.Value)) + } + driverConfig["lxc"] = lxc + } +} + type checker struct { runtime *Runtime } diff --git a/utils/utils.go b/utils/utils.go index 2702555973..1fe2e87b4f 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -25,6 +25,11 @@ import ( "time" ) +type KeyValuePair struct { + Key string + Value string +} + // A common interface to access the Fatal method of // both testing.B and testing.T. type Fataler interface { @@ -1071,3 +1076,11 @@ func ReadSymlinkedDirectory(path string) (string, error) { } return realPath, nil } + +func ParseKeyValueOpt(opt string) (string, string, error) { + parts := strings.SplitN(opt, "=", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt) + } + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil +} From 7f7d8419a71d49b25e4d38196b36e93b568bb61d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 14 Mar 2014 10:47:49 +0100 Subject: [PATCH 45/50] cgroups: Splity out Apply/Cleanup to separate file/interface This leaves only the generic cgroup helper functions in cgroups.go and will allow easy implementations of other cgroup managers. This also wires up the call to Cleanup the cgroup which was missing before. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/cgroups/apply_raw.go | 189 ++++++++++++++++++++++++++++++++ pkg/cgroups/cgroups.go | 171 +---------------------------- pkg/libcontainer/nsinit/exec.go | 16 ++- 3 files changed, 205 insertions(+), 171 deletions(-) create mode 100644 pkg/cgroups/apply_raw.go diff --git a/pkg/cgroups/apply_raw.go b/pkg/cgroups/apply_raw.go new file mode 100644 index 0000000000..bce96f4951 --- /dev/null +++ b/pkg/cgroups/apply_raw.go @@ -0,0 +1,189 @@ +package cgroups + +import ( + "fmt" + "os" + "path/filepath" + "strconv" +) + +type rawCgroup struct { + root string + cgroup string +} + +func rawApply(c *Cgroup, pid int) (ActiveCgroup, error) { + // We have two implementation of cgroups support, one is based on + // systemd and the dbus api, and one is based on raw cgroup fs operations + // following the pre-single-writer model docs at: + // http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ + // + // we can pick any subsystem to find the root + + cgroupRoot, err := FindCgroupMountpoint("cpu") + if err != nil { + return nil, err + } + cgroupRoot = filepath.Dir(cgroupRoot) + + if _, err := os.Stat(cgroupRoot); err != nil { + return nil, fmt.Errorf("cgroups fs not found") + } + + cgroup := c.Name + if c.Parent != "" { + cgroup = filepath.Join(c.Parent, cgroup) + } + + raw := &rawCgroup{ + root: cgroupRoot, + cgroup: cgroup, + } + + if err := raw.setupDevices(c, pid); err != nil { + return nil, err + } + if err := raw.setupMemory(c, pid); err != nil { + return nil, err + } + if err := raw.setupCpu(c, pid); err != nil { + return nil, err + } + return raw, nil +} + +func (raw *rawCgroup) path(subsystem string) (string, error) { + initPath, err := GetInitCgroupDir(subsystem) + if err != nil { + return "", err + } + return filepath.Join(raw.root, subsystem, initPath, raw.cgroup), nil +} + +func (raw *rawCgroup) join(subsystem string, pid int) (string, error) { + path, err := raw.path(subsystem) + if err != nil { + return "", err + } + if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { + return "", err + } + if err := writeFile(path, "tasks", strconv.Itoa(pid)); err != nil { + return "", err + } + return path, nil +} + +func (raw *rawCgroup) setupDevices(c *Cgroup, pid int) (err error) { + if !c.DeviceAccess { + dir, err := raw.join("devices", pid) + if err != nil { + return err + } + + defer func() { + if err != nil { + os.RemoveAll(dir) + } + }() + + if err := writeFile(dir, "devices.deny", "a"); err != nil { + return err + } + + allow := []string{ + // /dev/null, zero, full + "c 1:3 rwm", + "c 1:5 rwm", + "c 1:7 rwm", + + // consoles + "c 5:1 rwm", + "c 5:0 rwm", + "c 4:0 rwm", + "c 4:1 rwm", + + // /dev/urandom,/dev/random + "c 1:9 rwm", + "c 1:8 rwm", + + // /dev/pts/ - pts namespaces are "coming soon" + "c 136:* rwm", + "c 5:2 rwm", + + // tuntap + "c 10:200 rwm", + } + + for _, val := range allow { + if err := writeFile(dir, "devices.allow", val); err != nil { + return err + } + } + } + return nil +} + +func (raw *rawCgroup) setupMemory(c *Cgroup, pid int) (err error) { + if c.Memory != 0 || c.MemorySwap != 0 { + dir, err := raw.join("memory", pid) + if err != nil { + return err + } + defer func() { + if err != nil { + os.RemoveAll(dir) + } + }() + + if c.Memory != 0 { + if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil { + return err + } + if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil { + return err + } + } + // By default, MemorySwap is set to twice the size of RAM. + // If you want to omit MemorySwap, set it to `-1'. + if c.MemorySwap != -1 { + if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(c.Memory*2, 10)); err != nil { + return err + } + } + } + return nil +} + +func (raw *rawCgroup) setupCpu(c *Cgroup, pid int) (err error) { + // We always want to join the cpu group, to allow fair cpu scheduling + // on a container basis + dir, err := raw.join("cpu", pid) + if err != nil { + return err + } + if c.CpuShares != 0 { + if err := writeFile(dir, "cpu.shares", strconv.FormatInt(c.CpuShares, 10)); err != nil { + return err + } + } + return nil +} + +func (raw *rawCgroup) Cleanup() error { + get := func(subsystem string) string { + path, _ := raw.path(subsystem) + return path + } + + for _, path := range []string{ + get("memory"), + get("devices"), + get("cpu"), + } { + if path != "" { + os.RemoveAll(path) + } + } + return nil +} diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index b40e1a31fa..f35556f712 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -8,7 +8,6 @@ import ( "io/ioutil" "os" "path/filepath" - "strconv" "strings" ) @@ -22,6 +21,10 @@ type Cgroup struct { CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) } +type ActiveCgroup interface { + Cleanup() error +} + // https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt func FindCgroupMountpoint(subsystem string) (string, error) { mounts, err := mount.GetMounts() @@ -62,48 +65,6 @@ func GetInitCgroupDir(subsystem string) (string, error) { return parseCgroupFile(subsystem, f) } -func (c *Cgroup) Path(root, subsystem string) (string, error) { - cgroup := c.Name - if c.Parent != "" { - cgroup = filepath.Join(c.Parent, cgroup) - } - initPath, err := GetInitCgroupDir(subsystem) - if err != nil { - return "", err - } - return filepath.Join(root, subsystem, initPath, cgroup), nil -} - -func (c *Cgroup) Join(root, subsystem string, pid int) (string, error) { - path, err := c.Path(root, subsystem) - if err != nil { - return "", err - } - if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { - return "", err - } - if err := writeFile(path, "tasks", strconv.Itoa(pid)); err != nil { - return "", err - } - return path, nil -} - -func (c *Cgroup) Cleanup(root string) error { - get := func(subsystem string) string { - path, _ := c.Path(root, subsystem) - return path - } - - for _, path := range []string{ - get("memory"), - get("devices"), - get("cpu"), - } { - os.RemoveAll(path) - } - return nil -} - func parseCgroupFile(subsystem string, r io.Reader) (string, error) { s := bufio.NewScanner(r) for s.Scan() { @@ -125,126 +86,6 @@ func writeFile(dir, file, data string) error { return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) } -func (c *Cgroup) Apply(pid int) error { - // We have two implementation of cgroups support, one is based on - // systemd and the dbus api, and one is based on raw cgroup fs operations - // following the pre-single-writer model docs at: - // http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ - // - // we can pick any subsystem to find the root - cgroupRoot, err := FindCgroupMountpoint("cpu") - if err != nil { - return err - } - cgroupRoot = filepath.Dir(cgroupRoot) - - if _, err := os.Stat(cgroupRoot); err != nil { - return fmt.Errorf("cgroups fs not found") - } - if err := c.setupDevices(cgroupRoot, pid); err != nil { - return err - } - if err := c.setupMemory(cgroupRoot, pid); err != nil { - return err - } - if err := c.setupCpu(cgroupRoot, pid); err != nil { - return err - } - return nil -} - -func (c *Cgroup) setupDevices(cgroupRoot string, pid int) (err error) { - if !c.DeviceAccess { - dir, err := c.Join(cgroupRoot, "devices", pid) - if err != nil { - return err - } - - defer func() { - if err != nil { - os.RemoveAll(dir) - } - }() - - if err := writeFile(dir, "devices.deny", "a"); err != nil { - return err - } - - allow := []string{ - // /dev/null, zero, full - "c 1:3 rwm", - "c 1:5 rwm", - "c 1:7 rwm", - - // consoles - "c 5:1 rwm", - "c 5:0 rwm", - "c 4:0 rwm", - "c 4:1 rwm", - - // /dev/urandom,/dev/random - "c 1:9 rwm", - "c 1:8 rwm", - - // /dev/pts/ - pts namespaces are "coming soon" - "c 136:* rwm", - "c 5:2 rwm", - - // tuntap - "c 10:200 rwm", - } - - for _, val := range allow { - if err := writeFile(dir, "devices.allow", val); err != nil { - return err - } - } - } - return nil -} - -func (c *Cgroup) setupMemory(cgroupRoot string, pid int) (err error) { - if c.Memory != 0 || c.MemorySwap != 0 { - dir, err := c.Join(cgroupRoot, "memory", pid) - if err != nil { - return err - } - defer func() { - if err != nil { - os.RemoveAll(dir) - } - }() - - if c.Memory != 0 { - if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil { - return err - } - if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(c.Memory, 10)); err != nil { - return err - } - } - // By default, MemorySwap is set to twice the size of RAM. - // If you want to omit MemorySwap, set it to `-1'. - if c.MemorySwap != -1 { - if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(c.Memory*2, 10)); err != nil { - return err - } - } - } - return nil -} - -func (c *Cgroup) setupCpu(cgroupRoot string, pid int) (err error) { - // We always want to join the cpu group, to allow fair cpu scheduling - // on a container basis - dir, err := c.Join(cgroupRoot, "cpu", pid) - if err != nil { - return err - } - if c.CpuShares != 0 { - if err := writeFile(dir, "cpu.shares", strconv.FormatInt(c.CpuShares, 10)); err != nil { - return err - } - } - return nil +func (c *Cgroup) Apply(pid int) (ActiveCgroup, error) { + return rawApply(c, pid) } diff --git a/pkg/libcontainer/nsinit/exec.go b/pkg/libcontainer/nsinit/exec.go index 61286cc13c..a44faafe0e 100644 --- a/pkg/libcontainer/nsinit/exec.go +++ b/pkg/libcontainer/nsinit/exec.go @@ -3,6 +3,7 @@ package nsinit import ( + "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/pkg/libcontainer/network" "github.com/dotcloud/docker/pkg/system" @@ -61,10 +62,15 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [ // Do this before syncing with child so that no children // can escape the cgroup ns.logger.Println("setting cgroups") - if err := ns.SetupCgroups(container, command.Process.Pid); err != nil { + activeCgroup, err := ns.SetupCgroups(container, command.Process.Pid) + if err != nil { command.Process.Kill() return -1, err } + if activeCgroup != nil { + defer activeCgroup.Cleanup() + } + ns.logger.Println("setting up network") if err := ns.InitializeNetworking(container, command.Process.Pid, syncPipe); err != nil { command.Process.Kill() @@ -85,13 +91,11 @@ func (ns *linuxNs) Exec(container *libcontainer.Container, term Terminal, args [ return status, err } -func (ns *linuxNs) SetupCgroups(container *libcontainer.Container, nspid int) error { +func (ns *linuxNs) SetupCgroups(container *libcontainer.Container, nspid int) (cgroups.ActiveCgroup, error) { if container.Cgroups != nil { - if err := container.Cgroups.Apply(nspid); err != nil { - return err - } + return container.Cgroups.Apply(nspid) } - return nil + return nil, nil } func (ns *linuxNs) InitializeNetworking(container *libcontainer.Container, nspid int, pipe *SyncPipe) error { From 9294d7f2af6ecb7c18be11fb5043fad4a61d8f09 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 14 Mar 2014 11:45:29 +0100 Subject: [PATCH 46/50] cgroups: Join groups by writing to cgroups.procs, not tasks cgroups.procs moves all the threads of the process, and "tasks" just the one thread. I believe there is a risk that we move the main thread, but then we accidentally fork off one of the other threads if the go scheduler randomly switched to another thread. So, it seems safer (and more correct) to use cgroups.procs. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/cgroups/apply_raw.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/cgroups/apply_raw.go b/pkg/cgroups/apply_raw.go index bce96f4951..47a2a002b8 100644 --- a/pkg/cgroups/apply_raw.go +++ b/pkg/cgroups/apply_raw.go @@ -68,7 +68,7 @@ func (raw *rawCgroup) join(subsystem string, pid int) (string, error) { if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { return "", err } - if err := writeFile(path, "tasks", strconv.Itoa(pid)); err != nil { + if err := writeFile(path, "cgroup.procs", strconv.Itoa(pid)); err != nil { return "", err } return path, nil From d4725801b3401d04b3f35b5783bdc0fc362f7f00 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 21 Feb 2014 10:34:06 +0100 Subject: [PATCH 47/50] Vendor github.com/godbus/dbus and github.com/coreos/go-systemd We need this to do systemd API calls. We also add the static_build tag to make godbus not use os/user which is problematic for static builds. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- hack/make.sh | 2 +- hack/vendor.sh | 3 + .../github.com/coreos/go-systemd/.travis.yml | 8 + .../src/github.com/coreos/go-systemd/LICENSE | 191 ++++ .../github.com/coreos/go-systemd/README.md | 44 + .../coreos/go-systemd/activation/files.go | 56 ++ .../go-systemd/activation/files_test.go | 84 ++ .../coreos/go-systemd/activation/listeners.go | 38 + .../go-systemd/activation/listeners_test.go | 88 ++ .../github.com/coreos/go-systemd/dbus/dbus.go | 104 +++ .../coreos/go-systemd/dbus/dbus_test.go | 41 + .../coreos/go-systemd/dbus/methods.go | 354 ++++++++ .../coreos/go-systemd/dbus/methods_test.go | 314 +++++++ .../coreos/go-systemd/dbus/properties.go | 220 +++++ .../github.com/coreos/go-systemd/dbus/set.go | 26 + .../coreos/go-systemd/dbus/set_test.go | 26 + .../coreos/go-systemd/dbus/subscription.go | 249 ++++++ .../go-systemd/dbus/subscription_set.go | 32 + .../go-systemd/dbus/subscription_set_test.go | 67 ++ .../go-systemd/dbus/subscription_test.go | 90 ++ .../examples/activation/activation.go | 44 + .../examples/activation/httpserver/README.md | 19 + .../activation/httpserver/hello.service | 11 + .../activation/httpserver/hello.socket | 5 + .../activation/httpserver/httpserver.go | 26 + .../go-systemd/examples/activation/listen.go | 50 ++ .../go-systemd/fixtures/start-stop.service | 5 + .../fixtures/subscribe-events-set.service | 5 + .../fixtures/subscribe-events.service | 5 + .../coreos/go-systemd/journal/send.go | 168 ++++ vendor/src/github.com/coreos/go-systemd/test | 3 + vendor/src/github.com/godbus/dbus/LICENSE | 25 + .../github.com/godbus/dbus/README.markdown | 38 + .../godbus/dbus/_examples/eavesdrop.go | 30 + .../godbus/dbus/_examples/introspect.go | 21 + .../godbus/dbus/_examples/list-names.go | 27 + .../godbus/dbus/_examples/notification.go | 17 + .../github.com/godbus/dbus/_examples/prop.go | 68 ++ .../godbus/dbus/_examples/server.go | 45 + .../godbus/dbus/_examples/signal.go | 24 + vendor/src/github.com/godbus/dbus/auth.go | 253 ++++++ .../github.com/godbus/dbus/auth_external.go | 26 + .../src/github.com/godbus/dbus/auth_sha1.go | 102 +++ vendor/src/github.com/godbus/dbus/call.go | 147 ++++ vendor/src/github.com/godbus/dbus/conn.go | 601 +++++++++++++ .../src/github.com/godbus/dbus/conn_darwin.go | 21 + .../src/github.com/godbus/dbus/conn_other.go | 27 + .../src/github.com/godbus/dbus/conn_test.go | 199 +++++ vendor/src/github.com/godbus/dbus/dbus.go | 258 ++++++ vendor/src/github.com/godbus/dbus/decoder.go | 228 +++++ vendor/src/github.com/godbus/dbus/doc.go | 63 ++ vendor/src/github.com/godbus/dbus/encoder.go | 179 ++++ .../github.com/godbus/dbus/examples_test.go | 50 ++ vendor/src/github.com/godbus/dbus/export.go | 302 +++++++ vendor/src/github.com/godbus/dbus/homedir.go | 28 + .../github.com/godbus/dbus/homedir_dynamic.go | 15 + .../github.com/godbus/dbus/homedir_static.go | 45 + .../github.com/godbus/dbus/introspect/call.go | 27 + .../godbus/dbus/introspect/introspect.go | 80 ++ .../godbus/dbus/introspect/introspectable.go | 74 ++ vendor/src/github.com/godbus/dbus/message.go | 346 ++++++++ .../src/github.com/godbus/dbus/prop/prop.go | 264 ++++++ .../src/github.com/godbus/dbus/proto_test.go | 369 ++++++++ vendor/src/github.com/godbus/dbus/sig.go | 257 ++++++ vendor/src/github.com/godbus/dbus/sig_test.go | 70 ++ .../godbus/dbus/transport_darwin.go | 6 + .../godbus/dbus/transport_generic.go | 35 + .../github.com/godbus/dbus/transport_unix.go | 190 ++++ .../godbus/dbus/transport_unix_test.go | 49 ++ .../godbus/dbus/transport_unixcred.go | 22 + vendor/src/github.com/godbus/dbus/variant.go | 129 +++ .../github.com/godbus/dbus/variant_lexer.go | 284 ++++++ .../github.com/godbus/dbus/variant_parser.go | 817 ++++++++++++++++++ .../github.com/godbus/dbus/variant_test.go | 78 ++ 74 files changed, 8313 insertions(+), 1 deletion(-) create mode 100644 vendor/src/github.com/coreos/go-systemd/.travis.yml create mode 100644 vendor/src/github.com/coreos/go-systemd/LICENSE create mode 100644 vendor/src/github.com/coreos/go-systemd/README.md create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/files.go create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/files_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/listeners.go create mode 100644 vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/dbus.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/dbus_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/methods.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/properties.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/set.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/set_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/subscription.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/subscription_set.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go create mode 100644 vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go create mode 100644 vendor/src/github.com/coreos/go-systemd/fixtures/start-stop.service create mode 100644 vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service create mode 100644 vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events.service create mode 100644 vendor/src/github.com/coreos/go-systemd/journal/send.go create mode 100755 vendor/src/github.com/coreos/go-systemd/test create mode 100644 vendor/src/github.com/godbus/dbus/LICENSE create mode 100644 vendor/src/github.com/godbus/dbus/README.markdown create mode 100644 vendor/src/github.com/godbus/dbus/_examples/eavesdrop.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/introspect.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/list-names.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/notification.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/prop.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/server.go create mode 100644 vendor/src/github.com/godbus/dbus/_examples/signal.go create mode 100644 vendor/src/github.com/godbus/dbus/auth.go create mode 100644 vendor/src/github.com/godbus/dbus/auth_external.go create mode 100644 vendor/src/github.com/godbus/dbus/auth_sha1.go create mode 100644 vendor/src/github.com/godbus/dbus/call.go create mode 100644 vendor/src/github.com/godbus/dbus/conn.go create mode 100644 vendor/src/github.com/godbus/dbus/conn_darwin.go create mode 100644 vendor/src/github.com/godbus/dbus/conn_other.go create mode 100644 vendor/src/github.com/godbus/dbus/conn_test.go create mode 100644 vendor/src/github.com/godbus/dbus/dbus.go create mode 100644 vendor/src/github.com/godbus/dbus/decoder.go create mode 100644 vendor/src/github.com/godbus/dbus/doc.go create mode 100644 vendor/src/github.com/godbus/dbus/encoder.go create mode 100644 vendor/src/github.com/godbus/dbus/examples_test.go create mode 100644 vendor/src/github.com/godbus/dbus/export.go create mode 100644 vendor/src/github.com/godbus/dbus/homedir.go create mode 100644 vendor/src/github.com/godbus/dbus/homedir_dynamic.go create mode 100644 vendor/src/github.com/godbus/dbus/homedir_static.go create mode 100644 vendor/src/github.com/godbus/dbus/introspect/call.go create mode 100644 vendor/src/github.com/godbus/dbus/introspect/introspect.go create mode 100644 vendor/src/github.com/godbus/dbus/introspect/introspectable.go create mode 100644 vendor/src/github.com/godbus/dbus/message.go create mode 100644 vendor/src/github.com/godbus/dbus/prop/prop.go create mode 100644 vendor/src/github.com/godbus/dbus/proto_test.go create mode 100644 vendor/src/github.com/godbus/dbus/sig.go create mode 100644 vendor/src/github.com/godbus/dbus/sig_test.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_darwin.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_generic.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_unix.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_unix_test.go create mode 100644 vendor/src/github.com/godbus/dbus/transport_unixcred.go create mode 100644 vendor/src/github.com/godbus/dbus/variant.go create mode 100644 vendor/src/github.com/godbus/dbus/variant_lexer.go create mode 100644 vendor/src/github.com/godbus/dbus/variant_parser.go create mode 100644 vendor/src/github.com/godbus/dbus/variant_test.go diff --git a/hack/make.sh b/hack/make.sh index b77e9b7f44..dbb9dbfdfd 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -89,7 +89,7 @@ LDFLAGS=' ' LDFLAGS_STATIC='-linkmode external' EXTLDFLAGS_STATIC='-static' -BUILDFLAGS=( -a -tags "netgo $DOCKER_BUILDTAGS" ) +BUILDFLAGS=( -a -tags "netgo static_build $DOCKER_BUILDTAGS" ) # A few more flags that are specific just to building a completely-static binary (see hack/make/binary) # PLEASE do not use these anywhere else. diff --git a/hack/vendor.sh b/hack/vendor.sh index ac996dde12..4200d90867 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -58,3 +58,6 @@ mv src/code.google.com/p/go/src/pkg/archive/tar tmp-tar rm -rf src/code.google.com/p/go mkdir -p src/code.google.com/p/go/src/pkg/archive mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar + +clone git github.com/godbus/dbus cb98efbb933d8389ab549a060e880ea3c375d213 +clone git github.com/coreos/go-systemd 4c14ed39b8a643ac44b4f95b5a53c00e94261475 diff --git a/vendor/src/github.com/coreos/go-systemd/.travis.yml b/vendor/src/github.com/coreos/go-systemd/.travis.yml new file mode 100644 index 0000000000..8c9f56e44a --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/.travis.yml @@ -0,0 +1,8 @@ +language: go +go: 1.2 + +install: + - echo "Skip install" + +script: + - ./test diff --git a/vendor/src/github.com/coreos/go-systemd/LICENSE b/vendor/src/github.com/coreos/go-systemd/LICENSE new file mode 100644 index 0000000000..37ec93a14f --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/src/github.com/coreos/go-systemd/README.md b/vendor/src/github.com/coreos/go-systemd/README.md new file mode 100644 index 0000000000..0ee09fec0a --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/README.md @@ -0,0 +1,44 @@ +# go-systemd + +Go bindings to systemd. The project has three packages: + +- activation - for writing and using socket activation from Go +- journal - for writing to systemd's logging service, journal +- dbus - for starting/stopping/inspecting running services and units + +Go docs for the entire project are here: + +http://godoc.org/github.com/coreos/go-systemd + +## Socket Activation + +An example HTTP server using socket activation can be quickly setup by +following this README on a Linux machine running systemd: + +https://github.com/coreos/go-systemd/tree/master/examples/activation/httpserver + +## Journal + +Using this package you can submit journal entries directly to systemd's journal taking advantage of features like indexed key/value pairs for each log entry. + +## D-Bus + +The D-Bus API lets you start, stop and introspect systemd units. The API docs are here: + +http://godoc.org/github.com/coreos/go-systemd/dbus + +### Debugging + +Create `/etc/dbus-1/system-local.conf` that looks like this: + +``` + + + + + + + +``` diff --git a/vendor/src/github.com/coreos/go-systemd/activation/files.go b/vendor/src/github.com/coreos/go-systemd/activation/files.go new file mode 100644 index 0000000000..74b4fc10f3 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/files.go @@ -0,0 +1,56 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package activation implements primitives for systemd socket activation. +package activation + +import ( + "os" + "strconv" + "syscall" +) + +// based on: https://gist.github.com/alberts/4640792 +const ( + listenFdsStart = 3 +) + +func Files(unsetEnv bool) []*os.File { + if unsetEnv { + // there is no way to unset env in golang os package for now + // https://code.google.com/p/go/issues/detail?id=6423 + defer os.Setenv("LISTEN_PID", "") + defer os.Setenv("LISTEN_FDS", "") + } + + pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) + if err != nil || pid != os.Getpid() { + return nil + } + + nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) + if err != nil || nfds == 0 { + return nil + } + + var files []*os.File + for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { + syscall.CloseOnExec(fd) + files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) + } + + return files +} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/files_test.go b/vendor/src/github.com/coreos/go-systemd/activation/files_test.go new file mode 100644 index 0000000000..a1c6948fb2 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/files_test.go @@ -0,0 +1,84 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package activation + +import ( + "bytes" + "io" + "os" + "os/exec" + "testing" +) + +// correctStringWritten fails the text if the correct string wasn't written +// to the other side of the pipe. +func correctStringWritten(t *testing.T, r *os.File, expected string) bool { + bytes := make([]byte, len(expected)) + io.ReadAtLeast(r, bytes, len(expected)) + + if string(bytes) != expected { + t.Fatalf("Unexpected string %s", string(bytes)) + } + + return true +} + +// TestActivation forks out a copy of activation.go example and reads back two +// strings from the pipes that are passed in. +func TestActivation(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + + r1, w1, _ := os.Pipe() + r2, w2, _ := os.Pipe() + cmd.ExtraFiles = []*os.File{ + w1, + w2, + } + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") + + err := cmd.Run() + if err != nil { + t.Fatalf(err.Error()) + } + + correctStringWritten(t, r1, "Hello world") + correctStringWritten(t, r2, "Goodbye world") +} + +func TestActivationNoFix(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2") + + out, _ := cmd.CombinedOutput() + if bytes.Contains(out, []byte("No files")) == false { + t.Fatalf("Child didn't error out as expected") + } +} + +func TestActivationNoFiles(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/activation.go") + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=0", "FIX_LISTEN_PID=1") + + out, _ := cmd.CombinedOutput() + if bytes.Contains(out, []byte("No files")) == false { + t.Fatalf("Child didn't error out as expected") + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/listeners.go b/vendor/src/github.com/coreos/go-systemd/activation/listeners.go new file mode 100644 index 0000000000..cdb2cf4bb4 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/listeners.go @@ -0,0 +1,38 @@ +/* +Copyright 2014 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package activation + +import ( + "fmt" + "net" +) + +// Listeners returns net.Listeners for all socket activated fds passed to this process. +func Listeners(unsetEnv bool) ([]net.Listener, error) { + files := Files(unsetEnv) + listeners := make([]net.Listener, len(files)) + + for i, f := range files { + var err error + listeners[i], err = net.FileListener(f) + if err != nil { + return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) + } + } + + return listeners, nil +} diff --git a/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go b/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go new file mode 100644 index 0000000000..c3627d6d4d --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/activation/listeners_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2014 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package activation + +import ( + "io" + "net" + "os" + "os/exec" + "testing" +) + +// correctStringWritten fails the text if the correct string wasn't written +// to the other side of the pipe. +func correctStringWrittenNet(t *testing.T, r net.Conn, expected string) bool { + bytes := make([]byte, len(expected)) + io.ReadAtLeast(r, bytes, len(expected)) + + if string(bytes) != expected { + t.Fatalf("Unexpected string %s", string(bytes)) + } + + return true +} + +// TestActivation forks out a copy of activation.go example and reads back two +// strings from the pipes that are passed in. +func TestListeners(t *testing.T) { + cmd := exec.Command("go", "run", "../examples/activation/listen.go") + + l1, err := net.Listen("tcp", ":9999") + if err != nil { + t.Fatalf(err.Error()) + } + l2, err := net.Listen("tcp", ":1234") + if err != nil { + t.Fatalf(err.Error()) + } + + t1 := l1.(*net.TCPListener) + t2 := l2.(*net.TCPListener) + + f1, _ := t1.File() + f2, _ := t2.File() + + cmd.ExtraFiles = []*os.File{ + f1, + f2, + } + + r1, err := net.Dial("tcp", "127.0.0.1:9999") + if err != nil { + t.Fatalf(err.Error()) + } + r1.Write([]byte("Hi")) + + r2, err := net.Dial("tcp", "127.0.0.1:1234") + if err != nil { + t.Fatalf(err.Error()) + } + r2.Write([]byte("Hi")) + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "LISTEN_FDS=2", "FIX_LISTEN_PID=1") + + out, err := cmd.Output() + if err != nil { + println(string(out)) + t.Fatalf(err.Error()) + } + + correctStringWrittenNet(t, r1, "Hello world") + correctStringWrittenNet(t, r2, "Goodbye world") +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/dbus.go b/vendor/src/github.com/coreos/go-systemd/dbus/dbus.go new file mode 100644 index 0000000000..91d7112145 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/dbus.go @@ -0,0 +1,104 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ +package dbus + +import ( + "os" + "strconv" + "strings" + "sync" + + "github.com/godbus/dbus" +) + +const signalBuffer = 100 + +// ObjectPath creates a dbus.ObjectPath using the rules that systemd uses for +// serializing special characters. +func ObjectPath(path string) dbus.ObjectPath { + path = strings.Replace(path, ".", "_2e", -1) + path = strings.Replace(path, "-", "_2d", -1) + path = strings.Replace(path, "@", "_40", -1) + + return dbus.ObjectPath(path) +} + +// Conn is a connection to systemds dbus endpoint. +type Conn struct { + sysconn *dbus.Conn + sysobj *dbus.Object + jobListener struct { + jobs map[dbus.ObjectPath]chan string + sync.Mutex + } + subscriber struct { + updateCh chan<- *SubStateUpdate + errCh chan<- error + sync.Mutex + ignore map[dbus.ObjectPath]int64 + cleanIgnore int64 + } + dispatch map[string]func(dbus.Signal) +} + +// New() establishes a connection to the system bus and authenticates. +func New() (*Conn, error) { + c := new(Conn) + + if err := c.initConnection(); err != nil { + return nil, err + } + + c.initJobs() + return c, nil +} + +func (c *Conn) initConnection() error { + var err error + c.sysconn, err = dbus.SystemBusPrivate() + if err != nil { + return err + } + + // Only use EXTERNAL method, and hardcode the uid (not username) + // to avoid a username lookup (which requires a dynamically linked + // libc) + methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} + + err = c.sysconn.Auth(methods) + if err != nil { + c.sysconn.Close() + return err + } + + err = c.sysconn.Hello() + if err != nil { + c.sysconn.Close() + return err + } + + c.sysobj = c.sysconn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1")) + + // Setup the listeners on jobs so that we can get completions + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'") + c.initSubscription() + c.initDispatch() + + return nil +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/dbus_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/dbus_test.go new file mode 100644 index 0000000000..2e80f73ef7 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/dbus_test.go @@ -0,0 +1,41 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "testing" +) + +// TestObjectPath ensures path encoding of the systemd rules works. +func TestObjectPath(t *testing.T) { + input := "/silly-path/to@a/unit..service" + output := ObjectPath(input) + expected := "/silly_2dpath/to_40a/unit_2e_2eservice" + + if string(output) != expected { + t.Fatalf("Output '%s' did not match expected '%s'", output, expected) + } +} + +// TestNew ensures that New() works without errors. +func TestNew(t *testing.T) { + _, err := New() + + if err != nil { + t.Fatal(err) + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/methods.go b/vendor/src/github.com/coreos/go-systemd/dbus/methods.go new file mode 100644 index 0000000000..11d5cda945 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/methods.go @@ -0,0 +1,354 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "errors" + "github.com/godbus/dbus" +) + +func (c *Conn) initJobs() { + c.jobListener.jobs = make(map[dbus.ObjectPath]chan string) +} + +func (c *Conn) jobComplete(signal *dbus.Signal) { + var id uint32 + var job dbus.ObjectPath + var unit string + var result string + dbus.Store(signal.Body, &id, &job, &unit, &result) + c.jobListener.Lock() + out, ok := c.jobListener.jobs[job] + if ok { + out <- result + delete(c.jobListener.jobs, job) + } + c.jobListener.Unlock() +} + +func (c *Conn) startJob(job string, args ...interface{}) (<-chan string, error) { + c.jobListener.Lock() + defer c.jobListener.Unlock() + + ch := make(chan string, 1) + var path dbus.ObjectPath + err := c.sysobj.Call(job, 0, args...).Store(&path) + if err != nil { + return nil, err + } + c.jobListener.jobs[path] = ch + return ch, nil +} + +func (c *Conn) runJob(job string, args ...interface{}) (string, error) { + respCh, err := c.startJob(job, args...) + if err != nil { + return "", err + } + return <-respCh, nil +} + +// StartUnit enqeues a start job and depending jobs, if any (unless otherwise +// specified by the mode string). +// +// Takes the unit to activate, plus a mode string. The mode needs to be one of +// replace, fail, isolate, ignore-dependencies, ignore-requirements. If +// "replace" the call will start the unit and its dependencies, possibly +// replacing already queued jobs that conflict with this. If "fail" the call +// will start the unit and its dependencies, but will fail if this would change +// an already queued job. If "isolate" the call will start the unit in question +// and terminate all units that aren't dependencies of it. If +// "ignore-dependencies" it will start a unit but ignore all its dependencies. +// If "ignore-requirements" it will start a unit but only ignore the +// requirement dependencies. It is not recommended to make use of the latter +// two options. +// +// Result string: one of done, canceled, timeout, failed, dependency, skipped. +// done indicates successful execution of a job. canceled indicates that a job +// has been canceled before it finished execution. timeout indicates that the +// job timeout was reached. failed indicates that the job failed. dependency +// indicates that a job this job has been depending on failed and the job hence +// has been removed too. skipped indicates that a job was skipped because it +// didn't apply to the units current state. +func (c *Conn) StartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.StartUnit", name, mode) +} + +// StopUnit is similar to StartUnit but stops the specified unit rather +// than starting it. +func (c *Conn) StopUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.StopUnit", name, mode) +} + +// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise. +func (c *Conn) ReloadUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) +} + +// RestartUnit restarts a service. If a service is restarted that isn't +// running it will be started. +func (c *Conn) RestartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.RestartUnit", name, mode) +} + +// TryRestartUnit is like RestartUnit, except that a service that isn't running +// is not affected by the restart. +func (c *Conn) TryRestartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) +} + +// ReloadOrRestart attempts a reload if the unit supports it and use a restart +// otherwise. +func (c *Conn) ReloadOrRestartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) +} + +// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try" +// flavored restart otherwise. +func (c *Conn) ReloadOrTryRestartUnit(name string, mode string) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) +} + +// StartTransientUnit() may be used to create and start a transient unit, which +// will be released as soon as it is not running or referenced anymore or the +// system is rebooted. name is the unit name including suffix, and must be +// unique. mode is the same as in StartUnit(), properties contains properties +// of the unit. +func (c *Conn) StartTransientUnit(name string, mode string, properties ...Property) (string, error) { + return c.runJob("org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) +} + +// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's +// processes are killed. +func (c *Conn) KillUnit(name string, signal int32) { + c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store() +} + +// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface +func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) { + var err error + var props map[string]dbus.Variant + + path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit) + if !path.IsValid() { + return nil, errors.New("invalid unit name: " + unit) + } + + obj := c.sysconn.Object("org.freedesktop.systemd1", path) + err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props) + if err != nil { + return nil, err + } + + out := make(map[string]interface{}, len(props)) + for k, v := range props { + out[k] = v.Value() + } + + return out, nil +} + +// GetUnitProperties takes the unit name and returns all of its dbus object properties. +func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) { + return c.getProperties(unit, "org.freedesktop.systemd1.Unit") +} + +func (c *Conn) getProperty(unit string, dbusInterface string, propertyName string) (*Property, error) { + var err error + var prop dbus.Variant + + path := ObjectPath("/org/freedesktop/systemd1/unit/" + unit) + if !path.IsValid() { + return nil, errors.New("invalid unit name: " + unit) + } + + obj := c.sysconn.Object("org.freedesktop.systemd1", path) + err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop) + if err != nil { + return nil, err + } + + return &Property{Name: propertyName, Value: prop}, nil +} + +func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) { + return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName) +} + +// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type. +// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope +// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit +func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) { + return c.getProperties(unit, "org.freedesktop.systemd1."+unitType) +} + +// SetUnitProperties() may be used to modify certain unit properties at runtime. +// Not all properties may be changed at runtime, but many resource management +// settings (primarily those in systemd.cgroup(5)) may. The changes are applied +// instantly, and stored on disk for future boots, unless runtime is true, in which +// case the settings only apply until the next reboot. name is the name of the unit +// to modify. properties are the settings to set, encoded as an array of property +// name and value pairs. +func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error { + return c.sysobj.Call("SetUnitProperties", 0, name, runtime, properties).Store() +} + +func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { + return c.getProperty(unit, "org.freedesktop.systemd1." + unitType, propertyName) +} + +// ListUnits returns an array with all currently loaded units. Note that +// units may be known by multiple names at the same time, and hence there might +// be more unit names loaded than actual units behind them. +func (c *Conn) ListUnits() ([]UnitStatus, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnits", 0).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + status := make([]UnitStatus, len(result)) + statusInterface := make([]interface{}, len(status)) + for i := range status { + statusInterface[i] = &status[i] + } + + err = dbus.Store(resultInterface, statusInterface...) + if err != nil { + return nil, err + } + + return status, nil +} + +type UnitStatus struct { + Name string // The primary unit name as string + Description string // The human readable description string + LoadState string // The load state (i.e. whether the unit file has been loaded successfully) + ActiveState string // The active state (i.e. whether the unit is currently started or not) + SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not) + Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string. + Path dbus.ObjectPath // The unit object path + JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise + JobType string // The job type as string + JobPath dbus.ObjectPath // The job object path +} + +// EnableUnitFiles() may be used to enable one or more units in the system (by +// creating symlinks to them in /etc or /run). +// +// It takes a list of unit files to enable (either just file names or full +// absolute paths if the unit files are residing outside the usual unit +// search paths), and two booleans: the first controls whether the unit shall +// be enabled for runtime only (true, /run), or persistently (false, /etc). +// The second one controls whether symlinks pointing to other units shall +// be replaced if necessary. +// +// This call returns one boolean and an array with the changes made. The +// boolean signals whether the unit files contained any enablement +// information (i.e. an [Install]) section. The changes list consists of +// structures with three strings: the type of the change (one of symlink +// or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { + var carries_install_info bool + + result := make([][]interface{}, 0) + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result) + if err != nil { + return false, nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]EnableUnitFileChange, len(result)) + changesInterface := make([]interface{}, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return false, nil, err + } + + return carries_install_info, changes, nil +} + +type EnableUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// DisableUnitFiles() may be used to disable one or more units in the system (by +// removing symlinks to them from /etc or /run). +// +// It takes a list of unit files to disable (either just file names or full +// absolute paths if the unit files are residing outside the usual unit +// search paths), and one boolean: whether the unit was enabled for runtime +// only (true, /run), or persistently (false, /etc). +// +// This call returns an array with the changes made. The changes list +// consists of structures with three strings: the type of the change (one of +// symlink or unlink), the file name of the symlink and the destination of the +// symlink. +func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) { + result := make([][]interface{}, 0) + err := c.sysobj.Call("DisableUnitFiles", 0, files, runtime).Store(&result) + if err != nil { + return nil, err + } + + resultInterface := make([]interface{}, len(result)) + for i := range result { + resultInterface[i] = result[i] + } + + changes := make([]DisableUnitFileChange, len(result)) + changesInterface := make([]interface{}, len(changes)) + for i := range changes { + changesInterface[i] = &changes[i] + } + + err = dbus.Store(resultInterface, changesInterface...) + if err != nil { + return nil, err + } + + return changes, nil +} + +type DisableUnitFileChange struct { + Type string // Type of the change (one of symlink or unlink) + Filename string // File name of the symlink + Destination string // Destination of the symlink +} + +// Reload instructs systemd to scan for and reload unit files. This is +// equivalent to a 'systemctl daemon-reload'. +func (c *Conn) Reload() error { + return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store() +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go new file mode 100644 index 0000000000..9e2f22323f --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/methods_test.go @@ -0,0 +1,314 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "fmt" + "github.com/guelfey/go.dbus" + "math/rand" + "os" + "path/filepath" + "reflect" + "testing" +) + +func setupConn(t *testing.T) *Conn { + conn, err := New() + if err != nil { + t.Fatal(err) + } + + return conn +} + +func setupUnit(target string, conn *Conn, t *testing.T) { + // Blindly stop the unit in case it is running + conn.StopUnit(target, "replace") + + // Blindly remove the symlink in case it exists + targetRun := filepath.Join("/run/systemd/system/", target) + err := os.Remove(targetRun) + + // 1. Enable the unit + abs, err := filepath.Abs("../fixtures/" + target) + if err != nil { + t.Fatal(err) + } + + fixture := []string{abs} + + install, changes, err := conn.EnableUnitFiles(fixture, true, true) + if err != nil { + t.Fatal(err) + } + + if install != false { + t.Fatal("Install was true") + } + + if len(changes) < 1 { + t.Fatalf("Expected one change, got %v", changes) + } + + if changes[0].Filename != targetRun { + t.Fatal("Unexpected target filename") + } +} + +// Ensure that basic unit starting and stopping works. +func TestStartStopUnit(t *testing.T) { + target := "start-stop.service" + conn := setupConn(t) + + setupUnit(target, conn, t) + + // 2. Start the unit + job, err := conn.StartUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + if job != "done" { + t.Fatal("Job is not done, %v", job) + } + + units, err := conn.ListUnits() + + var unit *UnitStatus + for _, u := range units { + if u.Name == target { + unit = &u + } + } + + if unit == nil { + t.Fatalf("Test unit not found in list") + } + + if unit.ActiveState != "active" { + t.Fatalf("Test unit not active") + } + + // 3. Stop the unit + job, err = conn.StopUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + units, err = conn.ListUnits() + + unit = nil + for _, u := range units { + if u.Name == target { + unit = &u + } + } + + if unit != nil { + t.Fatalf("Test unit found in list, should be stopped") + } +} + +// Enables a unit and then immediately tears it down +func TestEnableDisableUnit(t *testing.T) { + target := "enable-disable.service" + conn := setupConn(t) + + setupUnit(target, conn, t) + + abs, err := filepath.Abs("../fixtures/" + target) + if err != nil { + t.Fatal(err) + } + + path := filepath.Join("/run/systemd/system/", target) + + // 2. Disable the unit + changes, err := conn.DisableUnitFiles([]string{abs}, true) + if err != nil { + t.Fatal(err) + } + + if len(changes) != 1 { + t.Fatalf("Changes should include the path, %v", changes) + } + if changes[0].Filename != path { + t.Fatalf("Change should include correct filename, %+v", changes[0]) + } + if changes[0].Destination != "" { + t.Fatalf("Change destination should be empty, %+v", changes[0]) + } +} + +// TestGetUnitProperties reads the `-.mount` which should exist on all systemd +// systems and ensures that one of its properties is valid. +func TestGetUnitProperties(t *testing.T) { + conn := setupConn(t) + + unit := "-.mount" + + info, err := conn.GetUnitProperties(unit) + if err != nil { + t.Fatal(err) + } + + names := info["Wants"].([]string) + + if len(names) < 1 { + t.Fatal("/ is unwanted") + } + + if names[0] != "system.slice" { + t.Fatal("unexpected wants for /") + } + + prop, err := conn.GetUnitProperty(unit, "Wants") + if err != nil { + t.Fatal(err) + } + + if prop.Name != "Wants" { + t.Fatal("unexpected property name") + } + + val := prop.Value.Value().([]string) + if !reflect.DeepEqual(val, names) { + t.Fatal("unexpected property value") + } +} + +// TestGetUnitPropertiesRejectsInvalidName attempts to get the properties for a +// unit with an invalid name. This test should be run with --test.timeout set, +// as a fail will manifest as GetUnitProperties hanging indefinitely. +func TestGetUnitPropertiesRejectsInvalidName(t *testing.T) { + conn := setupConn(t) + + unit := "//invalid#$^/" + + _, err := conn.GetUnitProperties(unit) + if err == nil { + t.Fatal("Expected an error, got nil") + } + + _, err = conn.GetUnitProperty(unit, "Wants") + if err == nil { + t.Fatal("Expected an error, got nil") + } +} + +// TestSetUnitProperties changes a cgroup setting on the `tmp.mount` +// which should exist on all systemd systems and ensures that the +// property was set. +func TestSetUnitProperties(t *testing.T) { + conn := setupConn(t) + + unit := "tmp.mount" + + if err := conn.SetUnitProperties(unit, true, Property{"CPUShares", dbus.MakeVariant(uint64(1023))}); err != nil { + t.Fatal(err) + } + + info, err := conn.GetUnitTypeProperties(unit, "Mount") + if err != nil { + t.Fatal(err) + } + + value := info["CPUShares"].(uint64) + if value != 1023 { + t.Fatal("CPUShares of unit is not 1023, %s", value) + } +} + +// Ensure that basic transient unit starting and stopping works. +func TestStartStopTransientUnit(t *testing.T) { + conn := setupConn(t) + + props := []Property{ + PropExecStart([]string{"/bin/sleep", "400"}, false), + } + target := fmt.Sprintf("testing-transient-%d.service", rand.Int()) + + // Start the unit + job, err := conn.StartTransientUnit(target, "replace", props...) + if err != nil { + t.Fatal(err) + } + + if job != "done" { + t.Fatal("Job is not done, %v", job) + } + + units, err := conn.ListUnits() + + var unit *UnitStatus + for _, u := range units { + if u.Name == target { + unit = &u + } + } + + if unit == nil { + t.Fatalf("Test unit not found in list") + } + + if unit.ActiveState != "active" { + t.Fatalf("Test unit not active") + } + + // 3. Stop the unit + job, err = conn.StopUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + units, err = conn.ListUnits() + + unit = nil + for _, u := range units { + if u.Name == target { + unit = &u + } + } + + if unit != nil { + t.Fatalf("Test unit found in list, should be stopped") + } +} + +func TestConnJobListener(t *testing.T) { + target := "start-stop.service" + conn := setupConn(t) + + setupUnit(target, conn, t) + + jobSize := len(conn.jobListener.jobs) + + _, err := conn.StartUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + _, err = conn.StopUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + currentJobSize := len(conn.jobListener.jobs) + if jobSize != currentJobSize { + t.Fatal("JobListener jobs leaked") + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/properties.go b/vendor/src/github.com/coreos/go-systemd/dbus/properties.go new file mode 100644 index 0000000000..a06ccda761 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/properties.go @@ -0,0 +1,220 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "github.com/godbus/dbus" +) + +// From the systemd docs: +// +// The properties array of StartTransientUnit() may take many of the settings +// that may also be configured in unit files. Not all parameters are currently +// accepted though, but we plan to cover more properties with future release. +// Currently you may set the Description, Slice and all dependency types of +// units, as well as RemainAfterExit, ExecStart for service units, +// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares, +// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth, +// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit, +// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map +// directly to their counterparts in unit files and as normal D-Bus object +// properties. The exception here is the PIDs field of scope units which is +// used for construction of the scope only and specifies the initial PIDs to +// add to the scope object. + +type Property struct { + Name string + Value dbus.Variant +} + +type PropertyCollection struct { + Name string + Properties []Property +} + +type execStart struct { + Path string // the binary path to execute + Args []string // an array with all arguments to pass to the executed command, starting with argument 0 + UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly +} + +// PropExecStart sets the ExecStart service property. The first argument is a +// slice with the binary path to execute followed by the arguments to pass to +// the executed command. See +// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= +func PropExecStart(command []string, uncleanIsFailure bool) Property { + execStarts := []execStart{ + execStart{ + Path: command[0], + Args: command, + UncleanIsFailure: uncleanIsFailure, + }, + } + + return Property{ + Name: "ExecStart", + Value: dbus.MakeVariant(execStarts), + } +} + +// PropRemainAfterExit sets the RemainAfterExit service property. See +// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit= +func PropRemainAfterExit(b bool) Property { + return Property{ + Name: "RemainAfterExit", + Value: dbus.MakeVariant(b), + } +} + +// PropDescription sets the Description unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description= +func PropDescription(desc string) Property { + return Property{ + Name: "Description", + Value: dbus.MakeVariant(desc), + } +} + +func propDependency(name string, units []string) Property { + return Property{ + Name: name, + Value: dbus.MakeVariant(units), + } +} + +// PropRequires sets the Requires unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires= +func PropRequires(units ...string) Property { + return propDependency("Requires", units) +} + +// PropRequiresOverridable sets the RequiresOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable= +func PropRequiresOverridable(units ...string) Property { + return propDependency("RequiresOverridable", units) +} + +// PropRequisite sets the Requisite unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite= +func PropRequisite(units ...string) Property { + return propDependency("Requisite", units) +} + +// PropRequisiteOverridable sets the RequisiteOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable= +func PropRequisiteOverridable(units ...string) Property { + return propDependency("RequisiteOverridable", units) +} + +// PropWants sets the Wants unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants= +func PropWants(units ...string) Property { + return propDependency("Wants", units) +} + +// PropBindsTo sets the BindsTo unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo= +func PropBindsTo(units ...string) Property { + return propDependency("BindsTo", units) +} + +// PropRequiredBy sets the RequiredBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy= +func PropRequiredBy(units ...string) Property { + return propDependency("RequiredBy", units) +} + +// PropRequiredByOverridable sets the RequiredByOverridable unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable= +func PropRequiredByOverridable(units ...string) Property { + return propDependency("RequiredByOverridable", units) +} + +// PropWantedBy sets the WantedBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy= +func PropWantedBy(units ...string) Property { + return propDependency("WantedBy", units) +} + +// PropBoundBy sets the BoundBy unit property. See +// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy= +func PropBoundBy(units ...string) Property { + return propDependency("BoundBy", units) +} + +// PropConflicts sets the Conflicts unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts= +func PropConflicts(units ...string) Property { + return propDependency("Conflicts", units) +} + +// PropConflictedBy sets the ConflictedBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy= +func PropConflictedBy(units ...string) Property { + return propDependency("ConflictedBy", units) +} + +// PropBefore sets the Before unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before= +func PropBefore(units ...string) Property { + return propDependency("Before", units) +} + +// PropAfter sets the After unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After= +func PropAfter(units ...string) Property { + return propDependency("After", units) +} + +// PropOnFailure sets the OnFailure unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure= +func PropOnFailure(units ...string) Property { + return propDependency("OnFailure", units) +} + +// PropTriggers sets the Triggers unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers= +func PropTriggers(units ...string) Property { + return propDependency("Triggers", units) +} + +// PropTriggeredBy sets the TriggeredBy unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy= +func PropTriggeredBy(units ...string) Property { + return propDependency("TriggeredBy", units) +} + +// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo= +func PropPropagatesReloadTo(units ...string) Property { + return propDependency("PropagatesReloadTo", units) +} + +// PropRequiresMountsFor sets the RequiresMountsFor unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor= +func PropRequiresMountsFor(units ...string) Property { + return propDependency("RequiresMountsFor", units) +} + +// PropSlice sets the Slice unit property. See +// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice= +func PropSlice(slice string) Property { + return Property{ + Name: "Slice", + Value: dbus.MakeVariant(slice), + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/set.go b/vendor/src/github.com/coreos/go-systemd/dbus/set.go new file mode 100644 index 0000000000..88378b29a1 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/set.go @@ -0,0 +1,26 @@ +package dbus + +type set struct { + data map[string]bool +} + +func (s *set) Add(value string) { + s.data[value] = true +} + +func (s *set) Remove(value string) { + delete(s.data, value) +} + +func (s *set) Contains(value string) (exists bool) { + _, exists = s.data[value] + return +} + +func (s *set) Length() (int) { + return len(s.data) +} + +func newSet() (*set) { + return &set{make(map[string] bool)} +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/set_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/set_test.go new file mode 100644 index 0000000000..d8d174d0c4 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/set_test.go @@ -0,0 +1,26 @@ +package dbus + +import ( + "testing" +) + +// TestBasicSetActions asserts that Add & Remove behavior is correct +func TestBasicSetActions(t *testing.T) { + s := newSet() + + if s.Contains("foo") { + t.Fatal("set should not contain 'foo'") + } + + s.Add("foo") + + if !s.Contains("foo") { + t.Fatal("set should contain 'foo'") + } + + s.Remove("foo") + + if s.Contains("foo") { + t.Fatal("set should not contain 'foo'") + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go new file mode 100644 index 0000000000..3d896d896f --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription.go @@ -0,0 +1,249 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package dbus + +import ( + "errors" + "time" + + "github.com/godbus/dbus" +) + +const ( + cleanIgnoreInterval = int64(10 * time.Second) + ignoreInterval = int64(30 * time.Millisecond) +) + +// Subscribe sets up this connection to subscribe to all systemd dbus events. +// This is required before calling SubscribeUnits. When the connection closes +// systemd will automatically stop sending signals so there is no need to +// explicitly call Unsubscribe(). +func (c *Conn) Subscribe() error { + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'") + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'") + + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store() + if err != nil { + c.sysconn.Close() + return err + } + + return nil +} + +// Unsubscribe this connection from systemd dbus events. +func (c *Conn) Unsubscribe() error { + err := c.sysobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store() + if err != nil { + c.sysconn.Close() + return err + } + + return nil +} + +func (c *Conn) initSubscription() { + c.subscriber.ignore = make(map[dbus.ObjectPath]int64) +} + +func (c *Conn) initDispatch() { + ch := make(chan *dbus.Signal, signalBuffer) + + c.sysconn.Signal(ch) + + go func() { + for { + signal := <-ch + switch signal.Name { + case "org.freedesktop.systemd1.Manager.JobRemoved": + c.jobComplete(signal) + + unitName := signal.Body[2].(string) + var unitPath dbus.ObjectPath + c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) + if unitPath != dbus.ObjectPath("") { + c.sendSubStateUpdate(unitPath) + } + case "org.freedesktop.systemd1.Manager.UnitNew": + c.sendSubStateUpdate(signal.Body[1].(dbus.ObjectPath)) + case "org.freedesktop.DBus.Properties.PropertiesChanged": + if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" { + // we only care about SubState updates, which are a Unit property + c.sendSubStateUpdate(signal.Path) + } + } + } + }() +} + +// Returns two unbuffered channels which will receive all changed units every +// interval. Deleted units are sent as nil. +func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) { + return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil) +} + +// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer +// size of the channels, the comparison function for detecting changes and a filter +// function for cutting down on the noise that your channel receives. +func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func (string) bool) (<-chan map[string]*UnitStatus, <-chan error) { + old := make(map[string]*UnitStatus) + statusChan := make(chan map[string]*UnitStatus, buffer) + errChan := make(chan error, buffer) + + go func() { + for { + timerChan := time.After(interval) + + units, err := c.ListUnits() + if err == nil { + cur := make(map[string]*UnitStatus) + for i := range units { + if filterUnit != nil && filterUnit(units[i].Name) { + continue + } + cur[units[i].Name] = &units[i] + } + + // add all new or changed units + changed := make(map[string]*UnitStatus) + for n, u := range cur { + if oldU, ok := old[n]; !ok || isChanged(oldU, u) { + changed[n] = u + } + delete(old, n) + } + + // add all deleted units + for oldN := range old { + changed[oldN] = nil + } + + old = cur + + if len(changed) != 0 { + statusChan <- changed + } + } else { + errChan <- err + } + + <-timerChan + } + }() + + return statusChan, errChan +} + +type SubStateUpdate struct { + UnitName string + SubState string +} + +// SetSubStateSubscriber writes to updateCh when any unit's substate changes. +// Although this writes to updateCh on every state change, the reported state +// may be more recent than the change that generated it (due to an unavoidable +// race in the systemd dbus interface). That is, this method provides a good +// way to keep a current view of all units' states, but is not guaranteed to +// show every state transition they go through. Furthermore, state changes +// will only be written to the channel with non-blocking writes. If updateCh +// is full, it attempts to write an error to errCh; if errCh is full, the error +// passes silently. +func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) { + c.subscriber.Lock() + defer c.subscriber.Unlock() + c.subscriber.updateCh = updateCh + c.subscriber.errCh = errCh +} + +func (c *Conn) sendSubStateUpdate(path dbus.ObjectPath) { + c.subscriber.Lock() + defer c.subscriber.Unlock() + if c.subscriber.updateCh == nil { + return + } + + if c.shouldIgnore(path) { + return + } + + info, err := c.GetUnitProperties(string(path)) + if err != nil { + select { + case c.subscriber.errCh <- err: + default: + } + } + + name := info["Id"].(string) + substate := info["SubState"].(string) + + update := &SubStateUpdate{name, substate} + select { + case c.subscriber.updateCh <- update: + default: + select { + case c.subscriber.errCh <- errors.New("update channel full!"): + default: + } + } + + c.updateIgnore(path, info) +} + +// The ignore functions work around a wart in the systemd dbus interface. +// Requesting the properties of an unloaded unit will cause systemd to send a +// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's +// properties on UnitNew (as that's the only indication of a new unit coming up +// for the first time), we would enter an infinite loop if we did not attempt +// to detect and ignore these spurious signals. The signal themselves are +// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an +// unloaded unit's signals for a short time after requesting its properties. +// This means that we will miss e.g. a transient unit being restarted +// *immediately* upon failure and also a transient unit being started +// immediately after requesting its status (with systemctl status, for example, +// because this causes a UnitNew signal to be sent which then causes us to fetch +// the properties). + +func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool { + t, ok := c.subscriber.ignore[path] + return ok && t >= time.Now().UnixNano() +} + +func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) { + c.cleanIgnore() + + // unit is unloaded - it will trigger bad systemd dbus behavior + if info["LoadState"].(string) == "not-found" { + c.subscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval + } +} + +// without this, ignore would grow unboundedly over time +func (c *Conn) cleanIgnore() { + now := time.Now().UnixNano() + if c.subscriber.cleanIgnore < now { + c.subscriber.cleanIgnore = now + cleanIgnoreInterval + + for p, t := range c.subscriber.ignore { + if t < now { + delete(c.subscriber.ignore, p) + } + } + } +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set.go new file mode 100644 index 0000000000..2625786052 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set.go @@ -0,0 +1,32 @@ +package dbus + +import ( + "time" +) + +// SubscriptionSet returns a subscription set which is like conn.Subscribe but +// can filter to only return events for a set of units. +type SubscriptionSet struct { + *set + conn *Conn +} + + +func (s *SubscriptionSet) filter(unit string) bool { + return !s.Contains(unit) +} + +// Subscribe starts listening for dbus events for all of the units in the set. +// Returns channels identical to conn.SubscribeUnits. +func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { + // TODO: Make fully evented by using systemd 209 with properties changed values + return s.conn.SubscribeUnitsCustom(time.Second, 0, + func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, + func(unit string) bool { return s.filter(unit) }, + ) +} + +// NewSubscriptionSet returns a new subscription set. +func (conn *Conn) NewSubscriptionSet() (*SubscriptionSet) { + return &SubscriptionSet{newSet(), conn} +} diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go new file mode 100644 index 0000000000..db600850c2 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_set_test.go @@ -0,0 +1,67 @@ +package dbus + +import ( + "testing" + "time" +) + +// TestSubscribeUnit exercises the basics of subscription of a particular unit. +func TestSubscriptionSetUnit(t *testing.T) { + target := "subscribe-events-set.service" + + conn, err := New() + + if err != nil { + t.Fatal(err) + } + + err = conn.Subscribe() + if err != nil { + t.Fatal(err) + } + + subSet := conn.NewSubscriptionSet() + evChan, errChan := subSet.Subscribe() + + subSet.Add(target) + setupUnit(target, conn, t) + + job, err := conn.StartUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + if job != "done" { + t.Fatal("Couldn't start", target) + } + + timeout := make(chan bool, 1) + go func() { + time.Sleep(3 * time.Second) + close(timeout) + }() + + for { + select { + case changes := <-evChan: + tCh, ok := changes[target] + + if !ok { + t.Fatal("Unexpected event %v", changes) + } + + if tCh.ActiveState == "active" && tCh.Name == target { + goto success + } + case err = <-errChan: + t.Fatal(err) + case <-timeout: + t.Fatal("Reached timeout") + } + } + +success: + return +} + + diff --git a/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go new file mode 100644 index 0000000000..6f4d0b32a6 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/dbus/subscription_test.go @@ -0,0 +1,90 @@ +package dbus + +import ( + "testing" + "time" +) + +// TestSubscribe exercises the basics of subscription +func TestSubscribe(t *testing.T) { + conn, err := New() + + if err != nil { + t.Fatal(err) + } + + err = conn.Subscribe() + if err != nil { + t.Fatal(err) + } + + err = conn.Unsubscribe() + if err != nil { + t.Fatal(err) + } +} + +// TestSubscribeUnit exercises the basics of subscription of a particular unit. +func TestSubscribeUnit(t *testing.T) { + target := "subscribe-events.service" + + conn, err := New() + + if err != nil { + t.Fatal(err) + } + + err = conn.Subscribe() + if err != nil { + t.Fatal(err) + } + + err = conn.Unsubscribe() + if err != nil { + t.Fatal(err) + } + + evChan, errChan := conn.SubscribeUnits(time.Second) + + setupUnit(target, conn, t) + + job, err := conn.StartUnit(target, "replace") + if err != nil { + t.Fatal(err) + } + + if job != "done" { + t.Fatal("Couldn't start", target) + } + + timeout := make(chan bool, 1) + go func() { + time.Sleep(3 * time.Second) + close(timeout) + }() + + for { + select { + case changes := <-evChan: + tCh, ok := changes[target] + + // Just continue until we see our event. + if !ok { + continue + } + + if tCh.ActiveState == "active" && tCh.Name == target { + goto success + } + case err = <-errChan: + t.Fatal(err) + case <-timeout: + t.Fatal("Reached timeout") + } + } + +success: + return +} + + diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go new file mode 100644 index 0000000000..b3cf70ed84 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/activation.go @@ -0,0 +1,44 @@ +// Activation example used by the activation unit tests. +package main + +import ( + "fmt" + "os" + + "github.com/coreos/go-systemd/activation" +) + +func fixListenPid() { + if os.Getenv("FIX_LISTEN_PID") != "" { + // HACK: real systemd would set LISTEN_PID before exec'ing but + // this is too difficult in golang for the purpose of a test. + // Do not do this in real code. + os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) + } +} + +func main() { + fixListenPid() + + files := activation.Files(false) + + if len(files) == 0 { + panic("No files") + } + + if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { + panic("Should not unset envs") + } + + files = activation.Files(true) + + if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { + panic("Can not unset envs") + } + + // Write out the expected strings to the two pipes + files[0].Write([]byte("Hello world")) + files[1].Write([]byte("Goodbye world")) + + return +} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md new file mode 100644 index 0000000000..a350cca5e5 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/README.md @@ -0,0 +1,19 @@ +## socket activated http server + +This is a simple example of using socket activation with systemd to serve a +simple HTTP server on http://127.0.0.1:8076 + +To try it out `go get` the httpserver and run it under the systemd-activate helper + +``` +export GOPATH=`pwd` +go get github.com/coreos/go-systemd/examples/activation/httpserver +sudo /usr/lib/systemd/systemd-activate -l 127.0.0.1:8076 ./bin/httpserver +``` + +Then curl the URL and you will notice that it starts up: + +``` +curl 127.0.0.1:8076 +hello socket activated world! +``` diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service new file mode 100644 index 0000000000..c8dea0f6b3 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.service @@ -0,0 +1,11 @@ +[Unit] +Description=Hello World HTTP +Requires=network.target +After=multi-user.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/httpserver + +[Install] +WantedBy=multi-user.target diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket new file mode 100644 index 0000000000..723ed7ed92 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/hello.socket @@ -0,0 +1,5 @@ +[Socket] +ListenStream=127.0.0.1:8076 + +[Install] +WantedBy=sockets.target diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go new file mode 100644 index 0000000000..380c325d61 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/httpserver/httpserver.go @@ -0,0 +1,26 @@ +package main + +import ( + "io" + "net/http" + + "github.com/coreos/go-systemd/activation" +) + +func HelloServer(w http.ResponseWriter, req *http.Request) { + io.WriteString(w, "hello socket activated world!\n") +} + +func main() { + listeners, err := activation.Listeners(true) + if err != nil { + panic(err) + } + + if len(listeners) != 1 { + panic("Unexpected number of socket activation fds") + } + + http.HandleFunc("/", HelloServer) + http.Serve(listeners[0], nil) +} diff --git a/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go b/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go new file mode 100644 index 0000000000..5850a8b796 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/examples/activation/listen.go @@ -0,0 +1,50 @@ +// Activation example used by the activation unit tests. +package main + +import ( + "fmt" + "os" + + "github.com/coreos/go-systemd/activation" +) + +func fixListenPid() { + if os.Getenv("FIX_LISTEN_PID") != "" { + // HACK: real systemd would set LISTEN_PID before exec'ing but + // this is too difficult in golang for the purpose of a test. + // Do not do this in real code. + os.Setenv("LISTEN_PID", fmt.Sprintf("%d", os.Getpid())) + } +} + +func main() { + fixListenPid() + + listeners, _ := activation.Listeners(false) + + if len(listeners) == 0 { + panic("No listeners") + } + + if os.Getenv("LISTEN_PID") == "" || os.Getenv("LISTEN_FDS") == "" { + panic("Should not unset envs") + } + + listeners, err := activation.Listeners(true) + if err != nil { + panic(err) + } + + if os.Getenv("LISTEN_PID") != "" || os.Getenv("LISTEN_FDS") != "" { + panic("Can not unset envs") + } + + c0, _ := listeners[0].Accept() + c1, _ := listeners[1].Accept() + + // Write out the expected strings to the two pipes + c0.Write([]byte("Hello world")) + c1.Write([]byte("Goodbye world")) + + return +} diff --git a/vendor/src/github.com/coreos/go-systemd/fixtures/start-stop.service b/vendor/src/github.com/coreos/go-systemd/fixtures/start-stop.service new file mode 100644 index 0000000000..a1f8c36773 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/fixtures/start-stop.service @@ -0,0 +1,5 @@ +[Unit] +Description=start stop test + +[Service] +ExecStart=/bin/sleep 400 diff --git a/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service b/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service new file mode 100644 index 0000000000..a1f8c36773 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events-set.service @@ -0,0 +1,5 @@ +[Unit] +Description=start stop test + +[Service] +ExecStart=/bin/sleep 400 diff --git a/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events.service b/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events.service new file mode 100644 index 0000000000..a1f8c36773 --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/fixtures/subscribe-events.service @@ -0,0 +1,5 @@ +[Unit] +Description=start stop test + +[Service] +ExecStart=/bin/sleep 400 diff --git a/vendor/src/github.com/coreos/go-systemd/journal/send.go b/vendor/src/github.com/coreos/go-systemd/journal/send.go new file mode 100644 index 0000000000..a29bcbf0fa --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/journal/send.go @@ -0,0 +1,168 @@ +/* +Copyright 2013 CoreOS Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package journal provides write bindings to the systemd journal +package journal + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "strconv" + "strings" + "syscall" +) + +// Priority of a journal message +type Priority int + +const ( + PriEmerg Priority = iota + PriAlert + PriCrit + PriErr + PriWarning + PriNotice + PriInfo + PriDebug +) + +var conn net.Conn + +func init() { + var err error + conn, err = net.Dial("unixgram", "/run/systemd/journal/socket") + if err != nil { + conn = nil + } +} + +// Enabled returns true iff the systemd journal is available for logging +func Enabled() bool { + return conn != nil +} + +// Send a message to the systemd journal. vars is a map of journald fields to +// values. Fields must be composed of uppercase letters, numbers, and +// underscores, but must not start with an underscore. Within these +// restrictions, any arbitrary field name may be used. Some names have special +// significance: see the journalctl documentation +// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html) +// for more details. vars may be nil. +func Send(message string, priority Priority, vars map[string]string) error { + if conn == nil { + return journalError("could not connect to journald socket") + } + + data := new(bytes.Buffer) + appendVariable(data, "PRIORITY", strconv.Itoa(int(priority))) + appendVariable(data, "MESSAGE", message) + for k, v := range vars { + appendVariable(data, k, v) + } + + _, err := io.Copy(conn, data) + if err != nil && isSocketSpaceError(err) { + file, err := tempFd() + if err != nil { + return journalError(err.Error()) + } + _, err = io.Copy(file, data) + if err != nil { + return journalError(err.Error()) + } + + rights := syscall.UnixRights(int(file.Fd())) + + /* this connection should always be a UnixConn, but better safe than sorry */ + unixConn, ok := conn.(*net.UnixConn) + if !ok { + return journalError("can't send file through non-Unix connection") + } + unixConn.WriteMsgUnix([]byte{}, rights, nil) + } else if err != nil { + return journalError(err.Error()) + } + return nil +} + +func appendVariable(w io.Writer, name, value string) { + if !validVarName(name) { + journalError("variable name contains invalid character, ignoring") + } + if strings.ContainsRune(value, '\n') { + /* When the value contains a newline, we write: + * - the variable name, followed by a newline + * - the size (in 64bit little endian format) + * - the data, followed by a newline + */ + fmt.Fprintln(w, name) + binary.Write(w, binary.LittleEndian, uint64(len(value))) + fmt.Fprintln(w, value) + } else { + /* just write the variable and value all on one line */ + fmt.Fprintln(w, "%s=%s", name, value) + } +} + +func validVarName(name string) bool { + /* The variable name must be in uppercase and consist only of characters, + * numbers and underscores, and may not begin with an underscore. (from the docs) + */ + + valid := name[0] != '_' + for _, c := range name { + valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_' + } + return valid +} + +func isSocketSpaceError(err error) bool { + opErr, ok := err.(*net.OpError) + if !ok { + return false + } + + sysErr, ok := opErr.Err.(syscall.Errno) + if !ok { + return false + } + + return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS +} + +func tempFd() (*os.File, error) { + file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX") + if err != nil { + return nil, err + } + syscall.Unlink(file.Name()) + if err != nil { + return nil, err + } + return file, nil +} + +func journalError(s string) error { + s = "journal error: " + s + fmt.Fprintln(os.Stderr, s) + return errors.New(s) +} diff --git a/vendor/src/github.com/coreos/go-systemd/test b/vendor/src/github.com/coreos/go-systemd/test new file mode 100755 index 0000000000..6e043658ae --- /dev/null +++ b/vendor/src/github.com/coreos/go-systemd/test @@ -0,0 +1,3 @@ +#!/bin/sh -e + +go test -v ./... diff --git a/vendor/src/github.com/godbus/dbus/LICENSE b/vendor/src/github.com/godbus/dbus/LICENSE new file mode 100644 index 0000000000..06b252bcbc --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2013, Georg Reinke () +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/src/github.com/godbus/dbus/README.markdown b/vendor/src/github.com/godbus/dbus/README.markdown new file mode 100644 index 0000000000..3ab2116651 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/README.markdown @@ -0,0 +1,38 @@ +dbus +---- + +dbus is a simple library that implements native Go client bindings for the +D-Bus message bus system. + +### Features + +* Complete native implementation of the D-Bus message protocol +* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections) +* Subpackages that help with the introspection / property interfaces + +### Installation + +This packages requires Go 1.1. If you installed it and set up your GOPATH, just run: + +``` +go get github.com/godbus/dbus +``` + +If you want to use the subpackages, you can install them the same way. + +### Usage + +The complete package documentation and some simple examples are available at +[godoc.org](http://godoc.org/github.com/godbus/dbus). Also, the +[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory +gives a short overview over the basic usage. + +Please note that the API is considered unstable for now and may change without +further notice. + +### License + +go.dbus is available under the Simplified BSD License; see LICENSE for the full +text. + +Nearly all of the credit for this library goes to github.com/guelfey/go.dbus. diff --git a/vendor/src/github.com/godbus/dbus/_examples/eavesdrop.go b/vendor/src/github.com/godbus/dbus/_examples/eavesdrop.go new file mode 100644 index 0000000000..11deef3cf8 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/eavesdrop.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "os" +) + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err) + os.Exit(1) + } + + for _, v := range []string{"method_call", "method_return", "error", "signal"} { + call := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "eavesdrop='true',type='"+v+"'") + if call.Err != nil { + fmt.Fprintln(os.Stderr, "Failed to add match:", call.Err) + os.Exit(1) + } + } + c := make(chan *dbus.Message, 10) + conn.Eavesdrop(c) + fmt.Println("Listening for everything") + for v := range c { + fmt.Println(v) + } +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/introspect.go b/vendor/src/github.com/godbus/dbus/_examples/introspect.go new file mode 100644 index 0000000000..a2af4e5f24 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/introspect.go @@ -0,0 +1,21 @@ +package main + +import ( + "encoding/json" + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "os" +) + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + node, err := introspect.Call(conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")) + if err != nil { + panic(err) + } + data, _ := json.MarshalIndent(node, "", " ") + os.Stdout.Write(data) +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/list-names.go b/vendor/src/github.com/godbus/dbus/_examples/list-names.go new file mode 100644 index 0000000000..ce1f7ec52e --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/list-names.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "os" +) + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err) + os.Exit(1) + } + + var s []string + err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&s) + if err != nil { + fmt.Fprintln(os.Stderr, "Failed to get list of owned names:", err) + os.Exit(1) + } + + fmt.Println("Currently owned names on the session bus:") + for _, v := range s { + fmt.Println(v) + } +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/notification.go b/vendor/src/github.com/godbus/dbus/_examples/notification.go new file mode 100644 index 0000000000..5fe11d04c4 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/notification.go @@ -0,0 +1,17 @@ +package main + +import "github.com/godbus/dbus" + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") + call := obj.Call("org.freedesktop.Notifications.Notify", 0, "", uint32(0), + "", "Test", "This is a test of the DBus bindings for go.", []string{}, + map[string]dbus.Variant{}, int32(5000)) + if call.Err != nil { + panic(call.Err) + } +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/prop.go b/vendor/src/github.com/godbus/dbus/_examples/prop.go new file mode 100644 index 0000000000..e3408c53e9 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/prop.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "github.com/godbus/dbus/prop" + "os" +) + +type foo string + +func (f foo) Foo() (string, *dbus.Error) { + fmt.Println(f) + return string(f), nil +} + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + reply, err := conn.RequestName("com.github.guelfey.Demo", + dbus.NameFlagDoNotQueue) + if err != nil { + panic(err) + } + if reply != dbus.RequestNameReplyPrimaryOwner { + fmt.Fprintln(os.Stderr, "name already taken") + os.Exit(1) + } + propsSpec := map[string]map[string]*prop.Prop{ + "com.github.guelfey.Demo": { + "SomeInt": { + int32(0), + true, + prop.EmitTrue, + func(c *prop.Change) *dbus.Error { + fmt.Println(c.Name, "changed to", c.Value) + return nil + }, + }, + }, + } + f := foo("Bar") + conn.Export(f, "/com/github/guelfey/Demo", "com.github.guelfey.Demo") + props := prop.New(conn, "/com/github/guelfey/Demo", propsSpec) + n := &introspect.Node{ + Name: "/com/github/guelfey/Demo", + Interfaces: []introspect.Interface{ + introspect.IntrospectData, + prop.IntrospectData, + { + Name: "com.github.guelfey.Demo", + Methods: introspect.Methods(f), + Properties: props.Introspection("com.github.guelfey.Demo"), + }, + }, + } + conn.Export(introspect.NewIntrospectable(n), "/com/github/guelfey/Demo", + "org.freedesktop.DBus.Introspectable") + fmt.Println("Listening on com.github.guelfey.Demo / /com/github/guelfey/Demo ...") + + c := make(chan *dbus.Signal) + conn.Signal(c) + for _ = range c { + } +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/server.go b/vendor/src/github.com/godbus/dbus/_examples/server.go new file mode 100644 index 0000000000..32b7b291c7 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/server.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "os" +) + +const intro = ` + + + + + + ` + introspect.IntrospectDataString + ` ` + +type foo string + +func (f foo) Foo() (string, *dbus.Error) { + fmt.Println(f) + return string(f), nil +} + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + panic(err) + } + reply, err := conn.RequestName("com.github.guelfey.Demo", + dbus.NameFlagDoNotQueue) + if err != nil { + panic(err) + } + if reply != dbus.RequestNameReplyPrimaryOwner { + fmt.Fprintln(os.Stderr, "name already taken") + os.Exit(1) + } + f := foo("Bar!") + conn.Export(f, "/com/github/guelfey/Demo", "com.github.guelfey.Demo") + conn.Export(introspect.Introspectable(intro), "/com/github/guelfey/Demo", + "org.freedesktop.DBus.Introspectable") + fmt.Println("Listening on com.github.guelfey.Demo / /com/github/guelfey/Demo ...") + select {} +} diff --git a/vendor/src/github.com/godbus/dbus/_examples/signal.go b/vendor/src/github.com/godbus/dbus/_examples/signal.go new file mode 100644 index 0000000000..8f3f809759 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/_examples/signal.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + "github.com/godbus/dbus" + "os" +) + +func main() { + conn, err := dbus.SessionBus() + if err != nil { + fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err) + os.Exit(1) + } + + conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, + "type='signal',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',sender='org.freedesktop.DBus'") + + c := make(chan *dbus.Signal, 10) + conn.Signal(c) + for v := range c { + fmt.Println(v) + } +} diff --git a/vendor/src/github.com/godbus/dbus/auth.go b/vendor/src/github.com/godbus/dbus/auth.go new file mode 100644 index 0000000000..98017b693e --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/auth.go @@ -0,0 +1,253 @@ +package dbus + +import ( + "bufio" + "bytes" + "errors" + "io" + "os" + "strconv" +) + +// AuthStatus represents the Status of an authentication mechanism. +type AuthStatus byte + +const ( + // AuthOk signals that authentication is finished; the next command + // from the server should be an OK. + AuthOk AuthStatus = iota + + // AuthContinue signals that additional data is needed; the next command + // from the server should be a DATA. + AuthContinue + + // AuthError signals an error; the server sent invalid data or some + // other unexpected thing happened and the current authentication + // process should be aborted. + AuthError +) + +type authState byte + +const ( + waitingForData authState = iota + waitingForOk + waitingForReject +) + +// Auth defines the behaviour of an authentication mechanism. +type Auth interface { + // Return the name of the mechnism, the argument to the first AUTH command + // and the next status. + FirstData() (name, resp []byte, status AuthStatus) + + // Process the given DATA command, and return the argument to the DATA + // command and the next status. If len(resp) == 0, no DATA command is sent. + HandleData(data []byte) (resp []byte, status AuthStatus) +} + +// Auth authenticates the connection, trying the given list of authentication +// mechanisms (in that order). If nil is passed, the EXTERNAL and +// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private +// connections, this method must be called before sending any messages to the +// bus. Auth must not be called on shared connections. +func (conn *Conn) Auth(methods []Auth) error { + if methods == nil { + uid := strconv.Itoa(os.Getuid()) + methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())} + } + in := bufio.NewReader(conn.transport) + err := conn.transport.SendNullByte() + if err != nil { + return err + } + err = authWriteLine(conn.transport, []byte("AUTH")) + if err != nil { + return err + } + s, err := authReadLine(in) + if err != nil { + return err + } + if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) { + return errors.New("dbus: authentication protocol error") + } + s = s[1:] + for _, v := range s { + for _, m := range methods { + if name, data, status := m.FirstData(); bytes.Equal(v, name) { + var ok bool + err = authWriteLine(conn.transport, []byte("AUTH"), []byte(v), data) + if err != nil { + return err + } + switch status { + case AuthOk: + err, ok = conn.tryAuth(m, waitingForOk, in) + case AuthContinue: + err, ok = conn.tryAuth(m, waitingForData, in) + default: + panic("dbus: invalid authentication status") + } + if err != nil { + return err + } + if ok { + if conn.transport.SupportsUnixFDs() { + err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD")) + if err != nil { + return err + } + line, err := authReadLine(in) + if err != nil { + return err + } + switch { + case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")): + conn.EnableUnixFDs() + conn.unixFD = true + case bytes.Equal(line[0], []byte("ERROR")): + default: + return errors.New("dbus: authentication protocol error") + } + } + err = authWriteLine(conn.transport, []byte("BEGIN")) + if err != nil { + return err + } + go conn.inWorker() + go conn.outWorker() + return nil + } + } + } + } + return errors.New("dbus: authentication failed") +} + +// tryAuth tries to authenticate with m as the mechanism, using state as the +// initial authState and in for reading input. It returns (nil, true) on +// success, (nil, false) on a REJECTED and (someErr, false) if some other +// error occured. +func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) { + for { + s, err := authReadLine(in) + if err != nil { + return err, false + } + switch { + case state == waitingForData && string(s[0]) == "DATA": + if len(s) != 2 { + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + continue + } + data, status := m.HandleData(s[1]) + switch status { + case AuthOk, AuthContinue: + if len(data) != 0 { + err = authWriteLine(conn.transport, []byte("DATA"), data) + if err != nil { + return err, false + } + } + if status == AuthOk { + state = waitingForOk + } + case AuthError: + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + } + case state == waitingForData && string(s[0]) == "REJECTED": + return nil, false + case state == waitingForData && string(s[0]) == "ERROR": + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + case state == waitingForData && string(s[0]) == "OK": + if len(s) != 2 { + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + } + conn.uuid = string(s[1]) + return nil, true + case state == waitingForData: + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + case state == waitingForOk && string(s[0]) == "OK": + if len(s) != 2 { + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + } + conn.uuid = string(s[1]) + return nil, true + case state == waitingForOk && string(s[0]) == "REJECTED": + return nil, false + case state == waitingForOk && (string(s[0]) == "DATA" || + string(s[0]) == "ERROR"): + + err = authWriteLine(conn.transport, []byte("CANCEL")) + if err != nil { + return err, false + } + state = waitingForReject + case state == waitingForOk: + err = authWriteLine(conn.transport, []byte("ERROR")) + if err != nil { + return err, false + } + case state == waitingForReject && string(s[0]) == "REJECTED": + return nil, false + case state == waitingForReject: + return errors.New("dbus: authentication protocol error"), false + default: + panic("dbus: invalid auth state") + } + } +} + +// authReadLine reads a line and separates it into its fields. +func authReadLine(in *bufio.Reader) ([][]byte, error) { + data, err := in.ReadBytes('\n') + if err != nil { + return nil, err + } + data = bytes.TrimSuffix(data, []byte("\r\n")) + return bytes.Split(data, []byte{' '}), nil +} + +// authWriteLine writes the given line in the authentication protocol format +// (elements of data separated by a " " and terminated by "\r\n"). +func authWriteLine(out io.Writer, data ...[]byte) error { + buf := make([]byte, 0) + for i, v := range data { + buf = append(buf, v...) + if i != len(data)-1 { + buf = append(buf, ' ') + } + } + buf = append(buf, '\r') + buf = append(buf, '\n') + n, err := out.Write(buf) + if err != nil { + return err + } + if n != len(buf) { + return io.ErrUnexpectedEOF + } + return nil +} diff --git a/vendor/src/github.com/godbus/dbus/auth_external.go b/vendor/src/github.com/godbus/dbus/auth_external.go new file mode 100644 index 0000000000..7e376d3ef6 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/auth_external.go @@ -0,0 +1,26 @@ +package dbus + +import ( + "encoding/hex" +) + +// AuthExternal returns an Auth that authenticates as the given user with the +// EXTERNAL mechanism. +func AuthExternal(user string) Auth { + return authExternal{user} +} + +// AuthExternal implements the EXTERNAL authentication mechanism. +type authExternal struct { + user string +} + +func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) { + b := make([]byte, 2*len(a.user)) + hex.Encode(b, []byte(a.user)) + return []byte("EXTERNAL"), b, AuthOk +} + +func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) { + return nil, AuthError +} diff --git a/vendor/src/github.com/godbus/dbus/auth_sha1.go b/vendor/src/github.com/godbus/dbus/auth_sha1.go new file mode 100644 index 0000000000..df15b46119 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/auth_sha1.go @@ -0,0 +1,102 @@ +package dbus + +import ( + "bufio" + "bytes" + "crypto/rand" + "crypto/sha1" + "encoding/hex" + "os" +) + +// AuthCookieSha1 returns an Auth that authenticates as the given user with the +// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home +// directory of the user. +func AuthCookieSha1(user, home string) Auth { + return authCookieSha1{user, home} +} + +type authCookieSha1 struct { + user, home string +} + +func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) { + b := make([]byte, 2*len(a.user)) + hex.Encode(b, []byte(a.user)) + return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue +} + +func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) { + challenge := make([]byte, len(data)/2) + _, err := hex.Decode(challenge, data) + if err != nil { + return nil, AuthError + } + b := bytes.Split(challenge, []byte{' '}) + if len(b) != 3 { + return nil, AuthError + } + context := b[0] + id := b[1] + svchallenge := b[2] + cookie := a.getCookie(context, id) + if cookie == nil { + return nil, AuthError + } + clchallenge := a.generateChallenge() + if clchallenge == nil { + return nil, AuthError + } + hash := sha1.New() + hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'})) + hexhash := make([]byte, 2*hash.Size()) + hex.Encode(hexhash, hash.Sum(nil)) + data = append(clchallenge, ' ') + data = append(data, hexhash...) + resp := make([]byte, 2*len(data)) + hex.Encode(resp, data) + return resp, AuthOk +} + +// getCookie searches for the cookie identified by id in context and returns +// the cookie content or nil. (Since HandleData can't return a specific error, +// but only whether an error occured, this function also doesn't bother to +// return an error.) +func (a authCookieSha1) getCookie(context, id []byte) []byte { + file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context)) + if err != nil { + return nil + } + defer file.Close() + rd := bufio.NewReader(file) + for { + line, err := rd.ReadBytes('\n') + if err != nil { + return nil + } + line = line[:len(line)-1] + b := bytes.Split(line, []byte{' '}) + if len(b) != 3 { + return nil + } + if bytes.Equal(b[0], id) { + return b[2] + } + } +} + +// generateChallenge returns a random, hex-encoded challenge, or nil on error +// (see above). +func (a authCookieSha1) generateChallenge() []byte { + b := make([]byte, 16) + n, err := rand.Read(b) + if err != nil { + return nil + } + if n != 16 { + return nil + } + enc := make([]byte, 32) + hex.Encode(enc, b) + return enc +} diff --git a/vendor/src/github.com/godbus/dbus/call.go b/vendor/src/github.com/godbus/dbus/call.go new file mode 100644 index 0000000000..1d2fbc7efd --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/call.go @@ -0,0 +1,147 @@ +package dbus + +import ( + "errors" + "strings" +) + +// Call represents a pending or completed method call. +type Call struct { + Destination string + Path ObjectPath + Method string + Args []interface{} + + // Strobes when the call is complete. + Done chan *Call + + // After completion, the error status. If this is non-nil, it may be an + // error message from the peer (with Error as its type) or some other error. + Err error + + // Holds the response once the call is done. + Body []interface{} +} + +var errSignature = errors.New("dbus: mismatched signature") + +// Store stores the body of the reply into the provided pointers. It returns +// an error if the signatures of the body and retvalues don't match, or if +// the error status is not nil. +func (c *Call) Store(retvalues ...interface{}) error { + if c.Err != nil { + return c.Err + } + + return Store(c.Body, retvalues...) +} + +// Object represents a remote object on which methods can be invoked. +type Object struct { + conn *Conn + dest string + path ObjectPath +} + +// Call calls a method with (*Object).Go and waits for its reply. +func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call { + return <-o.Go(method, flags, make(chan *Call, 1), args...).Done +} + +// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given +// object. The property name must be given in interface.member notation. +func (o *Object) GetProperty(p string) (Variant, error) { + idx := strings.LastIndex(p, ".") + if idx == -1 || idx+1 == len(p) { + return Variant{}, errors.New("dbus: invalid property " + p) + } + + iface := p[:idx] + prop := p[idx+1:] + + result := Variant{} + err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result) + + if err != nil { + return Variant{}, err + } + + return result, nil +} + +// Go calls a method with the given arguments asynchronously. It returns a +// Call structure representing this method call. The passed channel will +// return the same value once the call is done. If ch is nil, a new channel +// will be allocated. Otherwise, ch has to be buffered or Go will panic. +// +// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure +// is returned of which only the Err member is valid. +// +// If the method parameter contains a dot ('.'), the part before the last dot +// specifies the interface on which the method is called. +func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call { + iface := "" + i := strings.LastIndex(method, ".") + if i != -1 { + iface = method[:i] + } + method = method[i+1:] + msg := new(Message) + msg.Type = TypeMethodCall + msg.serial = o.conn.getSerial() + msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected) + msg.Headers = make(map[HeaderField]Variant) + msg.Headers[FieldPath] = MakeVariant(o.path) + msg.Headers[FieldDestination] = MakeVariant(o.dest) + msg.Headers[FieldMember] = MakeVariant(method) + if iface != "" { + msg.Headers[FieldInterface] = MakeVariant(iface) + } + msg.Body = args + if len(args) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...)) + } + if msg.Flags&FlagNoReplyExpected == 0 { + if ch == nil { + ch = make(chan *Call, 10) + } else if cap(ch) == 0 { + panic("dbus: unbuffered channel passed to (*Object).Go") + } + call := &Call{ + Destination: o.dest, + Path: o.path, + Method: method, + Args: args, + Done: ch, + } + o.conn.callsLck.Lock() + o.conn.calls[msg.serial] = call + o.conn.callsLck.Unlock() + o.conn.outLck.RLock() + if o.conn.closed { + call.Err = ErrClosed + call.Done <- call + } else { + o.conn.out <- msg + } + o.conn.outLck.RUnlock() + return call + } + o.conn.outLck.RLock() + defer o.conn.outLck.RUnlock() + if o.conn.closed { + return &Call{Err: ErrClosed} + } + o.conn.out <- msg + return &Call{Err: nil} +} + +// Destination returns the destination that calls on o are sent to. +func (o *Object) Destination() string { + return o.dest +} + +// Path returns the path that calls on o are sent to. +func (o *Object) Path() ObjectPath { + return o.path +} diff --git a/vendor/src/github.com/godbus/dbus/conn.go b/vendor/src/github.com/godbus/dbus/conn.go new file mode 100644 index 0000000000..75dd22652a --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/conn.go @@ -0,0 +1,601 @@ +package dbus + +import ( + "errors" + "io" + "os" + "reflect" + "strings" + "sync" +) + +const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket" + +var ( + systemBus *Conn + systemBusLck sync.Mutex + sessionBus *Conn + sessionBusLck sync.Mutex +) + +// ErrClosed is the error returned by calls on a closed connection. +var ErrClosed = errors.New("dbus: connection closed by user") + +// Conn represents a connection to a message bus (usually, the system or +// session bus). +// +// Connections are either shared or private. Shared connections +// are shared between calls to the functions that return them. As a result, +// the methods Close, Auth and Hello must not be called on them. +// +// Multiple goroutines may invoke methods on a connection simultaneously. +type Conn struct { + transport + + busObj *Object + unixFD bool + uuid string + + names []string + namesLck sync.RWMutex + + serialLck sync.Mutex + nextSerial uint32 + serialUsed map[uint32]bool + + calls map[uint32]*Call + callsLck sync.RWMutex + + handlers map[ObjectPath]map[string]interface{} + handlersLck sync.RWMutex + + out chan *Message + closed bool + outLck sync.RWMutex + + signals []chan<- *Signal + signalsLck sync.Mutex + + eavesdropped chan<- *Message + eavesdroppedLck sync.Mutex +} + +// SessionBus returns a shared connection to the session bus, connecting to it +// if not already done. +func SessionBus() (conn *Conn, err error) { + sessionBusLck.Lock() + defer sessionBusLck.Unlock() + if sessionBus != nil { + return sessionBus, nil + } + defer func() { + if conn != nil { + sessionBus = conn + } + }() + conn, err = SessionBusPrivate() + if err != nil { + return + } + if err = conn.Auth(nil); err != nil { + conn.Close() + conn = nil + return + } + if err = conn.Hello(); err != nil { + conn.Close() + conn = nil + } + return +} + +// SessionBusPrivate returns a new private connection to the session bus. +func SessionBusPrivate() (*Conn, error) { + address := os.Getenv("DBUS_SESSION_BUS_ADDRESS") + if address != "" && address != "autolaunch:" { + return Dial(address) + } + + return sessionBusPlatform() +} + +// SystemBus returns a shared connection to the system bus, connecting to it if +// not already done. +func SystemBus() (conn *Conn, err error) { + systemBusLck.Lock() + defer systemBusLck.Unlock() + if systemBus != nil { + return systemBus, nil + } + defer func() { + if conn != nil { + systemBus = conn + } + }() + conn, err = SystemBusPrivate() + if err != nil { + return + } + if err = conn.Auth(nil); err != nil { + conn.Close() + conn = nil + return + } + if err = conn.Hello(); err != nil { + conn.Close() + conn = nil + } + return +} + +// SystemBusPrivate returns a new private connection to the system bus. +func SystemBusPrivate() (*Conn, error) { + address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS") + if address != "" { + return Dial(address) + } + return Dial(defaultSystemBusAddress) +} + +// Dial establishes a new private connection to the message bus specified by address. +func Dial(address string) (*Conn, error) { + tr, err := getTransport(address) + if err != nil { + return nil, err + } + return newConn(tr) +} + +// NewConn creates a new private *Conn from an already established connection. +func NewConn(conn io.ReadWriteCloser) (*Conn, error) { + return newConn(genericTransport{conn}) +} + +// newConn creates a new *Conn from a transport. +func newConn(tr transport) (*Conn, error) { + conn := new(Conn) + conn.transport = tr + conn.calls = make(map[uint32]*Call) + conn.out = make(chan *Message, 10) + conn.handlers = make(map[ObjectPath]map[string]interface{}) + conn.nextSerial = 1 + conn.serialUsed = map[uint32]bool{0: true} + conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus") + return conn, nil +} + +// BusObject returns the object owned by the bus daemon which handles +// administrative requests. +func (conn *Conn) BusObject() *Object { + return conn.busObj +} + +// Close closes the connection. Any blocked operations will return with errors +// and the channels passed to Eavesdrop and Signal are closed. This method must +// not be called on shared connections. +func (conn *Conn) Close() error { + conn.outLck.Lock() + close(conn.out) + conn.closed = true + conn.outLck.Unlock() + conn.signalsLck.Lock() + for _, ch := range conn.signals { + close(ch) + } + conn.signalsLck.Unlock() + conn.eavesdroppedLck.Lock() + if conn.eavesdropped != nil { + close(conn.eavesdropped) + } + conn.eavesdroppedLck.Unlock() + return conn.transport.Close() +} + +// Eavesdrop causes conn to send all incoming messages to the given channel +// without further processing. Method replies, errors and signals will not be +// sent to the appropiate channels and method calls will not be handled. If nil +// is passed, the normal behaviour is restored. +// +// The caller has to make sure that ch is sufficiently buffered; +// if a message arrives when a write to ch is not possible, the message is +// discarded. +func (conn *Conn) Eavesdrop(ch chan<- *Message) { + conn.eavesdroppedLck.Lock() + conn.eavesdropped = ch + conn.eavesdroppedLck.Unlock() +} + +// getSerial returns an unused serial. +func (conn *Conn) getSerial() uint32 { + conn.serialLck.Lock() + defer conn.serialLck.Unlock() + n := conn.nextSerial + for conn.serialUsed[n] { + n++ + } + conn.serialUsed[n] = true + conn.nextSerial = n + 1 + return n +} + +// Hello sends the initial org.freedesktop.DBus.Hello call. This method must be +// called after authentication, but before sending any other messages to the +// bus. Hello must not be called for shared connections. +func (conn *Conn) Hello() error { + var s string + err := conn.busObj.Call("org.freedesktop.DBus.Hello", 0).Store(&s) + if err != nil { + return err + } + conn.namesLck.Lock() + conn.names = make([]string, 1) + conn.names[0] = s + conn.namesLck.Unlock() + return nil +} + +// inWorker runs in an own goroutine, reading incoming messages from the +// transport and dispatching them appropiately. +func (conn *Conn) inWorker() { + for { + msg, err := conn.ReadMessage() + if err == nil { + conn.eavesdroppedLck.Lock() + if conn.eavesdropped != nil { + select { + case conn.eavesdropped <- msg: + default: + } + conn.eavesdroppedLck.Unlock() + continue + } + conn.eavesdroppedLck.Unlock() + dest, _ := msg.Headers[FieldDestination].value.(string) + found := false + if dest == "" { + found = true + } else { + conn.namesLck.RLock() + if len(conn.names) == 0 { + found = true + } + for _, v := range conn.names { + if dest == v { + found = true + break + } + } + conn.namesLck.RUnlock() + } + if !found { + // Eavesdropped a message, but no channel for it is registered. + // Ignore it. + continue + } + switch msg.Type { + case TypeMethodReply, TypeError: + serial := msg.Headers[FieldReplySerial].value.(uint32) + conn.callsLck.Lock() + if c, ok := conn.calls[serial]; ok { + if msg.Type == TypeError { + name, _ := msg.Headers[FieldErrorName].value.(string) + c.Err = Error{name, msg.Body} + } else { + c.Body = msg.Body + } + c.Done <- c + conn.serialLck.Lock() + delete(conn.serialUsed, serial) + conn.serialLck.Unlock() + delete(conn.calls, serial) + } + conn.callsLck.Unlock() + case TypeSignal: + iface := msg.Headers[FieldInterface].value.(string) + member := msg.Headers[FieldMember].value.(string) + // as per http://dbus.freedesktop.org/doc/dbus-specification.html , + // sender is optional for signals. + sender, _ := msg.Headers[FieldSender].value.(string) + if iface == "org.freedesktop.DBus" && member == "NameLost" && + sender == "org.freedesktop.DBus" { + + name, _ := msg.Body[0].(string) + conn.namesLck.Lock() + for i, v := range conn.names { + if v == name { + copy(conn.names[i:], conn.names[i+1:]) + conn.names = conn.names[:len(conn.names)-1] + } + } + conn.namesLck.Unlock() + } + signal := &Signal{ + Sender: sender, + Path: msg.Headers[FieldPath].value.(ObjectPath), + Name: iface + "." + member, + Body: msg.Body, + } + conn.signalsLck.Lock() + for _, ch := range conn.signals { + // don't block trying to send a signal + select { + case ch <- signal: + default: + } + } + conn.signalsLck.Unlock() + case TypeMethodCall: + go conn.handleCall(msg) + } + } else if _, ok := err.(InvalidMessageError); !ok { + // Some read error occured (usually EOF); we can't really do + // anything but to shut down all stuff and returns errors to all + // pending replies. + conn.Close() + conn.callsLck.RLock() + for _, v := range conn.calls { + v.Err = err + v.Done <- v + } + conn.callsLck.RUnlock() + return + } + // invalid messages are ignored + } +} + +// Names returns the list of all names that are currently owned by this +// connection. The slice is always at least one element long, the first element +// being the unique name of the connection. +func (conn *Conn) Names() []string { + conn.namesLck.RLock() + // copy the slice so it can't be modified + s := make([]string, len(conn.names)) + copy(s, conn.names) + conn.namesLck.RUnlock() + return s +} + +// Object returns the object identified by the given destination name and path. +func (conn *Conn) Object(dest string, path ObjectPath) *Object { + return &Object{conn, dest, path} +} + +// outWorker runs in an own goroutine, encoding and sending messages that are +// sent to conn.out. +func (conn *Conn) outWorker() { + for msg := range conn.out { + err := conn.SendMessage(msg) + conn.callsLck.RLock() + if err != nil { + if c := conn.calls[msg.serial]; c != nil { + c.Err = err + c.Done <- c + } + conn.serialLck.Lock() + delete(conn.serialUsed, msg.serial) + conn.serialLck.Unlock() + } else if msg.Type != TypeMethodCall { + conn.serialLck.Lock() + delete(conn.serialUsed, msg.serial) + conn.serialLck.Unlock() + } + conn.callsLck.RUnlock() + } +} + +// Send sends the given message to the message bus. You usually don't need to +// use this; use the higher-level equivalents (Call / Go, Emit and Export) +// instead. If msg is a method call and NoReplyExpected is not set, a non-nil +// call is returned and the same value is sent to ch (which must be buffered) +// once the call is complete. Otherwise, ch is ignored and a Call structure is +// returned of which only the Err member is valid. +func (conn *Conn) Send(msg *Message, ch chan *Call) *Call { + var call *Call + + msg.serial = conn.getSerial() + if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 { + if ch == nil { + ch = make(chan *Call, 5) + } else if cap(ch) == 0 { + panic("dbus: unbuffered channel passed to (*Conn).Send") + } + call = new(Call) + call.Destination, _ = msg.Headers[FieldDestination].value.(string) + call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath) + iface, _ := msg.Headers[FieldInterface].value.(string) + member, _ := msg.Headers[FieldMember].value.(string) + call.Method = iface + "." + member + call.Args = msg.Body + call.Done = ch + conn.callsLck.Lock() + conn.calls[msg.serial] = call + conn.callsLck.Unlock() + conn.outLck.RLock() + if conn.closed { + call.Err = ErrClosed + call.Done <- call + } else { + conn.out <- msg + } + conn.outLck.RUnlock() + } else { + conn.outLck.RLock() + if conn.closed { + call = &Call{Err: ErrClosed} + } else { + conn.out <- msg + call = &Call{Err: nil} + } + conn.outLck.RUnlock() + } + return call +} + +// sendError creates an error message corresponding to the parameters and sends +// it to conn.out. +func (conn *Conn) sendError(e Error, dest string, serial uint32) { + msg := new(Message) + msg.Type = TypeError + msg.serial = conn.getSerial() + msg.Headers = make(map[HeaderField]Variant) + if dest != "" { + msg.Headers[FieldDestination] = MakeVariant(dest) + } + msg.Headers[FieldErrorName] = MakeVariant(e.Name) + msg.Headers[FieldReplySerial] = MakeVariant(serial) + msg.Body = e.Body + if len(e.Body) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...)) + } + conn.outLck.RLock() + if !conn.closed { + conn.out <- msg + } + conn.outLck.RUnlock() +} + +// sendReply creates a method reply message corresponding to the parameters and +// sends it to conn.out. +func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) { + msg := new(Message) + msg.Type = TypeMethodReply + msg.serial = conn.getSerial() + msg.Headers = make(map[HeaderField]Variant) + if dest != "" { + msg.Headers[FieldDestination] = MakeVariant(dest) + } + msg.Headers[FieldReplySerial] = MakeVariant(serial) + msg.Body = values + if len(values) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...)) + } + conn.outLck.RLock() + if !conn.closed { + conn.out <- msg + } + conn.outLck.RUnlock() +} + +// Signal registers the given channel to be passed all received signal messages. +// The caller has to make sure that ch is sufficiently buffered; if a message +// arrives when a write to c is not possible, it is discarded. +// +// Multiple of these channels can be registered at the same time. Passing a +// channel that already is registered will remove it from the list of the +// registered channels. +// +// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a +// channel for eavesdropped messages, this channel receives all signals, and +// none of the channels passed to Signal will receive any signals. +func (conn *Conn) Signal(ch chan<- *Signal) { + conn.signalsLck.Lock() + conn.signals = append(conn.signals, ch) + conn.signalsLck.Unlock() +} + +// SupportsUnixFDs returns whether the underlying transport supports passing of +// unix file descriptors. If this is false, method calls containing unix file +// descriptors will return an error and emitted signals containing them will +// not be sent. +func (conn *Conn) SupportsUnixFDs() bool { + return conn.unixFD +} + +// Error represents a D-Bus message of type Error. +type Error struct { + Name string + Body []interface{} +} + +func (e Error) Error() string { + if len(e.Body) >= 1 { + s, ok := e.Body[0].(string) + if ok { + return s + } + } + return e.Name +} + +// Signal represents a D-Bus message of type Signal. The name member is given in +// "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost. +type Signal struct { + Sender string + Path ObjectPath + Name string + Body []interface{} +} + +// transport is a D-Bus transport. +type transport interface { + // Read and Write raw data (for example, for the authentication protocol). + io.ReadWriteCloser + + // Send the initial null byte used for the EXTERNAL mechanism. + SendNullByte() error + + // Returns whether this transport supports passing Unix FDs. + SupportsUnixFDs() bool + + // Signal the transport that Unix FD passing is enabled for this connection. + EnableUnixFDs() + + // Read / send a message, handling things like Unix FDs. + ReadMessage() (*Message, error) + SendMessage(*Message) error +} + +func getTransport(address string) (transport, error) { + var err error + var t transport + + m := map[string]func(string) (transport, error){ + "unix": newUnixTransport, + } + addresses := strings.Split(address, ";") + for _, v := range addresses { + i := strings.IndexRune(v, ':') + if i == -1 { + err = errors.New("dbus: invalid bus address (no transport)") + continue + } + f := m[v[:i]] + if f == nil { + err = errors.New("dbus: invalid bus address (invalid or unsupported transport)") + } + t, err = f(v[i+1:]) + if err == nil { + return t, nil + } + } + return nil, err +} + +// dereferenceAll returns a slice that, assuming that vs is a slice of pointers +// of arbitrary types, containes the values that are obtained from dereferencing +// all elements in vs. +func dereferenceAll(vs []interface{}) []interface{} { + for i := range vs { + v := reflect.ValueOf(vs[i]) + v = v.Elem() + vs[i] = v.Interface() + } + return vs +} + +// getKey gets a key from a the list of keys. Returns "" on error / not found... +func getKey(s, key string) string { + i := strings.Index(s, key) + if i == -1 { + return "" + } + if i+len(key)+1 >= len(s) || s[i+len(key)] != '=' { + return "" + } + j := strings.Index(s, ",") + if j == -1 { + j = len(s) + } + return s[i+len(key)+1 : j] +} diff --git a/vendor/src/github.com/godbus/dbus/conn_darwin.go b/vendor/src/github.com/godbus/dbus/conn_darwin.go new file mode 100644 index 0000000000..b67bb1b81d --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/conn_darwin.go @@ -0,0 +1,21 @@ +package dbus + +import ( + "errors" + "os/exec" +) + +func sessionBusPlatform() (*Conn, error) { + cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET") + b, err := cmd.CombinedOutput() + + if err != nil { + return nil, err + } + + if len(b) == 0 { + return nil, errors.New("dbus: couldn't determine address of session bus") + } + + return Dial("unix:path=" + string(b[:len(b)-1])) +} diff --git a/vendor/src/github.com/godbus/dbus/conn_other.go b/vendor/src/github.com/godbus/dbus/conn_other.go new file mode 100644 index 0000000000..f74b8758d4 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/conn_other.go @@ -0,0 +1,27 @@ +// +build !darwin + +package dbus + +import ( + "bytes" + "errors" + "os/exec" +) + +func sessionBusPlatform() (*Conn, error) { + cmd := exec.Command("dbus-launch") + b, err := cmd.CombinedOutput() + + if err != nil { + return nil, err + } + + i := bytes.IndexByte(b, '=') + j := bytes.IndexByte(b, '\n') + + if i == -1 || j == -1 { + return nil, errors.New("dbus: couldn't determine address of session bus") + } + + return Dial(string(b[i+1 : j])) +} diff --git a/vendor/src/github.com/godbus/dbus/conn_test.go b/vendor/src/github.com/godbus/dbus/conn_test.go new file mode 100644 index 0000000000..a2b14e8cc4 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/conn_test.go @@ -0,0 +1,199 @@ +package dbus + +import "testing" + +func TestSessionBus(t *testing.T) { + _, err := SessionBus() + if err != nil { + t.Error(err) + } +} + +func TestSystemBus(t *testing.T) { + _, err := SystemBus() + if err != nil { + t.Error(err) + } +} + +func TestSend(t *testing.T) { + bus, err := SessionBus() + if err != nil { + t.Error(err) + } + ch := make(chan *Call, 1) + msg := &Message{ + Type: TypeMethodCall, + Flags: 0, + Headers: map[HeaderField]Variant{ + FieldDestination: MakeVariant(bus.Names()[0]), + FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")), + FieldInterface: MakeVariant("org.freedesktop.DBus.Peer"), + FieldMember: MakeVariant("Ping"), + }, + } + call := bus.Send(msg, ch) + <-ch + if call.Err != nil { + t.Error(call.Err) + } +} + +type server struct{} + +func (server) Double(i int64) (int64, *Error) { + return 2 * i, nil +} + +func BenchmarkCall(b *testing.B) { + b.StopTimer() + var s string + bus, err := SessionBus() + if err != nil { + b.Fatal(err) + } + name := bus.Names()[0] + obj := bus.BusObject() + b.StartTimer() + for i := 0; i < b.N; i++ { + err := obj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&s) + if err != nil { + b.Fatal(err) + } + if s != name { + b.Errorf("got %s, wanted %s", s, name) + } + } +} + +func BenchmarkCallAsync(b *testing.B) { + b.StopTimer() + bus, err := SessionBus() + if err != nil { + b.Fatal(err) + } + name := bus.Names()[0] + obj := bus.BusObject() + c := make(chan *Call, 50) + done := make(chan struct{}) + go func() { + for i := 0; i < b.N; i++ { + v := <-c + if v.Err != nil { + b.Error(v.Err) + } + s := v.Body[0].(string) + if s != name { + b.Errorf("got %s, wanted %s", s, name) + } + } + close(done) + }() + b.StartTimer() + for i := 0; i < b.N; i++ { + obj.Go("org.freedesktop.DBus.GetNameOwner", 0, c, name) + } + <-done +} + +func BenchmarkServe(b *testing.B) { + b.StopTimer() + srv, err := SessionBus() + if err != nil { + b.Fatal(err) + } + cli, err := SessionBusPrivate() + if err != nil { + b.Fatal(err) + } + if err = cli.Auth(nil); err != nil { + b.Fatal(err) + } + if err = cli.Hello(); err != nil { + b.Fatal(err) + } + benchmarkServe(b, srv, cli) +} + +func BenchmarkServeAsync(b *testing.B) { + b.StopTimer() + srv, err := SessionBus() + if err != nil { + b.Fatal(err) + } + cli, err := SessionBusPrivate() + if err != nil { + b.Fatal(err) + } + if err = cli.Auth(nil); err != nil { + b.Fatal(err) + } + if err = cli.Hello(); err != nil { + b.Fatal(err) + } + benchmarkServeAsync(b, srv, cli) +} + +func BenchmarkServeSameConn(b *testing.B) { + b.StopTimer() + bus, err := SessionBus() + if err != nil { + b.Fatal(err) + } + + benchmarkServe(b, bus, bus) +} + +func BenchmarkServeSameConnAsync(b *testing.B) { + b.StopTimer() + bus, err := SessionBus() + if err != nil { + b.Fatal(err) + } + + benchmarkServeAsync(b, bus, bus) +} + +func benchmarkServe(b *testing.B, srv, cli *Conn) { + var r int64 + var err error + dest := srv.Names()[0] + srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") + obj := cli.Object(dest, "/org/guelfey/DBus/Test") + b.StartTimer() + for i := 0; i < b.N; i++ { + err = obj.Call("org.guelfey.DBus.Test.Double", 0, int64(i)).Store(&r) + if err != nil { + b.Fatal(err) + } + if r != 2*int64(i) { + b.Errorf("got %d, wanted %d", r, 2*int64(i)) + } + } +} + +func benchmarkServeAsync(b *testing.B, srv, cli *Conn) { + dest := srv.Names()[0] + srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test") + obj := cli.Object(dest, "/org/guelfey/DBus/Test") + c := make(chan *Call, 50) + done := make(chan struct{}) + go func() { + for i := 0; i < b.N; i++ { + v := <-c + if v.Err != nil { + b.Fatal(v.Err) + } + i, r := v.Args[0].(int64), v.Body[0].(int64) + if 2*i != r { + b.Errorf("got %d, wanted %d", r, 2*i) + } + } + close(done) + }() + b.StartTimer() + for i := 0; i < b.N; i++ { + obj.Go("org.guelfey.DBus.Test.Double", 0, c, int64(i)) + } + <-done +} diff --git a/vendor/src/github.com/godbus/dbus/dbus.go b/vendor/src/github.com/godbus/dbus/dbus.go new file mode 100644 index 0000000000..2ce68735cd --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/dbus.go @@ -0,0 +1,258 @@ +package dbus + +import ( + "errors" + "reflect" + "strings" +) + +var ( + byteType = reflect.TypeOf(byte(0)) + boolType = reflect.TypeOf(false) + uint8Type = reflect.TypeOf(uint8(0)) + int16Type = reflect.TypeOf(int16(0)) + uint16Type = reflect.TypeOf(uint16(0)) + int32Type = reflect.TypeOf(int32(0)) + uint32Type = reflect.TypeOf(uint32(0)) + int64Type = reflect.TypeOf(int64(0)) + uint64Type = reflect.TypeOf(uint64(0)) + float64Type = reflect.TypeOf(float64(0)) + stringType = reflect.TypeOf("") + signatureType = reflect.TypeOf(Signature{""}) + objectPathType = reflect.TypeOf(ObjectPath("")) + variantType = reflect.TypeOf(Variant{Signature{""}, nil}) + interfacesType = reflect.TypeOf([]interface{}{}) + unixFDType = reflect.TypeOf(UnixFD(0)) + unixFDIndexType = reflect.TypeOf(UnixFDIndex(0)) +) + +// An InvalidTypeError signals that a value which cannot be represented in the +// D-Bus wire format was passed to a function. +type InvalidTypeError struct { + Type reflect.Type +} + +func (e InvalidTypeError) Error() string { + return "dbus: invalid type " + e.Type.String() +} + +// Store copies the values contained in src to dest, which must be a slice of +// pointers. It converts slices of interfaces from src to corresponding structs +// in dest. An error is returned if the lengths of src and dest or the types of +// their elements don't match. +func Store(src []interface{}, dest ...interface{}) error { + if len(src) != len(dest) { + return errors.New("dbus.Store: length mismatch") + } + + for i := range src { + if err := store(src[i], dest[i]); err != nil { + return err + } + } + return nil +} + +func store(src, dest interface{}) error { + if reflect.TypeOf(dest).Elem() == reflect.TypeOf(src) { + reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src)) + return nil + } else if hasStruct(dest) { + rv := reflect.ValueOf(dest).Elem() + switch rv.Kind() { + case reflect.Struct: + vs, ok := src.([]interface{}) + if !ok { + return errors.New("dbus.Store: type mismatch") + } + t := rv.Type() + ndest := make([]interface{}, 0, rv.NumField()) + for i := 0; i < rv.NumField(); i++ { + field := t.Field(i) + if field.PkgPath == "" && field.Tag.Get("dbus") != "-" { + ndest = append(ndest, rv.Field(i).Addr().Interface()) + } + } + if len(vs) != len(ndest) { + return errors.New("dbus.Store: type mismatch") + } + err := Store(vs, ndest...) + if err != nil { + return errors.New("dbus.Store: type mismatch") + } + case reflect.Slice: + sv := reflect.ValueOf(src) + if sv.Kind() != reflect.Slice { + return errors.New("dbus.Store: type mismatch") + } + rv.Set(reflect.MakeSlice(rv.Type(), sv.Len(), sv.Len())) + for i := 0; i < sv.Len(); i++ { + if err := store(sv.Index(i).Interface(), rv.Index(i).Addr().Interface()); err != nil { + return err + } + } + case reflect.Map: + sv := reflect.ValueOf(src) + if sv.Kind() != reflect.Map { + return errors.New("dbus.Store: type mismatch") + } + keys := sv.MapKeys() + rv.Set(reflect.MakeMap(sv.Type())) + for _, key := range keys { + v := reflect.New(sv.Type().Elem()) + if err := store(v, sv.MapIndex(key).Interface()); err != nil { + return err + } + rv.SetMapIndex(key, v.Elem()) + } + default: + return errors.New("dbus.Store: type mismatch") + } + return nil + } else { + return errors.New("dbus.Store: type mismatch") + } +} + +func hasStruct(v interface{}) bool { + t := reflect.TypeOf(v) + for { + switch t.Kind() { + case reflect.Struct: + return true + case reflect.Slice, reflect.Ptr, reflect.Map: + t = t.Elem() + default: + return false + } + } +} + +// An ObjectPath is an object path as defined by the D-Bus spec. +type ObjectPath string + +// IsValid returns whether the object path is valid. +func (o ObjectPath) IsValid() bool { + s := string(o) + if len(s) == 0 { + return false + } + if s[0] != '/' { + return false + } + if s[len(s)-1] == '/' && len(s) != 1 { + return false + } + // probably not used, but technically possible + if s == "/" { + return true + } + split := strings.Split(s[1:], "/") + for _, v := range split { + if len(v) == 0 { + return false + } + for _, c := range v { + if !isMemberChar(c) { + return false + } + } + } + return true +} + +// A UnixFD is a Unix file descriptor sent over the wire. See the package-level +// documentation for more information about Unix file descriptor passsing. +type UnixFD int32 + +// A UnixFDIndex is the representation of a Unix file descriptor in a message. +type UnixFDIndex uint32 + +// alignment returns the alignment of values of type t. +func alignment(t reflect.Type) int { + switch t { + case variantType: + return 1 + case objectPathType: + return 4 + case signatureType: + return 1 + case interfacesType: // sometimes used for structs + return 8 + } + switch t.Kind() { + case reflect.Uint8: + return 1 + case reflect.Uint16, reflect.Int16: + return 2 + case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map: + return 4 + case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct: + return 8 + case reflect.Ptr: + return alignment(t.Elem()) + } + return 1 +} + +// isKeyType returns whether t is a valid type for a D-Bus dict. +func isKeyType(t reflect.Type) bool { + switch t.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64, + reflect.String: + + return true + } + return false +} + +// isValidInterface returns whether s is a valid name for an interface. +func isValidInterface(s string) bool { + if len(s) == 0 || len(s) > 255 || s[0] == '.' { + return false + } + elem := strings.Split(s, ".") + if len(elem) < 2 { + return false + } + for _, v := range elem { + if len(v) == 0 { + return false + } + if v[0] >= '0' && v[0] <= '9' { + return false + } + for _, c := range v { + if !isMemberChar(c) { + return false + } + } + } + return true +} + +// isValidMember returns whether s is a valid name for a member. +func isValidMember(s string) bool { + if len(s) == 0 || len(s) > 255 { + return false + } + i := strings.Index(s, ".") + if i != -1 { + return false + } + if s[0] >= '0' && s[0] <= '9' { + return false + } + for _, c := range s { + if !isMemberChar(c) { + return false + } + } + return true +} + +func isMemberChar(c rune) bool { + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || c == '_' +} diff --git a/vendor/src/github.com/godbus/dbus/decoder.go b/vendor/src/github.com/godbus/dbus/decoder.go new file mode 100644 index 0000000000..ef50dcab98 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/decoder.go @@ -0,0 +1,228 @@ +package dbus + +import ( + "encoding/binary" + "io" + "reflect" +) + +type decoder struct { + in io.Reader + order binary.ByteOrder + pos int +} + +// newDecoder returns a new decoder that reads values from in. The input is +// expected to be in the given byte order. +func newDecoder(in io.Reader, order binary.ByteOrder) *decoder { + dec := new(decoder) + dec.in = in + dec.order = order + return dec +} + +// align aligns the input to the given boundary and panics on error. +func (dec *decoder) align(n int) { + if dec.pos%n != 0 { + newpos := (dec.pos + n - 1) & ^(n - 1) + empty := make([]byte, newpos-dec.pos) + if _, err := io.ReadFull(dec.in, empty); err != nil { + panic(err) + } + dec.pos = newpos + } +} + +// Calls binary.Read(dec.in, dec.order, v) and panics on read errors. +func (dec *decoder) binread(v interface{}) { + if err := binary.Read(dec.in, dec.order, v); err != nil { + panic(err) + } +} + +func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) { + defer func() { + var ok bool + v := recover() + if err, ok = v.(error); ok { + if err == io.EOF || err == io.ErrUnexpectedEOF { + err = FormatError("unexpected EOF") + } + } + }() + vs = make([]interface{}, 0) + s := sig.str + for s != "" { + err, rem := validSingle(s, 0) + if err != nil { + return nil, err + } + v := dec.decode(s[:len(s)-len(rem)], 0) + vs = append(vs, v) + s = rem + } + return vs, nil +} + +func (dec *decoder) decode(s string, depth int) interface{} { + dec.align(alignment(typeFor(s))) + switch s[0] { + case 'y': + var b [1]byte + if _, err := dec.in.Read(b[:]); err != nil { + panic(err) + } + dec.pos++ + return b[0] + case 'b': + i := dec.decode("u", depth).(uint32) + switch { + case i == 0: + return false + case i == 1: + return true + default: + panic(FormatError("invalid value for boolean")) + } + case 'n': + var i int16 + dec.binread(&i) + dec.pos += 2 + return i + case 'i': + var i int32 + dec.binread(&i) + dec.pos += 4 + return i + case 'x': + var i int64 + dec.binread(&i) + dec.pos += 8 + return i + case 'q': + var i uint16 + dec.binread(&i) + dec.pos += 2 + return i + case 'u': + var i uint32 + dec.binread(&i) + dec.pos += 4 + return i + case 't': + var i uint64 + dec.binread(&i) + dec.pos += 8 + return i + case 'd': + var f float64 + dec.binread(&f) + dec.pos += 8 + return f + case 's': + length := dec.decode("u", depth).(uint32) + b := make([]byte, int(length)+1) + if _, err := io.ReadFull(dec.in, b); err != nil { + panic(err) + } + dec.pos += int(length) + 1 + return string(b[:len(b)-1]) + case 'o': + return ObjectPath(dec.decode("s", depth).(string)) + case 'g': + length := dec.decode("y", depth).(byte) + b := make([]byte, int(length)+1) + if _, err := io.ReadFull(dec.in, b); err != nil { + panic(err) + } + dec.pos += int(length) + 1 + sig, err := ParseSignature(string(b[:len(b)-1])) + if err != nil { + panic(err) + } + return sig + case 'v': + if depth >= 64 { + panic(FormatError("input exceeds container depth limit")) + } + var variant Variant + sig := dec.decode("g", depth).(Signature) + if len(sig.str) == 0 { + panic(FormatError("variant signature is empty")) + } + err, rem := validSingle(sig.str, 0) + if err != nil { + panic(err) + } + if rem != "" { + panic(FormatError("variant signature has multiple types")) + } + variant.sig = sig + variant.value = dec.decode(sig.str, depth+1) + return variant + case 'h': + return UnixFDIndex(dec.decode("u", depth).(uint32)) + case 'a': + if len(s) > 1 && s[1] == '{' { + ksig := s[2:3] + vsig := s[3 : len(s)-1] + v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig))) + if depth >= 63 { + panic(FormatError("input exceeds container depth limit")) + } + length := dec.decode("u", depth).(uint32) + // Even for empty maps, the correct padding must be included + dec.align(8) + spos := dec.pos + for dec.pos < spos+int(length) { + dec.align(8) + if !isKeyType(v.Type().Key()) { + panic(InvalidTypeError{v.Type()}) + } + kv := dec.decode(ksig, depth+2) + vv := dec.decode(vsig, depth+2) + v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv)) + } + return v.Interface() + } + if depth >= 64 { + panic(FormatError("input exceeds container depth limit")) + } + length := dec.decode("u", depth).(uint32) + v := reflect.MakeSlice(reflect.SliceOf(typeFor(s[1:])), 0, int(length)) + // Even for empty arrays, the correct padding must be included + dec.align(alignment(typeFor(s[1:]))) + spos := dec.pos + for dec.pos < spos+int(length) { + ev := dec.decode(s[1:], depth+1) + v = reflect.Append(v, reflect.ValueOf(ev)) + } + return v.Interface() + case '(': + if depth >= 64 { + panic(FormatError("input exceeds container depth limit")) + } + dec.align(8) + v := make([]interface{}, 0) + s = s[1 : len(s)-1] + for s != "" { + err, rem := validSingle(s, 0) + if err != nil { + panic(err) + } + ev := dec.decode(s[:len(s)-len(rem)], depth+1) + v = append(v, ev) + s = rem + } + return v + default: + panic(SignatureError{Sig: s}) + } +} + +// A FormatError is an error in the wire format. +type FormatError string + +func (e FormatError) Error() string { + return "dbus: wire format error: " + string(e) +} diff --git a/vendor/src/github.com/godbus/dbus/doc.go b/vendor/src/github.com/godbus/dbus/doc.go new file mode 100644 index 0000000000..deff554a38 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/doc.go @@ -0,0 +1,63 @@ +/* +Package dbus implements bindings to the D-Bus message bus system. + +To use the message bus API, you first need to connect to a bus (usually the +session or system bus). The acquired connection then can be used to call methods +on remote objects and emit or receive signals. Using the Export method, you can +arrange D-Bus methods calls to be directly translated to method calls on a Go +value. + +Conversion Rules + +For outgoing messages, Go types are automatically converted to the +corresponding D-Bus types. The following types are directly encoded as their +respective D-Bus equivalents: + + Go type | D-Bus type + ------------+----------- + byte | BYTE + bool | BOOLEAN + int16 | INT16 + uint16 | UINT16 + int32 | INT32 + uint32 | UINT32 + int64 | INT64 + uint64 | UINT64 + float64 | DOUBLE + string | STRING + ObjectPath | OBJECT_PATH + Signature | SIGNATURE + Variant | VARIANT + UnixFDIndex | UNIX_FD + +Slices and arrays encode as ARRAYs of their element type. + +Maps encode as DICTs, provided that their key type can be used as a key for +a DICT. + +Structs other than Variant and Signature encode as a STRUCT containing their +exported fields. Fields whose tags contain `dbus:"-"` and unexported fields will +be skipped. + +Pointers encode as the value they're pointed to. + +Trying to encode any other type or a slice, map or struct containing an +unsupported type will result in an InvalidTypeError. + +For incoming messages, the inverse of these rules are used, with the exception +of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces +containing the struct fields in the correct order. The Store function can be +used to convert such values to Go structs. + +Unix FD passing + +Handling Unix file descriptors deserves special mention. To use them, you should +first check that they are supported on a connection by calling SupportsUnixFDs. +If it returns true, all method of Connection will translate messages containing +UnixFD's to messages that are accompanied by the given file descriptors with the +UnixFD values being substituted by the correct indices. Similarily, the indices +of incoming messages are automatically resolved. It shouldn't be necessary to use +UnixFDIndex. + +*/ +package dbus diff --git a/vendor/src/github.com/godbus/dbus/encoder.go b/vendor/src/github.com/godbus/dbus/encoder.go new file mode 100644 index 0000000000..f9d2f05716 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/encoder.go @@ -0,0 +1,179 @@ +package dbus + +import ( + "bytes" + "encoding/binary" + "io" + "reflect" +) + +// An encoder encodes values to the D-Bus wire format. +type encoder struct { + out io.Writer + order binary.ByteOrder + pos int +} + +// NewEncoder returns a new encoder that writes to out in the given byte order. +func newEncoder(out io.Writer, order binary.ByteOrder) *encoder { + enc := new(encoder) + enc.out = out + enc.order = order + return enc +} + +// Aligns the next output to be on a multiple of n. Panics on write errors. +func (enc *encoder) align(n int) { + if enc.pos%n != 0 { + newpos := (enc.pos + n - 1) & ^(n - 1) + empty := make([]byte, newpos-enc.pos) + if _, err := enc.out.Write(empty); err != nil { + panic(err) + } + enc.pos = newpos + } +} + +// Calls binary.Write(enc.out, enc.order, v) and panics on write errors. +func (enc *encoder) binwrite(v interface{}) { + if err := binary.Write(enc.out, enc.order, v); err != nil { + panic(err) + } +} + +// Encode encodes the given values to the underyling reader. All written values +// are aligned properly as required by the D-Bus spec. +func (enc *encoder) Encode(vs ...interface{}) (err error) { + defer func() { + err, _ = recover().(error) + }() + for _, v := range vs { + enc.encode(reflect.ValueOf(v), 0) + } + return nil +} + +// encode encodes the given value to the writer and panics on error. depth holds +// the depth of the container nesting. +func (enc *encoder) encode(v reflect.Value, depth int) { + enc.align(alignment(v.Type())) + switch v.Kind() { + case reflect.Uint8: + var b [1]byte + b[0] = byte(v.Uint()) + if _, err := enc.out.Write(b[:]); err != nil { + panic(err) + } + enc.pos++ + case reflect.Bool: + if v.Bool() { + enc.encode(reflect.ValueOf(uint32(1)), depth) + } else { + enc.encode(reflect.ValueOf(uint32(0)), depth) + } + case reflect.Int16: + enc.binwrite(int16(v.Int())) + enc.pos += 2 + case reflect.Uint16: + enc.binwrite(uint16(v.Uint())) + enc.pos += 2 + case reflect.Int32: + enc.binwrite(int32(v.Int())) + enc.pos += 4 + case reflect.Uint32: + enc.binwrite(uint32(v.Uint())) + enc.pos += 4 + case reflect.Int64: + enc.binwrite(v.Int()) + enc.pos += 8 + case reflect.Uint64: + enc.binwrite(v.Uint()) + enc.pos += 8 + case reflect.Float64: + enc.binwrite(v.Float()) + enc.pos += 8 + case reflect.String: + enc.encode(reflect.ValueOf(uint32(len(v.String()))), depth) + b := make([]byte, v.Len()+1) + copy(b, v.String()) + b[len(b)-1] = 0 + n, err := enc.out.Write(b) + if err != nil { + panic(err) + } + enc.pos += n + case reflect.Ptr: + enc.encode(v.Elem(), depth) + case reflect.Slice, reflect.Array: + if depth >= 64 { + panic(FormatError("input exceeds container depth limit")) + } + var buf bytes.Buffer + bufenc := newEncoder(&buf, enc.order) + + for i := 0; i < v.Len(); i++ { + bufenc.encode(v.Index(i), depth+1) + } + enc.encode(reflect.ValueOf(uint32(buf.Len())), depth) + length := buf.Len() + enc.align(alignment(v.Type().Elem())) + if _, err := buf.WriteTo(enc.out); err != nil { + panic(err) + } + enc.pos += length + case reflect.Struct: + if depth >= 64 && v.Type() != signatureType { + panic(FormatError("input exceeds container depth limit")) + } + switch t := v.Type(); t { + case signatureType: + str := v.Field(0) + enc.encode(reflect.ValueOf(byte(str.Len())), depth+1) + b := make([]byte, str.Len()+1) + copy(b, str.String()) + b[len(b)-1] = 0 + n, err := enc.out.Write(b) + if err != nil { + panic(err) + } + enc.pos += n + case variantType: + variant := v.Interface().(Variant) + enc.encode(reflect.ValueOf(variant.sig), depth+1) + enc.encode(reflect.ValueOf(variant.value), depth+1) + default: + for i := 0; i < v.Type().NumField(); i++ { + field := t.Field(i) + if field.PkgPath == "" && field.Tag.Get("dbus") != "-" { + enc.encode(v.Field(i), depth+1) + } + } + } + case reflect.Map: + // Maps are arrays of structures, so they actually increase the depth by + // 2. + if depth >= 63 { + panic(FormatError("input exceeds container depth limit")) + } + if !isKeyType(v.Type().Key()) { + panic(InvalidTypeError{v.Type()}) + } + keys := v.MapKeys() + var buf bytes.Buffer + bufenc := newEncoder(&buf, enc.order) + for _, k := range keys { + bufenc.align(8) + bufenc.encode(k, depth+2) + bufenc.encode(v.MapIndex(k), depth+2) + } + enc.encode(reflect.ValueOf(uint32(buf.Len())), depth) + length := buf.Len() + enc.align(8) + if _, err := buf.WriteTo(enc.out); err != nil { + panic(err) + } + enc.pos += length + default: + panic(InvalidTypeError{v.Type()}) + } +} diff --git a/vendor/src/github.com/godbus/dbus/examples_test.go b/vendor/src/github.com/godbus/dbus/examples_test.go new file mode 100644 index 0000000000..0218ac5598 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/examples_test.go @@ -0,0 +1,50 @@ +package dbus + +import "fmt" + +func ExampleConn_Emit() { + conn, err := SessionBus() + if err != nil { + panic(err) + } + + conn.Emit("/foo/bar", "foo.bar.Baz", uint32(0xDAEDBEEF)) +} + +func ExampleObject_Call() { + var list []string + + conn, err := SessionBus() + if err != nil { + panic(err) + } + + err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&list) + if err != nil { + panic(err) + } + for _, v := range list { + fmt.Println(v) + } +} + +func ExampleObject_Go() { + conn, err := SessionBus() + if err != nil { + panic(err) + } + + ch := make(chan *Call, 10) + conn.BusObject().Go("org.freedesktop.DBus.ListActivatableNames", 0, ch) + select { + case call := <-ch: + if call.Err != nil { + panic(err) + } + list := call.Body[0].([]string) + for _, v := range list { + fmt.Println(v) + } + // put some other cases here + } +} diff --git a/vendor/src/github.com/godbus/dbus/export.go b/vendor/src/github.com/godbus/dbus/export.go new file mode 100644 index 0000000000..1dd1591528 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/export.go @@ -0,0 +1,302 @@ +package dbus + +import ( + "errors" + "reflect" + "strings" + "unicode" +) + +var ( + errmsgInvalidArg = Error{ + "org.freedesktop.DBus.Error.InvalidArgs", + []interface{}{"Invalid type / number of args"}, + } + errmsgNoObject = Error{ + "org.freedesktop.DBus.Error.NoSuchObject", + []interface{}{"No such object"}, + } + errmsgUnknownMethod = Error{ + "org.freedesktop.DBus.Error.UnknownMethod", + []interface{}{"Unknown / invalid method"}, + } +) + +// Sender is a type which can be used in exported methods to receive the message +// sender. +type Sender string + +func exportedMethod(v interface{}, name string) reflect.Value { + if v == nil { + return reflect.Value{} + } + m := reflect.ValueOf(v).MethodByName(name) + if !m.IsValid() { + return reflect.Value{} + } + t := m.Type() + if t.NumOut() == 0 || + t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) { + + return reflect.Value{} + } + return m +} + +// handleCall handles the given method call (i.e. looks if it's one of the +// pre-implemented ones and searches for a corresponding handler if not). +func (conn *Conn) handleCall(msg *Message) { + name := msg.Headers[FieldMember].value.(string) + path := msg.Headers[FieldPath].value.(ObjectPath) + ifaceName, hasIface := msg.Headers[FieldInterface].value.(string) + sender, hasSender := msg.Headers[FieldSender].value.(string) + serial := msg.serial + if ifaceName == "org.freedesktop.DBus.Peer" { + switch name { + case "Ping": + conn.sendReply(sender, serial) + case "GetMachineId": + conn.sendReply(sender, serial, conn.uuid) + default: + conn.sendError(errmsgUnknownMethod, sender, serial) + } + return + } + if len(name) == 0 || unicode.IsLower([]rune(name)[0]) { + conn.sendError(errmsgUnknownMethod, sender, serial) + } + var m reflect.Value + if hasIface { + conn.handlersLck.RLock() + obj, ok := conn.handlers[path] + if !ok { + conn.sendError(errmsgNoObject, sender, serial) + conn.handlersLck.RUnlock() + return + } + iface := obj[ifaceName] + conn.handlersLck.RUnlock() + m = exportedMethod(iface, name) + } else { + conn.handlersLck.RLock() + if _, ok := conn.handlers[path]; !ok { + conn.sendError(errmsgNoObject, sender, serial) + conn.handlersLck.RUnlock() + return + } + for _, v := range conn.handlers[path] { + m = exportedMethod(v, name) + if m.IsValid() { + break + } + } + conn.handlersLck.RUnlock() + } + if !m.IsValid() { + conn.sendError(errmsgUnknownMethod, sender, serial) + return + } + t := m.Type() + vs := msg.Body + pointers := make([]interface{}, t.NumIn()) + decode := make([]interface{}, 0, len(vs)) + for i := 0; i < t.NumIn(); i++ { + tp := t.In(i) + val := reflect.New(tp) + pointers[i] = val.Interface() + if tp == reflect.TypeOf((*Sender)(nil)).Elem() { + val.Elem().SetString(sender) + } else { + decode = append(decode, pointers[i]) + } + } + if len(decode) != len(vs) { + conn.sendError(errmsgInvalidArg, sender, serial) + return + } + if err := Store(vs, decode...); err != nil { + conn.sendError(errmsgInvalidArg, sender, serial) + return + } + params := make([]reflect.Value, len(pointers)) + for i := 0; i < len(pointers); i++ { + params[i] = reflect.ValueOf(pointers[i]).Elem() + } + ret := m.Call(params) + if em := ret[t.NumOut()-1].Interface().(*Error); em != nil { + conn.sendError(*em, sender, serial) + return + } + if msg.Flags&FlagNoReplyExpected == 0 { + reply := new(Message) + reply.Type = TypeMethodReply + reply.serial = conn.getSerial() + reply.Headers = make(map[HeaderField]Variant) + if hasSender { + reply.Headers[FieldDestination] = msg.Headers[FieldSender] + } + reply.Headers[FieldReplySerial] = MakeVariant(msg.serial) + reply.Body = make([]interface{}, len(ret)-1) + for i := 0; i < len(ret)-1; i++ { + reply.Body[i] = ret[i].Interface() + } + if len(ret) != 1 { + reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...)) + } + conn.outLck.RLock() + if !conn.closed { + conn.out <- reply + } + conn.outLck.RUnlock() + } +} + +// Emit emits the given signal on the message bus. The name parameter must be +// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost". +func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error { + if !path.IsValid() { + return errors.New("dbus: invalid object path") + } + i := strings.LastIndex(name, ".") + if i == -1 { + return errors.New("dbus: invalid method name") + } + iface := name[:i] + member := name[i+1:] + if !isValidMember(member) { + return errors.New("dbus: invalid method name") + } + if !isValidInterface(iface) { + return errors.New("dbus: invalid interface name") + } + msg := new(Message) + msg.Type = TypeSignal + msg.serial = conn.getSerial() + msg.Headers = make(map[HeaderField]Variant) + msg.Headers[FieldInterface] = MakeVariant(iface) + msg.Headers[FieldMember] = MakeVariant(member) + msg.Headers[FieldPath] = MakeVariant(path) + msg.Body = values + if len(values) > 0 { + msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...)) + } + conn.outLck.RLock() + defer conn.outLck.RUnlock() + if conn.closed { + return ErrClosed + } + conn.out <- msg + return nil +} + +// Export registers the given value to be exported as an object on the +// message bus. +// +// If a method call on the given path and interface is received, an exported +// method with the same name is called with v as the receiver if the +// parameters match and the last return value is of type *Error. If this +// *Error is not nil, it is sent back to the caller as an error. +// Otherwise, a method reply is sent with the other return values as its body. +// +// Any parameters with the special type Sender are set to the sender of the +// dbus message when the method is called. Parameters of this type do not +// contribute to the dbus signature of the method (i.e. the method is exposed +// as if the parameters of type Sender were not there). +// +// Every method call is executed in a new goroutine, so the method may be called +// in multiple goroutines at once. +// +// Method calls on the interface org.freedesktop.DBus.Peer will be automatically +// handled for every object. +// +// Passing nil as the first parameter will cause conn to cease handling calls on +// the given combination of path and interface. +// +// Export returns an error if path is not a valid path name. +func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error { + if !path.IsValid() { + return errors.New("dbus: invalid path name") + } + conn.handlersLck.Lock() + if v == nil { + if _, ok := conn.handlers[path]; ok { + delete(conn.handlers[path], iface) + if len(conn.handlers[path]) == 0 { + delete(conn.handlers, path) + } + } + return nil + } + if _, ok := conn.handlers[path]; !ok { + conn.handlers[path] = make(map[string]interface{}) + } + conn.handlers[path][iface] = v + conn.handlersLck.Unlock() + return nil +} + +// ReleaseName calls org.freedesktop.DBus.ReleaseName. You should use only this +// method to release a name (see below). +func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) { + var r uint32 + err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r) + if err != nil { + return 0, err + } + if r == uint32(ReleaseNameReplyReleased) { + conn.namesLck.Lock() + for i, v := range conn.names { + if v == name { + copy(conn.names[i:], conn.names[i+1:]) + conn.names = conn.names[:len(conn.names)-1] + } + } + conn.namesLck.Unlock() + } + return ReleaseNameReply(r), nil +} + +// RequestName calls org.freedesktop.DBus.RequestName. You should use only this +// method to request a name because package dbus needs to keep track of all +// names that the connection has. +func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) { + var r uint32 + err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r) + if err != nil { + return 0, err + } + if r == uint32(RequestNameReplyPrimaryOwner) { + conn.namesLck.Lock() + conn.names = append(conn.names, name) + conn.namesLck.Unlock() + } + return RequestNameReply(r), nil +} + +// ReleaseNameReply is the reply to a ReleaseName call. +type ReleaseNameReply uint32 + +const ( + ReleaseNameReplyReleased ReleaseNameReply = 1 + iota + ReleaseNameReplyNonExistent + ReleaseNameReplyNotOwner +) + +// RequestNameFlags represents the possible flags for a RequestName call. +type RequestNameFlags uint32 + +const ( + NameFlagAllowReplacement RequestNameFlags = 1 << iota + NameFlagReplaceExisting + NameFlagDoNotQueue +) + +// RequestNameReply is the reply to a RequestName call. +type RequestNameReply uint32 + +const ( + RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota + RequestNameReplyInQueue + RequestNameReplyExists + RequestNameReplyAlreadyOwner +) diff --git a/vendor/src/github.com/godbus/dbus/homedir.go b/vendor/src/github.com/godbus/dbus/homedir.go new file mode 100644 index 0000000000..0b745f9313 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/homedir.go @@ -0,0 +1,28 @@ +package dbus + +import ( + "os" + "sync" +) + +var ( + homeDir string + homeDirLock sync.Mutex +) + +func getHomeDir() string { + homeDirLock.Lock() + defer homeDirLock.Unlock() + + if homeDir != "" { + return homeDir + } + + homeDir = os.Getenv("HOME") + if homeDir != "" { + return homeDir + } + + homeDir = lookupHomeDir() + return homeDir +} diff --git a/vendor/src/github.com/godbus/dbus/homedir_dynamic.go b/vendor/src/github.com/godbus/dbus/homedir_dynamic.go new file mode 100644 index 0000000000..2732081e73 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/homedir_dynamic.go @@ -0,0 +1,15 @@ +// +build !static_build + +package dbus + +import ( + "os/user" +) + +func lookupHomeDir() string { + u, err := user.Current() + if err != nil { + return "/" + } + return u.HomeDir +} diff --git a/vendor/src/github.com/godbus/dbus/homedir_static.go b/vendor/src/github.com/godbus/dbus/homedir_static.go new file mode 100644 index 0000000000..b9d9cb5525 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/homedir_static.go @@ -0,0 +1,45 @@ +// +build static_build + +package dbus + +import ( + "bufio" + "os" + "strconv" + "strings" +) + +func lookupHomeDir() string { + myUid := os.Getuid() + + f, err := os.Open("/etc/passwd") + if err != nil { + return "/" + } + defer f.Close() + + s := bufio.NewScanner(f) + + for s.Scan() { + if err := s.Err(); err != nil { + break + } + + line := strings.TrimSpace(s.Text()) + if line == "" { + continue + } + + parts := strings.Split(line, ":") + + if len(parts) >= 6 { + uid, err := strconv.Atoi(parts[2]) + if err == nil && uid == myUid { + return parts[5] + } + } + } + + // Default to / if we can't get a better value + return "/" +} diff --git a/vendor/src/github.com/godbus/dbus/introspect/call.go b/vendor/src/github.com/godbus/dbus/introspect/call.go new file mode 100644 index 0000000000..4aca2ea63e --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/introspect/call.go @@ -0,0 +1,27 @@ +package introspect + +import ( + "encoding/xml" + "github.com/godbus/dbus" + "strings" +) + +// Call calls org.freedesktop.Introspectable.Introspect on a remote object +// and returns the introspection data. +func Call(o *dbus.Object) (*Node, error) { + var xmldata string + var node Node + + err := o.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xmldata) + if err != nil { + return nil, err + } + err = xml.NewDecoder(strings.NewReader(xmldata)).Decode(&node) + if err != nil { + return nil, err + } + if node.Name == "" { + node.Name = string(o.Path()) + } + return &node, nil +} diff --git a/vendor/src/github.com/godbus/dbus/introspect/introspect.go b/vendor/src/github.com/godbus/dbus/introspect/introspect.go new file mode 100644 index 0000000000..dafcdb8b7a --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/introspect/introspect.go @@ -0,0 +1,80 @@ +// Package introspect provides some utilities for dealing with the DBus +// introspection format. +package introspect + +import "encoding/xml" + +// The introspection data for the org.freedesktop.DBus.Introspectable interface. +var IntrospectData = Interface{ + Name: "org.freedesktop.DBus.Introspectable", + Methods: []Method{ + { + Name: "Introspect", + Args: []Arg{ + {"out", "s", "out"}, + }, + }, + }, +} + +// The introspection data for the org.freedesktop.DBus.Introspectable interface, +// as a string. +const IntrospectDataString = ` + + + + + +` + +// Node is the root element of an introspection. +type Node struct { + XMLName xml.Name `xml:"node"` + Name string `xml:"name,attr,omitempty"` + Interfaces []Interface `xml:"interface"` + Children []Node `xml:"node,omitempty"` +} + +// Interface describes a DBus interface that is available on the message bus. +type Interface struct { + Name string `xml:"name,attr"` + Methods []Method `xml:"method"` + Signals []Signal `xml:"signal"` + Properties []Property `xml:"property"` + Annotations []Annotation `xml:"annotation"` +} + +// Method describes a Method on an Interface as retured by an introspection. +type Method struct { + Name string `xml:"name,attr"` + Args []Arg `xml:"arg"` + Annotations []Annotation `xml:"annotation"` +} + +// Signal describes a Signal emitted on an Interface. +type Signal struct { + Name string `xml:"name,attr"` + Args []Arg `xml:"arg"` + Annotations []Annotation `xml:"annotation"` +} + +// Property describes a property of an Interface. +type Property struct { + Name string `xml:"name,attr"` + Type string `xml:"type,attr"` + Access string `xml:"access,attr"` + Annotations []Annotation `xml:"annotation"` +} + +// Arg represents an argument of a method or a signal. +type Arg struct { + Name string `xml:"name,attr,omitempty"` + Type string `xml:"type,attr"` + Direction string `xml:"direction,attr,omitempty"` +} + +// Annotation is an annotation in the introspection format. +type Annotation struct { + Name string `xml:"name,attr"` + Value string `xml:"value,attr"` +} diff --git a/vendor/src/github.com/godbus/dbus/introspect/introspectable.go b/vendor/src/github.com/godbus/dbus/introspect/introspectable.go new file mode 100644 index 0000000000..a2a965a343 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/introspect/introspectable.go @@ -0,0 +1,74 @@ +package introspect + +import ( + "encoding/xml" + "github.com/godbus/dbus" + "reflect" +) + +// Introspectable implements org.freedesktop.Introspectable. +// +// You can create it by converting the XML-formatted introspection data from a +// string to an Introspectable or call NewIntrospectable with a Node. Then, +// export it as org.freedesktop.Introspectable on you object. +type Introspectable string + +// NewIntrospectable returns an Introspectable that returns the introspection +// data that corresponds to the given Node. If n.Interfaces doesn't contain the +// data for org.freedesktop.DBus.Introspectable, it is added automatically. +func NewIntrospectable(n *Node) Introspectable { + found := false + for _, v := range n.Interfaces { + if v.Name == "org.freedesktop.DBus.Introspectable" { + found = true + break + } + } + if !found { + n.Interfaces = append(n.Interfaces, IntrospectData) + } + b, err := xml.Marshal(n) + if err != nil { + panic(err) + } + return Introspectable(b) +} + +// Introspect implements org.freedesktop.Introspectable.Introspect. +func (i Introspectable) Introspect() (string, *dbus.Error) { + return string(i), nil +} + +// Methods returns the description of the methods of v. This can be used to +// create a Node which can be passed to NewIntrospectable. +func Methods(v interface{}) []Method { + t := reflect.TypeOf(v) + ms := make([]Method, 0, t.NumMethod()) + for i := 0; i < t.NumMethod(); i++ { + if t.Method(i).PkgPath != "" { + continue + } + mt := t.Method(i).Type + if mt.NumOut() == 0 || + mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{"", nil}) { + + continue + } + var m Method + m.Name = t.Method(i).Name + m.Args = make([]Arg, 0, mt.NumIn()+mt.NumOut()-2) + for j := 1; j < mt.NumIn(); j++ { + if mt.In(j) != reflect.TypeOf((*dbus.Sender)(nil)).Elem() { + arg := Arg{"", dbus.SignatureOfType(mt.In(j)).String(), "in"} + m.Args = append(m.Args, arg) + } + } + for j := 0; j < mt.NumOut()-1; j++ { + arg := Arg{"", dbus.SignatureOfType(mt.Out(j)).String(), "out"} + m.Args = append(m.Args, arg) + } + m.Annotations = make([]Annotation, 0) + ms = append(ms, m) + } + return ms +} diff --git a/vendor/src/github.com/godbus/dbus/message.go b/vendor/src/github.com/godbus/dbus/message.go new file mode 100644 index 0000000000..075d6e38ba --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/message.go @@ -0,0 +1,346 @@ +package dbus + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "reflect" + "strconv" +) + +const protoVersion byte = 1 + +// Flags represents the possible flags of a D-Bus message. +type Flags byte + +const ( + // FlagNoReplyExpected signals that the message is not expected to generate + // a reply. If this flag is set on outgoing messages, any possible reply + // will be discarded. + FlagNoReplyExpected Flags = 1 << iota + // FlagNoAutoStart signals that the message bus should not automatically + // start an application when handling this message. + FlagNoAutoStart +) + +// Type represents the possible types of a D-Bus message. +type Type byte + +const ( + TypeMethodCall Type = 1 + iota + TypeMethodReply + TypeError + TypeSignal + typeMax +) + +func (t Type) String() string { + switch t { + case TypeMethodCall: + return "method call" + case TypeMethodReply: + return "reply" + case TypeError: + return "error" + case TypeSignal: + return "signal" + } + return "invalid" +} + +// HeaderField represents the possible byte codes for the headers +// of a D-Bus message. +type HeaderField byte + +const ( + FieldPath HeaderField = 1 + iota + FieldInterface + FieldMember + FieldErrorName + FieldReplySerial + FieldDestination + FieldSender + FieldSignature + FieldUnixFDs + fieldMax +) + +// An InvalidMessageError describes the reason why a D-Bus message is regarded as +// invalid. +type InvalidMessageError string + +func (e InvalidMessageError) Error() string { + return "dbus: invalid message: " + string(e) +} + +// fieldType are the types of the various header fields. +var fieldTypes = [fieldMax]reflect.Type{ + FieldPath: objectPathType, + FieldInterface: stringType, + FieldMember: stringType, + FieldErrorName: stringType, + FieldReplySerial: uint32Type, + FieldDestination: stringType, + FieldSender: stringType, + FieldSignature: signatureType, + FieldUnixFDs: uint32Type, +} + +// requiredFields lists the header fields that are required by the different +// message types. +var requiredFields = [typeMax][]HeaderField{ + TypeMethodCall: {FieldPath, FieldMember}, + TypeMethodReply: {FieldReplySerial}, + TypeError: {FieldErrorName, FieldReplySerial}, + TypeSignal: {FieldPath, FieldInterface, FieldMember}, +} + +// Message represents a single D-Bus message. +type Message struct { + Type + Flags + Headers map[HeaderField]Variant + Body []interface{} + + serial uint32 +} + +type header struct { + Field byte + Variant +} + +// DecodeMessage tries to decode a single message in the D-Bus wire format +// from the given reader. The byte order is figured out from the first byte. +// The possibly returned error can be an error of the underlying reader, an +// InvalidMessageError or a FormatError. +func DecodeMessage(rd io.Reader) (msg *Message, err error) { + var order binary.ByteOrder + var hlength, length uint32 + var typ, flags, proto byte + var headers []header + + b := make([]byte, 1) + _, err = rd.Read(b) + if err != nil { + return + } + switch b[0] { + case 'l': + order = binary.LittleEndian + case 'B': + order = binary.BigEndian + default: + return nil, InvalidMessageError("invalid byte order") + } + + dec := newDecoder(rd, order) + dec.pos = 1 + + msg = new(Message) + vs, err := dec.Decode(Signature{"yyyuu"}) + if err != nil { + return nil, err + } + if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil { + return nil, err + } + msg.Type = Type(typ) + msg.Flags = Flags(flags) + + // get the header length separately because we need it later + b = make([]byte, 4) + _, err = io.ReadFull(rd, b) + if err != nil { + return nil, err + } + binary.Read(bytes.NewBuffer(b), order, &hlength) + if hlength+length+16 > 1<<27 { + return nil, InvalidMessageError("message is too long") + } + dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order) + dec.pos = 12 + vs, err = dec.Decode(Signature{"a(yv)"}) + if err != nil { + return nil, err + } + if err = Store(vs, &headers); err != nil { + return nil, err + } + + msg.Headers = make(map[HeaderField]Variant) + for _, v := range headers { + msg.Headers[HeaderField(v.Field)] = v.Variant + } + + dec.align(8) + body := make([]byte, int(length)) + if length != 0 { + _, err := io.ReadFull(rd, body) + if err != nil { + return nil, err + } + } + + if err = msg.IsValid(); err != nil { + return nil, err + } + sig, _ := msg.Headers[FieldSignature].value.(Signature) + if sig.str != "" { + buf := bytes.NewBuffer(body) + dec = newDecoder(buf, order) + vs, err := dec.Decode(sig) + if err != nil { + return nil, err + } + msg.Body = vs + } + + return +} + +// EncodeTo encodes and sends a message to the given writer. The byte order must +// be either binary.LittleEndian or binary.BigEndian. If the message is not +// valid or an error occurs when writing, an error is returned. +func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error { + if err := msg.IsValid(); err != nil { + return err + } + var vs [7]interface{} + switch order { + case binary.LittleEndian: + vs[0] = byte('l') + case binary.BigEndian: + vs[0] = byte('B') + default: + return errors.New("dbus: invalid byte order") + } + body := new(bytes.Buffer) + enc := newEncoder(body, order) + if len(msg.Body) != 0 { + enc.Encode(msg.Body...) + } + vs[1] = msg.Type + vs[2] = msg.Flags + vs[3] = protoVersion + vs[4] = uint32(len(body.Bytes())) + vs[5] = msg.serial + headers := make([]header, 0, len(msg.Headers)) + for k, v := range msg.Headers { + headers = append(headers, header{byte(k), v}) + } + vs[6] = headers + var buf bytes.Buffer + enc = newEncoder(&buf, order) + enc.Encode(vs[:]...) + enc.align(8) + body.WriteTo(&buf) + if buf.Len() > 1<<27 { + return InvalidMessageError("message is too long") + } + if _, err := buf.WriteTo(out); err != nil { + return err + } + return nil +} + +// IsValid checks whether msg is a valid message and returns an +// InvalidMessageError if it is not. +func (msg *Message) IsValid() error { + if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected) != 0 { + return InvalidMessageError("invalid flags") + } + if msg.Type == 0 || msg.Type >= typeMax { + return InvalidMessageError("invalid message type") + } + for k, v := range msg.Headers { + if k == 0 || k >= fieldMax { + return InvalidMessageError("invalid header") + } + if reflect.TypeOf(v.value) != fieldTypes[k] { + return InvalidMessageError("invalid type of header field") + } + } + for _, v := range requiredFields[msg.Type] { + if _, ok := msg.Headers[v]; !ok { + return InvalidMessageError("missing required header") + } + } + if path, ok := msg.Headers[FieldPath]; ok { + if !path.value.(ObjectPath).IsValid() { + return InvalidMessageError("invalid path name") + } + } + if iface, ok := msg.Headers[FieldInterface]; ok { + if !isValidInterface(iface.value.(string)) { + return InvalidMessageError("invalid interface name") + } + } + if member, ok := msg.Headers[FieldMember]; ok { + if !isValidMember(member.value.(string)) { + return InvalidMessageError("invalid member name") + } + } + if errname, ok := msg.Headers[FieldErrorName]; ok { + if !isValidInterface(errname.value.(string)) { + return InvalidMessageError("invalid error name") + } + } + if len(msg.Body) != 0 { + if _, ok := msg.Headers[FieldSignature]; !ok { + return InvalidMessageError("missing signature") + } + } + return nil +} + +// Serial returns the message's serial number. The returned value is only valid +// for messages received by eavesdropping. +func (msg *Message) Serial() uint32 { + return msg.serial +} + +// String returns a string representation of a message similar to the format of +// dbus-monitor. +func (msg *Message) String() string { + if err := msg.IsValid(); err != nil { + return "" + } + s := msg.Type.String() + if v, ok := msg.Headers[FieldSender]; ok { + s += " from " + v.value.(string) + } + if v, ok := msg.Headers[FieldDestination]; ok { + s += " to " + v.value.(string) + } + s += " serial " + strconv.FormatUint(uint64(msg.serial), 10) + if v, ok := msg.Headers[FieldReplySerial]; ok { + s += " reply_serial " + strconv.FormatUint(uint64(v.value.(uint32)), 10) + } + if v, ok := msg.Headers[FieldUnixFDs]; ok { + s += " unixfds " + strconv.FormatUint(uint64(v.value.(uint32)), 10) + } + if v, ok := msg.Headers[FieldPath]; ok { + s += " path " + string(v.value.(ObjectPath)) + } + if v, ok := msg.Headers[FieldInterface]; ok { + s += " interface " + v.value.(string) + } + if v, ok := msg.Headers[FieldErrorName]; ok { + s += " error " + v.value.(string) + } + if v, ok := msg.Headers[FieldMember]; ok { + s += " member " + v.value.(string) + } + if len(msg.Body) != 0 { + s += "\n" + } + for i, v := range msg.Body { + s += " " + MakeVariant(v).String() + if i != len(msg.Body)-1 { + s += "\n" + } + } + return s +} diff --git a/vendor/src/github.com/godbus/dbus/prop/prop.go b/vendor/src/github.com/godbus/dbus/prop/prop.go new file mode 100644 index 0000000000..ed5bdf2243 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/prop/prop.go @@ -0,0 +1,264 @@ +// Package prop provides the Properties struct which can be used to implement +// org.freedesktop.DBus.Properties. +package prop + +import ( + "github.com/godbus/dbus" + "github.com/godbus/dbus/introspect" + "sync" +) + +// EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is +// emitted for a property. If it is EmitTrue, the signal is emitted. If it is +// EmitInvalidates, the signal is also emitted, but the new value of the property +// is not disclosed. +type EmitType byte + +const ( + EmitFalse EmitType = iota + EmitTrue + EmitInvalidates +) + +// ErrIfaceNotFound is the error returned to peers who try to access properties +// on interfaces that aren't found. +var ErrIfaceNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil} + +// ErrPropNotFound is the error returned to peers trying to access properties +// that aren't found. +var ErrPropNotFound = &dbus.Error{"org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil} + +// ErrReadOnly is the error returned to peers trying to set a read-only +// property. +var ErrReadOnly = &dbus.Error{"org.freedesktop.DBus.Properties.Error.ReadOnly", nil} + +// ErrInvalidArg is returned to peers if the type of the property that is being +// changed and the argument don't match. +var ErrInvalidArg = &dbus.Error{"org.freedesktop.DBus.Properties.Error.InvalidArg", nil} + +// The introspection data for the org.freedesktop.DBus.Properties interface. +var IntrospectData = introspect.Interface{ + Name: "org.freedesktop.DBus.Properties", + Methods: []introspect.Method{ + { + Name: "Get", + Args: []introspect.Arg{ + {"interface", "s", "in"}, + {"property", "s", "in"}, + {"value", "v", "out"}, + }, + }, + { + Name: "GetAll", + Args: []introspect.Arg{ + {"interface", "s", "in"}, + {"props", "a{sv}", "out"}, + }, + }, + { + Name: "Set", + Args: []introspect.Arg{ + {"interface", "s", "in"}, + {"property", "s", "in"}, + {"value", "v", "in"}, + }, + }, + }, + Signals: []introspect.Signal{ + { + Name: "PropertiesChanged", + Args: []introspect.Arg{ + {"interface", "s", "out"}, + {"changed_properties", "a{sv}", "out"}, + {"invalidates_properties", "as", "out"}, + }, + }, + }, +} + +// The introspection data for the org.freedesktop.DBus.Properties interface, as +// a string. +const IntrospectDataString = ` + + + + + + + + + + + + + + + + + + + + + +` + +// Prop represents a single property. It is used for creating a Properties +// value. +type Prop struct { + // Initial value. Must be a DBus-representable type. + Value interface{} + + // If true, the value can be modified by calls to Set. + Writable bool + + // Controls how org.freedesktop.DBus.Properties.PropertiesChanged is + // emitted if this property changes. + Emit EmitType + + // If not nil, anytime this property is changed by Set, this function is + // called with an appropiate Change as its argument. If the returned error + // is not nil, it is sent back to the caller of Set and the property is not + // changed. + Callback func(*Change) *dbus.Error +} + +// Change represents a change of a property by a call to Set. +type Change struct { + Props *Properties + Iface string + Name string + Value interface{} +} + +// Properties is a set of values that can be made available to the message bus +// using the org.freedesktop.DBus.Properties interface. It is safe for +// concurrent use by multiple goroutines. +type Properties struct { + m map[string]map[string]*Prop + mut sync.RWMutex + conn *dbus.Conn + path dbus.ObjectPath +} + +// New returns a new Properties structure that manages the given properties. +// The key for the first-level map of props is the name of the interface; the +// second-level key is the name of the property. The returned structure will be +// exported as org.freedesktop.DBus.Properties on path. +func New(conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Prop) *Properties { + p := &Properties{m: props, conn: conn, path: path} + conn.Export(p, path, "org.freedesktop.DBus.Properties") + return p +} + +// Get implements org.freedesktop.DBus.Properties.Get. +func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) { + p.mut.RLock() + defer p.mut.RUnlock() + m, ok := p.m[iface] + if !ok { + return dbus.Variant{}, ErrIfaceNotFound + } + prop, ok := m[property] + if !ok { + return dbus.Variant{}, ErrPropNotFound + } + return dbus.MakeVariant(prop.Value), nil +} + +// GetAll implements org.freedesktop.DBus.Properties.GetAll. +func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) { + p.mut.RLock() + defer p.mut.RUnlock() + m, ok := p.m[iface] + if !ok { + return nil, ErrIfaceNotFound + } + rm := make(map[string]dbus.Variant, len(m)) + for k, v := range m { + rm[k] = dbus.MakeVariant(v.Value) + } + return rm, nil +} + +// GetMust returns the value of the given property and panics if either the +// interface or the property name are invalid. +func (p *Properties) GetMust(iface, property string) interface{} { + p.mut.RLock() + defer p.mut.RUnlock() + return p.m[iface][property].Value +} + +// Introspection returns the introspection data that represents the properties +// of iface. +func (p *Properties) Introspection(iface string) []introspect.Property { + p.mut.RLock() + defer p.mut.RUnlock() + m := p.m[iface] + s := make([]introspect.Property, 0, len(m)) + for k, v := range m { + p := introspect.Property{Name: k, Type: dbus.SignatureOf(v.Value).String()} + if v.Writable { + p.Access = "readwrite" + } else { + p.Access = "read" + } + s = append(s, p) + } + return s +} + +// set sets the given property and emits PropertyChanged if appropiate. p.mut +// must already be locked. +func (p *Properties) set(iface, property string, v interface{}) { + prop := p.m[iface][property] + prop.Value = v + switch prop.Emit { + case EmitFalse: + // do nothing + case EmitInvalidates: + p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", + iface, map[string]dbus.Variant{}, []string{property}) + case EmitTrue: + p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged", + iface, map[string]dbus.Variant{property: dbus.MakeVariant(v)}, + []string{}) + default: + panic("invalid value for EmitType") + } +} + +// Set implements org.freedesktop.Properties.Set. +func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error { + p.mut.Lock() + defer p.mut.Unlock() + m, ok := p.m[iface] + if !ok { + return ErrIfaceNotFound + } + prop, ok := m[property] + if !ok { + return ErrPropNotFound + } + if !prop.Writable { + return ErrReadOnly + } + if newv.Signature() != dbus.SignatureOf(prop.Value) { + return ErrInvalidArg + } + if prop.Callback != nil { + err := prop.Callback(&Change{p, iface, property, newv.Value()}) + if err != nil { + return err + } + } + p.set(iface, property, newv.Value()) + return nil +} + +// SetMust sets the value of the given property and panics if the interface or +// the property name are invalid. +func (p *Properties) SetMust(iface, property string, v interface{}) { + p.mut.Lock() + p.set(iface, property, v) + p.mut.Unlock() +} diff --git a/vendor/src/github.com/godbus/dbus/proto_test.go b/vendor/src/github.com/godbus/dbus/proto_test.go new file mode 100644 index 0000000000..608a770d41 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/proto_test.go @@ -0,0 +1,369 @@ +package dbus + +import ( + "bytes" + "encoding/binary" + "io/ioutil" + "math" + "reflect" + "testing" +) + +var protoTests = []struct { + vs []interface{} + bigEndian []byte + littleEndian []byte +}{ + { + []interface{}{int32(0)}, + []byte{0, 0, 0, 0}, + []byte{0, 0, 0, 0}, + }, + { + []interface{}{true, false}, + []byte{0, 0, 0, 1, 0, 0, 0, 0}, + []byte{1, 0, 0, 0, 0, 0, 0, 0}, + }, + { + []interface{}{byte(0), uint16(12), int16(32), uint32(43)}, + []byte{0, 0, 0, 12, 0, 32, 0, 0, 0, 0, 0, 43}, + []byte{0, 0, 12, 0, 32, 0, 0, 0, 43, 0, 0, 0}, + }, + { + []interface{}{int64(-1), uint64(1<<64 - 1)}, + bytes.Repeat([]byte{255}, 16), + bytes.Repeat([]byte{255}, 16), + }, + { + []interface{}{math.Inf(+1)}, + []byte{0x7f, 0xf0, 0, 0, 0, 0, 0, 0}, + []byte{0, 0, 0, 0, 0, 0, 0xf0, 0x7f}, + }, + { + []interface{}{"foo"}, + []byte{0, 0, 0, 3, 'f', 'o', 'o', 0}, + []byte{3, 0, 0, 0, 'f', 'o', 'o', 0}, + }, + { + []interface{}{Signature{"ai"}}, + []byte{2, 'a', 'i', 0}, + []byte{2, 'a', 'i', 0}, + }, + { + []interface{}{[]int16{42, 256}}, + []byte{0, 0, 0, 4, 0, 42, 1, 0}, + []byte{4, 0, 0, 0, 42, 0, 0, 1}, + }, + { + []interface{}{MakeVariant("foo")}, + []byte{1, 's', 0, 0, 0, 0, 0, 3, 'f', 'o', 'o', 0}, + []byte{1, 's', 0, 0, 3, 0, 0, 0, 'f', 'o', 'o', 0}, + }, + { + []interface{}{MakeVariant(MakeVariant(Signature{"v"}))}, + []byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0}, + []byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0}, + }, + { + []interface{}{map[int32]bool{42: true}}, + []byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1}, + []byte{8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1, 0, 0, 0}, + }, + { + []interface{}{map[string]Variant{}, byte(42)}, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 42}, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 42}, + }, + { + []interface{}{[]uint64{}, byte(42)}, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 42}, + []byte{0, 0, 0, 0, 0, 0, 0, 0, 42}, + }, +} + +func TestProto(t *testing.T) { + for i, v := range protoTests { + buf := new(bytes.Buffer) + bigEnc := newEncoder(buf, binary.BigEndian) + bigEnc.Encode(v.vs...) + marshalled := buf.Bytes() + if bytes.Compare(marshalled, v.bigEndian) != 0 { + t.Errorf("test %d (marshal be): got '%v', but expected '%v'\n", i+1, marshalled, + v.bigEndian) + } + buf.Reset() + litEnc := newEncoder(buf, binary.LittleEndian) + litEnc.Encode(v.vs...) + marshalled = buf.Bytes() + if bytes.Compare(marshalled, v.littleEndian) != 0 { + t.Errorf("test %d (marshal le): got '%v', but expected '%v'\n", i+1, marshalled, + v.littleEndian) + } + unmarshalled := reflect.MakeSlice(reflect.TypeOf(v.vs), + 0, 0) + for i := range v.vs { + unmarshalled = reflect.Append(unmarshalled, + reflect.New(reflect.TypeOf(v.vs[i]))) + } + bigDec := newDecoder(bytes.NewReader(v.bigEndian), binary.BigEndian) + vs, err := bigDec.Decode(SignatureOf(v.vs...)) + if err != nil { + t.Errorf("test %d (unmarshal be): %s\n", i+1, err) + continue + } + if !reflect.DeepEqual(vs, v.vs) { + t.Errorf("test %d (unmarshal be): got %#v, but expected %#v\n", i+1, vs, v.vs) + } + litDec := newDecoder(bytes.NewReader(v.littleEndian), binary.LittleEndian) + vs, err = litDec.Decode(SignatureOf(v.vs...)) + if err != nil { + t.Errorf("test %d (unmarshal le): %s\n", i+1, err) + continue + } + if !reflect.DeepEqual(vs, v.vs) { + t.Errorf("test %d (unmarshal le): got %#v, but expected %#v\n", i+1, vs, v.vs) + } + + } +} + +func TestProtoMap(t *testing.T) { + m := map[string]uint8{ + "foo": 23, + "bar": 2, + } + var n map[string]uint8 + buf := new(bytes.Buffer) + enc := newEncoder(buf, binary.LittleEndian) + enc.Encode(m) + dec := newDecoder(buf, binary.LittleEndian) + vs, err := dec.Decode(Signature{"a{sy}"}) + if err != nil { + t.Fatal(err) + } + if err = Store(vs, &n); err != nil { + t.Fatal(err) + } + if len(n) != 2 || n["foo"] != 23 || n["bar"] != 2 { + t.Error("got", n) + } +} + +func TestProtoVariantStruct(t *testing.T) { + var variant Variant + v := MakeVariant(struct { + A int32 + B int16 + }{1, 2}) + buf := new(bytes.Buffer) + enc := newEncoder(buf, binary.LittleEndian) + enc.Encode(v) + dec := newDecoder(buf, binary.LittleEndian) + vs, err := dec.Decode(Signature{"v"}) + if err != nil { + t.Fatal(err) + } + if err = Store(vs, &variant); err != nil { + t.Fatal(err) + } + sl := variant.Value().([]interface{}) + v1, v2 := sl[0].(int32), sl[1].(int16) + if v1 != int32(1) { + t.Error("got", v1, "as first int") + } + if v2 != int16(2) { + t.Error("got", v2, "as second int") + } +} + +func TestProtoStructTag(t *testing.T) { + type Bar struct { + A int32 + B chan interface{} `dbus:"-"` + C int32 + } + var bar1, bar2 Bar + bar1.A = 234 + bar2.C = 345 + buf := new(bytes.Buffer) + enc := newEncoder(buf, binary.LittleEndian) + enc.Encode(bar1) + dec := newDecoder(buf, binary.LittleEndian) + vs, err := dec.Decode(Signature{"(ii)"}) + if err != nil { + t.Fatal(err) + } + if err = Store(vs, &bar2); err != nil { + t.Fatal(err) + } + if bar1 != bar2 { + t.Error("struct tag test: got", bar2) + } +} + +func TestProtoStoreStruct(t *testing.T) { + var foo struct { + A int32 + B string + c chan interface{} + D interface{} `dbus:"-"` + } + src := []interface{}{[]interface{}{int32(42), "foo"}} + err := Store(src, &foo) + if err != nil { + t.Fatal(err) + } +} + +func TestProtoStoreNestedStruct(t *testing.T) { + var foo struct { + A int32 + B struct { + C string + D float64 + } + } + src := []interface{}{ + []interface{}{ + int32(42), + []interface{}{ + "foo", + 3.14, + }, + }, + } + err := Store(src, &foo) + if err != nil { + t.Fatal(err) + } +} + +func TestMessage(t *testing.T) { + buf := new(bytes.Buffer) + message := new(Message) + message.Type = TypeMethodCall + message.serial = 32 + message.Headers = map[HeaderField]Variant{ + FieldPath: MakeVariant(ObjectPath("/org/foo/bar")), + FieldMember: MakeVariant("baz"), + } + message.Body = make([]interface{}, 0) + err := message.EncodeTo(buf, binary.LittleEndian) + if err != nil { + t.Error(err) + } + _, err = DecodeMessage(buf) + if err != nil { + t.Error(err) + } +} + +func TestProtoStructInterfaces(t *testing.T) { + b := []byte{42} + vs, err := newDecoder(bytes.NewReader(b), binary.LittleEndian).Decode(Signature{"(y)"}) + if err != nil { + t.Fatal(err) + } + if vs[0].([]interface{})[0].(byte) != 42 { + t.Errorf("wrongs results (got %v)", vs) + } +} + +// ordinary org.freedesktop.DBus.Hello call +var smallMessage = &Message{ + Type: TypeMethodCall, + serial: 1, + Headers: map[HeaderField]Variant{ + FieldDestination: MakeVariant("org.freedesktop.DBus"), + FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")), + FieldInterface: MakeVariant("org.freedesktop.DBus"), + FieldMember: MakeVariant("Hello"), + }, +} + +// org.freedesktop.Notifications.Notify +var bigMessage = &Message{ + Type: TypeMethodCall, + serial: 2, + Headers: map[HeaderField]Variant{ + FieldDestination: MakeVariant("org.freedesktop.Notifications"), + FieldPath: MakeVariant(ObjectPath("/org/freedesktop/Notifications")), + FieldInterface: MakeVariant("org.freedesktop.Notifications"), + FieldMember: MakeVariant("Notify"), + FieldSignature: MakeVariant(Signature{"susssasa{sv}i"}), + }, + Body: []interface{}{ + "app_name", + uint32(0), + "dialog-information", + "Notification", + "This is the body of a notification", + []string{"ok", "Ok"}, + map[string]Variant{ + "sound-name": MakeVariant("dialog-information"), + }, + int32(-1), + }, +} + +func BenchmarkDecodeMessageSmall(b *testing.B) { + var err error + var rd *bytes.Reader + + b.StopTimer() + buf := new(bytes.Buffer) + err = smallMessage.EncodeTo(buf, binary.LittleEndian) + if err != nil { + b.Fatal(err) + } + decoded := buf.Bytes() + b.StartTimer() + for i := 0; i < b.N; i++ { + rd = bytes.NewReader(decoded) + _, err = DecodeMessage(rd) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkDecodeMessageBig(b *testing.B) { + var err error + var rd *bytes.Reader + + b.StopTimer() + buf := new(bytes.Buffer) + err = bigMessage.EncodeTo(buf, binary.LittleEndian) + if err != nil { + b.Fatal(err) + } + decoded := buf.Bytes() + b.StartTimer() + for i := 0; i < b.N; i++ { + rd = bytes.NewReader(decoded) + _, err = DecodeMessage(rd) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEncodeMessageSmall(b *testing.B) { + var err error + for i := 0; i < b.N; i++ { + err = smallMessage.EncodeTo(ioutil.Discard, binary.LittleEndian) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkEncodeMessageBig(b *testing.B) { + var err error + for i := 0; i < b.N; i++ { + err = bigMessage.EncodeTo(ioutil.Discard, binary.LittleEndian) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/vendor/src/github.com/godbus/dbus/sig.go b/vendor/src/github.com/godbus/dbus/sig.go new file mode 100644 index 0000000000..f45b53ce1b --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/sig.go @@ -0,0 +1,257 @@ +package dbus + +import ( + "fmt" + "reflect" + "strings" +) + +var sigToType = map[byte]reflect.Type{ + 'y': byteType, + 'b': boolType, + 'n': int16Type, + 'q': uint16Type, + 'i': int32Type, + 'u': uint32Type, + 'x': int64Type, + 't': uint64Type, + 'd': float64Type, + 's': stringType, + 'g': signatureType, + 'o': objectPathType, + 'v': variantType, + 'h': unixFDIndexType, +} + +// Signature represents a correct type signature as specified by the D-Bus +// specification. The zero value represents the empty signature, "". +type Signature struct { + str string +} + +// SignatureOf returns the concatenation of all the signatures of the given +// values. It panics if one of them is not representable in D-Bus. +func SignatureOf(vs ...interface{}) Signature { + var s string + for _, v := range vs { + s += getSignature(reflect.TypeOf(v)) + } + return Signature{s} +} + +// SignatureOfType returns the signature of the given type. It panics if the +// type is not representable in D-Bus. +func SignatureOfType(t reflect.Type) Signature { + return Signature{getSignature(t)} +} + +// getSignature returns the signature of the given type and panics on unknown types. +func getSignature(t reflect.Type) string { + // handle simple types first + switch t.Kind() { + case reflect.Uint8: + return "y" + case reflect.Bool: + return "b" + case reflect.Int16: + return "n" + case reflect.Uint16: + return "q" + case reflect.Int32: + if t == unixFDType { + return "h" + } + return "i" + case reflect.Uint32: + if t == unixFDIndexType { + return "h" + } + return "u" + case reflect.Int64: + return "x" + case reflect.Uint64: + return "t" + case reflect.Float64: + return "d" + case reflect.Ptr: + return getSignature(t.Elem()) + case reflect.String: + if t == objectPathType { + return "o" + } + return "s" + case reflect.Struct: + if t == variantType { + return "v" + } else if t == signatureType { + return "g" + } + var s string + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.PkgPath == "" && field.Tag.Get("dbus") != "-" { + s += getSignature(t.Field(i).Type) + } + } + return "(" + s + ")" + case reflect.Array, reflect.Slice: + return "a" + getSignature(t.Elem()) + case reflect.Map: + if !isKeyType(t.Key()) { + panic(InvalidTypeError{t}) + } + return "a{" + getSignature(t.Key()) + getSignature(t.Elem()) + "}" + } + panic(InvalidTypeError{t}) +} + +// ParseSignature returns the signature represented by this string, or a +// SignatureError if the string is not a valid signature. +func ParseSignature(s string) (sig Signature, err error) { + if len(s) == 0 { + return + } + if len(s) > 255 { + return Signature{""}, SignatureError{s, "too long"} + } + sig.str = s + for err == nil && len(s) != 0 { + err, s = validSingle(s, 0) + } + if err != nil { + sig = Signature{""} + } + + return +} + +// ParseSignatureMust behaves like ParseSignature, except that it panics if s +// is not valid. +func ParseSignatureMust(s string) Signature { + sig, err := ParseSignature(s) + if err != nil { + panic(err) + } + return sig +} + +// Empty retruns whether the signature is the empty signature. +func (s Signature) Empty() bool { + return s.str == "" +} + +// Single returns whether the signature represents a single, complete type. +func (s Signature) Single() bool { + err, r := validSingle(s.str, 0) + return err != nil && r == "" +} + +// String returns the signature's string representation. +func (s Signature) String() string { + return s.str +} + +// A SignatureError indicates that a signature passed to a function or received +// on a connection is not a valid signature. +type SignatureError struct { + Sig string + Reason string +} + +func (e SignatureError) Error() string { + return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason) +} + +// Try to read a single type from this string. If it was successfull, err is nil +// and rem is the remaining unparsed part. Otherwise, err is a non-nil +// SignatureError and rem is "". depth is the current recursion depth which may +// not be greater than 64 and should be given as 0 on the first call. +func validSingle(s string, depth int) (err error, rem string) { + if s == "" { + return SignatureError{Sig: s, Reason: "empty signature"}, "" + } + if depth > 64 { + return SignatureError{Sig: s, Reason: "container nesting too deep"}, "" + } + switch s[0] { + case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h': + return nil, s[1:] + case 'a': + if len(s) > 1 && s[1] == '{' { + i := findMatching(s[1:], '{', '}') + if i == -1 { + return SignatureError{Sig: s, Reason: "unmatched '{'"}, "" + } + i++ + rem = s[i+1:] + s = s[2:i] + if err, _ = validSingle(s[:1], depth+1); err != nil { + return err, "" + } + err, nr := validSingle(s[1:], depth+1) + if err != nil { + return err, "" + } + if nr != "" { + return SignatureError{Sig: s, Reason: "too many types in dict"}, "" + } + return nil, rem + } + return validSingle(s[1:], depth+1) + case '(': + i := findMatching(s, '(', ')') + if i == -1 { + return SignatureError{Sig: s, Reason: "unmatched ')'"}, "" + } + rem = s[i+1:] + s = s[1:i] + for err == nil && s != "" { + err, s = validSingle(s, depth+1) + } + if err != nil { + rem = "" + } + return + } + return SignatureError{Sig: s, Reason: "invalid type character"}, "" +} + +func findMatching(s string, left, right rune) int { + n := 0 + for i, v := range s { + if v == left { + n++ + } else if v == right { + n-- + } + if n == 0 { + return i + } + } + return -1 +} + +// typeFor returns the type of the given signature. It ignores any left over +// characters and panics if s doesn't start with a valid type signature. +func typeFor(s string) (t reflect.Type) { + err, _ := validSingle(s, 0) + if err != nil { + panic(err) + } + + if t, ok := sigToType[s[0]]; ok { + return t + } + switch s[0] { + case 'a': + if s[1] == '{' { + i := strings.LastIndex(s, "}") + t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i])) + } else { + t = reflect.SliceOf(typeFor(s[1:])) + } + case '(': + t = interfacesType + } + return +} diff --git a/vendor/src/github.com/godbus/dbus/sig_test.go b/vendor/src/github.com/godbus/dbus/sig_test.go new file mode 100644 index 0000000000..da37bc968e --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/sig_test.go @@ -0,0 +1,70 @@ +package dbus + +import ( + "testing" +) + +var sigTests = []struct { + vs []interface{} + sig Signature +}{ + { + []interface{}{new(int32)}, + Signature{"i"}, + }, + { + []interface{}{new(string)}, + Signature{"s"}, + }, + { + []interface{}{new(Signature)}, + Signature{"g"}, + }, + { + []interface{}{new([]int16)}, + Signature{"an"}, + }, + { + []interface{}{new(int16), new(uint32)}, + Signature{"nu"}, + }, + { + []interface{}{new(map[byte]Variant)}, + Signature{"a{yv}"}, + }, + { + []interface{}{new(Variant), new([]map[int32]string)}, + Signature{"vaa{is}"}, + }, +} + +func TestSig(t *testing.T) { + for i, v := range sigTests { + sig := SignatureOf(v.vs...) + if sig != v.sig { + t.Errorf("test %d: got %q, expected %q", i+1, sig.str, v.sig.str) + } + } +} + +var getSigTest = []interface{}{ + []struct { + b byte + i int32 + t uint64 + s string + }{}, + map[string]Variant{}, +} + +func BenchmarkGetSignatureSimple(b *testing.B) { + for i := 0; i < b.N; i++ { + SignatureOf("", int32(0)) + } +} + +func BenchmarkGetSignatureLong(b *testing.B) { + for i := 0; i < b.N; i++ { + SignatureOf(getSigTest...) + } +} diff --git a/vendor/src/github.com/godbus/dbus/transport_darwin.go b/vendor/src/github.com/godbus/dbus/transport_darwin.go new file mode 100644 index 0000000000..1bba0d6bf7 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_darwin.go @@ -0,0 +1,6 @@ +package dbus + +func (t *unixTransport) SendNullByte() error { + _, err := t.Write([]byte{0}) + return err +} diff --git a/vendor/src/github.com/godbus/dbus/transport_generic.go b/vendor/src/github.com/godbus/dbus/transport_generic.go new file mode 100644 index 0000000000..46f8f49d69 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_generic.go @@ -0,0 +1,35 @@ +package dbus + +import ( + "encoding/binary" + "errors" + "io" +) + +type genericTransport struct { + io.ReadWriteCloser +} + +func (t genericTransport) SendNullByte() error { + _, err := t.Write([]byte{0}) + return err +} + +func (t genericTransport) SupportsUnixFDs() bool { + return false +} + +func (t genericTransport) EnableUnixFDs() {} + +func (t genericTransport) ReadMessage() (*Message, error) { + return DecodeMessage(t) +} + +func (t genericTransport) SendMessage(msg *Message) error { + for _, v := range msg.Body { + if _, ok := v.(UnixFD); ok { + return errors.New("dbus: unix fd passing not enabled") + } + } + return msg.EncodeTo(t, binary.LittleEndian) +} diff --git a/vendor/src/github.com/godbus/dbus/transport_unix.go b/vendor/src/github.com/godbus/dbus/transport_unix.go new file mode 100644 index 0000000000..d16229be40 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_unix.go @@ -0,0 +1,190 @@ +package dbus + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "net" + "syscall" +) + +type oobReader struct { + conn *net.UnixConn + oob []byte + buf [4096]byte +} + +func (o *oobReader) Read(b []byte) (n int, err error) { + n, oobn, flags, _, err := o.conn.ReadMsgUnix(b, o.buf[:]) + if err != nil { + return n, err + } + if flags&syscall.MSG_CTRUNC != 0 { + return n, errors.New("dbus: control data truncated (too many fds received)") + } + o.oob = append(o.oob, o.buf[:oobn]...) + return n, nil +} + +type unixTransport struct { + *net.UnixConn + hasUnixFDs bool +} + +func newUnixTransport(keys string) (transport, error) { + var err error + + t := new(unixTransport) + abstract := getKey(keys, "abstract") + path := getKey(keys, "path") + switch { + case abstract == "" && path == "": + return nil, errors.New("dbus: invalid address (neither path nor abstract set)") + case abstract != "" && path == "": + t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: "@" + abstract, Net: "unix"}) + if err != nil { + return nil, err + } + return t, nil + case abstract == "" && path != "": + t.UnixConn, err = net.DialUnix("unix", nil, &net.UnixAddr{Name: path, Net: "unix"}) + if err != nil { + return nil, err + } + return t, nil + default: + return nil, errors.New("dbus: invalid address (both path and abstract set)") + } +} + +func (t *unixTransport) EnableUnixFDs() { + t.hasUnixFDs = true +} + +func (t *unixTransport) ReadMessage() (*Message, error) { + var ( + blen, hlen uint32 + csheader [16]byte + headers []header + order binary.ByteOrder + unixfds uint32 + ) + // To be sure that all bytes of out-of-band data are read, we use a special + // reader that uses ReadUnix on the underlying connection instead of Read + // and gathers the out-of-band data in a buffer. + rd := &oobReader{conn: t.UnixConn} + // read the first 16 bytes (the part of the header that has a constant size), + // from which we can figure out the length of the rest of the message + if _, err := io.ReadFull(rd, csheader[:]); err != nil { + return nil, err + } + switch csheader[0] { + case 'l': + order = binary.LittleEndian + case 'B': + order = binary.BigEndian + default: + return nil, InvalidMessageError("invalid byte order") + } + // csheader[4:8] -> length of message body, csheader[12:16] -> length of + // header fields (without alignment) + binary.Read(bytes.NewBuffer(csheader[4:8]), order, &blen) + binary.Read(bytes.NewBuffer(csheader[12:]), order, &hlen) + if hlen%8 != 0 { + hlen += 8 - (hlen % 8) + } + + // decode headers and look for unix fds + headerdata := make([]byte, hlen+4) + copy(headerdata, csheader[12:]) + if _, err := io.ReadFull(t, headerdata[4:]); err != nil { + return nil, err + } + dec := newDecoder(bytes.NewBuffer(headerdata), order) + dec.pos = 12 + vs, err := dec.Decode(Signature{"a(yv)"}) + if err != nil { + return nil, err + } + Store(vs, &headers) + for _, v := range headers { + if v.Field == byte(FieldUnixFDs) { + unixfds, _ = v.Variant.value.(uint32) + } + } + all := make([]byte, 16+hlen+blen) + copy(all, csheader[:]) + copy(all[16:], headerdata[4:]) + if _, err := io.ReadFull(rd, all[16+hlen:]); err != nil { + return nil, err + } + if unixfds != 0 { + if !t.hasUnixFDs { + return nil, errors.New("dbus: got unix fds on unsupported transport") + } + // read the fds from the OOB data + scms, err := syscall.ParseSocketControlMessage(rd.oob) + if err != nil { + return nil, err + } + if len(scms) != 1 { + return nil, errors.New("dbus: received more than one socket control message") + } + fds, err := syscall.ParseUnixRights(&scms[0]) + if err != nil { + return nil, err + } + msg, err := DecodeMessage(bytes.NewBuffer(all)) + if err != nil { + return nil, err + } + // substitute the values in the message body (which are indices for the + // array receiver via OOB) with the actual values + for i, v := range msg.Body { + if j, ok := v.(UnixFDIndex); ok { + if uint32(j) >= unixfds { + return nil, InvalidMessageError("invalid index for unix fd") + } + msg.Body[i] = UnixFD(fds[j]) + } + } + return msg, nil + } + return DecodeMessage(bytes.NewBuffer(all)) +} + +func (t *unixTransport) SendMessage(msg *Message) error { + fds := make([]int, 0) + for i, v := range msg.Body { + if fd, ok := v.(UnixFD); ok { + msg.Body[i] = UnixFDIndex(len(fds)) + fds = append(fds, int(fd)) + } + } + if len(fds) != 0 { + if !t.hasUnixFDs { + return errors.New("dbus: unix fd passing not enabled") + } + msg.Headers[FieldUnixFDs] = MakeVariant(uint32(len(fds))) + oob := syscall.UnixRights(fds...) + buf := new(bytes.Buffer) + msg.EncodeTo(buf, binary.LittleEndian) + n, oobn, err := t.UnixConn.WriteMsgUnix(buf.Bytes(), oob, nil) + if err != nil { + return err + } + if n != buf.Len() || oobn != len(oob) { + return io.ErrShortWrite + } + } else { + if err := msg.EncodeTo(t, binary.LittleEndian); err != nil { + return nil + } + } + return nil +} + +func (t *unixTransport) SupportsUnixFDs() bool { + return true +} diff --git a/vendor/src/github.com/godbus/dbus/transport_unix_test.go b/vendor/src/github.com/godbus/dbus/transport_unix_test.go new file mode 100644 index 0000000000..302233fc65 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_unix_test.go @@ -0,0 +1,49 @@ +package dbus + +import ( + "os" + "testing" +) + +const testString = `This is a test! +This text should be read from the file that is created by this test.` + +type unixFDTest struct{} + +func (t unixFDTest) Test(fd UnixFD) (string, *Error) { + var b [4096]byte + file := os.NewFile(uintptr(fd), "testfile") + defer file.Close() + n, err := file.Read(b[:]) + if err != nil { + return "", &Error{"com.github.guelfey.test.Error", nil} + } + return string(b[:n]), nil +} + +func TestUnixFDs(t *testing.T) { + conn, err := SessionBus() + if err != nil { + t.Fatal(err) + } + r, w, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + defer w.Close() + if _, err := w.Write([]byte(testString)); err != nil { + t.Fatal(err) + } + name := conn.Names()[0] + test := unixFDTest{} + conn.Export(test, "/com/github/guelfey/test", "com.github.guelfey.test") + var s string + obj := conn.Object(name, "/com/github/guelfey/test") + err = obj.Call("com.github.guelfey.test.Test", 0, UnixFD(r.Fd())).Store(&s) + if err != nil { + t.Fatal(err) + } + if s != testString { + t.Fatal("got", s, "wanted", testString) + } +} diff --git a/vendor/src/github.com/godbus/dbus/transport_unixcred.go b/vendor/src/github.com/godbus/dbus/transport_unixcred.go new file mode 100644 index 0000000000..42a0e769ef --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/transport_unixcred.go @@ -0,0 +1,22 @@ +// +build !darwin + +package dbus + +import ( + "io" + "os" + "syscall" +) + +func (t *unixTransport) SendNullByte() error { + ucred := &syscall.Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())} + b := syscall.UnixCredentials(ucred) + _, oobn, err := t.UnixConn.WriteMsgUnix([]byte{0}, b, nil) + if err != nil { + return err + } + if oobn != len(b) { + return io.ErrShortWrite + } + return nil +} diff --git a/vendor/src/github.com/godbus/dbus/variant.go b/vendor/src/github.com/godbus/dbus/variant.go new file mode 100644 index 0000000000..b1b53ceb47 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/variant.go @@ -0,0 +1,129 @@ +package dbus + +import ( + "bytes" + "fmt" + "reflect" + "strconv" +) + +// Variant represents the D-Bus variant type. +type Variant struct { + sig Signature + value interface{} +} + +// MakeVariant converts the given value to a Variant. It panics if v cannot be +// represented as a D-Bus type. +func MakeVariant(v interface{}) Variant { + return Variant{SignatureOf(v), v} +} + +// ParseVariant parses the given string as a variant as described at +// https://developer.gnome.org/glib/unstable/gvariant-text.html. If sig is not +// empty, it is taken to be the expected signature for the variant. +func ParseVariant(s string, sig Signature) (Variant, error) { + tokens := varLex(s) + p := &varParser{tokens: tokens} + n, err := varMakeNode(p) + if err != nil { + return Variant{}, err + } + if sig.str == "" { + sig, err = varInfer(n) + if err != nil { + return Variant{}, err + } + } + v, err := n.Value(sig) + if err != nil { + return Variant{}, err + } + return MakeVariant(v), nil +} + +// format returns a formatted version of v and whether this string can be parsed +// unambigously. +func (v Variant) format() (string, bool) { + switch v.sig.str[0] { + case 'b', 'i': + return fmt.Sprint(v.value), true + case 'n', 'q', 'u', 'x', 't', 'd', 'h': + return fmt.Sprint(v.value), false + case 's': + return strconv.Quote(v.value.(string)), true + case 'o': + return strconv.Quote(string(v.value.(ObjectPath))), false + case 'g': + return strconv.Quote(v.value.(Signature).str), false + case 'v': + s, unamb := v.value.(Variant).format() + if !unamb { + return "<@" + v.value.(Variant).sig.str + " " + s + ">", true + } + return "<" + s + ">", true + case 'y': + return fmt.Sprintf("%#x", v.value.(byte)), false + } + rv := reflect.ValueOf(v.value) + switch rv.Kind() { + case reflect.Slice: + if rv.Len() == 0 { + return "[]", false + } + unamb := true + buf := bytes.NewBuffer([]byte("[")) + for i := 0; i < rv.Len(); i++ { + // TODO: slooow + s, b := MakeVariant(rv.Index(i).Interface()).format() + unamb = unamb && b + buf.WriteString(s) + if i != rv.Len()-1 { + buf.WriteString(", ") + } + } + buf.WriteByte(']') + return buf.String(), unamb + case reflect.Map: + if rv.Len() == 0 { + return "{}", false + } + unamb := true + buf := bytes.NewBuffer([]byte("{")) + for i, k := range rv.MapKeys() { + s, b := MakeVariant(k.Interface()).format() + unamb = unamb && b + buf.WriteString(s) + buf.WriteString(": ") + s, b = MakeVariant(rv.MapIndex(k).Interface()).format() + unamb = unamb && b + buf.WriteString(s) + if i != rv.Len()-1 { + buf.WriteString(", ") + } + } + buf.WriteByte('}') + return buf.String(), unamb + } + return `"INVALID"`, true +} + +// Signature returns the D-Bus signature of the underlying value of v. +func (v Variant) Signature() Signature { + return v.sig +} + +// String returns the string representation of the underlying value of v as +// described at https://developer.gnome.org/glib/unstable/gvariant-text.html. +func (v Variant) String() string { + s, unamb := v.format() + if !unamb { + return "@" + v.sig.str + " " + s + } + return s +} + +// Value returns the underlying value of v. +func (v Variant) Value() interface{} { + return v.value +} diff --git a/vendor/src/github.com/godbus/dbus/variant_lexer.go b/vendor/src/github.com/godbus/dbus/variant_lexer.go new file mode 100644 index 0000000000..332007d6f1 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/variant_lexer.go @@ -0,0 +1,284 @@ +package dbus + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" +) + +// Heavily inspired by the lexer from text/template. + +type varToken struct { + typ varTokenType + val string +} + +type varTokenType byte + +const ( + tokEOF varTokenType = iota + tokError + tokNumber + tokString + tokBool + tokArrayStart + tokArrayEnd + tokDictStart + tokDictEnd + tokVariantStart + tokVariantEnd + tokComma + tokColon + tokType + tokByteString +) + +type varLexer struct { + input string + start int + pos int + width int + tokens []varToken +} + +type lexState func(*varLexer) lexState + +func varLex(s string) []varToken { + l := &varLexer{input: s} + l.run() + return l.tokens +} + +func (l *varLexer) accept(valid string) bool { + if strings.IndexRune(valid, l.next()) >= 0 { + return true + } + l.backup() + return false +} + +func (l *varLexer) backup() { + l.pos -= l.width +} + +func (l *varLexer) emit(t varTokenType) { + l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]}) + l.start = l.pos +} + +func (l *varLexer) errorf(format string, v ...interface{}) lexState { + l.tokens = append(l.tokens, varToken{ + tokError, + fmt.Sprintf(format, v...), + }) + return nil +} + +func (l *varLexer) ignore() { + l.start = l.pos +} + +func (l *varLexer) next() rune { + var r rune + + if l.pos >= len(l.input) { + l.width = 0 + return -1 + } + r, l.width = utf8.DecodeRuneInString(l.input[l.pos:]) + l.pos += l.width + return r +} + +func (l *varLexer) run() { + for state := varLexNormal; state != nil; { + state = state(l) + } +} + +func (l *varLexer) peek() rune { + r := l.next() + l.backup() + return r +} + +func varLexNormal(l *varLexer) lexState { + for { + r := l.next() + switch { + case r == -1: + l.emit(tokEOF) + return nil + case r == '[': + l.emit(tokArrayStart) + case r == ']': + l.emit(tokArrayEnd) + case r == '{': + l.emit(tokDictStart) + case r == '}': + l.emit(tokDictEnd) + case r == '<': + l.emit(tokVariantStart) + case r == '>': + l.emit(tokVariantEnd) + case r == ':': + l.emit(tokColon) + case r == ',': + l.emit(tokComma) + case r == '\'' || r == '"': + l.backup() + return varLexString + case r == '@': + l.backup() + return varLexType + case unicode.IsSpace(r): + l.ignore() + case unicode.IsNumber(r) || r == '+' || r == '-': + l.backup() + return varLexNumber + case r == 'b': + pos := l.start + if n := l.peek(); n == '"' || n == '\'' { + return varLexByteString + } + // not a byte string; try to parse it as a type or bool below + l.pos = pos + 1 + l.width = 1 + fallthrough + default: + // either a bool or a type. Try bools first. + l.backup() + if l.pos+4 <= len(l.input) { + if l.input[l.pos:l.pos+4] == "true" { + l.pos += 4 + l.emit(tokBool) + continue + } + } + if l.pos+5 <= len(l.input) { + if l.input[l.pos:l.pos+5] == "false" { + l.pos += 5 + l.emit(tokBool) + continue + } + } + // must be a type. + return varLexType + } + } +} + +var varTypeMap = map[string]string{ + "boolean": "b", + "byte": "y", + "int16": "n", + "uint16": "q", + "int32": "i", + "uint32": "u", + "int64": "x", + "uint64": "t", + "double": "f", + "string": "s", + "objectpath": "o", + "signature": "g", +} + +func varLexByteString(l *varLexer) lexState { + q := l.next() +Loop: + for { + switch l.next() { + case '\\': + if r := l.next(); r != -1 { + break + } + fallthrough + case -1: + return l.errorf("unterminated bytestring") + case q: + break Loop + } + } + l.emit(tokByteString) + return varLexNormal +} + +func varLexNumber(l *varLexer) lexState { + l.accept("+-") + digits := "0123456789" + if l.accept("0") { + if l.accept("x") { + digits = "0123456789abcdefABCDEF" + } else { + digits = "01234567" + } + } + for strings.IndexRune(digits, l.next()) >= 0 { + } + l.backup() + if l.accept(".") { + for strings.IndexRune(digits, l.next()) >= 0 { + } + l.backup() + } + if l.accept("eE") { + l.accept("+-") + for strings.IndexRune("0123456789", l.next()) >= 0 { + } + l.backup() + } + if r := l.peek(); unicode.IsLetter(r) { + l.next() + return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) + } + l.emit(tokNumber) + return varLexNormal +} + +func varLexString(l *varLexer) lexState { + q := l.next() +Loop: + for { + switch l.next() { + case '\\': + if r := l.next(); r != -1 { + break + } + fallthrough + case -1: + return l.errorf("unterminated string") + case q: + break Loop + } + } + l.emit(tokString) + return varLexNormal +} + +func varLexType(l *varLexer) lexState { + at := l.accept("@") + for { + r := l.next() + if r == -1 { + break + } + if unicode.IsSpace(r) { + l.backup() + break + } + } + if at { + if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil { + return l.errorf("%s", err) + } + } else { + if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok { + l.emit(tokType) + return varLexNormal + } + return l.errorf("unrecognized type %q", l.input[l.start:l.pos]) + } + l.emit(tokType) + return varLexNormal +} diff --git a/vendor/src/github.com/godbus/dbus/variant_parser.go b/vendor/src/github.com/godbus/dbus/variant_parser.go new file mode 100644 index 0000000000..d20f5da6dd --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/variant_parser.go @@ -0,0 +1,817 @@ +package dbus + +import ( + "bytes" + "errors" + "fmt" + "io" + "reflect" + "strconv" + "strings" + "unicode/utf8" +) + +type varParser struct { + tokens []varToken + i int +} + +func (p *varParser) backup() { + p.i-- +} + +func (p *varParser) next() varToken { + if p.i < len(p.tokens) { + t := p.tokens[p.i] + p.i++ + return t + } + return varToken{typ: tokEOF} +} + +type varNode interface { + Infer() (Signature, error) + String() string + Sigs() sigSet + Value(Signature) (interface{}, error) +} + +func varMakeNode(p *varParser) (varNode, error) { + var sig Signature + + for { + t := p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + case tokNumber: + return varMakeNumNode(t, sig) + case tokString: + return varMakeStringNode(t, sig) + case tokBool: + if sig.str != "" && sig.str != "b" { + return nil, varTypeError{t.val, sig} + } + b, err := strconv.ParseBool(t.val) + if err != nil { + return nil, err + } + return boolNode(b), nil + case tokArrayStart: + return varMakeArrayNode(p, sig) + case tokVariantStart: + return varMakeVariantNode(p, sig) + case tokDictStart: + return varMakeDictNode(p, sig) + case tokType: + if sig.str != "" { + return nil, errors.New("unexpected type annotation") + } + if t.val[0] == '@' { + sig.str = t.val[1:] + } else { + sig.str = varTypeMap[t.val] + } + case tokByteString: + if sig.str != "" && sig.str != "ay" { + return nil, varTypeError{t.val, sig} + } + b, err := varParseByteString(t.val) + if err != nil { + return nil, err + } + return byteStringNode(b), nil + default: + return nil, fmt.Errorf("unexpected %q", t.val) + } + } +} + +type varTypeError struct { + val string + sig Signature +} + +func (e varTypeError) Error() string { + return fmt.Sprintf("dbus: can't parse %q as type %q", e.val, e.sig.str) +} + +type sigSet map[Signature]bool + +func (s sigSet) Empty() bool { + return len(s) == 0 +} + +func (s sigSet) Intersect(s2 sigSet) sigSet { + r := make(sigSet) + for k := range s { + if s2[k] { + r[k] = true + } + } + return r +} + +func (s sigSet) Single() (Signature, bool) { + if len(s) == 1 { + for k := range s { + return k, true + } + } + return Signature{}, false +} + +func (s sigSet) ToArray() sigSet { + r := make(sigSet, len(s)) + for k := range s { + r[Signature{"a" + k.str}] = true + } + return r +} + +type numNode struct { + sig Signature + str string + val interface{} +} + +var numSigSet = sigSet{ + Signature{"y"}: true, + Signature{"n"}: true, + Signature{"q"}: true, + Signature{"i"}: true, + Signature{"u"}: true, + Signature{"x"}: true, + Signature{"t"}: true, + Signature{"d"}: true, +} + +func (n numNode) Infer() (Signature, error) { + if strings.ContainsAny(n.str, ".e") { + return Signature{"d"}, nil + } + return Signature{"i"}, nil +} + +func (n numNode) String() string { + return n.str +} + +func (n numNode) Sigs() sigSet { + if n.sig.str != "" { + return sigSet{n.sig: true} + } + if strings.ContainsAny(n.str, ".e") { + return sigSet{Signature{"d"}: true} + } + return numSigSet +} + +func (n numNode) Value(sig Signature) (interface{}, error) { + if n.sig.str != "" && n.sig != sig { + return nil, varTypeError{n.str, sig} + } + if n.val != nil { + return n.val, nil + } + return varNumAs(n.str, sig) +} + +func varMakeNumNode(tok varToken, sig Signature) (varNode, error) { + if sig.str == "" { + return numNode{str: tok.val}, nil + } + num, err := varNumAs(tok.val, sig) + if err != nil { + return nil, err + } + return numNode{sig: sig, val: num}, nil +} + +func varNumAs(s string, sig Signature) (interface{}, error) { + isUnsigned := false + size := 32 + switch sig.str { + case "n": + size = 16 + case "i": + case "x": + size = 64 + case "y": + size = 8 + isUnsigned = true + case "q": + size = 16 + isUnsigned = true + case "u": + isUnsigned = true + case "t": + size = 64 + isUnsigned = true + case "d": + d, err := strconv.ParseFloat(s, 64) + if err != nil { + return nil, err + } + return d, nil + default: + return nil, varTypeError{s, sig} + } + base := 10 + if strings.HasPrefix(s, "0x") { + base = 16 + s = s[2:] + } + if strings.HasPrefix(s, "0") && len(s) != 1 { + base = 8 + s = s[1:] + } + if isUnsigned { + i, err := strconv.ParseUint(s, base, size) + if err != nil { + return nil, err + } + var v interface{} = i + switch sig.str { + case "y": + v = byte(i) + case "q": + v = uint16(i) + case "u": + v = uint32(i) + } + return v, nil + } + i, err := strconv.ParseInt(s, base, size) + if err != nil { + return nil, err + } + var v interface{} = i + switch sig.str { + case "n": + v = int16(i) + case "i": + v = int32(i) + } + return v, nil +} + +type stringNode struct { + sig Signature + str string // parsed + val interface{} // has correct type +} + +var stringSigSet = sigSet{ + Signature{"s"}: true, + Signature{"g"}: true, + Signature{"o"}: true, +} + +func (n stringNode) Infer() (Signature, error) { + return Signature{"s"}, nil +} + +func (n stringNode) String() string { + return n.str +} + +func (n stringNode) Sigs() sigSet { + if n.sig.str != "" { + return sigSet{n.sig: true} + } + return stringSigSet +} + +func (n stringNode) Value(sig Signature) (interface{}, error) { + if n.sig.str != "" && n.sig != sig { + return nil, varTypeError{n.str, sig} + } + if n.val != nil { + return n.val, nil + } + switch { + case sig.str == "g": + return Signature{n.str}, nil + case sig.str == "o": + return ObjectPath(n.str), nil + case sig.str == "s": + return n.str, nil + default: + return nil, varTypeError{n.str, sig} + } +} + +func varMakeStringNode(tok varToken, sig Signature) (varNode, error) { + if sig.str != "" && sig.str != "s" && sig.str != "g" && sig.str != "o" { + return nil, fmt.Errorf("invalid type %q for string", sig.str) + } + s, err := varParseString(tok.val) + if err != nil { + return nil, err + } + n := stringNode{str: s} + if sig.str == "" { + return stringNode{str: s}, nil + } + n.sig = sig + switch sig.str { + case "o": + n.val = ObjectPath(s) + case "g": + n.val = Signature{s} + case "s": + n.val = s + } + return n, nil +} + +func varParseString(s string) (string, error) { + // quotes are guaranteed to be there + s = s[1 : len(s)-1] + buf := new(bytes.Buffer) + for len(s) != 0 { + r, size := utf8.DecodeRuneInString(s) + if r == utf8.RuneError && size == 1 { + return "", errors.New("invalid UTF-8") + } + s = s[size:] + if r != '\\' { + buf.WriteRune(r) + continue + } + r, size = utf8.DecodeRuneInString(s) + if r == utf8.RuneError && size == 1 { + return "", errors.New("invalid UTF-8") + } + s = s[size:] + switch r { + case 'a': + buf.WriteRune(0x7) + case 'b': + buf.WriteRune(0x8) + case 'f': + buf.WriteRune(0xc) + case 'n': + buf.WriteRune('\n') + case 'r': + buf.WriteRune('\r') + case 't': + buf.WriteRune('\t') + case '\n': + case 'u': + if len(s) < 4 { + return "", errors.New("short unicode escape") + } + r, err := strconv.ParseUint(s[:4], 16, 32) + if err != nil { + return "", err + } + buf.WriteRune(rune(r)) + s = s[4:] + case 'U': + if len(s) < 8 { + return "", errors.New("short unicode escape") + } + r, err := strconv.ParseUint(s[:8], 16, 32) + if err != nil { + return "", err + } + buf.WriteRune(rune(r)) + s = s[8:] + default: + buf.WriteRune(r) + } + } + return buf.String(), nil +} + +var boolSigSet = sigSet{Signature{"b"}: true} + +type boolNode bool + +func (boolNode) Infer() (Signature, error) { + return Signature{"b"}, nil +} + +func (b boolNode) String() string { + if b { + return "true" + } + return "false" +} + +func (boolNode) Sigs() sigSet { + return boolSigSet +} + +func (b boolNode) Value(sig Signature) (interface{}, error) { + if sig.str != "b" { + return nil, varTypeError{b.String(), sig} + } + return bool(b), nil +} + +type arrayNode struct { + set sigSet + children []varNode + val interface{} +} + +func (n arrayNode) Infer() (Signature, error) { + for _, v := range n.children { + csig, err := varInfer(v) + if err != nil { + continue + } + return Signature{"a" + csig.str}, nil + } + return Signature{}, fmt.Errorf("can't infer type for %q", n.String()) +} + +func (n arrayNode) String() string { + s := "[" + for i, v := range n.children { + s += v.String() + if i != len(n.children)-1 { + s += ", " + } + } + return s + "]" +} + +func (n arrayNode) Sigs() sigSet { + return n.set +} + +func (n arrayNode) Value(sig Signature) (interface{}, error) { + if n.set.Empty() { + // no type information whatsoever, so this must be an empty slice + return reflect.MakeSlice(typeFor(sig.str), 0, 0).Interface(), nil + } + if !n.set[sig] { + return nil, varTypeError{n.String(), sig} + } + s := reflect.MakeSlice(typeFor(sig.str), len(n.children), len(n.children)) + for i, v := range n.children { + rv, err := v.Value(Signature{sig.str[1:]}) + if err != nil { + return nil, err + } + s.Index(i).Set(reflect.ValueOf(rv)) + } + return s.Interface(), nil +} + +func varMakeArrayNode(p *varParser, sig Signature) (varNode, error) { + var n arrayNode + if sig.str != "" { + n.set = sigSet{sig: true} + } + if t := p.next(); t.typ == tokArrayEnd { + return n, nil + } else { + p.backup() + } +Loop: + for { + t := p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + } + p.backup() + cn, err := varMakeNode(p) + if err != nil { + return nil, err + } + if cset := cn.Sigs(); !cset.Empty() { + if n.set.Empty() { + n.set = cset.ToArray() + } else { + nset := cset.ToArray().Intersect(n.set) + if nset.Empty() { + return nil, fmt.Errorf("can't parse %q with given type information", cn.String()) + } + n.set = nset + } + } + n.children = append(n.children, cn) + switch t := p.next(); t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + case tokArrayEnd: + break Loop + case tokComma: + continue + default: + return nil, fmt.Errorf("unexpected %q", t.val) + } + } + return n, nil +} + +type variantNode struct { + n varNode +} + +var variantSet = sigSet{ + Signature{"v"}: true, +} + +func (variantNode) Infer() (Signature, error) { + return Signature{"v"}, nil +} + +func (n variantNode) String() string { + return "<" + n.n.String() + ">" +} + +func (variantNode) Sigs() sigSet { + return variantSet +} + +func (n variantNode) Value(sig Signature) (interface{}, error) { + if sig.str != "v" { + return nil, varTypeError{n.String(), sig} + } + sig, err := varInfer(n.n) + if err != nil { + return nil, err + } + v, err := n.n.Value(sig) + if err != nil { + return nil, err + } + return MakeVariant(v), nil +} + +func varMakeVariantNode(p *varParser, sig Signature) (varNode, error) { + n, err := varMakeNode(p) + if err != nil { + return nil, err + } + if t := p.next(); t.typ != tokVariantEnd { + return nil, fmt.Errorf("unexpected %q", t.val) + } + vn := variantNode{n} + if sig.str != "" && sig.str != "v" { + return nil, varTypeError{vn.String(), sig} + } + return variantNode{n}, nil +} + +type dictEntry struct { + key, val varNode +} + +type dictNode struct { + kset, vset sigSet + children []dictEntry + val interface{} +} + +func (n dictNode) Infer() (Signature, error) { + for _, v := range n.children { + ksig, err := varInfer(v.key) + if err != nil { + continue + } + vsig, err := varInfer(v.val) + if err != nil { + continue + } + return Signature{"a{" + ksig.str + vsig.str + "}"}, nil + } + return Signature{}, fmt.Errorf("can't infer type for %q", n.String()) +} + +func (n dictNode) String() string { + s := "{" + for i, v := range n.children { + s += v.key.String() + ": " + v.val.String() + if i != len(n.children)-1 { + s += ", " + } + } + return s + "}" +} + +func (n dictNode) Sigs() sigSet { + r := sigSet{} + for k := range n.kset { + for v := range n.vset { + sig := "a{" + k.str + v.str + "}" + r[Signature{sig}] = true + } + } + return r +} + +func (n dictNode) Value(sig Signature) (interface{}, error) { + set := n.Sigs() + if set.Empty() { + // no type information -> empty dict + return reflect.MakeMap(typeFor(sig.str)).Interface(), nil + } + if !set[sig] { + return nil, varTypeError{n.String(), sig} + } + m := reflect.MakeMap(typeFor(sig.str)) + ksig := Signature{sig.str[2:3]} + vsig := Signature{sig.str[3 : len(sig.str)-1]} + for _, v := range n.children { + kv, err := v.key.Value(ksig) + if err != nil { + return nil, err + } + vv, err := v.val.Value(vsig) + if err != nil { + return nil, err + } + m.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv)) + } + return m.Interface(), nil +} + +func varMakeDictNode(p *varParser, sig Signature) (varNode, error) { + var n dictNode + + if sig.str != "" { + if len(sig.str) < 5 { + return nil, fmt.Errorf("invalid signature %q for dict type", sig) + } + ksig := Signature{string(sig.str[2])} + vsig := Signature{sig.str[3 : len(sig.str)-1]} + n.kset = sigSet{ksig: true} + n.vset = sigSet{vsig: true} + } + if t := p.next(); t.typ == tokDictEnd { + return n, nil + } else { + p.backup() + } +Loop: + for { + t := p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + } + p.backup() + kn, err := varMakeNode(p) + if err != nil { + return nil, err + } + if kset := kn.Sigs(); !kset.Empty() { + if n.kset.Empty() { + n.kset = kset + } else { + n.kset = kset.Intersect(n.kset) + if n.kset.Empty() { + return nil, fmt.Errorf("can't parse %q with given type information", kn.String()) + } + } + } + t = p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + case tokColon: + default: + return nil, fmt.Errorf("unexpected %q", t.val) + } + t = p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + } + p.backup() + vn, err := varMakeNode(p) + if err != nil { + return nil, err + } + if vset := vn.Sigs(); !vset.Empty() { + if n.vset.Empty() { + n.vset = vset + } else { + n.vset = n.vset.Intersect(vset) + if n.vset.Empty() { + return nil, fmt.Errorf("can't parse %q with given type information", vn.String()) + } + } + } + n.children = append(n.children, dictEntry{kn, vn}) + t = p.next() + switch t.typ { + case tokEOF: + return nil, io.ErrUnexpectedEOF + case tokError: + return nil, errors.New(t.val) + case tokDictEnd: + break Loop + case tokComma: + continue + default: + return nil, fmt.Errorf("unexpected %q", t.val) + } + } + return n, nil +} + +type byteStringNode []byte + +var byteStringSet = sigSet{ + Signature{"ay"}: true, +} + +func (byteStringNode) Infer() (Signature, error) { + return Signature{"ay"}, nil +} + +func (b byteStringNode) String() string { + return string(b) +} + +func (b byteStringNode) Sigs() sigSet { + return byteStringSet +} + +func (b byteStringNode) Value(sig Signature) (interface{}, error) { + if sig.str != "ay" { + return nil, varTypeError{b.String(), sig} + } + return []byte(b), nil +} + +func varParseByteString(s string) ([]byte, error) { + // quotes and b at start are guaranteed to be there + b := make([]byte, 0, 1) + s = s[2 : len(s)-1] + for len(s) != 0 { + c := s[0] + s = s[1:] + if c != '\\' { + b = append(b, c) + continue + } + c = s[0] + s = s[1:] + switch c { + case 'a': + b = append(b, 0x7) + case 'b': + b = append(b, 0x8) + case 'f': + b = append(b, 0xc) + case 'n': + b = append(b, '\n') + case 'r': + b = append(b, '\r') + case 't': + b = append(b, '\t') + case 'x': + if len(s) < 2 { + return nil, errors.New("short escape") + } + n, err := strconv.ParseUint(s[:2], 16, 8) + if err != nil { + return nil, err + } + b = append(b, byte(n)) + s = s[2:] + case '0': + if len(s) < 3 { + return nil, errors.New("short escape") + } + n, err := strconv.ParseUint(s[:3], 8, 8) + if err != nil { + return nil, err + } + b = append(b, byte(n)) + s = s[3:] + default: + b = append(b, c) + } + } + return append(b, 0), nil +} + +func varInfer(n varNode) (Signature, error) { + if sig, ok := n.Sigs().Single(); ok { + return sig, nil + } + return n.Infer() +} diff --git a/vendor/src/github.com/godbus/dbus/variant_test.go b/vendor/src/github.com/godbus/dbus/variant_test.go new file mode 100644 index 0000000000..da917c8e29 --- /dev/null +++ b/vendor/src/github.com/godbus/dbus/variant_test.go @@ -0,0 +1,78 @@ +package dbus + +import "reflect" +import "testing" + +var variantFormatTests = []struct { + v interface{} + s string +}{ + {int32(1), `1`}, + {"foo", `"foo"`}, + {ObjectPath("/org/foo"), `@o "/org/foo"`}, + {Signature{"i"}, `@g "i"`}, + {[]byte{}, `@ay []`}, + {[]int32{1, 2}, `[1, 2]`}, + {[]int64{1, 2}, `@ax [1, 2]`}, + {[][]int32{{3, 4}, {5, 6}}, `[[3, 4], [5, 6]]`}, + {[]Variant{MakeVariant(int32(1)), MakeVariant(1.0)}, `[<1>, <@d 1>]`}, + {map[string]int32{"one": 1, "two": 2}, `{"one": 1, "two": 2}`}, + {map[int32]ObjectPath{1: "/org/foo"}, `@a{io} {1: "/org/foo"}`}, + {map[string]Variant{}, `@a{sv} {}`}, +} + +func TestFormatVariant(t *testing.T) { + for i, v := range variantFormatTests { + if s := MakeVariant(v.v).String(); s != v.s { + t.Errorf("test %d: got %q, wanted %q", i+1, s, v.s) + } + } +} + +var variantParseTests = []struct { + s string + v interface{} +}{ + {"1", int32(1)}, + {"true", true}, + {"false", false}, + {"1.0", float64(1.0)}, + {"0x10", int32(16)}, + {"1e1", float64(10)}, + {`"foo"`, "foo"}, + {`"\a\b\f\n\r\t"`, "\x07\x08\x0c\n\r\t"}, + {`"\u00e4\U0001f603"`, "\u00e4\U0001f603"}, + {"[1]", []int32{1}}, + {"[1, 2, 3]", []int32{1, 2, 3}}, + {"@ai []", []int32{}}, + {"[1, 5.0]", []float64{1, 5.0}}, + {"[[1, 2], [3, 4.0]]", [][]float64{{1, 2}, {3, 4}}}, + {`[@o "/org/foo", "/org/bar"]`, []ObjectPath{"/org/foo", "/org/bar"}}, + {"<1>", MakeVariant(int32(1))}, + {"[<1>, <2.0>]", []Variant{MakeVariant(int32(1)), MakeVariant(2.0)}}, + {`[[], [""]]`, [][]string{{}, {""}}}, + {`@a{ss} {}`, map[string]string{}}, + {`{"foo": 1}`, map[string]int32{"foo": 1}}, + {`[{}, {"foo": "bar"}]`, []map[string]string{{}, {"foo": "bar"}}}, + {`{"a": <1>, "b": <"foo">}`, + map[string]Variant{"a": MakeVariant(int32(1)), "b": MakeVariant("foo")}}, + {`b''`, []byte{0}}, + {`b"abc"`, []byte{'a', 'b', 'c', 0}}, + {`b"\x01\0002\a\b\f\n\r\t"`, []byte{1, 2, 0x7, 0x8, 0xc, '\n', '\r', '\t', 0}}, + {`[[0], b""]`, [][]byte{{0}, {0}}}, + {"int16 0", int16(0)}, + {"byte 0", byte(0)}, +} + +func TestParseVariant(t *testing.T) { + for i, v := range variantParseTests { + nv, err := ParseVariant(v.s, Signature{}) + if err != nil { + t.Errorf("test %d: parsing failed: %s", i+1, err) + continue + } + if !reflect.DeepEqual(nv.value, v.v) { + t.Errorf("test %d: got %q, wanted %q", i+1, nv, v.v) + } + } +} From cb43fd007133fc05b6bb2b0d3d58fef8b1e60537 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 18 Mar 2014 18:40:00 +0100 Subject: [PATCH 48/50] pkg/systemd: Drop our copy-pasted version of go-systemd/activation Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/systemd/activation/files.go | 55 ----------------------------- pkg/systemd/activation/listeners.go | 37 ------------------- pkg/systemd/listendfd.go | 2 +- 3 files changed, 1 insertion(+), 93 deletions(-) delete mode 100644 pkg/systemd/activation/files.go delete mode 100644 pkg/systemd/activation/listeners.go diff --git a/pkg/systemd/activation/files.go b/pkg/systemd/activation/files.go deleted file mode 100644 index 0281146310..0000000000 --- a/pkg/systemd/activation/files.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright 2013 CoreOS Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -// Package activation implements primitives for systemd socket activation. -package activation - -import ( - "os" - "strconv" - "syscall" -) - -// based on: https://gist.github.com/alberts/4640792 -const ( - listenFdsStart = 3 -) - -func Files(unsetEnv bool) []*os.File { - if unsetEnv { - // there is no way to unset env in golang os package for now - // https://code.google.com/p/go/issues/detail?id=6423 - defer os.Setenv("LISTEN_PID", "") - defer os.Setenv("LISTEN_FDS", "") - } - - pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) - if err != nil || pid != os.Getpid() { - return nil - } - - nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) - if err != nil || nfds == 0 { - return nil - } - - var files []*os.File - for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { - syscall.CloseOnExec(fd) - files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) - } - - return files -} diff --git a/pkg/systemd/activation/listeners.go b/pkg/systemd/activation/listeners.go deleted file mode 100644 index 3296a08361..0000000000 --- a/pkg/systemd/activation/listeners.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2014 CoreOS Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package activation - -import ( - "fmt" - "net" -) - -// Listeners returns net.Listeners for all socket activated fds passed to this process. -func Listeners(unsetEnv bool) ([]net.Listener, error) { - files := Files(unsetEnv) - listeners := make([]net.Listener, len(files)) - - for i, f := range files { - var err error - listeners[i], err = net.FileListener(f) - if err != nil { - return nil, fmt.Errorf("Error setting up FileListener for fd %d: %s", f.Fd(), err.Error()) - } - } - - return listeners, nil -} diff --git a/pkg/systemd/listendfd.go b/pkg/systemd/listendfd.go index f6044328c2..0fbc0a6ab6 100644 --- a/pkg/systemd/listendfd.go +++ b/pkg/systemd/listendfd.go @@ -5,7 +5,7 @@ import ( "net" "strconv" - "github.com/dotcloud/docker/pkg/systemd/activation" + "github.com/coreos/go-systemd/activation" ) // ListenFD returns the specified socket activated files as a slice of From 1296d5ce9ad43d8d833f6e5661da45aef6d4c26b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 21 Feb 2014 10:48:02 +0100 Subject: [PATCH 49/50] Add systemd.SdBooted() This is a conversion of sd_booted() from libsystemd to go and checks if the system was booted with systemd. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/systemd/booted.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 pkg/systemd/booted.go diff --git a/pkg/systemd/booted.go b/pkg/systemd/booted.go new file mode 100644 index 0000000000..2aae931ec1 --- /dev/null +++ b/pkg/systemd/booted.go @@ -0,0 +1,15 @@ +package systemd + +import ( + "os" +) + +// Conversion to Go of systemd's sd_booted() +func SdBooted() bool { + s, err := os.Stat("/run/systemd/system") + if err != nil { + return false + } + + return s.IsDir() +} From 6c7835050e53b733181ddfca6152c358fd625400 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 21 Feb 2014 14:35:43 +0100 Subject: [PATCH 50/50] cgroups: Add systemd implementation of cgroups This implements cgroup.Apply() using the systemd apis. We create a transient unit called "docker-$id.scope" that contains the container processes. We also have a way to set unit specific properties, currently only defining the Slice to put the scope in. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- pkg/cgroups/apply_nosystemd.go | 15 ++ pkg/cgroups/apply_systemd.go | 158 ++++++++++++++++++ pkg/cgroups/cgroups.go | 13 +- runtime/execdriver/native/default_template.go | 14 ++ 4 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 pkg/cgroups/apply_nosystemd.go create mode 100644 pkg/cgroups/apply_systemd.go diff --git a/pkg/cgroups/apply_nosystemd.go b/pkg/cgroups/apply_nosystemd.go new file mode 100644 index 0000000000..f94d475907 --- /dev/null +++ b/pkg/cgroups/apply_nosystemd.go @@ -0,0 +1,15 @@ +// +build !linux + +package cgroups + +import ( + "fmt" +) + +func useSystemd() bool { + return false +} + +func systemdApply(c *Cgroup, pid int) (ActiveCgroup, error) { + return nil, fmt.Errorf("Systemd not supported") +} diff --git a/pkg/cgroups/apply_systemd.go b/pkg/cgroups/apply_systemd.go new file mode 100644 index 0000000000..c689d5753e --- /dev/null +++ b/pkg/cgroups/apply_systemd.go @@ -0,0 +1,158 @@ +// +build linux + +package cgroups + +import ( + "fmt" + systemd1 "github.com/coreos/go-systemd/dbus" + "github.com/dotcloud/docker/pkg/systemd" + "github.com/godbus/dbus" + "path/filepath" + "strings" + "sync" +) + +type systemdCgroup struct { +} + +var ( + connLock sync.Mutex + theConn *systemd1.Conn + hasStartTransientUnit bool +) + +func useSystemd() bool { + if !systemd.SdBooted() { + return false + } + + connLock.Lock() + defer connLock.Unlock() + + if theConn == nil { + var err error + theConn, err = systemd1.New() + if err != nil { + return false + } + + // Assume we have StartTransientUnit + hasStartTransientUnit = true + + // But if we get UnknownMethod error we don't + if _, err := theConn.StartTransientUnit("test.scope", "invalid"); err != nil { + if dbusError, ok := err.(dbus.Error); ok { + if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" { + hasStartTransientUnit = false + } + } + } + } + + return hasStartTransientUnit +} + +type DeviceAllow struct { + Node string + Permissions string +} + +func getIfaceForUnit(unitName string) string { + if strings.HasSuffix(unitName, ".scope") { + return "Scope" + } + if strings.HasSuffix(unitName, ".service") { + return "Service" + } + return "Unit" +} + +func systemdApply(c *Cgroup, pid int) (ActiveCgroup, error) { + unitName := c.Parent + "-" + c.Name + ".scope" + slice := "system.slice" + + var properties []systemd1.Property + + for _, v := range c.UnitProperties { + switch v[0] { + case "Slice": + slice = v[1] + default: + return nil, fmt.Errorf("Unknown unit propery %s", v[0]) + } + } + + properties = append(properties, + systemd1.Property{"Slice", dbus.MakeVariant(slice)}, + systemd1.Property{"Description", dbus.MakeVariant("docker container " + c.Name)}, + systemd1.Property{"PIDs", dbus.MakeVariant([]uint32{uint32(pid)})}) + + if !c.DeviceAccess { + properties = append(properties, + systemd1.Property{"DevicePolicy", dbus.MakeVariant("strict")}, + systemd1.Property{"DeviceAllow", dbus.MakeVariant([]DeviceAllow{ + {"/dev/null", "rwm"}, + {"/dev/zero", "rwm"}, + {"/dev/full", "rwm"}, + {"/dev/random", "rwm"}, + {"/dev/urandom", "rwm"}, + {"/dev/tty", "rwm"}, + {"/dev/console", "rwm"}, + {"/dev/tty0", "rwm"}, + {"/dev/tty1", "rwm"}, + {"/dev/pts/ptmx", "rwm"}, + // There is no way to add /dev/pts/* here atm, so we hack this manually below + // /dev/pts/* (how to add this?) + // Same with tuntap, which doesn't exist as a node most of the time + })}) + } + + if c.Memory != 0 { + properties = append(properties, + systemd1.Property{"MemoryLimit", dbus.MakeVariant(uint64(c.Memory))}) + } + // TODO: MemorySwap not available in systemd + + if c.CpuShares != 0 { + properties = append(properties, + systemd1.Property{"CPUShares", dbus.MakeVariant(uint64(c.CpuShares))}) + } + + if _, err := theConn.StartTransientUnit(unitName, "replace", properties...); err != nil { + return nil, err + } + + // To work around the lack of /dev/pts/* support above we need to manually add these + // so, ask systemd for the cgroup used + props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName)) + if err != nil { + return nil, err + } + + cgroup := props["ControlGroup"].(string) + + if !c.DeviceAccess { + mountpoint, err := FindCgroupMountpoint("devices") + if err != nil { + return nil, err + } + + path := filepath.Join(mountpoint, cgroup) + + // /dev/pts/* + if err := writeFile(path, "devices.allow", "c 136:* rwm"); err != nil { + return nil, err + } + // tuntap + if err := writeFile(path, "devices.allow", "c 10:200 rwm"); err != nil { + return nil, err + } + } + + return &systemdCgroup{}, nil +} + +func (c *systemdCgroup) Cleanup() error { + // systemd cleans up, we don't need to do anything + return nil +} diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index f35556f712..8554ba9376 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -19,6 +19,8 @@ type Cgroup struct { Memory int64 `json:"memory,omitempty"` // Memory limit (in bytes) MemorySwap int64 `json:"memory_swap,omitempty"` // Total memory usage (memory + swap); set `-1' to disable swap CpuShares int64 `json:"cpu_shares,omitempty"` // CPU shares (relative weight vs. other containers) + + UnitProperties [][2]string `json:"unit_properties,omitempty"` // systemd unit properties } type ActiveCgroup interface { @@ -87,5 +89,14 @@ func writeFile(dir, file, data string) error { } func (c *Cgroup) Apply(pid int) (ActiveCgroup, error) { - return rawApply(c, pid) + // We have two implementation of cgroups support, one is based on + // systemd and the dbus api, and one is based on raw cgroup fs operations + // following the pre-single-writer model docs at: + // http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/ + + if useSystemd() { + return systemdApply(c, pid) + } else { + return rawApply(c, pid) + } } diff --git a/runtime/execdriver/native/default_template.go b/runtime/execdriver/native/default_template.go index e11f2de1cf..32886f2396 100644 --- a/runtime/execdriver/native/default_template.go +++ b/runtime/execdriver/native/default_template.go @@ -5,6 +5,7 @@ import ( "github.com/dotcloud/docker/pkg/cgroups" "github.com/dotcloud/docker/pkg/libcontainer" "github.com/dotcloud/docker/runtime/execdriver" + "github.com/dotcloud/docker/utils" "os" ) @@ -59,6 +60,19 @@ func createContainer(c *execdriver.Command) *libcontainer.Container { container.Cgroups.MemorySwap = c.Resources.MemorySwap } + if opts, ok := c.Config["unit"]; ok { + props := [][2]string{} + for _, opt := range opts { + key, value, err := utils.ParseKeyValueOpt(opt) + if err == nil { + props = append(props, [2]string{key, value}) + } else { + props = append(props, [2]string{opt, ""}) + } + } + container.Cgroups.UnitProperties = props + } + // check to see if we are running in ramdisk to disable pivot root container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""