New oom sync (#257)

* Vendor in runc afaa21f79ade3b2e99a68f3f15e7219155aa4662

This updates the Dockerfile to use go 1.6.2 and install pkg-config are
both are now needed by runc.

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>

* Add support for runc create/start operation

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>

* Remove dependency on runc state directory for OOM handler

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>

* Add OOM test

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
This commit is contained in:
Kenfe-Mickaël Laventure 2016-06-09 13:33:26 -07:00 committed by Michael Crosby
parent cf554d59dd
commit 8040df4e89
214 changed files with 215 additions and 30135 deletions

View file

@ -11,13 +11,14 @@ RUN apt-get update && apt-get install -y \
git \
make \
jq \
pkg-config \
apparmor \
libapparmor-dev \
--no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
# Install Go
ENV GO_VERSION 1.5.3
ENV GO_VERSION 1.6.2
RUN curl -sSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar -v -C /usr/local -xz
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go:/go/src/github.com/docker/containerd/vendor
@ -54,7 +55,7 @@ RUN set -x \
&& rm -rf "$SECCOMP_PATH"
# Install runc
ENV RUNC_COMMIT d49ece5a83da3dcb820121d6850e2b61bd0a5fbe
ENV RUNC_COMMIT afaa21f79ade3b2e99a68f3f15e7219155aa4662
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \

View file

@ -3,6 +3,7 @@ package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"os/signal"
"path/filepath"
@ -77,7 +78,7 @@ func start(log *os.File) error {
writeMessage(log, "warn", err)
}
}()
if err := p.start(); err != nil {
if err := p.create(); err != nil {
p.delete()
return err
}
@ -102,6 +103,11 @@ func start(log *os.File) error {
Height: uint16(h),
}
term.SetWinsize(p.console.Fd(), &ws)
case 2:
// tell runtime to execute the init process
if err := p.start(); err != nil {
syscall.Kill(p.pid(), syscall.SIGKILL)
}
}
}
}()
@ -120,6 +126,7 @@ func start(log *os.File) error {
}
// runtime has exited so the shim can also exit
if exitShim {
ioutil.WriteFile(fmt.Sprintf("/tmp/shim-delete-%d", p.pid()), []byte("deleting"), 0600)
p.delete()
p.Wait()
return nil

View file

@ -115,7 +115,7 @@ func loadCheckpoint(checkpointPath string) (*checkpoint, error) {
return &cpt, nil
}
func (p *process) start() error {
func (p *process) create() error {
cwd, err := os.Getwd()
if err != nil {
return err
@ -127,6 +127,7 @@ func (p *process) start() error {
}, p.state.RuntimeArgs...)
if p.state.Exec {
args = append(args, "exec",
"-d",
"--process", filepath.Join(cwd, "process.json"),
"--console", p.consolePath,
)
@ -151,7 +152,7 @@ func (p *process) start() error {
add("--no-pivot")
}
} else {
args = append(args, "start",
args = append(args, "create",
"--bundle", p.bundle,
"--console", p.consolePath,
)
@ -160,7 +161,6 @@ func (p *process) start() error {
}
}
args = append(args,
"-d",
"--pid-file", filepath.Join(cwd, "pid"),
p.id,
)
@ -200,6 +200,25 @@ func (p *process) start() error {
return nil
}
func (p *process) start() error {
cwd, err := os.Getwd()
if err != nil {
return err
}
logPath := filepath.Join(cwd, "log.json")
args := append([]string{
"--log", logPath,
"--log-format", "json",
}, p.state.RuntimeArgs...)
args = append(args, "start", p.id)
cmd := exec.Command(p.runtime, args...)
cmd.Dir = p.bundle
cmd.Stdin = p.stdio.stdin
cmd.Stdout = p.stdio.stdout
cmd.Stderr = p.stdio.stderr
return cmd.Run()
}
func (p *process) pid() int {
return p.containerPid
}

View file

@ -14,7 +14,7 @@ clone git github.com/docker/go-units 5d2041e26a699eaca682e2ea41c8f891e1060444
clone git github.com/godbus/dbus e2cf28118e66a6a63db46cf6088a35d2054d3bb0
clone git github.com/golang/glog 23def4e6c14b4da8ac2ed8007337bc5eb5007998
clone git github.com/golang/protobuf 3c84672111d91bb5ac31719e112f9f7126a0e26e
clone git github.com/opencontainers/runc d49ece5a83da3dcb820121d6850e2b61bd0a5fbe
clone git github.com/opencontainers/runc afaa21f79ade3b2e99a68f3f15e7219155aa4662
clone git github.com/opencontainers/runtime-spec f955d90e70a98ddfb886bd930ffd076da9b67998
clone git github.com/rcrowley/go-metrics eeba7bd0dd01ace6e690fa833b3f22aaec29af43
clone git github.com/satori/go.uuid f9ab0dce87d815821e221626b772e3475a0d2749

View file

@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
@ -13,6 +14,11 @@ import (
"golang.org/x/net/context"
)
func (cs *ContainerdSuite) GetLogs() string {
b, _ := ioutil.ReadFile(cs.logFile.Name())
return string(b)
}
func (cs *ContainerdSuite) ListRunningContainers() ([]*types.Container, error) {
resp, err := cs.grpcClient.State(context.Background(), &types.StateRequest{})
if err != nil {
@ -38,6 +44,16 @@ func (cs *ContainerdSuite) KillContainer(id string) error {
return cs.SignalContainerProcess(id, "init", uint32(syscall.SIGKILL))
}
func (cs *ContainerdSuite) UpdateContainerResource(id string, rs *types.UpdateResource) error {
_, err := cs.grpcClient.UpdateContainer(context.Background(), &types.UpdateContainerRequest{
Id: id,
Pid: "init",
Status: "",
Resources: rs,
})
return err
}
func (cs *ContainerdSuite) PauseContainer(id string) error {
_, err := cs.grpcClient.UpdateContainer(context.Background(), &types.UpdateContainerRequest{
Id: id,

View file

@ -9,6 +9,7 @@ import (
"github.com/docker/containerd/api/grpc/types"
"github.com/docker/docker/pkg/integration/checker"
"github.com/go-check/check"
ocs "github.com/opencontainers/runtime-spec/specs-go"
"google.golang.org/grpc"
)
@ -61,7 +62,8 @@ func (cs *ContainerdSuite) TestStartBusyboxTop(t *check.C) {
t.Fatal(err)
}
_, err := cs.StartContainer("top", bundleName)
containerID := "start-busybox-top"
_, err := cs.StartContainer(containerID, bundleName)
t.Assert(err, checker.Equals, nil)
containers, err := cs.ListRunningContainers()
@ -69,7 +71,7 @@ func (cs *ContainerdSuite) TestStartBusyboxTop(t *check.C) {
t.Fatal(err)
}
t.Assert(len(containers), checker.Equals, 1)
t.Assert(containers[0].Id, checker.Equals, "top")
t.Assert(containers[0].Id, checker.Equals, containerID)
t.Assert(containers[0].Status, checker.Equals, "running")
t.Assert(containers[0].BundlePath, check.Equals, filepath.Join(cs.cwd, GetBundle(bundleName).Path))
}
@ -144,8 +146,8 @@ func (cs *ContainerdSuite) TestStartBusyboxTopKill(t *check.C) {
t.Fatal(err)
}
containerID := "top"
c, err := cs.StartContainer("top", bundleName)
containerID := "top-kill"
c, err := cs.StartContainer(containerID, bundleName)
if err != nil {
t.Fatal(err)
}
@ -189,8 +191,8 @@ func (cs *ContainerdSuite) TestStartBusyboxTopSignalSigterm(t *check.C) {
t.Fatal(err)
}
containerID := "top"
c, err := cs.StartContainer("top", bundleName)
containerID := "top-sigterm"
c, err := cs.StartContainer(containerID, bundleName)
if err != nil {
t.Fatal(err)
}
@ -229,7 +231,7 @@ func (cs *ContainerdSuite) TestStartBusyboxTopSignalSigterm(t *check.C) {
}
func (cs *ContainerdSuite) TestStartBusyboxTrapUSR1(t *check.C) {
if err := CreateBusyboxBundle("busybox-trap-usr1", []string{"sh", "-c", "trap 'echo -n booh!' SIGUSR1 ; sleep 100 & wait"}); err != nil {
if err := CreateBusyboxBundle("busybox-trap-usr1", []string{"sh", "-c", "trap 'echo -n booh!' SIGUSR1 ; sleep 60 & wait"}); err != nil {
t.Fatal(err)
}
@ -239,6 +241,8 @@ func (cs *ContainerdSuite) TestStartBusyboxTrapUSR1(t *check.C) {
t.Fatal(err)
}
<-time.After(1 * time.Second)
if err := cs.SignalContainer(containerID, uint32(syscall.SIGUSR1)); err != nil {
t.Fatal(err)
}
@ -259,7 +263,7 @@ func (cs *ContainerdSuite) TestStartBusyboxTopPauseResume(t *check.C) {
t.Fatal(err)
}
containerID := "top"
containerID := "top-pause-resume"
c, err := cs.StartContainer(containerID, bundleName)
if err != nil {
t.Fatal(err)
@ -310,10 +314,59 @@ func (cs *ContainerdSuite) TestStartBusyboxTopPauseResume(t *check.C) {
t.Fatal(err)
}
t.Assert(len(containers), checker.Equals, 1)
t.Assert(containers[0].Id, checker.Equals, "top")
t.Assert(containers[0].Id, checker.Equals, containerID)
t.Assert(containers[0].Status, checker.Equals, "running")
}
func (cs *ContainerdSuite) TestOOM(t *check.C) {
bundleName := "busybox-sh-512k-memlimit"
if err := CreateBundleWithFilter("busybox", bundleName, []string{"sh", "-c", "x=oom-party-time; while true; do x=$x$x$x$x$x$x$x$x$x$x; done"}, func(spec *ocs.Spec) {
// Limit to 512k for quick oom
var limit uint64 = 8 * 1024 * 1024
spec.Linux.Resources.Memory = &ocs.Memory{
Limit: &limit,
}
}); err != nil {
t.Fatal(err)
}
containerID := "sh-oom"
c, err := cs.StartContainer(containerID, bundleName)
if err != nil {
t.Fatal(err)
}
for _, evt := range []types.Event{
{
Type: "start-container",
Id: containerID,
Status: 0,
Pid: "",
},
{
Type: "oom",
Id: containerID,
Status: 0,
Pid: "",
},
{
Type: "exit",
Id: containerID,
Status: 137,
Pid: "init",
},
} {
ch := c.GetEventsChannel()
select {
case e := <-ch:
evt.Timestamp = e.Timestamp
t.Assert(*e, checker.Equals, evt)
case <-time.After(60 * time.Second):
t.Fatal("Container took more than 10 seconds to terminate")
}
}
}
func (cs *ContainerdSuite) TestRestart(t *check.C) {
bundleName := "busybox-top"
if err := CreateBusyboxBundle(bundleName, []string{"top"}); err != nil {

View file

@ -413,7 +413,7 @@ func (c *container) Start(checkpointPath string, s Stdio) (Process, error) {
if err != nil {
return nil, err
}
if err := c.startCmd(InitProcessID, cmd, p); err != nil {
if err := c.createCmd(InitProcessID, cmd, p); err != nil {
return nil, err
}
return p, nil
@ -453,13 +453,13 @@ func (c *container) Exec(pid string, pspec specs.ProcessSpec, s Stdio) (pp Proce
if err != nil {
return nil, err
}
if err := c.startCmd(pid, cmd, p); err != nil {
if err := c.createCmd(pid, cmd, p); err != nil {
return nil, err
}
return p, nil
}
func (c *container) startCmd(pid string, cmd *exec.Cmd, p *process) error {
func (c *container) createCmd(pid string, cmd *exec.Cmd, p *process) error {
p.cmd = cmd
if err := cmd.Start(); err != nil {
if exErr, ok := err.(*exec.Error); ok {
@ -469,7 +469,7 @@ func (c *container) startCmd(pid string, cmd *exec.Cmd, p *process) error {
}
return err
}
if err := c.waitForStart(p, cmd); err != nil {
if err := c.waitForCreate(p, cmd); err != nil {
return err
}
c.processes[pid] = p
@ -552,7 +552,7 @@ type waitArgs struct {
err error
}
func (c *container) waitForStart(p *process, cmd *exec.Cmd) error {
func (c *container) waitForCreate(p *process, cmd *exec.Cmd) error {
wc := make(chan error, 1)
go func() {
for {

View file

@ -1,6 +1,7 @@
package runtime
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
@ -11,46 +12,84 @@ import (
"syscall"
"github.com/docker/containerd/specs"
"github.com/opencontainers/runc/libcontainer"
ocs "github.com/opencontainers/runtime-spec/specs-go"
)
func (c *container) getLibctContainer() (libcontainer.Container, error) {
runtimeRoot := "/run/runc"
func findCgroupMountpointAndRoot(pid int, subsystem string) (string, string, error) {
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
if err != nil {
return "", "", err
}
defer f.Close()
// Check that the root wasn't changed
for _, opt := range c.runtimeArgs {
if strings.HasPrefix(opt, "--root=") {
runtimeRoot = strings.TrimPrefix(opt, "--root=")
break
scanner := bufio.NewScanner(f)
for scanner.Scan() {
txt := scanner.Text()
fields := strings.Split(txt, " ")
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if opt == subsystem {
return fields[4], fields[3], nil
}
}
}
if err := scanner.Err(); err != nil {
return "", "", err
}
f, err := libcontainer.New(runtimeRoot, libcontainer.Cgroupfs)
return "", "", fmt.Errorf("cgroup path for %s not found", subsystem)
}
func parseCgroupFile(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
return f.Load(c.id)
defer f.Close()
s := bufio.NewScanner(f)
cgroups := make(map[string]string)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text()
parts := strings.Split(text, ":")
for _, subs := range strings.Split(parts[1], ",") {
cgroups[subs] = parts[2]
}
}
return cgroups, nil
}
func (c *container) OOM() (OOM, error) {
container, err := c.getLibctContainer()
if err != nil {
if lerr, ok := err.(libcontainer.Error); ok {
// with oom registration sometimes the container can run, exit, and be destroyed
// faster than we can get the state back so we can just ignore this
if lerr.Code() == libcontainer.ContainerNotExists {
return nil, ErrContainerExited
}
}
return nil, err
p := c.processes[InitProcessID]
if p == nil {
return nil, fmt.Errorf("no init process found")
}
state, err := container.State()
mountpoint, hostRoot, err := findCgroupMountpointAndRoot(os.Getpid(), "memory")
if err != nil {
return nil, err
}
memoryPath := state.CgroupPaths["memory"]
return c.getMemeoryEventFD(memoryPath)
cgroups, err := parseCgroupFile(fmt.Sprintf("/proc/%d/cgroup", p.pid))
if err != nil {
return nil, err
}
root, ok := cgroups["memory"]
if !ok {
return nil, fmt.Errorf("no memory cgroup for container %s", c.ID())
}
// Take care of the case were we're running inside a container
// ourself
root = strings.TrimPrefix(root, hostRoot)
return c.getMemeoryEventFD(filepath.Join(mountpoint, root))
}
func u64Ptr(i uint64) *uint64 { return &i }

View file

@ -23,6 +23,9 @@ type Process interface {
// This is either "init" when it is the container's init process or
// it is a user provided id for the process similar to the container id
ID() string
// Start unblocks the associated container init process.
// This should only be called on the process with ID "init"
Start() error
CloseStdin() error
Resize(int, int) error
// ExitFD returns the fd the provides an event when the process exits
@ -252,3 +255,13 @@ func getControlPipe(path string) (*os.File, error) {
func (p *process) Signal(s os.Signal) error {
return syscall.Kill(p.pid, s.(syscall.Signal))
}
// Start unblocks the associated container init process.
// This should only be called on the process with ID "init"
func (p *process) Start() error {
if p.ID() == InitProcessID {
_, err := fmt.Fprintf(p.controlPipe, "%d %d %d\n", 2, 0, 0)
return err
}
return nil
}

View file

@ -17,6 +17,10 @@ func (p *testProcess) ID() string {
return p.id
}
func (p *testProcess) Start() error {
return nil
}
func (p *testProcess) CloseStdin() error {
return nil
}

View file

@ -63,6 +63,9 @@ func (w *worker) Start() {
if err := w.s.monitorProcess(process); err != nil {
logrus.WithField("error", err).Error("containerd: add process to monitor")
}
if err := process.Start(); err != nil {
logrus.WithField("error", err).Error("containerd: start init process")
}
ContainerStartTimer.UpdateSince(started)
t.Err <- nil
t.StartResponse <- StartResponse{

View file

@ -1,198 +0,0 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/
package dbus
import (
"fmt"
"os"
"strconv"
"strings"
"sync"
"github.com/godbus/dbus"
)
const (
alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`
num = `0123456789`
alphanum = alpha + num
signalBuffer = 100
)
// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped
func needsEscape(i int, b byte) bool {
// Escape everything that is not a-z-A-Z-0-9
// Also escape 0-9 if it's the first character
return strings.IndexByte(alphanum, b) == -1 ||
(i == 0 && strings.IndexByte(num, b) != -1)
}
// PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the
// rules that systemd uses for serializing special characters.
func PathBusEscape(path string) string {
// Special case the empty string
if len(path) == 0 {
return "_"
}
n := []byte{}
for i := 0; i < len(path); i++ {
c := path[i]
if needsEscape(i, c) {
e := fmt.Sprintf("_%x", c)
n = append(n, []byte(e)...)
} else {
n = append(n, c)
}
}
return string(n)
}
// Conn is a connection to systemd's dbus endpoint.
type Conn struct {
// sysconn/sysobj are only used to call dbus methods
sysconn *dbus.Conn
sysobj dbus.BusObject
// sigconn/sigobj are only used to receive dbus signals
sigconn *dbus.Conn
sigobj dbus.BusObject
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
}
}
// New establishes a connection to the system bus and authenticates.
// Callers should call Close() when done with the connection.
func New() (*Conn, error) {
return newConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SystemBusPrivate)
})
}
// NewUserConnection establishes a connection to the session bus and
// authenticates. This can be used to connect to systemd user instances.
// Callers should call Close() when done with the connection.
func NewUserConnection() (*Conn, error) {
return newConnection(func() (*dbus.Conn, error) {
return dbusAuthHelloConnection(dbus.SessionBusPrivate)
})
}
// NewSystemdConnection establishes a private, direct connection to systemd.
// This can be used for communicating with systemd without a dbus daemon.
// Callers should call Close() when done with the connection.
func NewSystemdConnection() (*Conn, error) {
return newConnection(func() (*dbus.Conn, error) {
// We skip Hello when talking directly to systemd.
return dbusAuthConnection(func() (*dbus.Conn, error) {
return dbus.Dial("unix:path=/run/systemd/private")
})
})
}
// Close closes an established connection
func (c *Conn) Close() {
c.sysconn.Close()
c.sigconn.Close()
}
func newConnection(createBus func() (*dbus.Conn, error)) (*Conn, error) {
sysconn, err := createBus()
if err != nil {
return nil, err
}
sigconn, err := createBus()
if err != nil {
sysconn.Close()
return nil, err
}
c := &Conn{
sysconn: sysconn,
sysobj: systemdObject(sysconn),
sigconn: sigconn,
sigobj: systemdObject(sigconn),
}
c.subscriber.ignore = make(map[dbus.ObjectPath]int64)
c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string)
// Setup the listeners on jobs so that we can get completions
c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'")
c.dispatch()
return c, nil
}
// GetManagerProperty returns the value of a property on the org.freedesktop.systemd1.Manager
// interface. The value is returned in its string representation, as defined at
// https://developer.gnome.org/glib/unstable/gvariant-text.html
func (c *Conn) GetManagerProperty(prop string) (string, error) {
variant, err := c.sysobj.GetProperty("org.freedesktop.systemd1.Manager." + prop)
if err != nil {
return "", err
}
return variant.String(), nil
}
func dbusAuthConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
conn, err := createBus()
if err != nil {
return nil, 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 = conn.Auth(methods)
if err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
func dbusAuthHelloConnection(createBus func() (*dbus.Conn, error)) (*dbus.Conn, error) {
conn, err := dbusAuthConnection(createBus)
if err != nil {
return nil, err
}
if err = conn.Hello(); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
func systemdObject(conn *dbus.Conn) dbus.BusObject {
return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1"))
}

View file

@ -1,442 +0,0 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus
import (
"errors"
"path"
"strconv"
"github.com/godbus/dbus"
)
func (c *Conn) 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(ch chan<- string, job string, args ...interface{}) (int, error) {
if ch != nil {
c.jobListener.Lock()
defer c.jobListener.Unlock()
}
var p dbus.ObjectPath
err := c.sysobj.Call(job, 0, args...).Store(&p)
if err != nil {
return 0, err
}
if ch != nil {
c.jobListener.jobs[p] = ch
}
// ignore error since 0 is fine if conversion fails
jobID, _ := strconv.Atoi(path.Base(string(p)))
return jobID, nil
}
// StartUnit enqueues a start job and depending jobs, if any (unless otherwise
// specified by the mode string).
//
// Takes the unit to activate, plus a mode string. The mode needs to be one of
// 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.
//
// If the provided channel is non-nil, a result string will be sent to it upon
// job completion: one of done, canceled, timeout, failed, dependency, skipped.
// done indicates successful execution of a job. canceled indicates that a job
// has been canceled before it finished execution. timeout indicates that the
// job timeout was reached. failed indicates that the job failed. dependency
// indicates that a job this job has been depending on failed and the job hence
// has been removed too. skipped indicates that a job was skipped because it
// didn't apply to the units current state.
//
// If no error occurs, the ID of the underlying systemd job will be returned. There
// does exist the possibility for no error to be returned, but for the returned job
// ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint
// should not be considered authoritative.
//
// If an error does occur, it will be returned to the user alongside a job ID of 0.
func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode)
}
// StopUnit is similar to StartUnit but stops the specified unit rather
// than starting it.
func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode)
}
// ReloadUnit reloads a unit. Reloading is done only if the unit is already running and fails otherwise.
func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode)
}
// RestartUnit restarts a service. If a service is restarted that isn't
// running it will be started.
func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode)
}
// TryRestartUnit is like RestartUnit, except that a service that isn't running
// is not affected by the restart.
func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode)
}
// ReloadOrRestart attempts a reload if the unit supports it and use a restart
// otherwise.
func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode)
}
// ReloadOrTryRestart attempts a reload if the unit supports it and use a "Try"
// flavored restart otherwise.
func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode)
}
// StartTransientUnit() may be used to create and start a transient unit, which
// 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, ch chan<- string) (int, error) {
return c.startJob(ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0))
}
// KillUnit takes the unit name and a UNIX signal number to send. All of the unit's
// processes are killed.
func (c *Conn) KillUnit(name string, signal int32) {
c.sysobj.Call("org.freedesktop.systemd1.Manager.KillUnit", 0, name, "all", signal).Store()
}
// ResetFailedUnit resets the "failed" state of a specific unit.
func (c *Conn) ResetFailedUnit(name string) error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store()
}
// getProperties takes the unit name and returns all of its dbus object properties, for the given dbus interface
func (c *Conn) getProperties(unit string, dbusInterface string) (map[string]interface{}, error) {
var err error
var props map[string]dbus.Variant
path := unitPath(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 := unitPath(unit)
if !path.IsValid() {
return nil, errors.New("invalid unit name: " + unit)
}
obj := c.sysconn.Object("org.freedesktop.systemd1", path)
err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop)
if err != nil {
return nil, err
}
return &Property{Name: propertyName, Value: prop}, nil
}
func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1.Unit", propertyName)
}
// GetUnitTypeProperties returns the extra properties for a unit, specific to the unit type.
// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope
// return "dbus.Error: Unknown interface" if the unitType is not the correct type of the unit
func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) {
return c.getProperties(unit, "org.freedesktop.systemd1."+unitType)
}
// SetUnitProperties() may be used to modify certain unit properties at runtime.
// Not all properties may be changed at runtime, but many resource management
// settings (primarily those in systemd.cgroup(5)) may. The changes are applied
// instantly, and stored on disk for future boots, unless runtime is true, in which
// case the settings only apply until the next reboot. name is the name of the unit
// to modify. properties are the settings to set, encoded as an array of property
// name and value pairs.
func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store()
}
func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
return c.getProperty(unit, "org.freedesktop.systemd1."+unitType, propertyName)
}
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
}
// 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 UnitFile struct {
Path string
Type string
}
// ListUnitFiles returns an array of all available units on disk.
func (c *Conn) ListUnitFiles() ([]UnitFile, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
files := make([]UnitFile, len(result))
fileInterface := make([]interface{}, len(files))
for i := range files {
fileInterface[i] = &files[i]
}
err = dbus.Store(resultInterface, fileInterface...)
if err != nil {
return nil, err
}
return files, nil
}
type LinkUnitFileChange EnableUnitFileChange
// LinkUnitFiles() links unit files (that are located outside of the
// usual unit search paths) into the unit search path.
//
// It takes a list of absolute paths to unit files to link and two
// booleans. The first boolean controls whether the unit shall be
// enabled for runtime only (true, /run), or persistently (false,
// /etc).
// The second controls whether symlinks pointing to other units shall
// be replaced if necessary.
//
// This call returns a list of the changes made. The list consists of
// structures with three strings: the type of the change (one of symlink
// or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]LinkUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
// EnableUnitFiles() may be used to enable one or more units in the system (by
// creating symlinks to them in /etc or /run).
//
// It takes a list of unit files to enable (either just file names or full
// absolute paths if the unit files are residing outside the usual unit
// search paths), and two booleans: the first controls whether the unit shall
// be enabled for runtime only (true, /run), or persistently (false, /etc).
// The second one controls whether symlinks pointing to other units shall
// be replaced if necessary.
//
// This call returns one boolean and an array with the changes made. The
// boolean signals whether the unit files contained any enablement
// information (i.e. an [Install]) section. The changes list consists of
// structures with three strings: the type of the change (one of symlink
// or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) {
var carries_install_info bool
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result)
if err != nil {
return false, nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]EnableUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return false, nil, err
}
return carries_install_info, changes, nil
}
type EnableUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// DisableUnitFiles() may be used to disable one or more units in the system (by
// removing symlinks to them from /etc or /run).
//
// It takes a list of unit files to disable (either just file names or full
// absolute paths if the unit files are residing outside the usual unit
// search paths), and one boolean: whether the unit was enabled for runtime
// only (true, /run), or persistently (false, /etc).
//
// This call returns an array with the changes made. The changes list
// consists of structures with three strings: the type of the change (one of
// symlink or unlink), the file name of the symlink and the destination of the
// symlink.
func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) {
result := make([][]interface{}, 0)
err := c.sysobj.Call("org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result)
if err != nil {
return nil, err
}
resultInterface := make([]interface{}, len(result))
for i := range result {
resultInterface[i] = result[i]
}
changes := make([]DisableUnitFileChange, len(result))
changesInterface := make([]interface{}, len(changes))
for i := range changes {
changesInterface[i] = &changes[i]
}
err = dbus.Store(resultInterface, changesInterface...)
if err != nil {
return nil, err
}
return changes, nil
}
type DisableUnitFileChange struct {
Type string // Type of the change (one of symlink or unlink)
Filename string // File name of the symlink
Destination string // Destination of the symlink
}
// Reload instructs systemd to scan for and reload unit files. This is
// equivalent to a 'systemctl daemon-reload'.
func (c *Conn) Reload() error {
return c.sysobj.Call("org.freedesktop.systemd1.Manager.Reload", 0).Store()
}
func unitPath(name string) dbus.ObjectPath {
return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name))
}

View file

@ -1,218 +0,0 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus
import (
"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),
}
}

View file

@ -1,47 +0,0 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus
type set struct {
data map[string]bool
}
func (s *set) Add(value string) {
s.data[value] = true
}
func (s *set) Remove(value string) {
delete(s.data, value)
}
func (s *set) Contains(value string) (exists bool) {
_, exists = s.data[value]
return
}
func (s *set) Length() int {
return len(s.data)
}
func (s *set) Values() (values []string) {
for val, _ := range s.data {
values = append(values, val)
}
return
}
func newSet() *set {
return &set{make(map[string]bool)}
}

View file

@ -1,250 +0,0 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus
import (
"errors"
"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.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'")
c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'")
err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store()
if err != nil {
return err
}
return nil
}
// Unsubscribe this connection from systemd dbus events.
func (c *Conn) Unsubscribe() error {
err := c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store()
if err != nil {
return err
}
return nil
}
func (c *Conn) dispatch() {
ch := make(chan *dbus.Signal, signalBuffer)
c.sigconn.Signal(ch)
go func() {
for {
signal, ok := <-ch
if !ok {
return
}
if signal.Name == "org.freedesktop.systemd1.Manager.JobRemoved" {
c.jobComplete(signal)
}
if c.subscriber.updateCh == nil {
continue
}
var unitPath dbus.ObjectPath
switch signal.Name {
case "org.freedesktop.systemd1.Manager.JobRemoved":
unitName := signal.Body[2].(string)
c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath)
case "org.freedesktop.systemd1.Manager.UnitNew":
unitPath = signal.Body[1].(dbus.ObjectPath)
case "org.freedesktop.DBus.Properties.PropertiesChanged":
if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" {
unitPath = signal.Path
}
}
if unitPath == dbus.ObjectPath("") {
continue
}
c.sendSubStateUpdate(unitPath)
}
}()
}
// 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.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)
}
}
}
}

View file

@ -1,57 +0,0 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dbus
import (
"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,
mismatchUnitStatus,
func(unit string) bool { return s.filter(unit) },
)
}
// NewSubscriptionSet returns a new subscription set.
func (conn *Conn) NewSubscriptionSet() *SubscriptionSet {
return &SubscriptionSet{newSet(), conn}
}
// mismatchUnitStatus returns true if the provided UnitStatus objects
// are not equivalent. false is returned if the objects are equivalent.
// Only the Name, Description and state-related fields are used in
// the comparison.
func mismatchUnitStatus(u1, u2 *UnitStatus) bool {
return u1.Name != u2.Name ||
u1.Description != u2.Description ||
u1.LoadState != u2.LoadState ||
u1.ActiveState != u2.ActiveState ||
u1.SubState != u2.SubState
}

View file

@ -1,270 +0,0 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package util contains utility functions related to systemd that applications
// can use to check things like whether systemd is running. Note that some of
// these functions attempt to manually load systemd libraries at runtime rather
// than linking against them.
package util
// #cgo LDFLAGS: -ldl
// #include <stdlib.h>
// #include <dlfcn.h>
// #include <sys/types.h>
// #include <unistd.h>
//
// int
// my_sd_pid_get_owner_uid(void *f, pid_t pid, uid_t *uid)
// {
// int (*sd_pid_get_owner_uid)(pid_t, uid_t *);
//
// sd_pid_get_owner_uid = (int (*)(pid_t, uid_t *))f;
// return sd_pid_get_owner_uid(pid, uid);
// }
//
// int
// my_sd_pid_get_unit(void *f, pid_t pid, char **unit)
// {
// int (*sd_pid_get_unit)(pid_t, char **);
//
// sd_pid_get_unit = (int (*)(pid_t, char **))f;
// return sd_pid_get_unit(pid, unit);
// }
//
// int
// my_sd_pid_get_slice(void *f, pid_t pid, char **slice)
// {
// int (*sd_pid_get_slice)(pid_t, char **);
//
// sd_pid_get_slice = (int (*)(pid_t, char **))f;
// return sd_pid_get_slice(pid, slice);
// }
//
// int
// am_session_leader()
// {
// return (getsid(0) == getpid());
// }
import "C"
import (
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"syscall"
"unsafe"
)
var ErrSoNotFound = errors.New("unable to open a handle to libsystemd")
// libHandle represents an open handle to the systemd C library
type libHandle struct {
handle unsafe.Pointer
libname string
}
func (h *libHandle) Close() error {
if r := C.dlclose(h.handle); r != 0 {
return fmt.Errorf("error closing %v: %d", h.libname, r)
}
return nil
}
// getHandle tries to get a handle to a systemd library (.so), attempting to
// access it by several different names and returning the first that is
// successfully opened. Callers are responsible for closing the handler.
// If no library can be successfully opened, an error is returned.
func getHandle() (*libHandle, error) {
for _, name := range []string{
// systemd < 209
"libsystemd-login.so",
"libsystemd-login.so.0",
// systemd >= 209 merged libsystemd-login into libsystemd proper
"libsystemd.so",
"libsystemd.so.0",
} {
libname := C.CString(name)
defer C.free(unsafe.Pointer(libname))
handle := C.dlopen(libname, C.RTLD_LAZY)
if handle != nil {
h := &libHandle{
handle: handle,
libname: name,
}
return h, nil
}
}
return nil, ErrSoNotFound
}
// GetRunningSlice attempts to retrieve the name of the systemd slice in which
// the current process is running.
// This function is a wrapper around the libsystemd C library; if it cannot be
// opened, an error is returned.
func GetRunningSlice() (slice string, err error) {
var h *libHandle
h, err = getHandle()
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sym := C.CString("sd_pid_get_slice")
defer C.free(unsafe.Pointer(sym))
sd_pid_get_slice := C.dlsym(h.handle, sym)
if sd_pid_get_slice == nil {
err = fmt.Errorf("error resolving sd_pid_get_slice function")
return
}
var s string
sl := C.CString(s)
defer C.free(unsafe.Pointer(sl))
ret := C.my_sd_pid_get_slice(sd_pid_get_slice, 0, &sl)
if ret < 0 {
err = fmt.Errorf("error calling sd_pid_get_slice: %v", syscall.Errno(-ret))
return
}
return C.GoString(sl), nil
}
// RunningFromSystemService tries to detect whether the current process has
// been invoked from a system service. The condition for this is whether the
// process is _not_ a user process. User processes are those running in session
// scopes or under per-user `systemd --user` instances.
//
// To avoid false positives on systems without `pam_systemd` (which is
// responsible for creating user sessions), this function also uses a heuristic
// to detect whether it's being invoked from a session leader process. This is
// the case if the current process is executed directly from a service file
// (e.g. with `ExecStart=/this/cmd`). Note that this heuristic will fail if the
// command is instead launched in a subshell or similar so that it is not
// session leader (e.g. `ExecStart=/bin/bash -c "/this/cmd"`)
//
// This function is a wrapper around the libsystemd C library; if this is
// unable to successfully open a handle to the library for any reason (e.g. it
// cannot be found), an errr will be returned
func RunningFromSystemService() (ret bool, err error) {
var h *libHandle
h, err = getHandle()
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sym := C.CString("sd_pid_get_owner_uid")
defer C.free(unsafe.Pointer(sym))
sd_pid_get_owner_uid := C.dlsym(h.handle, sym)
if sd_pid_get_owner_uid == nil {
err = fmt.Errorf("error resolving sd_pid_get_owner_uid function")
return
}
var uid C.uid_t
errno := C.my_sd_pid_get_owner_uid(sd_pid_get_owner_uid, 0, &uid)
serrno := syscall.Errno(-errno)
// when we're running from a unit file, sd_pid_get_owner_uid returns
// ENOENT (systemd <220) or ENXIO (systemd >=220)
switch {
case errno >= 0:
ret = false
case serrno == syscall.ENOENT, serrno == syscall.ENXIO:
// Since the implementation of sessions in systemd relies on
// the `pam_systemd` module, using the sd_pid_get_owner_uid
// heuristic alone can result in false positives if that module
// (or PAM itself) is not present or properly configured on the
// system. As such, we also check if we're the session leader,
// which should be the case if we're invoked from a unit file,
// but not if e.g. we're invoked from the command line from a
// user's login session
ret = C.am_session_leader() == 1
default:
err = fmt.Errorf("error calling sd_pid_get_owner_uid: %v", syscall.Errno(-errno))
}
return
}
// CurrentUnitName attempts to retrieve the name of the systemd system unit
// from which the calling process has been invoked. It wraps the systemd
// `sd_pid_get_unit` call, with the same caveat: for processes not part of a
// systemd system unit, this function will return an error.
func CurrentUnitName() (unit string, err error) {
var h *libHandle
h, err = getHandle()
if err != nil {
return
}
defer func() {
if err1 := h.Close(); err1 != nil {
err = err1
}
}()
sym := C.CString("sd_pid_get_unit")
defer C.free(unsafe.Pointer(sym))
sd_pid_get_unit := C.dlsym(h.handle, sym)
if sd_pid_get_unit == nil {
err = fmt.Errorf("error resolving sd_pid_get_unit function")
return
}
var s string
u := C.CString(s)
defer C.free(unsafe.Pointer(u))
ret := C.my_sd_pid_get_unit(sd_pid_get_unit, 0, &u)
if ret < 0 {
err = fmt.Errorf("error calling sd_pid_get_unit: %v", syscall.Errno(-ret))
return
}
unit = C.GoString(u)
return
}
// IsRunningSystemd checks whether the host was booted with systemd as its init
// system. This functions similarly to systemd's `sd_booted(3)`: internally, it
// checks whether /run/systemd/system/ exists and is a directory.
// http://www.freedesktop.org/software/systemd/man/sd_booted.html
func IsRunningSystemd() bool {
fi, err := os.Lstat("/run/systemd/system")
if err != nil {
return false
}
return fi.IsDir()
}
// GetMachineID returns a host's 128-bit machine ID as a string. This functions
// similarly to systemd's `sd_id128_get_machine`: internally, it simply reads
// the contents of /etc/machine-id
// http://www.freedesktop.org/software/systemd/man/sd_id128_get_machine.html
func GetMachineID() (string, error) {
machineID, err := ioutil.ReadFile("/etc/machine-id")
if err != nil {
return "", fmt.Errorf("failed to read /etc/machine-id: %v", err)
}
return strings.TrimSpace(string(machineID)), nil
}

View file

@ -1,92 +0,0 @@
package mount
import (
"fmt"
"strings"
)
// Parse fstab type mount options into mount() flags
// and device specific data
func parseOptions(options string) (int, string) {
var (
flag int
data []string
)
flags := map[string]struct {
clear bool
flag int
}{
"defaults": {false, 0},
"ro": {false, RDONLY},
"rw": {true, RDONLY},
"suid": {true, NOSUID},
"nosuid": {false, NOSUID},
"dev": {true, NODEV},
"nodev": {false, NODEV},
"exec": {true, NOEXEC},
"noexec": {false, NOEXEC},
"sync": {false, SYNCHRONOUS},
"async": {true, SYNCHRONOUS},
"dirsync": {false, DIRSYNC},
"remount": {false, REMOUNT},
"mand": {false, MANDLOCK},
"nomand": {true, MANDLOCK},
"atime": {true, NOATIME},
"noatime": {false, NOATIME},
"diratime": {true, NODIRATIME},
"nodiratime": {false, NODIRATIME},
"bind": {false, BIND},
"rbind": {false, RBIND},
"unbindable": {false, UNBINDABLE},
"runbindable": {false, RUNBINDABLE},
"private": {false, PRIVATE},
"rprivate": {false, RPRIVATE},
"shared": {false, SHARED},
"rshared": {false, RSHARED},
"slave": {false, SLAVE},
"rslave": {false, RSLAVE},
"relatime": {false, RELATIME},
"norelatime": {true, RELATIME},
"strictatime": {false, STRICTATIME},
"nostrictatime": {true, STRICTATIME},
}
for _, o := range strings.Split(options, ",") {
// If the option does not exist in the flags table or the flag
// is not supported on the platform,
// then it is a data value for a specific fs type
if f, exists := flags[o]; exists && f.flag != 0 {
if f.clear {
flag &= ^f.flag
} else {
flag |= f.flag
}
} else {
data = append(data, o)
}
}
return flag, strings.Join(data, ",")
}
// ParseTmpfsOptions parse fstab type mount options into flags and data
func ParseTmpfsOptions(options string) (int, string, error) {
flags, data := parseOptions(options)
validFlags := map[string]bool{
"": true,
"size": true,
"mode": true,
"uid": true,
"gid": true,
"nr_inodes": true,
"nr_blocks": true,
"mpol": true,
}
for _, o := range strings.Split(data, ",") {
opt := strings.SplitN(o, "=", 2)
if !validFlags[opt[0]] {
return 0, "", fmt.Errorf("Invalid tmpfs option %q", opt)
}
}
return flags, data, nil
}

View file

@ -1,48 +0,0 @@
// +build freebsd,cgo
package mount
/*
#include <sys/mount.h>
*/
import "C"
const (
// RDONLY will mount the filesystem as read-only.
RDONLY = C.MNT_RDONLY
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
// take effect.
NOSUID = C.MNT_NOSUID
// NOEXEC will not allow execution of any binaries on the mounted file system.
NOEXEC = C.MNT_NOEXEC
// SYNCHRONOUS will allow any I/O to the file system to be done synchronously.
SYNCHRONOUS = C.MNT_SYNCHRONOUS
// NOATIME will not update the file access time when reading from a file.
NOATIME = C.MNT_NOATIME
)
// These flags are unsupported.
const (
BIND = 0
DIRSYNC = 0
MANDLOCK = 0
NODEV = 0
NODIRATIME = 0
UNBINDABLE = 0
RUNBINDABLE = 0
PRIVATE = 0
RPRIVATE = 0
SHARED = 0
RSHARED = 0
SLAVE = 0
RSLAVE = 0
RBIND = 0
RELATIVE = 0
RELATIME = 0
REMOUNT = 0
STRICTATIME = 0
)

View file

@ -1,85 +0,0 @@
package mount
import (
"syscall"
)
const (
// RDONLY will mount the file system read-only.
RDONLY = syscall.MS_RDONLY
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
// take effect.
NOSUID = syscall.MS_NOSUID
// NODEV will not interpret character or block special devices on the file
// system.
NODEV = syscall.MS_NODEV
// NOEXEC will not allow execution of any binaries on the mounted file system.
NOEXEC = syscall.MS_NOEXEC
// SYNCHRONOUS will allow I/O to the file system to be done synchronously.
SYNCHRONOUS = syscall.MS_SYNCHRONOUS
// DIRSYNC will force all directory updates within the file system to be done
// synchronously. This affects the following system calls: create, link,
// unlink, symlink, mkdir, rmdir, mknod and rename.
DIRSYNC = syscall.MS_DIRSYNC
// REMOUNT will attempt to remount an already-mounted file system. This is
// commonly used to change the mount flags for a file system, especially to
// make a readonly file system writeable. It does not change device or mount
// point.
REMOUNT = syscall.MS_REMOUNT
// MANDLOCK will force mandatory locks on a filesystem.
MANDLOCK = syscall.MS_MANDLOCK
// NOATIME will not update the file access time when reading from a file.
NOATIME = syscall.MS_NOATIME
// NODIRATIME will not update the directory access time.
NODIRATIME = syscall.MS_NODIRATIME
// BIND remounts a subtree somewhere else.
BIND = syscall.MS_BIND
// RBIND remounts a subtree and all possible submounts somewhere else.
RBIND = syscall.MS_BIND | syscall.MS_REC
// UNBINDABLE creates a mount which cannot be cloned through a bind operation.
UNBINDABLE = syscall.MS_UNBINDABLE
// RUNBINDABLE marks the entire mount tree as UNBINDABLE.
RUNBINDABLE = syscall.MS_UNBINDABLE | syscall.MS_REC
// PRIVATE creates a mount which carries no propagation abilities.
PRIVATE = syscall.MS_PRIVATE
// RPRIVATE marks the entire mount tree as PRIVATE.
RPRIVATE = syscall.MS_PRIVATE | syscall.MS_REC
// SLAVE creates a mount which receives propagation from its master, but not
// vice versa.
SLAVE = syscall.MS_SLAVE
// RSLAVE marks the entire mount tree as SLAVE.
RSLAVE = syscall.MS_SLAVE | syscall.MS_REC
// SHARED creates a mount which provides the ability to create mirrors of
// that mount such that mounts and unmounts within any of the mirrors
// propagate to the other mirrors.
SHARED = syscall.MS_SHARED
// RSHARED marks the entire mount tree as SHARED.
RSHARED = syscall.MS_SHARED | syscall.MS_REC
// RELATIME updates inode access times relative to modify or change time.
RELATIME = syscall.MS_RELATIME
// STRICTATIME allows to explicitly request full atime updates. This makes
// it possible for the kernel to default to relatime or noatime but still
// allow userspace to override it.
STRICTATIME = syscall.MS_STRICTATIME
)

View file

@ -1,30 +0,0 @@
// +build !linux,!freebsd freebsd,!cgo
package mount
// These flags are unsupported.
const (
BIND = 0
DIRSYNC = 0
MANDLOCK = 0
NOATIME = 0
NODEV = 0
NODIRATIME = 0
NOEXEC = 0
NOSUID = 0
UNBINDABLE = 0
RUNBINDABLE = 0
PRIVATE = 0
RPRIVATE = 0
SHARED = 0
RSHARED = 0
SLAVE = 0
RSLAVE = 0
RBIND = 0
RELATIME = 0
RELATIVE = 0
REMOUNT = 0
STRICTATIME = 0
SYNCHRONOUS = 0
RDONLY = 0
)

View file

@ -1,74 +0,0 @@
package mount
import (
"time"
)
// GetMounts retrieves a list of mounts for the current running process.
func GetMounts() ([]*Info, error) {
return parseMountTable()
}
// Mounted looks at /proc/self/mountinfo to determine of the specified
// mountpoint has been mounted
func Mounted(mountpoint string) (bool, error) {
entries, err := parseMountTable()
if err != nil {
return false, err
}
// Search the table for the mountpoint
for _, e := range entries {
if e.Mountpoint == mountpoint {
return true, nil
}
}
return false, nil
}
// Mount will mount filesystem according to the specified configuration, on the
// condition that the target path is *not* already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func Mount(device, target, mType, options string) error {
flag, _ := parseOptions(options)
if flag&REMOUNT != REMOUNT {
if mounted, err := Mounted(target); err != nil || mounted {
return err
}
}
return ForceMount(device, target, mType, options)
}
// ForceMount will mount a filesystem according to the specified configuration,
// *regardless* if the target path is not already mounted. Options must be
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
// flags.go for supported option flags.
func ForceMount(device, target, mType, options string) error {
flag, data := parseOptions(options)
if err := mount(device, target, mType, uintptr(flag), data); err != nil {
return err
}
return nil
}
// Unmount will unmount the target filesystem, so long as it is mounted.
func Unmount(target string) error {
if mounted, err := Mounted(target); err != nil || !mounted {
return err
}
return ForceUnmount(target)
}
// ForceUnmount will force an unmount of the target filesystem, regardless if
// it is mounted or not.
func ForceUnmount(target string) (err error) {
// Simple retry logic for unmount
for i := 0; i < 10; i++ {
if err = unmount(target, 0); err == nil {
return nil
}
time.Sleep(100 * time.Millisecond)
}
return
}

View file

@ -1,59 +0,0 @@
package mount
/*
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <sys/_iovec.h>
#include <sys/mount.h>
#include <sys/param.h>
*/
import "C"
import (
"fmt"
"strings"
"syscall"
"unsafe"
)
func allocateIOVecs(options []string) []C.struct_iovec {
out := make([]C.struct_iovec, len(options))
for i, option := range options {
out[i].iov_base = unsafe.Pointer(C.CString(option))
out[i].iov_len = C.size_t(len(option) + 1)
}
return out
}
func mount(device, target, mType string, flag uintptr, data string) error {
isNullFS := false
xs := strings.Split(data, ",")
for _, x := range xs {
if x == "bind" {
isNullFS = true
}
}
options := []string{"fspath", target}
if isNullFS {
options = append(options, "fstype", "nullfs", "target", device)
} else {
options = append(options, "fstype", mType, "from", device)
}
rawOptions := allocateIOVecs(options)
for _, rawOption := range rawOptions {
defer C.free(rawOption.iov_base)
}
if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
reason := C.GoString(C.strerror(*C.__error()))
return fmt.Errorf("Failed to call nmount: %s", reason)
}
return nil
}
func unmount(target string, flag int) error {
return syscall.Unmount(target, flag)
}

View file

@ -1,21 +0,0 @@
package mount
import (
"syscall"
)
func mount(device, target, mType string, flag uintptr, data string) error {
if err := syscall.Mount(device, target, mType, flag, data); err != nil {
return err
}
// If we have a bind mount or remount, remount...
if flag&syscall.MS_BIND == syscall.MS_BIND && flag&syscall.MS_RDONLY == syscall.MS_RDONLY {
return syscall.Mount(device, target, mType, flag|syscall.MS_REMOUNT, data)
}
return nil
}
func unmount(target string, flag int) error {
return syscall.Unmount(target, flag)
}

View file

@ -1,11 +0,0 @@
// +build !linux,!freebsd freebsd,!cgo
package mount
func mount(device, target, mType string, flag uintptr, data string) error {
panic("Not implemented")
}
func unmount(target string, flag int) error {
panic("Not implemented")
}

View file

@ -1,40 +0,0 @@
package mount
// Info reveals information about a particular mounted filesystem. This
// struct is populated from the content in the /proc/<pid>/mountinfo file.
type Info struct {
// ID is a unique identifier of the mount (may be reused after umount).
ID int
// Parent indicates the ID of the mount parent (or of self for the top of the
// mount tree).
Parent int
// Major indicates one half of the device ID which identifies the device class.
Major int
// Minor indicates one half of the device ID which identifies a specific
// instance of device.
Minor int
// Root of the mount within the filesystem.
Root string
// Mountpoint indicates the mount point relative to the process's root.
Mountpoint string
// Opts represents mount-specific options.
Opts string
// Optional represents optional fields.
Optional string
// Fstype indicates the type of filesystem, such as EXT3.
Fstype string
// Source indicates filesystem specific information or "none".
Source string
// VfsOpts represents per super block options.
VfsOpts string
}

View file

@ -1,41 +0,0 @@
package mount
/*
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/mount.h>
*/
import "C"
import (
"fmt"
"reflect"
"unsafe"
)
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
// bind mounts.
func parseMountTable() ([]*Info, error) {
var rawEntries *C.struct_statfs
count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
if count == 0 {
return nil, fmt.Errorf("Failed to call getmntinfo")
}
var entries []C.struct_statfs
header := (*reflect.SliceHeader)(unsafe.Pointer(&entries))
header.Cap = count
header.Len = count
header.Data = uintptr(unsafe.Pointer(rawEntries))
var out []*Info
for _, entry := range entries {
var mountinfo Info
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
out = append(out, &mountinfo)
}
return out, nil
}

View file

@ -1,95 +0,0 @@
// +build linux
package mount
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
const (
/* 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
(1) mount ID: unique identifier of the mount (may be reused after umount)
(2) parent ID: ID of parent (or of self for the top of the mount tree)
(3) major:minor: value of st_dev for files on filesystem
(4) root: root of the mount within the filesystem
(5) mount point: mount point relative to the process's root
(6) mount options: per mount options
(7) optional fields: zero or more fields of the form "tag[:value]"
(8) separator: marks the end of the optional fields
(9) filesystem type: name of filesystem of the form "type[.subtype]"
(10) mount source: filesystem specific information or "none"
(11) super options: per super block options*/
mountinfoFormat = "%d %d %d:%d %s %s %s %s"
)
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
// bind mounts
func parseMountTable() ([]*Info, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return nil, err
}
defer f.Close()
return parseInfoFile(f)
}
func parseInfoFile(r io.Reader) ([]*Info, error) {
var (
s = bufio.NewScanner(r)
out = []*Info{}
)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
var (
p = &Info{}
text = s.Text()
optionalFields string
)
if _, err := fmt.Sscanf(text, mountinfoFormat,
&p.ID, &p.Parent, &p.Major, &p.Minor,
&p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil {
return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err)
}
// Safe as mountinfo encodes mountpoints with spaces as \040.
index := strings.Index(text, " - ")
postSeparatorFields := strings.Fields(text[index+3:])
if len(postSeparatorFields) < 3 {
return nil, fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
}
if optionalFields != "-" {
p.Optional = optionalFields
}
p.Fstype = postSeparatorFields[0]
p.Source = postSeparatorFields[1]
p.VfsOpts = strings.Join(postSeparatorFields[2:], " ")
out = append(out, p)
}
return out, nil
}
// PidMountInfo collects the mounts for a specific process ID. If the process
// ID is unknown, it is better to use `GetMounts` which will inspect
// "/proc/self/mountinfo" instead.
func PidMountInfo(pid int) ([]*Info, error) {
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
if err != nil {
return nil, err
}
defer f.Close()
return parseInfoFile(f)
}

View file

@ -1,12 +0,0 @@
// +build !windows,!linux,!freebsd freebsd,!cgo
package mount
import (
"fmt"
"runtime"
)
func parseMountTable() ([]*Info, error) {
return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
}

View file

@ -1,6 +0,0 @@
package mount
func parseMountTable() ([]*Info, error) {
// Do NOT return an error!
return nil, nil
}

View file

@ -1,69 +0,0 @@
// +build linux
package mount
// MakeShared ensures a mounted filesystem has the SHARED mount option enabled.
// See the supported options in flags.go for further reference.
func MakeShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "shared")
}
// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled.
// See the supported options in flags.go for further reference.
func MakeRShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "rshared")
}
// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled.
// See the supported options in flags.go for further reference.
func MakePrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "private")
}
// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option
// enabled. See the supported options in flags.go for further reference.
func MakeRPrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "rprivate")
}
// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled.
// See the supported options in flags.go for further reference.
func MakeSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "slave")
}
// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled.
// See the supported options in flags.go for further reference.
func MakeRSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "rslave")
}
// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option
// enabled. See the supported options in flags.go for further reference.
func MakeUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "unbindable")
}
// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount
// option enabled. See the supported options in flags.go for further reference.
func MakeRUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "runbindable")
}
func ensureMountedAs(mountPoint, options string) error {
mounted, err := Mounted(mountPoint)
if err != nil {
return err
}
if !mounted {
if err := Mount(mountPoint, mountPoint, "none", "bind,rw"); err != nil {
return err
}
}
if _, err = Mounted(mountPoint); err != nil {
return err
}
return ForceMount("", mountPoint, "none", options)
}

View file

@ -1,6 +0,0 @@
Package symlink implements EvalSymlinksInScope which is an extension of filepath.EvalSymlinks,
as well as a Windows long-path aware version of filepath.EvalSymlinks
from the [Go standard library](https://golang.org/pkg/path/filepath).
The code from filepath.EvalSymlinks has been adapted in fs.go.
Please read the LICENSE.BSD file that governs fs.go and LICENSE.APACHE for fs_test.go.

View file

@ -1,143 +0,0 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.BSD file.
// This code is a modified version of path/filepath/symlink.go from the Go standard library.
package symlink
import (
"bytes"
"errors"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/pkg/system"
)
// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an
// absolute path. This function handles paths in a platform-agnostic manner.
func FollowSymlinkInScope(path, root string) (string, error) {
path, err := filepath.Abs(filepath.FromSlash(path))
if err != nil {
return "", err
}
root, err = filepath.Abs(filepath.FromSlash(root))
if err != nil {
return "", err
}
return evalSymlinksInScope(path, root)
}
// evalSymlinksInScope will evaluate symlinks in `path` within a scope `root` and return
// a result guaranteed to be contained within the scope `root`, at the time of the call.
// Symlinks in `root` are not evaluated and left as-is.
// Errors encountered while attempting to evaluate symlinks in path will be returned.
// Non-existing paths are valid and do not constitute an error.
// `path` has to contain `root` as a prefix, or else an error will be returned.
// Trying to break out from `root` does not constitute an error.
//
// Example:
// If /foo/bar -> /outside,
// FollowSymlinkInScope("/foo/bar", "/foo") == "/foo/outside" instead of "/oustide"
//
// IMPORTANT: it is the caller's responsibility to call evalSymlinksInScope *after* relevant symlinks
// are created and not to create subsequently, additional symlinks that could potentially make a
// previously-safe path, unsafe. Example: if /foo/bar does not exist, evalSymlinksInScope("/foo/bar", "/foo")
// would return "/foo/bar". If one makes /foo/bar a symlink to /baz subsequently, then "/foo/bar" should
// no longer be considered safely contained in "/foo".
func evalSymlinksInScope(path, root string) (string, error) {
root = filepath.Clean(root)
if path == root {
return path, nil
}
if !strings.HasPrefix(path, root) {
return "", errors.New("evalSymlinksInScope: " + path + " is not in " + root)
}
const maxIter = 255
originalPath := path
// given root of "/a" and path of "/a/b/../../c" we want path to be "/b/../../c"
path = path[len(root):]
if root == string(filepath.Separator) {
path = string(filepath.Separator) + path
}
if !strings.HasPrefix(path, string(filepath.Separator)) {
return "", errors.New("evalSymlinksInScope: " + path + " is not in " + root)
}
path = filepath.Clean(path)
// consume path by taking each frontmost path element,
// expanding it if it's a symlink, and appending it to b
var b bytes.Buffer
// b here will always be considered to be the "current absolute path inside
// root" when we append paths to it, we also append a slash and use
// filepath.Clean after the loop to trim the trailing slash
for n := 0; path != ""; n++ {
if n > maxIter {
return "", errors.New("evalSymlinksInScope: too many links in " + originalPath)
}
// find next path component, p
i := strings.IndexRune(path, filepath.Separator)
var p string
if i == -1 {
p, path = path, ""
} else {
p, path = path[:i], path[i+1:]
}
if p == "" {
continue
}
// this takes a b.String() like "b/../" and a p like "c" and turns it
// into "/b/../c" which then gets filepath.Cleaned into "/c" and then
// root gets prepended and we Clean again (to remove any trailing slash
// if the first Clean gave us just "/")
cleanP := filepath.Clean(string(filepath.Separator) + b.String() + p)
if cleanP == string(filepath.Separator) {
// never Lstat "/" itself
b.Reset()
continue
}
fullP := filepath.Clean(root + cleanP)
fi, err := os.Lstat(fullP)
if os.IsNotExist(err) {
// if p does not exist, accept it
b.WriteString(p)
b.WriteRune(filepath.Separator)
continue
}
if err != nil {
return "", err
}
if fi.Mode()&os.ModeSymlink == 0 {
b.WriteString(p + string(filepath.Separator))
continue
}
// it's a symlink, put it at the front of path
dest, err := os.Readlink(fullP)
if err != nil {
return "", err
}
if system.IsAbs(dest) {
b.Reset()
}
path = dest + string(filepath.Separator) + path
}
// see note above on "fullP := ..." for why this is double-cleaned and
// what's happening here
return filepath.Clean(root + filepath.Clean(string(filepath.Separator)+b.String())), nil
}
// EvalSymlinks returns the path name after the evaluation of any symbolic
// links.
// If path is relative the result will be relative to the current directory,
// unless one of the components is an absolute symbolic link.
// This version has been updated to support long paths prepended with `\\?\`.
func EvalSymlinks(path string) (string, error) {
return evalSymlinks(path)
}

View file

@ -1,11 +0,0 @@
// +build !windows
package symlink
import (
"path/filepath"
)
func evalSymlinks(path string) (string, error) {
return filepath.EvalSymlinks(path)
}

View file

@ -1,155 +0,0 @@
package symlink
import (
"bytes"
"errors"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/docker/docker/pkg/longpath"
)
func toShort(path string) (string, error) {
p, err := syscall.UTF16FromString(path)
if err != nil {
return "", err
}
b := p // GetShortPathName says we can reuse buffer
n, err := syscall.GetShortPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
if n > uint32(len(b)) {
b = make([]uint16, n)
if _, err = syscall.GetShortPathName(&p[0], &b[0], uint32(len(b))); err != nil {
return "", err
}
}
return syscall.UTF16ToString(b), nil
}
func toLong(path string) (string, error) {
p, err := syscall.UTF16FromString(path)
if err != nil {
return "", err
}
b := p // GetLongPathName says we can reuse buffer
n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
if n > uint32(len(b)) {
b = make([]uint16, n)
n, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
if err != nil {
return "", err
}
}
b = b[:n]
return syscall.UTF16ToString(b), nil
}
func evalSymlinks(path string) (string, error) {
path, err := walkSymlinks(path)
if err != nil {
return "", err
}
p, err := toShort(path)
if err != nil {
return "", err
}
p, err = toLong(p)
if err != nil {
return "", err
}
// syscall.GetLongPathName does not change the case of the drive letter,
// but the result of EvalSymlinks must be unique, so we have
// EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`).
// Make drive letter upper case.
if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' {
p = string(p[0]+'A'-'a') + p[1:]
} else if len(p) >= 6 && p[5] == ':' && 'a' <= p[4] && p[4] <= 'z' {
p = p[:3] + string(p[4]+'A'-'a') + p[5:]
}
return filepath.Clean(p), nil
}
const utf8RuneSelf = 0x80
func walkSymlinks(path string) (string, error) {
const maxIter = 255
originalPath := path
// consume path by taking each frontmost path element,
// expanding it if it's a symlink, and appending it to b
var b bytes.Buffer
for n := 0; path != ""; n++ {
if n > maxIter {
return "", errors.New("EvalSymlinks: too many links in " + originalPath)
}
// A path beginning with `\\?\` represents the root, so automatically
// skip that part and begin processing the next segment.
if strings.HasPrefix(path, longpath.Prefix) {
b.WriteString(longpath.Prefix)
path = path[4:]
continue
}
// find next path component, p
var i = -1
for j, c := range path {
if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) {
i = j
break
}
}
var p string
if i == -1 {
p, path = path, ""
} else {
p, path = path[:i], path[i+1:]
}
if p == "" {
if b.Len() == 0 {
// must be absolute path
b.WriteRune(filepath.Separator)
}
continue
}
// If this is the first segment after the long path prefix, accept the
// current segment as a volume root or UNC share and move on to the next.
if b.String() == longpath.Prefix {
b.WriteString(p)
b.WriteRune(filepath.Separator)
continue
}
fi, err := os.Lstat(b.String() + p)
if err != nil {
return "", err
}
if fi.Mode()&os.ModeSymlink == 0 {
b.WriteString(p)
if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') {
b.WriteRune(filepath.Separator)
}
continue
}
// it's a symlink, put it at the front of path
dest, err := os.Readlink(b.String() + p)
if err != nil {
return "", err
}
if filepath.IsAbs(dest) || os.IsPathSeparator(dest[0]) {
b.Reset()
}
path = dest + string(filepath.Separator) + path
}
return filepath.Clean(b.String()), nil
}

View file

@ -1,67 +0,0 @@
# Contributing to go-units
Want to hack on go-units? Awesome! Here are instructions to get you started.
go-units is a part of the [Docker](https://www.docker.com) project, and follows
the same rules and principles. If you're already familiar with the way
Docker does things, you'll feel right at home.
Otherwise, go read Docker's
[contributions guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md),
[issue triaging](https://github.com/docker/docker/blob/master/project/ISSUE-TRIAGE.md),
[review process](https://github.com/docker/docker/blob/master/project/REVIEWING.md) and
[branches and tags](https://github.com/docker/docker/blob/master/project/BRANCHES-AND-TAGS.md).
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch 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 (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
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:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.

View file

@ -1,27 +0,0 @@
# go-connections maintainers file
#
# This file describes who runs the docker/go-connections project and how.
# This is a living document - if you see something out of date or missing, speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant parser.
#
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
people = [
"calavera",
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.calavera]
Name = "David Calavera"
Email = "david.calavera@gmail.com"
GitHub = "calavera"

View file

@ -1,18 +0,0 @@
[![GoDoc](https://godoc.org/github.com/docker/go-units?status.svg)](https://godoc.org/github.com/docker/go-units)
# Introduction
go-units is a library to transform human friendly measurements into machine friendly values.
## Usage
See the [docs in godoc](https://godoc.org/github.com/docker/go-units) for examples and documentation.
## Copyright and license
Copyright © 2015 Docker, Inc. All rights reserved, except as follows. Code
is released under the Apache 2.0 license. The README.md file, and files in the
"docs" folder are licensed under the Creative Commons Attribution 4.0
International License under the terms and conditions set forth in the file
"LICENSE.docs". You may obtain a duplicate copy of the same license, titled
CC-BY-SA-4.0, at http://creativecommons.org/licenses/by/4.0/.

View file

@ -1,11 +0,0 @@
dependencies:
post:
# install golint
- go get github.com/golang/lint/golint
test:
pre:
# run analysis before tests
- go vet ./...
- test -z "$(golint ./... | tee /dev/stderr)"
- test -z "$(gofmt -s -l . | tee /dev/stderr)"

View file

@ -1,33 +0,0 @@
// Package units provides helper function to parse and print size and time units
// in human-readable format.
package units
import (
"fmt"
"time"
)
// HumanDuration returns a human-readable approximation of a duration
// (eg. "About a minute", "4 hours ago", etc.).
func HumanDuration(d time.Duration) string {
if seconds := int(d.Seconds()); seconds < 1 {
return "Less than a second"
} else if seconds < 60 {
return fmt.Sprintf("%d seconds", seconds)
} else if minutes := int(d.Minutes()); minutes == 1 {
return "About a minute"
} else if minutes < 60 {
return fmt.Sprintf("%d minutes", minutes)
} else if hours := int(d.Hours()); hours == 1 {
return "About an hour"
} else if hours < 48 {
return fmt.Sprintf("%d hours", hours)
} else if hours < 24*7*2 {
return fmt.Sprintf("%d days", hours/24)
} else if hours < 24*30*3 {
return fmt.Sprintf("%d weeks", hours/24/7)
} else if hours < 24*365*2 {
return fmt.Sprintf("%d months", hours/24/30)
}
return fmt.Sprintf("%d years", int(d.Hours())/24/365)
}

View file

@ -1,95 +0,0 @@
package units
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// See: http://en.wikipedia.org/wiki/Binary_prefix
const (
// Decimal
KB = 1000
MB = 1000 * KB
GB = 1000 * MB
TB = 1000 * GB
PB = 1000 * TB
// Binary
KiB = 1024
MiB = 1024 * KiB
GiB = 1024 * MiB
TiB = 1024 * GiB
PiB = 1024 * TiB
)
type unitMap map[string]int64
var (
decimalMap = unitMap{"k": KB, "m": MB, "g": GB, "t": TB, "p": PB}
binaryMap = unitMap{"k": KiB, "m": MiB, "g": GiB, "t": TiB, "p": PiB}
sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)*) ?([kKmMgGtTpP])?[bB]?$`)
)
var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
// CustomSize returns a human-readable approximation of a size
// using custom format.
func CustomSize(format string, size float64, base float64, _map []string) string {
i := 0
for size >= base {
size = size / base
i++
}
return fmt.Sprintf(format, size, _map[i])
}
// HumanSize returns a human-readable approximation of a size
// capped at 4 valid numbers (eg. "2.746 MB", "796 KB").
func HumanSize(size float64) string {
return CustomSize("%.4g %s", size, 1000.0, decimapAbbrs)
}
// BytesSize returns a human-readable size in bytes, kibibytes,
// mebibytes, gibibytes, or tebibytes (eg. "44kiB", "17MiB").
func BytesSize(size float64) string {
return CustomSize("%.4g %s", size, 1024.0, binaryAbbrs)
}
// FromHumanSize returns an integer from a human-readable specification of a
// size using SI standard (eg. "44kB", "17MB").
func FromHumanSize(size string) (int64, error) {
return parseSize(size, decimalMap)
}
// RAMInBytes parses a human-readable string representing an amount of RAM
// in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
// returns the number of bytes, or -1 if the string is unparseable.
// Units are case-insensitive, and the 'b' suffix is optional.
func RAMInBytes(size string) (int64, error) {
return parseSize(size, binaryMap)
}
// Parses the human-readable size string into the amount it represents.
func parseSize(sizeStr string, uMap unitMap) (int64, error) {
matches := sizeRegex.FindStringSubmatch(sizeStr)
if len(matches) != 4 {
return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
}
size, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return -1, err
}
unitPrefix := strings.ToLower(matches[3])
if mul, ok := uMap[unitPrefix]; ok {
size *= float64(mul)
}
return int64(size), nil
}

View file

@ -1,118 +0,0 @@
package units
import (
"fmt"
"strconv"
"strings"
)
// Ulimit is a human friendly version of Rlimit.
type Ulimit struct {
Name string
Hard int64
Soft int64
}
// Rlimit specifies the resource limits, such as max open files.
type Rlimit struct {
Type int `json:"type,omitempty"`
Hard uint64 `json:"hard,omitempty"`
Soft uint64 `json:"soft,omitempty"`
}
const (
// magic numbers for making the syscall
// some of these are defined in the syscall package, but not all.
// Also since Windows client doesn't get access to the syscall package, need to
// define these here
rlimitAs = 9
rlimitCore = 4
rlimitCPU = 0
rlimitData = 2
rlimitFsize = 1
rlimitLocks = 10
rlimitMemlock = 8
rlimitMsgqueue = 12
rlimitNice = 13
rlimitNofile = 7
rlimitNproc = 6
rlimitRss = 5
rlimitRtprio = 14
rlimitRttime = 15
rlimitSigpending = 11
rlimitStack = 3
)
var ulimitNameMapping = map[string]int{
//"as": rlimitAs, // Disabled since this doesn't seem usable with the way Docker inits a container.
"core": rlimitCore,
"cpu": rlimitCPU,
"data": rlimitData,
"fsize": rlimitFsize,
"locks": rlimitLocks,
"memlock": rlimitMemlock,
"msgqueue": rlimitMsgqueue,
"nice": rlimitNice,
"nofile": rlimitNofile,
"nproc": rlimitNproc,
"rss": rlimitRss,
"rtprio": rlimitRtprio,
"rttime": rlimitRttime,
"sigpending": rlimitSigpending,
"stack": rlimitStack,
}
// ParseUlimit parses and returns a Ulimit from the specified string.
func ParseUlimit(val string) (*Ulimit, error) {
parts := strings.SplitN(val, "=", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid ulimit argument: %s", val)
}
if _, exists := ulimitNameMapping[parts[0]]; !exists {
return nil, fmt.Errorf("invalid ulimit type: %s", parts[0])
}
var (
soft int64
hard = &soft // default to soft in case no hard was set
temp int64
err error
)
switch limitVals := strings.Split(parts[1], ":"); len(limitVals) {
case 2:
temp, err = strconv.ParseInt(limitVals[1], 10, 64)
if err != nil {
return nil, err
}
hard = &temp
fallthrough
case 1:
soft, err = strconv.ParseInt(limitVals[0], 10, 64)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("too many limit value arguments - %s, can only have up to two, `soft[:hard]`", parts[1])
}
if soft > *hard {
return nil, fmt.Errorf("ulimit soft limit must be less than or equal to hard limit: %d > %d", soft, *hard)
}
return &Ulimit{Name: parts[0], Soft: soft, Hard: *hard}, nil
}
// GetRlimit returns the RLimit corresponding to Ulimit.
func (u *Ulimit) GetRlimit() (*Rlimit, error) {
t, exists := ulimitNameMapping[u.Name]
if !exists {
return nil, fmt.Errorf("invalid ulimit name %s", u.Name)
}
return &Rlimit{Type: t, Soft: uint64(u.Soft), Hard: uint64(u.Hard)}, nil
}
func (u *Ulimit) String() string {
return fmt.Sprintf("%s=%d:%d", u.Name, u.Soft, u.Hard)
}

View file

@ -1,50 +0,0 @@
# How to Contribute
## Getting Started
- Fork the repository on GitHub
- Read the [README](README.markdown) for build and test instructions
- Play with the project, submit bugs, submit patches!
## Contribution Flow
This is a rough outline of what a contributor's workflow looks like:
- Create a topic branch from where you want to base your work (usually master).
- Make commits of logical units.
- Make sure your commit messages are in the proper format (see below).
- Push your changes to a topic branch in your fork of the repository.
- Make sure the tests pass, and add any new tests as appropriate.
- Submit a pull request to the original repository.
Thanks for your contributions!
### Format of the Commit Message
We follow a rough convention for commit messages that is designed to answer two
questions: what changed and why. The subject line should feature the what and
the body of the commit should describe the why.
```
scripts: add the test-cluster command
this uses tmux to setup a test cluster that you can easily kill and
start for debugging.
Fixes #38
```
The format can be described more formally as follows:
```
<subsystem>: <what changed>
<BLANK LINE>
<why this change was made>
<BLANK LINE>
<footer>
```
The first line is the subject and should be no longer than 70 characters, the
second line is always blank, and other lines should be wrapped at 80 characters.
This allows the message to be easier to read on GitHub as well as in various
git tools.

View file

@ -1,2 +0,0 @@
Brandon Philips <brandon@ifup.org> (@philips)
Brian Waldon <brian@waldon.cc> (@bcwaldon)

View file

@ -1,41 +0,0 @@
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.
#### Projects using godbus
- [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library.
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.

View file

@ -1,253 +0,0 @@
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
}

View file

@ -1,26 +0,0 @@
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
}

View file

@ -1,102 +0,0 @@
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
}

View file

@ -1,36 +0,0 @@
package dbus
import (
"errors"
)
// 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...)
}

View file

@ -1,636 +0,0 @@
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 BusObject
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]exportedObj
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]exportedObj)
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() BusObject {
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()
if conn.closed {
// inWorker calls Close on read error, the read error may
// be caused by another caller calling Close to shutdown the
// dbus connection, a double-close scenario we prevent here.
conn.outLck.Unlock()
return nil
}
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" && sender == "org.freedesktop.DBus" {
if member == "NameLost" {
// If we lost the name on the bus, remove it from our
// tracking list.
name, ok := msg.Body[0].(string)
if !ok {
panic("Unable to read the lost name")
}
conn.namesLck.Lock()
for i, v := range conn.names {
if v == name {
conn.names = append(conn.names[:i],
conn.names[i+1:]...)
}
}
conn.namesLck.Unlock()
} else if member == "NameAcquired" {
// If we acquired the name on the bus, add it to our
// tracking list.
name, ok := msg.Body[0].(string)
if !ok {
panic("Unable to read the acquired name")
}
conn.namesLck.Lock()
conn.names = append(conn.names, name)
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 {
ch <- signal
}
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) BusObject {
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.
//
// 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()
}
// RemoveSignal removes the given channel from the list of the registered channels.
func (conn *Conn) RemoveSignal(ch chan<- *Signal) {
conn.signalsLck.Lock()
for i := len(conn.signals) - 1; i >= 0; i-- {
if ch == conn.signals[i] {
copy(conn.signals[i:], conn.signals[i+1:])
conn.signals[len(conn.signals)-1] = nil
conn.signals = conn.signals[:len(conn.signals)-1]
}
}
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 NewError(name string, body []interface{}) *Error {
return &Error{name, body}
}
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
}
var (
transports = make(map[string]func(string) (transport, error))
)
func getTransport(address string) (transport, error) {
var err error
var t transport
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 := transports[v[:i]]
if f == nil {
err = errors.New("dbus: invalid bus address (invalid or unsupported transport)")
continue
}
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]
}

View file

@ -1,21 +0,0 @@
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]))
}

View file

@ -1,27 +0,0 @@
// +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]))
}

View file

@ -1,258 +0,0 @@
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 == '_'
}

View file

@ -1,228 +0,0 @@
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)
}

View file

@ -1,63 +0,0 @@
/*
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

View file

@ -1,208 +0,0 @@
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 {
return newEncoderAtOffset(out, 0, order)
}
// newEncoderAtOffset returns a new encoder that writes to out in the given
// byte order. Specify the offset to initialize pos for proper alignment
// computation.
func newEncoderAtOffset(out io.Writer, offset int, order binary.ByteOrder) *encoder {
enc := new(encoder)
enc.out = out
enc.order = order
enc.pos = offset
return enc
}
// Aligns the next output to be on a multiple of n. Panics on write errors.
func (enc *encoder) align(n int) {
pad := enc.padding(0, n)
if pad > 0 {
empty := make([]byte, pad)
if _, err := enc.out.Write(empty); err != nil {
panic(err)
}
enc.pos += pad
}
}
// pad returns the number of bytes of padding, based on current position and additional offset.
// and alignment.
func (enc *encoder) padding(offset, algn int) int {
abs := enc.pos + offset
if abs%algn != 0 {
newabs := (abs + algn - 1) & ^(algn - 1)
return newabs - abs
}
return 0
}
// 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"))
}
// Lookahead offset: 4 bytes for uint32 length (with alignment),
// plus alignment for elements.
n := enc.padding(0, 4) + 4
offset := enc.pos + n + enc.padding(n, alignment(v.Type().Elem()))
var buf bytes.Buffer
bufenc := newEncoderAtOffset(&buf, offset, 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()
// Lookahead offset: 4 bytes for uint32 length (with alignment),
// plus 8-byte alignment
n := enc.padding(0, 4) + 4
offset := enc.pos + n + enc.padding(n, 8)
var buf bytes.Buffer
bufenc := newEncoderAtOffset(&buf, offset, 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()})
}
}

View file

@ -1,468 +0,0 @@
package dbus
import (
"bytes"
"errors"
"fmt"
"reflect"
"strings"
)
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"},
}
)
// exportedObj represents an exported object. It stores a precomputed
// method table that represents the methods exported on the bus.
type exportedObj struct {
methods map[string]reflect.Value
// Whether or not this export is for the entire subtree
includeSubtree bool
}
func (obj exportedObj) Method(name string) (reflect.Value, bool) {
out, exists := obj.methods[name]
return out, exists
}
// Sender is a type which can be used in exported methods to receive the message
// sender.
type Sender string
func computeMethodName(name string, mapping map[string]string) string {
newname, ok := mapping[name]
if ok {
name = newname
}
return name
}
func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Value {
if in == nil {
return nil
}
methods := make(map[string]reflect.Value)
val := reflect.ValueOf(in)
typ := val.Type()
for i := 0; i < typ.NumMethod(); i++ {
methtype := typ.Method(i)
method := val.Method(i)
t := method.Type()
// only track valid methods must return *Error as last arg
// and must be exported
if t.NumOut() == 0 ||
t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) ||
methtype.PkgPath != "" {
continue
}
// map names while building table
methods[computeMethodName(methtype.Name, mapping)] = method
}
return methods
}
// searchHandlers will look through all registered handlers looking for one
// to handle the given path. If a verbatim one isn't found, it will check for
// a subtree registration for the path as well.
func (conn *Conn) searchHandlers(path ObjectPath) (map[string]exportedObj, bool) {
conn.handlersLck.RLock()
defer conn.handlersLck.RUnlock()
handlers, ok := conn.handlers[path]
if ok {
return handlers, ok
}
// If handlers weren't found for this exact path, look for a matching subtree
// registration
handlers = make(map[string]exportedObj)
path = path[:strings.LastIndex(string(path), "/")]
for len(path) > 0 {
var subtreeHandlers map[string]exportedObj
subtreeHandlers, ok = conn.handlers[path]
if ok {
for iface, handler := range subtreeHandlers {
// Only include this handler if it registered for the subtree
if handler.includeSubtree {
handlers[iface] = handler
}
}
break
}
path = path[:strings.LastIndex(string(path), "/")]
}
return handlers, ok
}
// 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
} else if ifaceName == "org.freedesktop.DBus.Introspectable" && name == "Introspect" {
if _, ok := conn.handlers[path]; !ok {
subpath := make(map[string]struct{})
var xml bytes.Buffer
xml.WriteString("<node>")
for h, _ := range conn.handlers {
p := string(path)
if p != "/" {
p += "/"
}
if strings.HasPrefix(string(h), p) {
node_name := strings.Split(string(h[len(p):]), "/")[0]
subpath[node_name] = struct{}{}
}
}
for s, _ := range subpath {
xml.WriteString("\n\t<node name=\"" + s + "\"/>")
}
xml.WriteString("\n</node>")
conn.sendReply(sender, serial, xml.String())
return
}
}
if len(name) == 0 {
conn.sendError(errmsgUnknownMethod, sender, serial)
}
// Find the exported handler (if any) for this path
handlers, ok := conn.searchHandlers(path)
if !ok {
conn.sendError(errmsgNoObject, sender, serial)
return
}
var m reflect.Value
var exists bool
if hasIface {
iface := handlers[ifaceName]
m, exists = iface.Method(name)
} else {
for _, v := range handlers {
m, exists = v.Method(name)
if exists {
break
}
}
}
if !exists {
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 if tp == reflect.TypeOf((*Message)(nil)).Elem() {
val.Elem().Set(reflect.ValueOf(*msg))
} 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
}
// Extract parameters
params := make([]reflect.Value, len(pointers))
for i := 0; i < len(pointers); i++ {
params[i] = reflect.ValueOf(pointers[i]).Elem()
}
// Call method
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).
//
// Similarly, any parameters with the type Message are set to the raw message
// received on the bus. Again, parameters of this type do not contribute to the
// dbus signature of the method.
//
// 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 {
return conn.ExportWithMap(v, nil, path, iface)
}
// ExportWithMap works exactly like Export but provides the ability to remap
// method names (e.g. export a lower-case method).
//
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.export(getMethods(v, mapping), path, iface, false)
}
// ExportSubtree works exactly like Export but registers the given value for
// an entire subtree rather under the root path provided.
//
// In order to make this useful, one parameter in each of the value's exported
// methods should be a Message, in which case it will contain the raw message
// (allowing one to get access to the path that caused the method to be called).
//
// Note that more specific export paths take precedence over less specific. For
// example, a method call using the ObjectPath /foo/bar/baz will call a method
// exported on /foo/bar before a method exported on /foo.
func (conn *Conn) ExportSubtree(v interface{}, path ObjectPath, iface string) error {
return conn.ExportSubtreeWithMap(v, nil, path, iface)
}
// ExportSubtreeWithMap works exactly like ExportSubtree but provides the
// ability to remap method names (e.g. export a lower-case method).
//
// The keys in the map are the real method names (exported on the struct), and
// the values are the method names to be exported on DBus.
func (conn *Conn) ExportSubtreeWithMap(v interface{}, mapping map[string]string, path ObjectPath, iface string) error {
return conn.export(getMethods(v, mapping), path, iface, true)
}
// ExportMethodTable like Export registers the given methods as an object
// on the message bus. Unlike Export the it uses a method table to define
// the object instead of a native go object.
//
// The method table is a map from method name to function closure
// representing the method. This allows an object exported on the bus to not
// necessarily be a native go object. It can be useful for generating exposed
// methods on the fly.
//
// Any non-function objects in the method table are ignored.
func (conn *Conn) ExportMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
return conn.exportMethodTable(methods, path, iface, false)
}
// Like ExportSubtree, but with the same caveats as ExportMethodTable.
func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path ObjectPath, iface string) error {
return conn.exportMethodTable(methods, path, iface, true)
}
func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error {
out := make(map[string]reflect.Value)
for name, method := range methods {
rval := reflect.ValueOf(method)
if rval.Kind() != reflect.Func {
continue
}
t := rval.Type()
// only track valid methods must return *Error as last arg
if t.NumOut() == 0 ||
t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) {
continue
}
out[name] = rval
}
return conn.export(out, path, iface, includeSubtree)
}
// exportWithMap is the worker function for all exports/registrations.
func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error {
if !path.IsValid() {
return fmt.Errorf(`dbus: Invalid path name: "%s"`, path)
}
conn.handlersLck.Lock()
defer conn.handlersLck.Unlock()
// Remove a previous export if the interface is nil
if methods == 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 this is the first handler for this path, make a new map to hold all
// handlers for this path.
if _, ok := conn.handlers[path]; !ok {
conn.handlers[path] = make(map[string]exportedObj)
}
// Finally, save this handler
conn.handlers[path][iface] = exportedObj{
methods: methods,
includeSubtree: includeSubtree,
}
return nil
}
// ReleaseName calls org.freedesktop.DBus.ReleaseName and awaits a response.
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
}
return ReleaseNameReply(r), nil
}
// RequestName calls org.freedesktop.DBus.RequestName and awaits a response.
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
}
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
)

View file

@ -1,28 +0,0 @@
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
}

View file

@ -1,15 +0,0 @@
// +build !static_build
package dbus
import (
"os/user"
)
func lookupHomeDir() string {
u, err := user.Current()
if err != nil {
return "/"
}
return u.HomeDir
}

View file

@ -1,45 +0,0 @@
// +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 "/"
}

View file

@ -1,346 +0,0 @@
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 "<invalid>"
}
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
}

View file

@ -1,136 +0,0 @@
package dbus
import (
"errors"
"strings"
)
// BusObject is the interface of a remote object on which methods can be
// invoked.
type BusObject interface {
Call(method string, flags Flags, args ...interface{}) *Call
Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call
GetProperty(p string) (Variant, error)
Destination() string
Path() ObjectPath
}
// 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
}
// AddMatchSignal subscribes BusObject to signals from specified interface and
// method (member).
func (o *Object) AddMatchSignal(iface, member string) *Call {
return o.Call(
"org.freedesktop.DBus.AddMatch",
0,
"type='signal',interface='"+iface+"',member='"+member+"'",
)
}
// 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}
}
// 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
}
// 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
}

View file

@ -1,257 +0,0 @@
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
}

View file

@ -1,6 +0,0 @@
package dbus
func (t *unixTransport) SendNullByte() error {
_, err := t.Write([]byte{0})
return err
}

View file

@ -1,35 +0,0 @@
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)
}

View file

@ -1,196 +0,0 @@
//+build !windows,!solaris
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 init() {
transports["unix"] = newUnixTransport
}
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
}

View file

@ -1,95 +0,0 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
// Local implementation of the UnixCredentials system call for DragonFly BSD
package dbus
/*
#include <sys/ucred.h>
*/
import "C"
import (
"io"
"os"
"syscall"
"unsafe"
)
// http://golang.org/src/pkg/syscall/ztypes_linux_amd64.go
// http://golang.org/src/pkg/syscall/ztypes_dragonfly_amd64.go
type Ucred struct {
Pid int32
Uid uint32
Gid uint32
}
// http://golang.org/src/pkg/syscall/types_linux.go
// http://golang.org/src/pkg/syscall/types_dragonfly.go
// https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/ucred.h
const (
SizeofUcred = C.sizeof_struct_ucred
)
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgAlignOf(salen int) int {
// From http://golang.org/src/pkg/syscall/sockcmsg_unix.go
//salign := sizeofPtr
// NOTE: It seems like 64-bit Darwin and DragonFly BSD kernels
// still require 32-bit aligned access to network subsystem.
//if darwin64Bit || dragonfly64Bit {
// salign = 4
//}
salign := 4
return (salen + salign - 1) & ^(salign - 1)
}
// http://golang.org/src/pkg/syscall/sockcmsg_unix.go
func cmsgData(h *syscall.Cmsghdr) unsafe.Pointer {
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(syscall.SizeofCmsghdr)))
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// UnixCredentials encodes credentials into a socket control message
// for sending to another process. This can be used for
// authentication.
func UnixCredentials(ucred *Ucred) []byte {
b := make([]byte, syscall.CmsgSpace(SizeofUcred))
h := (*syscall.Cmsghdr)(unsafe.Pointer(&b[0]))
h.Level = syscall.SOL_SOCKET
h.Type = syscall.SCM_CREDS
h.SetLen(syscall.CmsgLen(SizeofUcred))
*((*Ucred)(cmsgData(h))) = *ucred
return b
}
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// ParseUnixCredentials decodes a socket control message that contains
// credentials in a Ucred structure. To receive such a message, the
// SO_PASSCRED option must be enabled on the socket.
func ParseUnixCredentials(m *syscall.SocketControlMessage) (*Ucred, error) {
if m.Header.Level != syscall.SOL_SOCKET {
return nil, syscall.EINVAL
}
if m.Header.Type != syscall.SCM_CREDS {
return nil, syscall.EINVAL
}
ucred := *(*Ucred)(unsafe.Pointer(&m.Data[0]))
return &ucred, nil
}
func (t *unixTransport) SendNullByte() error {
ucred := &Ucred{Pid: int32(os.Getpid()), Uid: uint32(os.Getuid()), Gid: uint32(os.Getgid())}
b := 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
}

View file

@ -1,25 +0,0 @@
// The UnixCredentials system call is currently only implemented on Linux
// http://golang.org/src/pkg/syscall/sockcmsg_linux.go
// https://golang.org/s/go1.4-syscall
// http://code.google.com/p/go/source/browse/unix/sockcmsg_linux.go?repo=sys
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
}

View file

@ -1,139 +0,0 @@
package dbus
import (
"bytes"
"fmt"
"reflect"
"sort"
"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
var buf bytes.Buffer
kvs := make([]string, rv.Len())
for i, k := range rv.MapKeys() {
s, b := MakeVariant(k.Interface()).format()
unamb = unamb && b
buf.Reset()
buf.WriteString(s)
buf.WriteString(": ")
s, b = MakeVariant(rv.MapIndex(k).Interface()).format()
unamb = unamb && b
buf.WriteString(s)
kvs[i] = buf.String()
}
buf.Reset()
buf.WriteByte('{')
sort.Strings(kvs)
for i, kv := range kvs {
if i > 0 {
buf.WriteString(", ")
}
buf.WriteString(kv)
}
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
}

View file

@ -1,284 +0,0 @@
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
}

View file

@ -1,817 +0,0 @@
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()
}

View file

@ -1,241 +0,0 @@
Libcontainer provides a native Go implementation for creating containers
with namespaces, cgroups, capabilities, and filesystem access controls.
It allows you to manage the lifecycle of the container performing additional operations
after the container is created.
#### Container
A container is a self contained execution environment that shares the kernel of the
host system and which is (optionally) isolated from other containers in the system.
#### Using libcontainer
Because containers are spawned in a two step process you will need a binary that
will be executed as the init process for the container. In libcontainer, we use
the current binary (/proc/self/exe) to be executed as the init process, and use
arg "init", we call the first step process "bootstrap", so you always need a "init"
function as the entry of "bootstrap".
```go
func init() {
if len(os.Args) > 1 && os.Args[1] == "init" {
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
factory, _ := libcontainer.New("")
if err := factory.StartInitialization(); err != nil {
logrus.Fatal(err)
}
panic("--this line should have never been executed, congratulations--")
}
}
```
Then to create a container you first have to initialize an instance of a factory
that will handle the creation and initialization for a container.
```go
factory, err := libcontainer.New("/var/lib/container", libcontainer.Cgroupfs, libcontainer.InitArgs(os.Args[0], "init"))
if err != nil {
logrus.Fatal(err)
return
}
```
Once you have an instance of the factory created we can create a configuration
struct describing how the container is to be created. A sample would look similar to this:
```go
defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
config := &configs.Config{
Rootfs: "/your/path/to/rootfs",
Capabilities: []string{
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE",
},
Namespaces: configs.Namespaces([]configs.Namespace{
{Type: configs.NEWNS},
{Type: configs.NEWUTS},
{Type: configs.NEWIPC},
{Type: configs.NEWPID},
{Type: configs.NEWUSER},
{Type: configs.NEWNET},
}),
Cgroups: &configs.Cgroup{
Name: "test-container",
Parent: "system",
Resources: &configs.Resources{
MemorySwappiness: nil,
AllowAllDevices: false,
AllowedDevices: configs.DefaultAllowedDevices,
},
},
MaskPaths: []string{
"/proc/kcore",
},
ReadonlyPaths: []string{
"/proc/sys", "/proc/sysrq-trigger", "/proc/irq", "/proc/bus",
},
Devices: configs.DefaultAutoCreatedDevices,
Hostname: "testing",
Mounts: []*configs.Mount{
{
Source: "proc",
Destination: "/proc",
Device: "proc",
Flags: defaultMountFlags,
},
{
Source: "tmpfs",
Destination: "/dev",
Device: "tmpfs",
Flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME,
Data: "mode=755",
},
{
Source: "devpts",
Destination: "/dev/pts",
Device: "devpts",
Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC,
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
},
{
Device: "tmpfs",
Source: "shm",
Destination: "/dev/shm",
Data: "mode=1777,size=65536k",
Flags: defaultMountFlags,
},
{
Source: "mqueue",
Destination: "/dev/mqueue",
Device: "mqueue",
Flags: defaultMountFlags,
},
{
Source: "sysfs",
Destination: "/sys",
Device: "sysfs",
Flags: defaultMountFlags | syscall.MS_RDONLY,
},
},
UidMappings: []configs.IDMap{
{
ContainerID: 0,
HostID: 1000,
Size: 65536,
},
},
GidMappings: []configs.IDMap{
{
ContainerID: 0,
HostID: 1000,
Size: 65536,
},
},
Networks: []*configs.Network{
{
Type: "loopback",
Address: "127.0.0.1/0",
Gateway: "localhost",
},
},
Rlimits: []configs.Rlimit{
{
Type: syscall.RLIMIT_NOFILE,
Hard: uint64(1025),
Soft: uint64(1025),
},
},
}
```
Once you have the configuration populated you can create a container:
```go
container, err := factory.Create("container-id", config)
if err != nil {
logrus.Fatal(err)
return
}
```
To spawn bash as the initial process inside the container and have the
processes pid returned in order to wait, signal, or kill the process:
```go
process := &libcontainer.Process{
Args: []string{"/bin/bash"},
Env: []string{"PATH=/bin"},
User: "daemon",
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}
err := container.Start(process)
if err != nil {
logrus.Fatal(err)
container.Destroy()
return
}
// wait for the process to finish.
_, err := process.Wait()
if err != nil {
logrus.Fatal(err)
}
// destroy the container.
container.Destroy()
```
Additional ways to interact with a running container are:
```go
// return all the pids for all processes running inside the container.
processes, err := container.Processes()
// get detailed cpu, memory, io, and network statistics for the container and
// it's processes.
stats, err := container.Stats()
// pause all processes inside the container.
container.Pause()
// resume all paused processes.
container.Resume()
// send signal to container's init process.
container.Signal(signal)
```
#### Checkpoint & Restore
libcontainer now integrates [CRIU](http://criu.org/) for checkpointing and restoring containers.
This let's you save the state of a process running inside a container to disk, and then restore
that state into a new process, on the same machine or on another machine.
`criu` version 1.5.2 or higher is required to use checkpoint and restore.
If you don't already have `criu` installed, you can build it from source, following the
[online instructions](http://criu.org/Installation). `criu` is also installed in the docker image
generated when building libcontainer with docker.
## Copyright and license
Code and documentation copyright 2014 Docker, inc. Code released under the Apache 2.0 license.
Docs released under Creative commons.

View file

@ -1,335 +0,0 @@
## Container Specification - v1
This is the standard configuration for version 1 containers. It includes
namespaces, standard filesystem setup, a default Linux capability set, and
information about resource reservations. It also has information about any
populated environment settings for the processes running inside a container.
Along with the configuration of how a container is created the standard also
discusses actions that can be performed on a container to manage and inspect
information about the processes running inside.
The v1 profile is meant to be able to accommodate the majority of applications
with a strong security configuration.
### System Requirements and Compatibility
Minimum requirements:
* Kernel version - 3.10 recommended 2.6.2x minimum(with backported patches)
* Mounted cgroups with each subsystem in its own hierarchy
### Namespaces
| Flag | Enabled |
| ------------ | ------- |
| CLONE_NEWPID | 1 |
| CLONE_NEWUTS | 1 |
| CLONE_NEWIPC | 1 |
| CLONE_NEWNET | 1 |
| CLONE_NEWNS | 1 |
| CLONE_NEWUSER | 1 |
Namespaces are created for the container via the `clone` syscall.
### Filesystem
A root filesystem must be provided to a container for execution. The container
will use this root filesystem (rootfs) to jail and spawn processes inside where
the binaries and system libraries are local to that directory. Any binaries
to be executed must be contained within this rootfs.
Mounts that happen inside the container are automatically cleaned up when the
container exits as the mount namespace is destroyed and the kernel will
unmount all the mounts that were setup within that namespace.
For a container to execute properly there are certain filesystems that
are required to be mounted within the rootfs that the runtime will setup.
| Path | Type | Flags | Data |
| ----------- | ------ | -------------------------------------- | ---------------------------------------- |
| /proc | proc | MS_NOEXEC,MS_NOSUID,MS_NODEV | |
| /dev | tmpfs | MS_NOEXEC,MS_STRICTATIME | mode=755 |
| /dev/shm | tmpfs | MS_NOEXEC,MS_NOSUID,MS_NODEV | mode=1777,size=65536k |
| /dev/mqueue | mqueue | MS_NOEXEC,MS_NOSUID,MS_NODEV | |
| /dev/pts | devpts | MS_NOEXEC,MS_NOSUID | newinstance,ptmxmode=0666,mode=620,gid=5 |
| /sys | sysfs | MS_NOEXEC,MS_NOSUID,MS_NODEV,MS_RDONLY | |
After a container's filesystems are mounted within the newly created
mount namespace `/dev` will need to be populated with a set of device nodes.
It is expected that a rootfs does not need to have any device nodes specified
for `/dev` within the rootfs as the container will setup the correct devices
that are required for executing a container's process.
| Path | Mode | Access |
| ------------ | ---- | ---------- |
| /dev/null | 0666 | rwm |
| /dev/zero | 0666 | rwm |
| /dev/full | 0666 | rwm |
| /dev/tty | 0666 | rwm |
| /dev/random | 0666 | rwm |
| /dev/urandom | 0666 | rwm |
| /dev/fuse | 0666 | rwm |
**ptmx**
`/dev/ptmx` will need to be a symlink to the host's `/dev/ptmx` within
the container.
The use of a pseudo TTY is optional within a container and it should support both.
If a pseudo is provided to the container `/dev/console` will need to be
setup by binding the console in `/dev/` after it has been populated and mounted
in tmpfs.
| Source | Destination | UID GID | Mode | Type |
| --------------- | ------------ | ------- | ---- | ---- |
| *pty host path* | /dev/console | 0 0 | 0600 | bind |
After `/dev/null` has been setup we check for any external links between
the container's io, STDIN, STDOUT, STDERR. If the container's io is pointing
to `/dev/null` outside the container we close and `dup2` the `/dev/null`
that is local to the container's rootfs.
After the container has `/proc` mounted a few standard symlinks are setup
within `/dev/` for the io.
| Source | Destination |
| --------------- | ----------- |
| /proc/self/fd | /dev/fd |
| /proc/self/fd/0 | /dev/stdin |
| /proc/self/fd/1 | /dev/stdout |
| /proc/self/fd/2 | /dev/stderr |
A `pivot_root` is used to change the root for the process, effectively
jailing the process inside the rootfs.
```c
put_old = mkdir(...);
pivot_root(rootfs, put_old);
chdir("/");
unmount(put_old, MS_DETACH);
rmdir(put_old);
```
For container's running with a rootfs inside `ramfs` a `MS_MOVE` combined
with a `chroot` is required as `pivot_root` is not supported in `ramfs`.
```c
mount(rootfs, "/", NULL, MS_MOVE, NULL);
chroot(".");
chdir("/");
```
The `umask` is set back to `0022` after the filesystem setup has been completed.
### Resources
Cgroups are used to handle resource allocation for containers. This includes
system resources like cpu, memory, and device access.
| Subsystem | Enabled |
| ---------- | ------- |
| devices | 1 |
| memory | 1 |
| cpu | 1 |
| cpuacct | 1 |
| cpuset | 1 |
| blkio | 1 |
| perf_event | 1 |
| freezer | 1 |
| hugetlb | 1 |
| pids | 1 |
All cgroup subsystem are joined so that statistics can be collected from
each of the subsystems. Freezer does not expose any stats but is joined
so that containers can be paused and resumed.
The parent process of the container's init must place the init pid inside
the correct cgroups before the initialization begins. This is done so
that no processes or threads escape the cgroups. This sync is
done via a pipe ( specified in the runtime section below ) that the container's
init process will block waiting for the parent to finish setup.
### Security
The standard set of Linux capabilities that are set in a container
provide a good default for security and flexibility for the applications.
| Capability | Enabled |
| -------------------- | ------- |
| CAP_NET_RAW | 1 |
| CAP_NET_BIND_SERVICE | 1 |
| CAP_AUDIT_READ | 1 |
| CAP_AUDIT_WRITE | 1 |
| CAP_DAC_OVERRIDE | 1 |
| CAP_SETFCAP | 1 |
| CAP_SETPCAP | 1 |
| CAP_SETGID | 1 |
| CAP_SETUID | 1 |
| CAP_MKNOD | 1 |
| CAP_CHOWN | 1 |
| CAP_FOWNER | 1 |
| CAP_FSETID | 1 |
| CAP_KILL | 1 |
| CAP_SYS_CHROOT | 1 |
| CAP_NET_BROADCAST | 0 |
| CAP_SYS_MODULE | 0 |
| CAP_SYS_RAWIO | 0 |
| CAP_SYS_PACCT | 0 |
| CAP_SYS_ADMIN | 0 |
| CAP_SYS_NICE | 0 |
| CAP_SYS_RESOURCE | 0 |
| CAP_SYS_TIME | 0 |
| CAP_SYS_TTY_CONFIG | 0 |
| CAP_AUDIT_CONTROL | 0 |
| CAP_MAC_OVERRIDE | 0 |
| CAP_MAC_ADMIN | 0 |
| CAP_NET_ADMIN | 0 |
| CAP_SYSLOG | 0 |
| CAP_DAC_READ_SEARCH | 0 |
| CAP_LINUX_IMMUTABLE | 0 |
| CAP_IPC_LOCK | 0 |
| CAP_IPC_OWNER | 0 |
| CAP_SYS_PTRACE | 0 |
| CAP_SYS_BOOT | 0 |
| CAP_LEASE | 0 |
| CAP_WAKE_ALARM | 0 |
| CAP_BLOCK_SUSPEND | 0 |
Additional security layers like [apparmor](https://wiki.ubuntu.com/AppArmor)
and [selinux](http://selinuxproject.org/page/Main_Page) can be used with
the containers. A container should support setting an apparmor profile or
selinux process and mount labels if provided in the configuration.
Standard apparmor profile:
```c
#include <tunables/global>
profile <profile_name> flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
network,
capability,
file,
umount,
deny @{PROC}/sys/fs/** wklx,
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/mem rwklx,
deny @{PROC}/kmem rwklx,
deny @{PROC}/sys/kernel/[^s][^h][^m]* wklx,
deny @{PROC}/sys/kernel/*/** wklx,
deny mount,
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
deny /sys/fs/[^c]*/** wklx,
deny /sys/fs/c[^g]*/** wklx,
deny /sys/fs/cg[^r]*/** wklx,
deny /sys/firmware/efi/efivars/** rwklx,
deny /sys/kernel/security/** rwklx,
}
```
*TODO: seccomp work is being done to find a good default config*
### Runtime and Init Process
During container creation the parent process needs to talk to the container's init
process and have a form of synchronization. This is accomplished by creating
a pipe that is passed to the container's init. When the init process first spawns
it will block on its side of the pipe until the parent closes its side. This
allows the parent to have time to set the new process inside a cgroup hierarchy
and/or write any uid/gid mappings required for user namespaces.
The pipe is passed to the init process via FD 3.
The application consuming libcontainer should be compiled statically. libcontainer
does not define any init process and the arguments provided are used to `exec` the
process inside the application. There should be no long running init within the
container spec.
If a pseudo tty is provided to a container it will open and `dup2` the console
as the container's STDIN, STDOUT, STDERR as well as mounting the console
as `/dev/console`.
An extra set of mounts are provided to a container and setup for use. A container's
rootfs can contain some non portable files inside that can cause side effects during
execution of a process. These files are usually created and populated with the container
specific information via the runtime.
**Extra runtime files:**
* /etc/hosts
* /etc/resolv.conf
* /etc/hostname
* /etc/localtime
#### Defaults
There are a few defaults that can be overridden by users, but in their omission
these apply to processes within a container.
| Type | Value |
| ------------------- | ------------------------------ |
| Parent Death Signal | SIGKILL |
| UID | 0 |
| GID | 0 |
| GROUPS | 0, NULL |
| CWD | "/" |
| $HOME | Current user's home dir or "/" |
| Readonly rootfs | false |
| Pseudo TTY | false |
## Actions
After a container is created there is a standard set of actions that can
be done to the container. These actions are part of the public API for
a container.
| Action | Description |
| -------------- | ------------------------------------------------------------------ |
| Get processes | Return all the pids for processes running inside a container |
| Get Stats | Return resource statistics for the container as a whole |
| Wait | Wait waits on the container's init process ( pid 1 ) |
| Wait Process | Wait on any of the container's processes returning the exit status |
| Destroy | Kill the container's init process and remove any filesystem state |
| Signal | Send a signal to the container's init process |
| Signal Process | Send a signal to any of the container's processes |
| Pause | Pause all processes inside the container |
| Resume | Resume all processes inside the container if paused |
| Exec | Execute a new process inside of the container ( requires setns ) |
| Set | Setup configs of the container after it's created |
### Execute a new process inside of a running container.
User can execute a new process inside of a running container. Any binaries to be
executed must be accessible within the container's rootfs.
The started process will run inside the container's rootfs. Any changes
made by the process to the container's filesystem will persist after the
process finished executing.
The started process will join all the container's existing namespaces. When the
container is paused, the process will also be paused and will resume when
the container is unpaused. The started process will only run when the container's
primary process (PID 1) is running, and will not be restarted when the container
is restarted.
#### Planned additions
The started process will have its own cgroups nested inside the container's
cgroups. This is used for process tracking and optionally resource allocation
handling for the new process. Freezer cgroup is required, the rest of the cgroups
are optional. The process executor must place its pid inside the correct
cgroups before starting the process. This is done so that no child processes or
threads can escape the cgroups.
When the process is stopped, the process executor will try (in a best-effort way)
to stop all its children and remove the sub-cgroups.

View file

@ -1,39 +0,0 @@
// +build apparmor,linux
package apparmor
// #cgo LDFLAGS: -lapparmor
// #include <sys/apparmor.h>
// #include <stdlib.h>
import "C"
import (
"fmt"
"io/ioutil"
"os"
"unsafe"
)
// IsEnabled returns true if apparmor is enabled for the host.
func IsEnabled() bool {
if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" {
if _, err = os.Stat("/sbin/apparmor_parser"); err == nil {
buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
return err == nil && len(buf) > 1 && buf[0] == 'Y'
}
}
return false
}
// ApplyProfile will apply the profile with the specified name to the process after
// the next exec.
func ApplyProfile(name string) error {
if name == "" {
return nil
}
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
if _, err := C.aa_change_onexec(cName); err != nil {
return fmt.Errorf("apparmor failed to apply profile: %s", err)
}
return nil
}

View file

@ -1,20 +0,0 @@
// +build !apparmor !linux
package apparmor
import (
"errors"
)
var ErrApparmorNotEnabled = errors.New("apparmor: config provided but apparmor not supported")
func IsEnabled() bool {
return false
}
func ApplyProfile(name string) error {
if name != "" {
return ErrApparmorNotEnabled
}
return nil
}

View file

@ -1,69 +0,0 @@
// +build linux
package libcontainer
import (
"fmt"
"os"
"strings"
"github.com/syndtr/gocapability/capability"
)
const allCapabilityTypes = capability.CAPS | capability.BOUNDS
var capabilityMap map[string]capability.Cap
func init() {
capabilityMap = make(map[string]capability.Cap)
last := capability.CAP_LAST_CAP
// workaround for RHEL6 which has no /proc/sys/kernel/cap_last_cap
if last == capability.Cap(63) {
last = capability.CAP_BLOCK_SUSPEND
}
for _, cap := range capability.List() {
if cap > last {
continue
}
capKey := fmt.Sprintf("CAP_%s", strings.ToUpper(cap.String()))
capabilityMap[capKey] = cap
}
}
func newCapWhitelist(caps []string) (*whitelist, error) {
l := []capability.Cap{}
for _, c := range caps {
v, ok := capabilityMap[c]
if !ok {
return nil, fmt.Errorf("unknown capability %q", c)
}
l = append(l, v)
}
pid, err := capability.NewPid(os.Getpid())
if err != nil {
return nil, err
}
return &whitelist{
keep: l,
pid: pid,
}, nil
}
type whitelist struct {
pid capability.Capabilities
keep []capability.Cap
}
// dropBoundingSet drops the capability bounding set to those specified in the whitelist.
func (w *whitelist) dropBoundingSet() error {
w.pid.Clear(capability.BOUNDS)
w.pid.Set(capability.BOUNDS, w.keep...)
return w.pid.Apply(capability.BOUNDS)
}
// drop drops all capabilities for the current process except those specified in the whitelist.
func (w *whitelist) drop() error {
w.pid.Clear(allCapabilityTypes)
w.pid.Set(allCapabilityTypes, w.keep...)
return w.pid.Apply(allCapabilityTypes)
}

View file

@ -1,64 +0,0 @@
// +build linux
package cgroups
import (
"fmt"
"github.com/opencontainers/runc/libcontainer/configs"
)
type Manager interface {
// Applies cgroup configuration to the process with the specified pid
Apply(pid int) error
// Returns the PIDs inside the cgroup set
GetPids() ([]int, error)
// Returns the PIDs inside the cgroup set & all sub-cgroups
GetAllPids() ([]int, error)
// Returns statistics for the cgroup set
GetStats() (*Stats, error)
// Toggles the freezer cgroup according with specified state
Freeze(state configs.FreezerState) error
// Destroys the cgroup set
Destroy() error
// NewCgroupManager() and LoadCgroupManager() require following attributes:
// Paths map[string]string
// Cgroups *cgroups.Cgroup
// Paths maps cgroup subsystem to path at which it is mounted.
// Cgroups specifies specific cgroup settings for the various subsystems
// Returns cgroup paths to save in a state file and to be able to
// restore the object later.
GetPaths() map[string]string
// Set the cgroup as configured.
Set(container *configs.Config) error
}
type NotFoundError struct {
Subsystem string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("mountpoint for %s not found", e.Subsystem)
}
func NewNotFoundError(sub string) error {
return &NotFoundError{
Subsystem: sub,
}
}
func IsNotFound(err error) bool {
if err == nil {
return false
}
_, ok := err.(*NotFoundError)
return ok
}

View file

@ -1,3 +0,0 @@
// +build !linux
package cgroups

View file

@ -1,400 +0,0 @@
// +build linux
package fs
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"sync"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
)
var (
subsystems = subsystemSet{
&CpusetGroup{},
&DevicesGroup{},
&MemoryGroup{},
&CpuGroup{},
&CpuacctGroup{},
&PidsGroup{},
&BlkioGroup{},
&HugetlbGroup{},
&NetClsGroup{},
&NetPrioGroup{},
&PerfEventGroup{},
&FreezerGroup{},
&NameGroup{GroupName: "name=systemd", Join: true},
}
CgroupProcesses = "cgroup.procs"
HugePageSizes, _ = cgroups.GetHugePageSize()
)
var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
type subsystemSet []subsystem
func (s subsystemSet) Get(name string) (subsystem, error) {
for _, ss := range s {
if ss.Name() == name {
return ss, nil
}
}
return nil, errSubsystemDoesNotExist
}
type subsystem interface {
// Name returns the name of the subsystem.
Name() string
// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
GetStats(path string, stats *cgroups.Stats) error
// Removes the cgroup represented by 'cgroupData'.
Remove(*cgroupData) error
// Creates and joins the cgroup represented by 'cgroupData'.
Apply(*cgroupData) error
// Set the cgroup represented by cgroup.
Set(path string, cgroup *configs.Cgroup) error
}
type Manager struct {
mu sync.Mutex
Cgroups *configs.Cgroup
Paths map[string]string
}
// The absolute path to the root of the cgroup hierarchies.
var cgroupRootLock sync.Mutex
var cgroupRoot string
// Gets the cgroupRoot.
func getCgroupRoot() (string, error) {
cgroupRootLock.Lock()
defer cgroupRootLock.Unlock()
if cgroupRoot != "" {
return cgroupRoot, nil
}
root, err := cgroups.FindCgroupMountpointDir()
if err != nil {
return "", err
}
if _, err := os.Stat(root); err != nil {
return "", err
}
cgroupRoot = root
return cgroupRoot, nil
}
type cgroupData struct {
root string
innerPath string
config *configs.Cgroup
pid int
}
func (m *Manager) Apply(pid int) (err error) {
if m.Cgroups == nil {
return nil
}
var c = m.Cgroups
d, err := getCgroupData(m.Cgroups, pid)
if err != nil {
return err
}
if c.Paths != nil {
paths := make(map[string]string)
for name, path := range c.Paths {
_, err := d.path(name)
if err != nil {
if cgroups.IsNotFound(err) {
continue
}
return err
}
paths[name] = path
}
m.Paths = paths
return cgroups.EnterPid(m.Paths, pid)
}
m.mu.Lock()
defer m.mu.Unlock()
paths := make(map[string]string)
for _, sys := range subsystems {
if err := sys.Apply(d); err != nil {
return err
}
// TODO: Apply should, ideally, be reentrant or be broken up into a separate
// create and join phase so that the cgroup hierarchy for a container can be
// created then join consists of writing the process pids to cgroup.procs
p, err := d.path(sys.Name())
if err != nil {
if cgroups.IsNotFound(err) {
continue
}
return err
}
paths[sys.Name()] = p
}
m.Paths = paths
return nil
}
func (m *Manager) Destroy() error {
if m.Cgroups.Paths != nil {
return nil
}
m.mu.Lock()
defer m.mu.Unlock()
if err := cgroups.RemovePaths(m.Paths); err != nil {
return err
}
m.Paths = make(map[string]string)
return nil
}
func (m *Manager) GetPaths() map[string]string {
m.mu.Lock()
paths := m.Paths
m.mu.Unlock()
return paths
}
func (m *Manager) GetStats() (*cgroups.Stats, error) {
m.mu.Lock()
defer m.mu.Unlock()
stats := cgroups.NewStats()
for name, path := range m.Paths {
sys, err := subsystems.Get(name)
if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
continue
}
if err := sys.GetStats(path, stats); err != nil {
return nil, err
}
}
return stats, nil
}
func (m *Manager) Set(container *configs.Config) error {
for _, sys := range subsystems {
// Generate fake cgroup data.
d, err := getCgroupData(container.Cgroups, -1)
if err != nil {
return err
}
// Get the path, but don't error out if the cgroup wasn't found.
path, err := d.path(sys.Name())
if err != nil && !cgroups.IsNotFound(err) {
return err
}
if err := sys.Set(path, container.Cgroups); err != nil {
return err
}
}
if m.Paths["cpu"] != "" {
if err := CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); err != nil {
return err
}
}
return nil
}
// Freeze toggles the container's freezer cgroup depending on the state
// provided
func (m *Manager) Freeze(state configs.FreezerState) error {
d, err := getCgroupData(m.Cgroups, 0)
if err != nil {
return err
}
dir, err := d.path("freezer")
if err != nil {
return err
}
prevState := m.Cgroups.Resources.Freezer
m.Cgroups.Resources.Freezer = state
freezer, err := subsystems.Get("freezer")
if err != nil {
return err
}
err = freezer.Set(dir, m.Cgroups)
if err != nil {
m.Cgroups.Resources.Freezer = prevState
return err
}
return nil
}
func (m *Manager) GetPids() ([]int, error) {
dir, err := getCgroupPath(m.Cgroups)
if err != nil {
return nil, err
}
return cgroups.GetPids(dir)
}
func (m *Manager) GetAllPids() ([]int, error) {
dir, err := getCgroupPath(m.Cgroups)
if err != nil {
return nil, err
}
return cgroups.GetAllPids(dir)
}
func getCgroupPath(c *configs.Cgroup) (string, error) {
d, err := getCgroupData(c, 0)
if err != nil {
return "", err
}
return d.path("devices")
}
func getCgroupData(c *configs.Cgroup, pid int) (*cgroupData, error) {
root, err := getCgroupRoot()
if err != nil {
return nil, err
}
if (c.Name != "" || c.Parent != "") && c.Path != "" {
return nil, fmt.Errorf("cgroup: either Path or Name and Parent should be used")
}
// XXX: Do not remove this code. Path safety is important! -- cyphar
cgPath := libcontainerUtils.CleanPath(c.Path)
cgParent := libcontainerUtils.CleanPath(c.Parent)
cgName := libcontainerUtils.CleanPath(c.Name)
innerPath := cgPath
if innerPath == "" {
innerPath = filepath.Join(cgParent, cgName)
}
return &cgroupData{
root: root,
innerPath: innerPath,
config: c,
pid: pid,
}, nil
}
func (raw *cgroupData) parentPath(subsystem, mountpoint, root string) (string, error) {
// Use GetThisCgroupDir instead of GetInitCgroupDir, because the creating
// process could in container and shared pid namespace with host, and
// /proc/1/cgroup could point to whole other world of cgroups.
initPath, err := cgroups.GetThisCgroupDir(subsystem)
if err != nil {
return "", err
}
// This is needed for nested containers, because in /proc/self/cgroup we
// see pathes from host, which don't exist in container.
relDir, err := filepath.Rel(root, initPath)
if err != nil {
return "", err
}
return filepath.Join(mountpoint, relDir), nil
}
func (raw *cgroupData) path(subsystem string) (string, error) {
mnt, root, err := cgroups.FindCgroupMountpointAndRoot(subsystem)
// If we didn't mount the subsystem, there is no point we make the path.
if err != nil {
return "", err
}
// If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
if filepath.IsAbs(raw.innerPath) {
// Sometimes subsystems can be mounted togethger as 'cpu,cpuacct'.
return filepath.Join(raw.root, filepath.Base(mnt), raw.innerPath), nil
}
parentPath, err := raw.parentPath(subsystem, mnt, root)
if err != nil {
return "", err
}
return filepath.Join(parentPath, raw.innerPath), nil
}
func (raw *cgroupData) join(subsystem string) (string, error) {
path, err := raw.path(subsystem)
if err != nil {
return "", err
}
if err := os.MkdirAll(path, 0755); err != nil {
return "", err
}
if err := writeFile(path, CgroupProcesses, strconv.Itoa(raw.pid)); err != nil {
return "", err
}
return path, nil
}
func writeFile(dir, file, data string) error {
// Normally dir should not be empty, one case is that cgroup subsystem
// is not mounted, we will get empty dir, and we want it fail here.
if dir == "" {
return fmt.Errorf("no such directory for %s", file)
}
if err := ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700); err != nil {
return fmt.Errorf("failed to write %v to %v: %v", data, file, err)
}
return nil
}
func readFile(dir, file string) (string, error) {
data, err := ioutil.ReadFile(filepath.Join(dir, file))
return string(data), err
}
func removePath(p string, err error) error {
if err != nil {
return err
}
if p != "" {
return os.RemoveAll(p)
}
return nil
}
func CheckCpushares(path string, c int64) error {
var cpuShares int64
if c == 0 {
return nil
}
fd, err := os.Open(filepath.Join(path, "cpu.shares"))
if err != nil {
return err
}
defer fd.Close()
_, err = fmt.Fscanf(fd, "%d", &cpuShares)
if err != nil && err != io.EOF {
return err
}
if c > cpuShares {
return fmt.Errorf("The maximum allowed cpu-shares is %d", cpuShares)
} else if c < cpuShares {
return fmt.Errorf("The minimum allowed cpu-shares is %d", cpuShares)
}
return nil
}

View file

@ -1,237 +0,0 @@
// +build linux
package fs
import (
"bufio"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type BlkioGroup struct {
}
func (s *BlkioGroup) Name() string {
return "blkio"
}
func (s *BlkioGroup) Apply(d *cgroupData) error {
_, err := d.join("blkio")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.BlkioWeight != 0 {
if err := writeFile(path, "blkio.weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioWeight), 10)); err != nil {
return err
}
}
if cgroup.Resources.BlkioLeafWeight != 0 {
if err := writeFile(path, "blkio.leaf_weight", strconv.FormatUint(uint64(cgroup.Resources.BlkioLeafWeight), 10)); err != nil {
return err
}
}
for _, wd := range cgroup.Resources.BlkioWeightDevice {
if err := writeFile(path, "blkio.weight_device", wd.WeightString()); err != nil {
return err
}
if err := writeFile(path, "blkio.leaf_weight_device", wd.LeafWeightString()); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleReadBpsDevice {
if err := writeFile(path, "blkio.throttle.read_bps_device", td.String()); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleWriteBpsDevice {
if err := writeFile(path, "blkio.throttle.write_bps_device", td.String()); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleReadIOPSDevice {
if err := writeFile(path, "blkio.throttle.read_iops_device", td.String()); err != nil {
return err
}
}
for _, td := range cgroup.Resources.BlkioThrottleWriteIOPSDevice {
if err := writeFile(path, "blkio.throttle.write_iops_device", td.String()); err != nil {
return err
}
}
return nil
}
func (s *BlkioGroup) Remove(d *cgroupData) error {
return removePath(d.path("blkio"))
}
/*
examples:
blkio.sectors
8:0 6792
blkio.io_service_bytes
8:0 Read 1282048
8:0 Write 2195456
8:0 Sync 2195456
8:0 Async 1282048
8:0 Total 3477504
Total 3477504
blkio.io_serviced
8:0 Read 124
8:0 Write 104
8:0 Sync 104
8:0 Async 124
8:0 Total 228
Total 228
blkio.io_queued
8:0 Read 0
8:0 Write 0
8:0 Sync 0
8:0 Async 0
8:0 Total 0
Total 0
*/
func splitBlkioStatLine(r rune) bool {
return r == ' ' || r == ':'
}
func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
var blkioStats []cgroups.BlkioStatEntry
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return blkioStats, nil
}
return nil, err
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
// format: dev type amount
fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine)
if len(fields) < 3 {
if len(fields) == 2 && fields[0] == "Total" {
// skip total line
continue
} else {
return nil, fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text())
}
}
v, err := strconv.ParseUint(fields[0], 10, 64)
if err != nil {
return nil, err
}
major := v
v, err = strconv.ParseUint(fields[1], 10, 64)
if err != nil {
return nil, err
}
minor := v
op := ""
valueField := 2
if len(fields) == 4 {
op = fields[2]
valueField = 3
}
v, err = strconv.ParseUint(fields[valueField], 10, 64)
if err != nil {
return nil, err
}
blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v})
}
return blkioStats, nil
}
func (s *BlkioGroup) GetStats(path string, stats *cgroups.Stats) error {
// Try to read CFQ stats available on all CFQ enabled kernels first
if blkioStats, err := getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err == nil && blkioStats != nil {
return getCFQStats(path, stats)
}
return getStats(path, stats) // Use generic stats as fallback
}
func getCFQStats(path string, stats *cgroups.Stats) error {
var blkioStats []cgroups.BlkioStatEntry
var err error
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil {
return err
}
stats.BlkioStats.SectorsRecursive = blkioStats
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_bytes_recursive")); err != nil {
return err
}
stats.BlkioStats.IoServiceBytesRecursive = blkioStats
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err != nil {
return err
}
stats.BlkioStats.IoServicedRecursive = blkioStats
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_queued_recursive")); err != nil {
return err
}
stats.BlkioStats.IoQueuedRecursive = blkioStats
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_time_recursive")); err != nil {
return err
}
stats.BlkioStats.IoServiceTimeRecursive = blkioStats
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_wait_time_recursive")); err != nil {
return err
}
stats.BlkioStats.IoWaitTimeRecursive = blkioStats
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_merged_recursive")); err != nil {
return err
}
stats.BlkioStats.IoMergedRecursive = blkioStats
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.time_recursive")); err != nil {
return err
}
stats.BlkioStats.IoTimeRecursive = blkioStats
return nil
}
func getStats(path string, stats *cgroups.Stats) error {
var blkioStats []cgroups.BlkioStatEntry
var err error
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_service_bytes")); err != nil {
return err
}
stats.BlkioStats.IoServiceBytesRecursive = blkioStats
if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.throttle.io_serviced")); err != nil {
return err
}
stats.BlkioStats.IoServicedRecursive = blkioStats
return nil
}

View file

@ -1,94 +0,0 @@
// +build linux
package fs
import (
"bufio"
"os"
"path/filepath"
"strconv"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type CpuGroup struct {
}
func (s *CpuGroup) Name() string {
return "cpu"
}
func (s *CpuGroup) Apply(d *cgroupData) error {
// We always want to join the cpu group, to allow fair cpu scheduling
// on a container basis
_, err := d.join("cpu")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *CpuGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.CpuShares != 0 {
if err := writeFile(path, "cpu.shares", strconv.FormatInt(cgroup.Resources.CpuShares, 10)); err != nil {
return err
}
}
if cgroup.Resources.CpuPeriod != 0 {
if err := writeFile(path, "cpu.cfs_period_us", strconv.FormatInt(cgroup.Resources.CpuPeriod, 10)); err != nil {
return err
}
}
if cgroup.Resources.CpuQuota != 0 {
if err := writeFile(path, "cpu.cfs_quota_us", strconv.FormatInt(cgroup.Resources.CpuQuota, 10)); err != nil {
return err
}
}
if cgroup.Resources.CpuRtPeriod != 0 {
if err := writeFile(path, "cpu.rt_period_us", strconv.FormatInt(cgroup.Resources.CpuRtPeriod, 10)); err != nil {
return err
}
}
if cgroup.Resources.CpuRtRuntime != 0 {
if err := writeFile(path, "cpu.rt_runtime_us", strconv.FormatInt(cgroup.Resources.CpuRtRuntime, 10)); err != nil {
return err
}
}
return nil
}
func (s *CpuGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpu"))
}
func (s *CpuGroup) GetStats(path string, stats *cgroups.Stats) error {
f, err := os.Open(filepath.Join(path, "cpu.stat"))
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
t, v, err := getCgroupParamKeyValue(sc.Text())
if err != nil {
return err
}
switch t {
case "nr_periods":
stats.CpuStats.ThrottlingData.Periods = v
case "nr_throttled":
stats.CpuStats.ThrottlingData.ThrottledPeriods = v
case "throttled_time":
stats.CpuStats.ThrottlingData.ThrottledTime = v
}
}
return nil
}

View file

@ -1,121 +0,0 @@
// +build linux
package fs
import (
"fmt"
"io/ioutil"
"path/filepath"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/system"
)
const (
cgroupCpuacctStat = "cpuacct.stat"
nanosecondsInSecond = 1000000000
)
var clockTicks = uint64(system.GetClockTicks())
type CpuacctGroup struct {
}
func (s *CpuacctGroup) Name() string {
return "cpuacct"
}
func (s *CpuacctGroup) Apply(d *cgroupData) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("cpuacct"); err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *CpuacctGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *CpuacctGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpuacct"))
}
func (s *CpuacctGroup) GetStats(path string, stats *cgroups.Stats) error {
userModeUsage, kernelModeUsage, err := getCpuUsageBreakdown(path)
if err != nil {
return err
}
totalUsage, err := getCgroupParamUint(path, "cpuacct.usage")
if err != nil {
return err
}
percpuUsage, err := getPercpuUsage(path)
if err != nil {
return err
}
stats.CpuStats.CpuUsage.TotalUsage = totalUsage
stats.CpuStats.CpuUsage.PercpuUsage = percpuUsage
stats.CpuStats.CpuUsage.UsageInUsermode = userModeUsage
stats.CpuStats.CpuUsage.UsageInKernelmode = kernelModeUsage
return nil
}
// Returns user and kernel usage breakdown in nanoseconds.
func getCpuUsageBreakdown(path string) (uint64, uint64, error) {
userModeUsage := uint64(0)
kernelModeUsage := uint64(0)
const (
userField = "user"
systemField = "system"
)
// Expected format:
// user <usage in ticks>
// system <usage in ticks>
data, err := ioutil.ReadFile(filepath.Join(path, cgroupCpuacctStat))
if err != nil {
return 0, 0, err
}
fields := strings.Fields(string(data))
if len(fields) != 4 {
return 0, 0, fmt.Errorf("failure - %s is expected to have 4 fields", filepath.Join(path, cgroupCpuacctStat))
}
if fields[0] != userField {
return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[0], cgroupCpuacctStat, userField)
}
if fields[2] != systemField {
return 0, 0, fmt.Errorf("unexpected field %q in %q, expected %q", fields[2], cgroupCpuacctStat, systemField)
}
if userModeUsage, err = strconv.ParseUint(fields[1], 10, 64); err != nil {
return 0, 0, err
}
if kernelModeUsage, err = strconv.ParseUint(fields[3], 10, 64); err != nil {
return 0, 0, err
}
return (userModeUsage * nanosecondsInSecond) / clockTicks, (kernelModeUsage * nanosecondsInSecond) / clockTicks, nil
}
func getPercpuUsage(path string) ([]uint64, error) {
percpuUsage := []uint64{}
data, err := ioutil.ReadFile(filepath.Join(path, "cpuacct.usage_percpu"))
if err != nil {
return percpuUsage, err
}
for _, value := range strings.Fields(string(data)) {
value, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return percpuUsage, fmt.Errorf("Unable to convert param value to uint64: %s", err)
}
percpuUsage = append(percpuUsage, value)
}
return percpuUsage, nil
}

View file

@ -1,139 +0,0 @@
// +build linux
package fs
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
)
type CpusetGroup struct {
}
func (s *CpusetGroup) Name() string {
return "cpuset"
}
func (s *CpusetGroup) Apply(d *cgroupData) error {
dir, err := d.path("cpuset")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return s.ApplyDir(dir, d.config, d.pid)
}
func (s *CpusetGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.CpusetCpus != "" {
if err := writeFile(path, "cpuset.cpus", cgroup.Resources.CpusetCpus); err != nil {
return err
}
}
if cgroup.Resources.CpusetMems != "" {
if err := writeFile(path, "cpuset.mems", cgroup.Resources.CpusetMems); err != nil {
return err
}
}
return nil
}
func (s *CpusetGroup) Remove(d *cgroupData) error {
return removePath(d.path("cpuset"))
}
func (s *CpusetGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}
func (s *CpusetGroup) ApplyDir(dir string, cgroup *configs.Cgroup, pid int) error {
// This might happen if we have no cpuset cgroup mounted.
// Just do nothing and don't fail.
if dir == "" {
return nil
}
root, err := getCgroupRoot()
if err != nil {
return err
}
if err := s.ensureParent(dir, root); err != nil {
return err
}
// because we are not using d.join we need to place the pid into the procs file
// unlike the other subsystems
if err := writeFile(dir, "cgroup.procs", strconv.Itoa(pid)); err != nil {
return err
}
return nil
}
func (s *CpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
return
}
if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil {
return
}
return cpus, mems, nil
}
// ensureParent makes sure that the parent directory of current is created
// and populated with the proper cpus and mems files copied from
// it's parent.
func (s *CpusetGroup) ensureParent(current, root string) error {
parent := filepath.Dir(current)
if libcontainerUtils.CleanPath(parent) == root {
return nil
}
// Avoid infinite recursion.
if parent == current {
return fmt.Errorf("cpuset: cgroup parent path outside cgroup root")
}
if err := s.ensureParent(parent, root); err != nil {
return err
}
if err := os.MkdirAll(current, 0755); err != nil {
return err
}
return s.copyIfNeeded(current, parent)
}
// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
// directory to the current directory if the file's contents are 0
func (s *CpusetGroup) copyIfNeeded(current, parent string) error {
var (
err error
currentCpus, currentMems []byte
parentCpus, parentMems []byte
)
if currentCpus, currentMems, err = s.getSubsystemSettings(current); err != nil {
return err
}
if parentCpus, parentMems, err = s.getSubsystemSettings(parent); err != nil {
return err
}
if s.isEmpty(currentCpus) {
if err := writeFile(current, "cpuset.cpus", string(parentCpus)); err != nil {
return err
}
}
if s.isEmpty(currentMems) {
if err := writeFile(current, "cpuset.mems", string(parentMems)); err != nil {
return err
}
}
return nil
}
func (s *CpusetGroup) isEmpty(b []byte) bool {
return len(bytes.Trim(b, "\n")) == 0
}

View file

@ -1,78 +0,0 @@
// +build linux
package fs
import (
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/system"
)
type DevicesGroup struct {
}
func (s *DevicesGroup) Name() string {
return "devices"
}
func (s *DevicesGroup) Apply(d *cgroupData) error {
_, err := d.join("devices")
if err != nil {
// We will return error even it's `not found` error, devices
// cgroup is hard requirement for container's security.
return err
}
return nil
}
func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error {
if system.RunningInUserNS() {
return nil
}
devices := cgroup.Resources.Devices
if len(devices) > 0 {
for _, dev := range devices {
file := "devices.deny"
if dev.Allow {
file = "devices.allow"
}
if err := writeFile(path, file, dev.CgroupString()); err != nil {
return err
}
}
return nil
}
if !cgroup.Resources.AllowAllDevices {
if err := writeFile(path, "devices.deny", "a"); err != nil {
return err
}
for _, dev := range cgroup.Resources.AllowedDevices {
if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil {
return err
}
}
return nil
}
if err := writeFile(path, "devices.allow", "a"); err != nil {
return err
}
for _, dev := range cgroup.Resources.DeniedDevices {
if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil {
return err
}
}
return nil
}
func (s *DevicesGroup) Remove(d *cgroupData) error {
return removePath(d.path("devices"))
}
func (s *DevicesGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View file

@ -1,61 +0,0 @@
// +build linux
package fs
import (
"fmt"
"strings"
"time"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type FreezerGroup struct {
}
func (s *FreezerGroup) Name() string {
return "freezer"
}
func (s *FreezerGroup) Apply(d *cgroupData) error {
_, err := d.join("freezer")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error {
switch cgroup.Resources.Freezer {
case configs.Frozen, configs.Thawed:
if err := writeFile(path, "freezer.state", string(cgroup.Resources.Freezer)); err != nil {
return err
}
for {
state, err := readFile(path, "freezer.state")
if err != nil {
return err
}
if strings.TrimSpace(state) == string(cgroup.Resources.Freezer) {
break
}
time.Sleep(1 * time.Millisecond)
}
case configs.Undefined:
return nil
default:
return fmt.Errorf("Invalid argument '%s' to freezer.state", string(cgroup.Resources.Freezer))
}
return nil
}
func (s *FreezerGroup) Remove(d *cgroupData) error {
return removePath(d.path("freezer"))
}
func (s *FreezerGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View file

@ -1,3 +0,0 @@
// +build !linux
package fs

View file

@ -1,71 +0,0 @@
// +build linux
package fs
import (
"fmt"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type HugetlbGroup struct {
}
func (s *HugetlbGroup) Name() string {
return "hugetlb"
}
func (s *HugetlbGroup) Apply(d *cgroupData) error {
_, err := d.join("hugetlb")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error {
for _, hugetlb := range cgroup.Resources.HugetlbLimit {
if err := writeFile(path, strings.Join([]string{"hugetlb", hugetlb.Pagesize, "limit_in_bytes"}, "."), strconv.FormatUint(hugetlb.Limit, 10)); err != nil {
return err
}
}
return nil
}
func (s *HugetlbGroup) Remove(d *cgroupData) error {
return removePath(d.path("hugetlb"))
}
func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error {
hugetlbStats := cgroups.HugetlbStats{}
for _, pageSize := range HugePageSizes {
usage := strings.Join([]string{"hugetlb", pageSize, "usage_in_bytes"}, ".")
value, err := getCgroupParamUint(path, usage)
if err != nil {
return fmt.Errorf("failed to parse %s - %v", usage, err)
}
hugetlbStats.Usage = value
maxUsage := strings.Join([]string{"hugetlb", pageSize, "max_usage_in_bytes"}, ".")
value, err = getCgroupParamUint(path, maxUsage)
if err != nil {
return fmt.Errorf("failed to parse %s - %v", maxUsage, err)
}
hugetlbStats.MaxUsage = value
failcnt := strings.Join([]string{"hugetlb", pageSize, "failcnt"}, ".")
value, err = getCgroupParamUint(path, failcnt)
if err != nil {
return fmt.Errorf("failed to parse %s - %v", failcnt, err)
}
hugetlbStats.Failcnt = value
stats.HugetlbStats[pageSize] = hugetlbStats
}
return nil
}

View file

@ -1,290 +0,0 @@
// +build linux
package fs
import (
"bufio"
"fmt"
"math"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/system"
)
type MemoryGroup struct {
}
func (s *MemoryGroup) Name() string {
return "memory"
}
func (s *MemoryGroup) Apply(d *cgroupData) (err error) {
path, err := d.path("memory")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
if memoryAssigned(d.config) {
if path != "" {
if err := os.MkdirAll(path, 0755); err != nil {
return err
}
}
// We have to set kernel memory here, as we can't change it once
// processes have been attached to the cgroup.
if err := s.SetKernelMemory(path, d.config); err != nil {
return err
}
}
defer func() {
if err != nil {
os.RemoveAll(path)
}
}()
// We need to join memory cgroup after set memory limits, because
// kmem.limit_in_bytes can only be set when the cgroup is empty.
_, err = d.join("memory")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *MemoryGroup) SetKernelMemory(path string, cgroup *configs.Cgroup) error {
// This has to be done separately because it has special
// constraints (it can only be initialized before setting up a
// hierarchy or adding a task to the cgroups. However, if
// sucessfully initialized, it can be updated anytime afterwards)
if cgroup.Resources.KernelMemory != 0 {
kmemInitialized := false
// Is kmem.limit_in_bytes already set?
kmemValue, err := getCgroupParamUint(path, "memory.kmem.limit_in_bytes")
if err != nil {
return err
}
switch system.GetLongBit() {
case 32:
kmemInitialized = uint32(kmemValue) != uint32(math.MaxUint32)
case 64:
kmemInitialized = kmemValue != uint64(math.MaxUint64)
}
if !kmemInitialized {
// If hierarchy is set, we can't change the limit
usesHierarchy, err := getCgroupParamUint(path, "memory.use_hierarchy")
if err != nil {
return err
}
if usesHierarchy != 0 {
return fmt.Errorf("cannot initialize kmem.limit_in_bytes if use_hierarchy is already set")
}
// If there's already tasks in the cgroup, we can't change the limit either
tasks, err := getCgroupParamString(path, "tasks")
if err != nil {
return err
}
if tasks != "" {
return fmt.Errorf("cannot initialize kmem.limit_in_bytes after task have joined this cgroup")
}
}
if err := writeFile(path, "memory.kmem.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemory, 10)); err != nil {
return err
}
}
return nil
}
func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error {
// When memory and swap memory are both set, we need to handle the cases
// for updating container.
if cgroup.Resources.Memory != 0 && cgroup.Resources.MemorySwap > 0 {
memoryUsage, err := getMemoryData(path, "")
if err != nil {
return err
}
// When update memory limit, we should adapt the write sequence
// for memory and swap memory, so it won't fail because the new
// value and the old value don't fit kernel's validation.
if memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) {
if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
return err
}
if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
return err
}
} else {
if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
return err
}
if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
return err
}
}
} else {
if cgroup.Resources.Memory != 0 {
if err := writeFile(path, "memory.limit_in_bytes", strconv.FormatInt(cgroup.Resources.Memory, 10)); err != nil {
return err
}
}
if cgroup.Resources.MemorySwap > 0 {
if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemorySwap, 10)); err != nil {
return err
}
}
}
return nil
}
func (s *MemoryGroup) Set(path string, cgroup *configs.Cgroup) error {
if err := setMemoryAndSwap(path, cgroup); err != nil {
return err
}
if err := s.SetKernelMemory(path, cgroup); err != nil {
return err
}
if cgroup.Resources.MemoryReservation != 0 {
if err := writeFile(path, "memory.soft_limit_in_bytes", strconv.FormatInt(cgroup.Resources.MemoryReservation, 10)); err != nil {
return err
}
}
if cgroup.Resources.KernelMemoryTCP != 0 {
if err := writeFile(path, "memory.kmem.tcp.limit_in_bytes", strconv.FormatInt(cgroup.Resources.KernelMemoryTCP, 10)); err != nil {
return err
}
}
if cgroup.Resources.OomKillDisable {
if err := writeFile(path, "memory.oom_control", "1"); err != nil {
return err
}
}
if cgroup.Resources.MemorySwappiness == nil || int64(*cgroup.Resources.MemorySwappiness) == -1 {
return nil
} else if int64(*cgroup.Resources.MemorySwappiness) >= 0 && int64(*cgroup.Resources.MemorySwappiness) <= 100 {
if err := writeFile(path, "memory.swappiness", strconv.FormatInt(*cgroup.Resources.MemorySwappiness, 10)); err != nil {
return err
}
} else {
return fmt.Errorf("invalid value:%d. valid memory swappiness range is 0-100", int64(*cgroup.Resources.MemorySwappiness))
}
return nil
}
func (s *MemoryGroup) Remove(d *cgroupData) error {
return removePath(d.path("memory"))
}
func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error {
// Set stats from memory.stat.
statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
defer statsFile.Close()
sc := bufio.NewScanner(statsFile)
for sc.Scan() {
t, v, err := getCgroupParamKeyValue(sc.Text())
if err != nil {
return fmt.Errorf("failed to parse memory.stat (%q) - %v", sc.Text(), err)
}
stats.MemoryStats.Stats[t] = v
}
stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"]
memoryUsage, err := getMemoryData(path, "")
if err != nil {
return err
}
stats.MemoryStats.Usage = memoryUsage
swapUsage, err := getMemoryData(path, "memsw")
if err != nil {
return err
}
stats.MemoryStats.SwapUsage = swapUsage
kernelUsage, err := getMemoryData(path, "kmem")
if err != nil {
return err
}
stats.MemoryStats.KernelUsage = kernelUsage
kernelTCPUsage, err := getMemoryData(path, "kmem.tcp")
if err != nil {
return err
}
stats.MemoryStats.KernelTCPUsage = kernelTCPUsage
return nil
}
func memoryAssigned(cgroup *configs.Cgroup) bool {
return cgroup.Resources.Memory != 0 ||
cgroup.Resources.MemoryReservation != 0 ||
cgroup.Resources.MemorySwap > 0 ||
cgroup.Resources.KernelMemory > 0 ||
cgroup.Resources.KernelMemoryTCP > 0 ||
cgroup.Resources.OomKillDisable ||
(cgroup.Resources.MemorySwappiness != nil && *cgroup.Resources.MemorySwappiness != -1)
}
func getMemoryData(path, name string) (cgroups.MemoryData, error) {
memoryData := cgroups.MemoryData{}
moduleName := "memory"
if name != "" {
moduleName = strings.Join([]string{"memory", name}, ".")
}
usage := strings.Join([]string{moduleName, "usage_in_bytes"}, ".")
maxUsage := strings.Join([]string{moduleName, "max_usage_in_bytes"}, ".")
failcnt := strings.Join([]string{moduleName, "failcnt"}, ".")
limit := strings.Join([]string{moduleName, "limit_in_bytes"}, ".")
value, err := getCgroupParamUint(path, usage)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", usage, err)
}
memoryData.Usage = value
value, err = getCgroupParamUint(path, maxUsage)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", maxUsage, err)
}
memoryData.MaxUsage = value
value, err = getCgroupParamUint(path, failcnt)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", failcnt, err)
}
memoryData.Failcnt = value
value, err = getCgroupParamUint(path, limit)
if err != nil {
if moduleName != "memory" && os.IsNotExist(err) {
return cgroups.MemoryData{}, nil
}
return cgroups.MemoryData{}, fmt.Errorf("failed to parse %s - %v", limit, err)
}
memoryData.Limit = value
return memoryData, nil
}

View file

@ -1,40 +0,0 @@
// +build linux
package fs
import (
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type NameGroup struct {
GroupName string
Join bool
}
func (s *NameGroup) Name() string {
return s.GroupName
}
func (s *NameGroup) Apply(d *cgroupData) error {
if s.Join {
// ignore errors if the named cgroup does not exist
d.join(s.GroupName)
}
return nil
}
func (s *NameGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *NameGroup) Remove(d *cgroupData) error {
if s.Join {
removePath(d.path(s.GroupName))
}
return nil
}
func (s *NameGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View file

@ -1,41 +0,0 @@
// +build linux
package fs
import (
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type NetClsGroup struct {
}
func (s *NetClsGroup) Name() string {
return "net_cls"
}
func (s *NetClsGroup) Apply(d *cgroupData) error {
_, err := d.join("net_cls")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *NetClsGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.NetClsClassid != "" {
if err := writeFile(path, "net_cls.classid", cgroup.Resources.NetClsClassid); err != nil {
return err
}
}
return nil
}
func (s *NetClsGroup) Remove(d *cgroupData) error {
return removePath(d.path("net_cls"))
}
func (s *NetClsGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View file

@ -1,41 +0,0 @@
// +build linux
package fs
import (
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type NetPrioGroup struct {
}
func (s *NetPrioGroup) Name() string {
return "net_prio"
}
func (s *NetPrioGroup) Apply(d *cgroupData) error {
_, err := d.join("net_prio")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *NetPrioGroup) Set(path string, cgroup *configs.Cgroup) error {
for _, prioMap := range cgroup.Resources.NetPrioIfpriomap {
if err := writeFile(path, "net_prio.ifpriomap", prioMap.CgroupString()); err != nil {
return err
}
}
return nil
}
func (s *NetPrioGroup) Remove(d *cgroupData) error {
return removePath(d.path("net_prio"))
}
func (s *NetPrioGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View file

@ -1,35 +0,0 @@
// +build linux
package fs
import (
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type PerfEventGroup struct {
}
func (s *PerfEventGroup) Name() string {
return "perf_event"
}
func (s *PerfEventGroup) Apply(d *cgroupData) error {
// we just want to join this group even though we don't set anything
if _, err := d.join("perf_event"); err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *PerfEventGroup) Set(path string, cgroup *configs.Cgroup) error {
return nil
}
func (s *PerfEventGroup) Remove(d *cgroupData) error {
return removePath(d.path("perf_event"))
}
func (s *PerfEventGroup) GetStats(path string, stats *cgroups.Stats) error {
return nil
}

View file

@ -1,73 +0,0 @@
// +build linux
package fs
import (
"fmt"
"path/filepath"
"strconv"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type PidsGroup struct {
}
func (s *PidsGroup) Name() string {
return "pids"
}
func (s *PidsGroup) Apply(d *cgroupData) error {
_, err := d.join("pids")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return nil
}
func (s *PidsGroup) Set(path string, cgroup *configs.Cgroup) error {
if cgroup.Resources.PidsLimit != 0 {
// "max" is the fallback value.
limit := "max"
if cgroup.Resources.PidsLimit > 0 {
limit = strconv.FormatInt(cgroup.Resources.PidsLimit, 10)
}
if err := writeFile(path, "pids.max", limit); err != nil {
return err
}
}
return nil
}
func (s *PidsGroup) Remove(d *cgroupData) error {
return removePath(d.path("pids"))
}
func (s *PidsGroup) GetStats(path string, stats *cgroups.Stats) error {
current, err := getCgroupParamUint(path, "pids.current")
if err != nil {
return fmt.Errorf("failed to parse pids.current - %s", err)
}
maxString, err := getCgroupParamString(path, "pids.max")
if err != nil {
return fmt.Errorf("failed to parse pids.max - %s", err)
}
// Default if pids.max == "max" is 0 -- which represents "no limit".
var max uint64
if maxString != "max" {
max, err = parseUint(maxString, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse pids.max - unable to parse %q as a uint from Cgroup file %q", maxString, filepath.Join(path, "pids.max"))
}
}
stats.PidsStats.Current = current
stats.PidsStats.Limit = max
return nil
}

View file

@ -1,79 +0,0 @@
// +build linux
package fs
import (
"errors"
"fmt"
"io/ioutil"
"path/filepath"
"strconv"
"strings"
)
var (
ErrNotSupportStat = errors.New("stats are not supported for subsystem")
ErrNotValidFormat = errors.New("line is not a valid key value format")
)
// Saturates negative values at zero and returns a uint64.
// Due to kernel bugs, some of the memory cgroup stats can be negative.
func parseUint(s string, base, bitSize int) (uint64, error) {
value, err := strconv.ParseUint(s, base, bitSize)
if err != nil {
intValue, intErr := strconv.ParseInt(s, base, bitSize)
// 1. Handle negative values greater than MinInt64 (and)
// 2. Handle negative values lesser than MinInt64
if intErr == nil && intValue < 0 {
return 0, nil
} else if intErr != nil && intErr.(*strconv.NumError).Err == strconv.ErrRange && intValue < 0 {
return 0, nil
}
return value, err
}
return value, nil
}
// Parses a cgroup param and returns as name, value
// i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234
func getCgroupParamKeyValue(t string) (string, uint64, error) {
parts := strings.Fields(t)
switch len(parts) {
case 2:
value, err := parseUint(parts[1], 10, 64)
if err != nil {
return "", 0, fmt.Errorf("unable to convert param value (%q) to uint64: %v", parts[1], err)
}
return parts[0], value, nil
default:
return "", 0, ErrNotValidFormat
}
}
// Gets a single uint64 value from the specified cgroup file.
func getCgroupParamUint(cgroupPath, cgroupFile string) (uint64, error) {
fileName := filepath.Join(cgroupPath, cgroupFile)
contents, err := ioutil.ReadFile(fileName)
if err != nil {
return 0, err
}
res, err := parseUint(strings.TrimSpace(string(contents)), 10, 64)
if err != nil {
return res, fmt.Errorf("unable to parse %q as a uint from Cgroup file %q", string(contents), fileName)
}
return res, nil
}
// Gets a string value from the specified cgroup file
func getCgroupParamString(cgroupPath, cgroupFile string) (string, error) {
contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile))
if err != nil {
return "", err
}
return strings.TrimSpace(string(contents)), nil
}

View file

@ -1,106 +0,0 @@
// +build linux
package cgroups
type ThrottlingData struct {
// Number of periods with throttling active
Periods uint64 `json:"periods,omitempty"`
// Number of periods when the container hit its throttling limit.
ThrottledPeriods uint64 `json:"throttled_periods,omitempty"`
// Aggregate time the container was throttled for in nanoseconds.
ThrottledTime uint64 `json:"throttled_time,omitempty"`
}
// CpuUsage denotes the usage of a CPU.
// All CPU stats are aggregate since container inception.
type CpuUsage struct {
// Total CPU time consumed.
// Units: nanoseconds.
TotalUsage uint64 `json:"total_usage,omitempty"`
// Total CPU time consumed per core.
// Units: nanoseconds.
PercpuUsage []uint64 `json:"percpu_usage,omitempty"`
// Time spent by tasks of the cgroup in kernel mode.
// Units: nanoseconds.
UsageInKernelmode uint64 `json:"usage_in_kernelmode"`
// Time spent by tasks of the cgroup in user mode.
// Units: nanoseconds.
UsageInUsermode uint64 `json:"usage_in_usermode"`
}
type CpuStats struct {
CpuUsage CpuUsage `json:"cpu_usage,omitempty"`
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
}
type MemoryData struct {
Usage uint64 `json:"usage,omitempty"`
MaxUsage uint64 `json:"max_usage,omitempty"`
Failcnt uint64 `json:"failcnt"`
Limit uint64 `json:"limit"`
}
type MemoryStats struct {
// memory used for cache
Cache uint64 `json:"cache,omitempty"`
// usage of memory
Usage MemoryData `json:"usage,omitempty"`
// usage of memory + swap
SwapUsage MemoryData `json:"swap_usage,omitempty"`
// usage of kernel memory
KernelUsage MemoryData `json:"kernel_usage,omitempty"`
// usage of kernel TCP memory
KernelTCPUsage MemoryData `json:"kernel_tcp_usage,omitempty"`
Stats map[string]uint64 `json:"stats,omitempty"`
}
type PidsStats struct {
// number of pids in the cgroup
Current uint64 `json:"current,omitempty"`
// active pids hard limit
Limit uint64 `json:"limit,omitempty"`
}
type BlkioStatEntry struct {
Major uint64 `json:"major,omitempty"`
Minor uint64 `json:"minor,omitempty"`
Op string `json:"op,omitempty"`
Value uint64 `json:"value,omitempty"`
}
type BlkioStats struct {
// number of bytes tranferred to and from the block device
IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"`
IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive,omitempty"`
IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"`
IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive,omitempty"`
IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive,omitempty"`
IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive,omitempty"`
IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive,omitempty"`
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"`
}
type HugetlbStats struct {
// current res_counter usage for hugetlb
Usage uint64 `json:"usage,omitempty"`
// maximum usage ever recorded.
MaxUsage uint64 `json:"max_usage,omitempty"`
// number of times hugetlb usage allocation failure.
Failcnt uint64 `json:"failcnt"`
}
type Stats struct {
CpuStats CpuStats `json:"cpu_stats,omitempty"`
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
PidsStats PidsStats `json:"pids_stats,omitempty"`
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
// the map is in the format "size of hugepage: stats of the hugepage"
HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"`
}
func NewStats() *Stats {
memoryStats := MemoryStats{Stats: make(map[string]uint64)}
hugetlbStats := make(map[string]HugetlbStats)
return &Stats{MemoryStats: memoryStats, HugetlbStats: hugetlbStats}
}

View file

@ -1,55 +0,0 @@
// +build !linux
package systemd
import (
"fmt"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/configs"
)
type Manager struct {
Cgroups *configs.Cgroup
Paths map[string]string
}
func UseSystemd() bool {
return false
}
func (m *Manager) Apply(pid int) error {
return fmt.Errorf("Systemd not supported")
}
func (m *Manager) GetPids() ([]int, error) {
return nil, fmt.Errorf("Systemd not supported")
}
func (m *Manager) GetAllPids() ([]int, error) {
return nil, fmt.Errorf("Systemd not supported")
}
func (m *Manager) Destroy() error {
return fmt.Errorf("Systemd not supported")
}
func (m *Manager) GetPaths() map[string]string {
return nil
}
func (m *Manager) GetStats() (*cgroups.Stats, error) {
return nil, fmt.Errorf("Systemd not supported")
}
func (m *Manager) Set(container *configs.Config) error {
return nil, fmt.Errorf("Systemd not supported")
}
func (m *Manager) Freeze(state configs.FreezerState) error {
return fmt.Errorf("Systemd not supported")
}
func Freeze(c *configs.Cgroup, state configs.FreezerState) error {
return fmt.Errorf("Systemd not supported")
}

View file

@ -1,471 +0,0 @@
// +build linux
package systemd
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"time"
systemdDbus "github.com/coreos/go-systemd/dbus"
systemdUtil "github.com/coreos/go-systemd/util"
"github.com/godbus/dbus"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fs"
"github.com/opencontainers/runc/libcontainer/configs"
)
type Manager struct {
mu sync.Mutex
Cgroups *configs.Cgroup
Paths map[string]string
}
type subsystem interface {
// Name returns the name of the subsystem.
Name() string
// Returns the stats, as 'stats', corresponding to the cgroup under 'path'.
GetStats(path string, stats *cgroups.Stats) error
// Set the cgroup represented by cgroup.
Set(path string, cgroup *configs.Cgroup) error
}
var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist")
type subsystemSet []subsystem
func (s subsystemSet) Get(name string) (subsystem, error) {
for _, ss := range s {
if ss.Name() == name {
return ss, nil
}
}
return nil, errSubsystemDoesNotExist
}
var subsystems = subsystemSet{
&fs.CpusetGroup{},
&fs.DevicesGroup{},
&fs.MemoryGroup{},
&fs.CpuGroup{},
&fs.CpuacctGroup{},
&fs.PidsGroup{},
&fs.BlkioGroup{},
&fs.HugetlbGroup{},
&fs.PerfEventGroup{},
&fs.FreezerGroup{},
&fs.NetPrioGroup{},
&fs.NetClsGroup{},
&fs.NameGroup{GroupName: "name=systemd"},
}
const (
testScopeWait = 4
)
var (
connLock sync.Mutex
theConn *systemdDbus.Conn
hasStartTransientUnit bool
hasTransientDefaultDependencies bool
)
func newProp(name string, units interface{}) systemdDbus.Property {
return systemdDbus.Property{
Name: name,
Value: dbus.MakeVariant(units),
}
}
func UseSystemd() bool {
if !systemdUtil.IsRunningSystemd() {
return false
}
connLock.Lock()
defer connLock.Unlock()
if theConn == nil {
var err error
theConn, err = systemdDbus.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", nil, nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" {
hasStartTransientUnit = false
return hasStartTransientUnit
}
}
}
// Ensure the scope name we use doesn't exist. Use the Pid to
// avoid collisions between multiple libcontainer users on a
// single host.
scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid())
testScopeExists := true
for i := 0; i <= testScopeWait; i++ {
if _, err := theConn.StopUnit(scope, "replace", nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") {
testScopeExists = false
break
}
}
}
time.Sleep(time.Millisecond)
}
// Bail out if we can't kill this scope without testing for DefaultDependencies
if testScopeExists {
return hasStartTransientUnit
}
// Assume StartTransientUnit on a scope allows DefaultDependencies
hasTransientDefaultDependencies = true
ddf := newProp("DefaultDependencies", false)
if _, err := theConn.StartTransientUnit(scope, "replace", []systemdDbus.Property{ddf}, nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") {
hasTransientDefaultDependencies = false
}
}
}
// Not critical because of the stop unit logic above.
theConn.StopUnit(scope, "replace", nil)
}
return hasStartTransientUnit
}
func (m *Manager) Apply(pid int) error {
var (
c = m.Cgroups
unitName = getUnitName(c)
slice = "system.slice"
properties []systemdDbus.Property
)
if c.Paths != nil {
paths := make(map[string]string)
for name, path := range c.Paths {
_, err := getSubsystemPath(m.Cgroups, name)
if err != nil {
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
if cgroups.IsNotFound(err) {
continue
}
return err
}
paths[name] = path
}
m.Paths = paths
return cgroups.EnterPid(m.Paths, pid)
}
if c.Parent != "" {
slice = c.Parent
}
properties = append(properties,
systemdDbus.PropSlice(slice),
systemdDbus.PropDescription("docker container "+c.Name),
newProp("PIDs", []uint32{uint32(pid)}),
// This is only supported on systemd versions 218 and above.
newProp("Delegate", true),
)
// Always enable accounting, this gets us the same behaviour as the fs implementation,
// plus the kernel has some problems with joining the memory cgroup at a later time.
properties = append(properties,
newProp("MemoryAccounting", true),
newProp("CPUAccounting", true),
newProp("BlockIOAccounting", true))
if hasTransientDefaultDependencies {
properties = append(properties,
newProp("DefaultDependencies", false))
}
if c.Resources.Memory != 0 {
properties = append(properties,
newProp("MemoryLimit", uint64(c.Resources.Memory)))
}
if c.Resources.CpuShares != 0 {
properties = append(properties,
newProp("CPUShares", uint64(c.Resources.CpuShares)))
}
if c.Resources.BlkioWeight != 0 {
properties = append(properties,
newProp("BlockIOWeight", uint64(c.Resources.BlkioWeight)))
}
// We have to set kernel memory here, as we can't change it once
// processes have been attached to the cgroup.
if c.Resources.KernelMemory != 0 {
if err := setKernelMemory(c); err != nil {
return err
}
}
if _, err := theConn.StartTransientUnit(unitName, "replace", properties, nil); err != nil {
return err
}
if err := joinCgroups(c, pid); err != nil {
return err
}
paths := make(map[string]string)
for _, s := range subsystems {
subsystemPath, err := getSubsystemPath(m.Cgroups, s.Name())
if err != nil {
// Don't fail if a cgroup hierarchy was not found, just skip this subsystem
if cgroups.IsNotFound(err) {
continue
}
return err
}
paths[s.Name()] = subsystemPath
}
m.Paths = paths
return nil
}
func (m *Manager) Destroy() error {
if m.Cgroups.Paths != nil {
return nil
}
m.mu.Lock()
defer m.mu.Unlock()
theConn.StopUnit(getUnitName(m.Cgroups), "replace", nil)
if err := cgroups.RemovePaths(m.Paths); err != nil {
return err
}
m.Paths = make(map[string]string)
return nil
}
func (m *Manager) GetPaths() map[string]string {
m.mu.Lock()
paths := m.Paths
m.mu.Unlock()
return paths
}
func writeFile(dir, file, data string) error {
// Normally dir should not be empty, one case is that cgroup subsystem
// is not mounted, we will get empty dir, and we want it fail here.
if dir == "" {
return fmt.Errorf("no such directory for %s", file)
}
return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
}
func join(c *configs.Cgroup, subsystem string, pid int) (string, error) {
path, err := getSubsystemPath(c, subsystem)
if err != nil {
return "", err
}
if err := os.MkdirAll(path, 0755); err != nil {
return "", err
}
if err := writeFile(path, "cgroup.procs", strconv.Itoa(pid)); err != nil {
return "", err
}
return path, nil
}
func joinCgroups(c *configs.Cgroup, pid int) error {
for _, sys := range subsystems {
name := sys.Name()
switch name {
case "name=systemd":
// let systemd handle this
break
case "cpuset":
path, err := getSubsystemPath(c, name)
if err != nil && !cgroups.IsNotFound(err) {
return err
}
s := &fs.CpusetGroup{}
if err := s.ApplyDir(path, c, pid); err != nil {
return err
}
break
default:
_, err := join(c, name, pid)
if err != nil {
// Even if it's `not found` error, we'll return err
// because devices cgroup is hard requirement for
// container security.
if name == "devices" {
return err
}
// For other subsystems, omit the `not found` error
// because they are optional.
if !cgroups.IsNotFound(err) {
return err
}
}
}
}
return nil
}
// systemd represents slice heirarchy using `-`, so we need to follow suit when
// generating the path of slice. Essentially, test-a-b.slice becomes
// test.slice/test-a.slice/test-a-b.slice.
func expandSlice(slice string) (string, error) {
suffix := ".slice"
// Name has to end with ".slice", but can't be just ".slice".
if len(slice) < len(suffix) || !strings.HasSuffix(slice, suffix) {
return "", fmt.Errorf("invalid slice name: %s", slice)
}
// Path-separators are not allowed.
if strings.Contains(slice, "/") {
return "", fmt.Errorf("invalid slice name: %s", slice)
}
var path, prefix string
sliceName := strings.TrimSuffix(slice, suffix)
for _, component := range strings.Split(sliceName, "-") {
// test--a.slice isn't permitted, nor is -test.slice.
if component == "" {
return "", fmt.Errorf("invalid slice name: %s", slice)
}
// Append the component to the path and to the prefix.
path += prefix + component + suffix + "/"
prefix += component + "-"
}
return path, nil
}
func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) {
mountpoint, err := cgroups.FindCgroupMountpoint(subsystem)
if err != nil {
return "", err
}
initPath, err := cgroups.GetInitCgroupDir(subsystem)
if err != nil {
return "", err
}
slice := "system.slice"
if c.Parent != "" {
slice = c.Parent
}
slice, err = expandSlice(slice)
if err != nil {
return "", err
}
return filepath.Join(mountpoint, initPath, slice, getUnitName(c)), nil
}
func (m *Manager) Freeze(state configs.FreezerState) error {
path, err := getSubsystemPath(m.Cgroups, "freezer")
if err != nil {
return err
}
prevState := m.Cgroups.Resources.Freezer
m.Cgroups.Resources.Freezer = state
freezer, err := subsystems.Get("freezer")
if err != nil {
return err
}
err = freezer.Set(path, m.Cgroups)
if err != nil {
m.Cgroups.Resources.Freezer = prevState
return err
}
return nil
}
func (m *Manager) GetPids() ([]int, error) {
path, err := getSubsystemPath(m.Cgroups, "devices")
if err != nil {
return nil, err
}
return cgroups.GetPids(path)
}
func (m *Manager) GetAllPids() ([]int, error) {
path, err := getSubsystemPath(m.Cgroups, "devices")
if err != nil {
return nil, err
}
return cgroups.GetAllPids(path)
}
func (m *Manager) GetStats() (*cgroups.Stats, error) {
m.mu.Lock()
defer m.mu.Unlock()
stats := cgroups.NewStats()
for name, path := range m.Paths {
sys, err := subsystems.Get(name)
if err == errSubsystemDoesNotExist || !cgroups.PathExists(path) {
continue
}
if err := sys.GetStats(path, stats); err != nil {
return nil, err
}
}
return stats, nil
}
func (m *Manager) Set(container *configs.Config) error {
for _, sys := range subsystems {
// Get the subsystem path, but don't error out for not found cgroups.
path, err := getSubsystemPath(container.Cgroups, sys.Name())
if err != nil && !cgroups.IsNotFound(err) {
return err
}
if err := sys.Set(path, container.Cgroups); err != nil {
return err
}
}
if m.Paths["cpu"] != "" {
if err := fs.CheckCpushares(m.Paths["cpu"], container.Cgroups.Resources.CpuShares); err != nil {
return err
}
}
return nil
}
func getUnitName(c *configs.Cgroup) string {
return fmt.Sprintf("%s-%s.scope", c.ScopePrefix, c.Name)
}
func setKernelMemory(c *configs.Cgroup) error {
path, err := getSubsystemPath(c, "memory")
if err != nil && !cgroups.IsNotFound(err) {
return err
}
return os.MkdirAll(path, 0755)
}

View file

@ -1,397 +0,0 @@
// +build linux
package cgroups
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/docker/go-units"
)
const cgroupNamePrefix = "name="
// https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
func FindCgroupMountpoint(subsystem string) (string, error) {
// We are not using mount.GetMounts() because it's super-inefficient,
// parsing it directly sped up x10 times because of not using Sscanf.
// It was one of two major performance drawbacks in container start.
if !isSubsystemAvailable(subsystem) {
return "", NewNotFoundError(subsystem)
}
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
txt := scanner.Text()
fields := strings.Split(txt, " ")
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if opt == subsystem {
return fields[4], nil
}
}
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", NewNotFoundError(subsystem)
}
func FindCgroupMountpointAndRoot(subsystem string) (string, string, error) {
if !isSubsystemAvailable(subsystem) {
return "", "", NewNotFoundError(subsystem)
}
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return "", "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
txt := scanner.Text()
fields := strings.Split(txt, " ")
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if opt == subsystem {
return fields[4], fields[3], nil
}
}
}
if err := scanner.Err(); err != nil {
return "", "", err
}
return "", "", NewNotFoundError(subsystem)
}
func isSubsystemAvailable(subsystem string) bool {
cgroups, err := ParseCgroupFile("/proc/self/cgroup")
if err != nil {
return false
}
_, avail := cgroups[subsystem]
return avail
}
func FindCgroupMountpointDir() (string, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
text := scanner.Text()
fields := strings.Split(text, " ")
// Safe as mountinfo encodes mountpoints with spaces as \040.
index := strings.Index(text, " - ")
postSeparatorFields := strings.Fields(text[index+3:])
numPostFields := len(postSeparatorFields)
// This is an error as we can't detect if the mount is for "cgroup"
if numPostFields == 0 {
return "", fmt.Errorf("Found no fields post '-' in %q", text)
}
if postSeparatorFields[0] == "cgroup" {
// Check that the mount is properly formated.
if numPostFields < 3 {
return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
}
return filepath.Dir(fields[4]), nil
}
}
if err := scanner.Err(); err != nil {
return "", err
}
return "", NewNotFoundError("cgroup")
}
type Mount struct {
Mountpoint string
Root string
Subsystems []string
}
func (m Mount) GetThisCgroupDir(cgroups map[string]string) (string, error) {
if len(m.Subsystems) == 0 {
return "", fmt.Errorf("no subsystem for mount")
}
return getControllerPath(m.Subsystems[0], cgroups)
}
func getCgroupMountsHelper(ss map[string]bool, mi io.Reader) ([]Mount, error) {
res := make([]Mount, 0, len(ss))
scanner := bufio.NewScanner(mi)
numFound := 0
for scanner.Scan() && numFound < len(ss) {
txt := scanner.Text()
sepIdx := strings.Index(txt, " - ")
if sepIdx == -1 {
return nil, fmt.Errorf("invalid mountinfo format")
}
if txt[sepIdx+3:sepIdx+9] != "cgroup" {
continue
}
fields := strings.Split(txt, " ")
m := Mount{
Mountpoint: fields[4],
Root: fields[3],
}
for _, opt := range strings.Split(fields[len(fields)-1], ",") {
if !ss[opt] {
continue
}
if strings.HasPrefix(opt, cgroupNamePrefix) {
m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):])
} else {
m.Subsystems = append(m.Subsystems, opt)
}
numFound++
}
res = append(res, m)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return res, nil
}
func GetCgroupMounts() ([]Mount, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return nil, err
}
defer f.Close()
all, err := ParseCgroupFile("/proc/self/cgroup")
if err != nil {
return nil, err
}
allMap := make(map[string]bool)
for s := range all {
allMap[s] = true
}
return getCgroupMountsHelper(allMap, f)
}
// GetAllSubsystems returns all the cgroup subsystems supported by the kernel
func GetAllSubsystems() ([]string, error) {
f, err := os.Open("/proc/cgroups")
if err != nil {
return nil, err
}
defer f.Close()
subsystems := []string{}
s := bufio.NewScanner(f)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text()
if text[0] != '#' {
parts := strings.Fields(text)
if len(parts) >= 4 && parts[3] != "0" {
subsystems = append(subsystems, parts[0])
}
}
}
return subsystems, nil
}
// GetThisCgroupDir returns the relative path to the cgroup docker is running in.
func GetThisCgroupDir(subsystem string) (string, error) {
cgroups, err := ParseCgroupFile("/proc/self/cgroup")
if err != nil {
return "", err
}
return getControllerPath(subsystem, cgroups)
}
func GetInitCgroupDir(subsystem string) (string, error) {
cgroups, err := ParseCgroupFile("/proc/1/cgroup")
if err != nil {
return "", err
}
return getControllerPath(subsystem, cgroups)
}
func readProcsFile(dir string) ([]int, error) {
f, err := os.Open(filepath.Join(dir, "cgroup.procs"))
if err != nil {
return nil, err
}
defer f.Close()
var (
s = bufio.NewScanner(f)
out = []int{}
)
for s.Scan() {
if t := s.Text(); t != "" {
pid, err := strconv.Atoi(t)
if err != nil {
return nil, err
}
out = append(out, pid)
}
}
return out, nil
}
func ParseCgroupFile(path string) (map[string]string, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
s := bufio.NewScanner(f)
cgroups := make(map[string]string)
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
text := s.Text()
parts := strings.Split(text, ":")
for _, subs := range strings.Split(parts[1], ",") {
cgroups[subs] = parts[2]
}
}
return cgroups, nil
}
func getControllerPath(subsystem string, cgroups map[string]string) (string, error) {
if p, ok := cgroups[subsystem]; ok {
return p, nil
}
if p, ok := cgroups[cgroupNamePrefix+subsystem]; ok {
return p, nil
}
return "", NewNotFoundError(subsystem)
}
func PathExists(path string) bool {
if _, err := os.Stat(path); err != nil {
return false
}
return true
}
func EnterPid(cgroupPaths map[string]string, pid int) error {
for _, path := range cgroupPaths {
if PathExists(path) {
if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"),
[]byte(strconv.Itoa(pid)), 0700); err != nil {
return err
}
}
}
return nil
}
// RemovePaths iterates over the provided paths removing them.
// We trying to remove all paths five times with increasing delay between tries.
// If after all there are not removed cgroups - appropriate error will be
// returned.
func RemovePaths(paths map[string]string) (err error) {
delay := 10 * time.Millisecond
for i := 0; i < 5; i++ {
if i != 0 {
time.Sleep(delay)
delay *= 2
}
for s, p := range paths {
os.RemoveAll(p)
// TODO: here probably should be logging
_, err := os.Stat(p)
// We need this strange way of checking cgroups existence because
// RemoveAll almost always returns error, even on already removed
// cgroups
if os.IsNotExist(err) {
delete(paths, s)
}
}
if len(paths) == 0 {
return nil
}
}
return fmt.Errorf("Failed to remove paths: %v", paths)
}
func GetHugePageSize() ([]string, error) {
var pageSizes []string
sizeList := []string{"B", "kB", "MB", "GB", "TB", "PB"}
files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages")
if err != nil {
return pageSizes, err
}
for _, st := range files {
nameArray := strings.Split(st.Name(), "-")
pageSize, err := units.RAMInBytes(nameArray[1])
if err != nil {
return []string{}, err
}
sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList)
pageSizes = append(pageSizes, sizeString)
}
return pageSizes, nil
}
// GetPids returns all pids, that were added to cgroup at path.
func GetPids(path string) ([]int, error) {
return readProcsFile(path)
}
// GetAllPids returns all pids, that were added to cgroup at path and to all its
// subcgroups.
func GetAllPids(path string) ([]int, error) {
var pids []int
// collect pids from all sub-cgroups
err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error {
dir, file := filepath.Split(p)
if file != "cgroup.procs" {
return nil
}
if iErr != nil {
return iErr
}
cPids, err := readProcsFile(dir)
if err != nil {
return err
}
pids = append(pids, cPids...)
return nil
})
return pids, err
}

View file

@ -1,10 +0,0 @@
// +build linux,!go1.5
package libcontainer
import "syscall"
// GidMappingsEnableSetgroups was added in Go 1.5, so do nothing when building
// with earlier versions
func enableSetgroups(sys *syscall.SysProcAttr) {
}

Some files were not shown because too many files have changed in this diff Show more