mirror of
https://github.com/clearlinux/libnetwork.git
synced 2026-07-14 00:05:47 +00:00
Compare commits
208 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a02aac8e0b | |||
| 7308fd4489 | |||
| 47f8b25ffb | |||
| f88f4f01c4 | |||
| fb2c0dfbad | |||
| fa78a98fc6 | |||
| 2b57d3f0ee | |||
| 8644d00d3c | |||
| 4ba1540409 | |||
| 6678d7202d | |||
| 15cbcc55eb | |||
| a11cac719f | |||
| d33168d764 | |||
| bef625fe8a | |||
| 376f1a5617 | |||
| e7a16734a2 | |||
| 56369550a7 | |||
| 06d7388e8e | |||
| 71361ee07d | |||
| 95ab32fb72 | |||
| d8ba1e2310 | |||
| 7242f8fe08 | |||
| c756c47613 | |||
| 88cb8027ab | |||
| 34fcbeb31e | |||
| 763c2ace65 | |||
| 99af9a6e45 | |||
| 71bf530cda | |||
| ee72ee177d | |||
| 63a5c47585 | |||
| 57e3bc4718 | |||
| 5e044b5b5f | |||
| 1e84379b1e | |||
| 1eca8d4088 | |||
| 78c81a0fa6 | |||
| 6a0f240c18 | |||
| 29250c28c1 | |||
| 6602342a7f | |||
| 655d7149af | |||
| 652f3d6182 | |||
| c562f5e262 | |||
| a971716f29 | |||
| 3c1dd60ebb | |||
| 0f3d06a0fd | |||
| 6a6a45a58d | |||
| 24d744dbf7 | |||
| eee7b94d82 | |||
| 0c2e1e2f9a | |||
| b3b3bfb556 | |||
| 58bbb00972 | |||
| c6e78965a9 | |||
| 37d554b85f | |||
| 9ea1fd082a | |||
| 9006619355 | |||
| 55ce254546 | |||
| 5968d0ce1f | |||
| e5d9dfb87b | |||
| d3f628541c | |||
| 1d34250950 | |||
| 731ffc20d9 | |||
| de690d7348 | |||
| 8c57fddf94 | |||
| 3db57f2a0e | |||
| 9a68cc06c9 | |||
| 51ec188af7 | |||
| 58a647c6f9 | |||
| c6facf773d | |||
| 0011d46ead | |||
| 914b9a56c6 | |||
| 81443fd853 | |||
| d4f6960b88 | |||
| f57b90ae61 | |||
| 44c96449c2 | |||
| 1ab172a0bb | |||
| 1a6371f39c | |||
| aa85b3e092 | |||
| 876f3fc639 | |||
| 6ce4099460 | |||
| a8556518f6 | |||
| f91e47193f | |||
| fd986bb1be | |||
| 5bb8c28b44 | |||
| 94fd20f941 | |||
| e1b5e17165 | |||
| 26779b6fea | |||
| 309ba92587 | |||
| 91bc12ef2d | |||
| ede31dce0a | |||
| 511459f1af | |||
| 9a7cceced6 | |||
| 8d2339324e | |||
| 0c167ba011 | |||
| fe2c704109 | |||
| f21785e451 | |||
| 417093c3f5 | |||
| 6bb9881907 | |||
| 4abc259f84 | |||
| 9897c23464 | |||
| c203b3959e | |||
| 3098ee6628 | |||
| fe87ce50bc | |||
| 3678144103 | |||
| 5d28b9f14c | |||
| 5f9906ddff | |||
| a7ac2c7454 | |||
| 418da5befb | |||
| ac1c5af15f | |||
| d35bd47790 | |||
| c8062e4ad4 | |||
| eeb243ca54 | |||
| bce6960c98 | |||
| eee24e4cec | |||
| df2327aec9 | |||
| 8e3458283e | |||
| f42056ad40 | |||
| 6261f5535c | |||
| bd00a055ee | |||
| ba38542e95 | |||
| 5025c5395f | |||
| dd65231f1c | |||
| 18e3679789 | |||
| eecfd7acb5 | |||
| 3bb407a21f | |||
| e321761977 | |||
| 2e27ab5aad | |||
| 3686b72c56 | |||
| b041485b68 | |||
| 6dcd957b12 | |||
| 0e34c21f8c | |||
| 95068e6583 | |||
| ce3965049e | |||
| b966b4a995 | |||
| 5b800ca696 | |||
| aeeb2fa909 | |||
| 68ba9ca5aa | |||
| e91f827b23 | |||
| b7fc310643 | |||
| d6c0c261a3 | |||
| 0a08d3a145 | |||
| eb91f0ba93 | |||
| 1abbe4d6fa | |||
| 84a964114c | |||
| 4d6dec8c43 | |||
| 31bacb525f | |||
| d581ee510d | |||
| d88d333e95 | |||
| c7b679e686 | |||
| efa85f73f7 | |||
| 63e19f02fc | |||
| 9c3c270d6f | |||
| 1e7b0b2caf | |||
| 043a23ae12 | |||
| 1933403d30 | |||
| 966c9a8884 | |||
| 61d75554ae | |||
| eca040df2b | |||
| 70b7d259af | |||
| cfa284ed18 | |||
| 810a4676c2 | |||
| 75d7d43f9c | |||
| 9bdba881be | |||
| d17b3c32b2 | |||
| ee4b5cc7e3 | |||
| 67ccf8ec77 | |||
| 3b184f863f | |||
| 3d34a574ad | |||
| 686ce2c14d | |||
| 778e2a72b3 | |||
| ddd11cce43 | |||
| 74119651d1 | |||
| 688cf5c646 | |||
| 9e3d6b5902 | |||
| c60935a41a | |||
| bf9450eec0 | |||
| 213a988b95 | |||
| dddec4bd84 | |||
| f8e436522e | |||
| 972c798cf0 | |||
| 7b97ea3b5f | |||
| 2ab19b7727 | |||
| 6d1d4375bf | |||
| 77715cf013 | |||
| 25eed6b0c2 | |||
| 9d600c0870 | |||
| f54087431c | |||
| 2327269e15 | |||
| a31f9a65a0 | |||
| 64fc8b02e3 | |||
| cb61545e60 | |||
| 55fe669caa | |||
| 2ab4d9ffd8 | |||
| a88fc50cbc | |||
| 7a8e0742ed | |||
| ea7f555446 | |||
| 3841876e17 | |||
| 58f62a22cd | |||
| b5a79b03a6 | |||
| 29f28ca925 | |||
| abcebc5108 | |||
| 6adf1defbb | |||
| ecb204b4be | |||
| e6f2ce6b70 | |||
| 839c8ef24d | |||
| 931b61fc59 | |||
| d02ed12be3 | |||
| 5ec1e4377a | |||
| b6f17e3de8 | |||
| c14c967d85 |
@@ -22,3 +22,12 @@ _testmain.go
|
|||||||
*.exe
|
*.exe
|
||||||
*.test
|
*.test
|
||||||
*.prof
|
*.prof
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
*.tmp
|
||||||
|
*.coverprofile
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.project
|
||||||
|
|
||||||
|
libnetwork-build.created
|
||||||
|
|||||||
Generated
+57
-11
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/libnetwork",
|
"ImportPath": "github.com/docker/libnetwork",
|
||||||
"GoVersion": "go1.2.1",
|
"GoVersion": "go1.4.1",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/Sirupsen/logrus",
|
"ImportPath": "github.com/Sirupsen/logrus",
|
||||||
@@ -8,24 +11,67 @@
|
|||||||
"Rev": "467d9d55c2d2c17248441a8fc661561161f40d5e"
|
"Rev": "467d9d55c2d2c17248441a8fc661561161f40d5e"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/pkg/iptables",
|
"ImportPath": "github.com/docker/docker/pkg/homedir",
|
||||||
"Comment": "v1.4.1-2492-ge690ad9",
|
"Comment": "v1.4.1-3479-ga9172f5",
|
||||||
"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
|
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/pkg/parsers/kernel",
|
"ImportPath": "github.com/docker/docker/pkg/ioutils",
|
||||||
"Comment": "v1.4.1-2492-ge690ad9",
|
"Comment": "v1.4.1-3479-ga9172f5",
|
||||||
"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
|
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/pkg/mflag",
|
||||||
|
"Comment": "v1.4.1-3479-ga9172f5",
|
||||||
|
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/pkg/parsers",
|
||||||
|
"Comment": "v1.4.1-3479-ga9172f5",
|
||||||
|
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/pkg/plugins",
|
||||||
|
"Comment": "v1.4.1-3479-ga9172f5",
|
||||||
|
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/pkg/proxy",
|
"ImportPath": "github.com/docker/docker/pkg/proxy",
|
||||||
"Comment": "v1.4.1-2492-ge690ad9",
|
"Comment": "v1.4.1-3479-ga9172f5",
|
||||||
"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
|
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/pkg/reexec",
|
"ImportPath": "github.com/docker/docker/pkg/reexec",
|
||||||
"Comment": "v1.4.1-2492-ge690ad9",
|
"Comment": "v1.4.1-3479-ga9172f5",
|
||||||
"Rev": "e690ad92925a045344bde8d2d59d7a7f602dded6"
|
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/pkg/stringid",
|
||||||
|
"Comment": "v1.4.1-3479-ga9172f5",
|
||||||
|
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/docker/pkg/term",
|
||||||
|
"Comment": "v1.4.1-3479-ga9172f5",
|
||||||
|
"Rev": "a9172f572e13086859c652e2d581950e910d63d4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/docker/libcontainer/user",
|
||||||
|
"Comment": "v1.4.0-495-g3e66118",
|
||||||
|
"Rev": "3e661186ba24f259d3860f067df052c7f6904bee"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/godbus/dbus",
|
||||||
|
"Comment": "v2-3-g4160802",
|
||||||
|
"Rev": "41608027bdce7bfa8959d653a00b954591220e67"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/context",
|
||||||
|
"Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/gorilla/mux",
|
||||||
|
"Rev": "8096f47503459bcc74d1f4c487b7e6e42e5746b5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/vishvananda/netlink",
|
"ImportPath": "github.com/vishvananda/netlink",
|
||||||
|
|||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package homedir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/docker/libcontainer/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key returns the env var name for the user's home dir based on
|
||||||
|
// the platform being run on
|
||||||
|
func Key() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "USERPROFILE"
|
||||||
|
}
|
||||||
|
return "HOME"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the home directory of the current user with the help of
|
||||||
|
// environment variables depending on the target operating system.
|
||||||
|
// Returned path should be used with "path/filepath" to form new paths.
|
||||||
|
func Get() string {
|
||||||
|
home := os.Getenv(Key())
|
||||||
|
if home == "" && runtime.GOOS != "windows" {
|
||||||
|
if u, err := user.CurrentUser(); err == nil {
|
||||||
|
return u.Home
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return home
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetShortcutString returns the string that is shortcut to user's home directory
|
||||||
|
// in the native shell of the platform running on.
|
||||||
|
func GetShortcutString() string {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return "%USERPROFILE%" // be careful while using in format functions
|
||||||
|
}
|
||||||
|
return "~"
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
package homedir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
home := Get()
|
||||||
|
if home == "" {
|
||||||
|
t.Fatal("returned home directory is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filepath.IsAbs(home) {
|
||||||
|
t.Fatalf("returned path is not absolute: %s", home)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetShortcutString(t *testing.T) {
|
||||||
|
shortcut := GetShortcutString()
|
||||||
|
if shortcut == "" {
|
||||||
|
t.Fatal("returned shortcut string is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
+227
@@ -0,0 +1,227 @@
|
|||||||
|
package ioutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type readCloserWrapper struct {
|
||||||
|
io.Reader
|
||||||
|
closer func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *readCloserWrapper) Close() error {
|
||||||
|
return r.closer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser {
|
||||||
|
return &readCloserWrapper{
|
||||||
|
Reader: r,
|
||||||
|
closer: closer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type readerErrWrapper struct {
|
||||||
|
reader io.Reader
|
||||||
|
closer func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *readerErrWrapper) Read(p []byte) (int, error) {
|
||||||
|
n, err := r.reader.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
r.closer()
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
|
||||||
|
return &readerErrWrapper{
|
||||||
|
reader: r,
|
||||||
|
closer: closer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufReader allows the underlying reader to continue to produce
|
||||||
|
// output by pre-emptively reading from the wrapped reader.
|
||||||
|
// This is achieved by buffering this data in bufReader's
|
||||||
|
// expanding buffer.
|
||||||
|
type bufReader struct {
|
||||||
|
sync.Mutex
|
||||||
|
buf *bytes.Buffer
|
||||||
|
reader io.Reader
|
||||||
|
err error
|
||||||
|
wait sync.Cond
|
||||||
|
drainBuf []byte
|
||||||
|
reuseBuf []byte
|
||||||
|
maxReuse int64
|
||||||
|
resetTimeout time.Duration
|
||||||
|
bufLenResetThreshold int64
|
||||||
|
maxReadDataReset int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBufReader(r io.Reader) *bufReader {
|
||||||
|
var timeout int
|
||||||
|
if randVal, err := rand.Int(rand.Reader, big.NewInt(120)); err == nil {
|
||||||
|
timeout = int(randVal.Int64()) + 180
|
||||||
|
} else {
|
||||||
|
timeout = 300
|
||||||
|
}
|
||||||
|
reader := &bufReader{
|
||||||
|
buf: &bytes.Buffer{},
|
||||||
|
drainBuf: make([]byte, 1024),
|
||||||
|
reuseBuf: make([]byte, 4096),
|
||||||
|
maxReuse: 1000,
|
||||||
|
resetTimeout: time.Second * time.Duration(timeout),
|
||||||
|
bufLenResetThreshold: 100 * 1024,
|
||||||
|
maxReadDataReset: 10 * 1024 * 1024,
|
||||||
|
reader: r,
|
||||||
|
}
|
||||||
|
reader.wait.L = &reader.Mutex
|
||||||
|
go reader.drain()
|
||||||
|
return reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer *bytes.Buffer) *bufReader {
|
||||||
|
reader := &bufReader{
|
||||||
|
buf: buffer,
|
||||||
|
drainBuf: drainBuffer,
|
||||||
|
reader: r,
|
||||||
|
}
|
||||||
|
reader.wait.L = &reader.Mutex
|
||||||
|
go reader.drain()
|
||||||
|
return reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *bufReader) drain() {
|
||||||
|
var (
|
||||||
|
duration time.Duration
|
||||||
|
lastReset time.Time
|
||||||
|
now time.Time
|
||||||
|
reset bool
|
||||||
|
bufLen int64
|
||||||
|
dataSinceReset int64
|
||||||
|
maxBufLen int64
|
||||||
|
reuseBufLen int64
|
||||||
|
reuseCount int64
|
||||||
|
)
|
||||||
|
reuseBufLen = int64(len(r.reuseBuf))
|
||||||
|
lastReset = time.Now()
|
||||||
|
for {
|
||||||
|
n, err := r.reader.Read(r.drainBuf)
|
||||||
|
dataSinceReset += int64(n)
|
||||||
|
r.Lock()
|
||||||
|
bufLen = int64(r.buf.Len())
|
||||||
|
if bufLen > maxBufLen {
|
||||||
|
maxBufLen = bufLen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid unbounded growth of the buffer over time.
|
||||||
|
// This has been discovered to be the only non-intrusive
|
||||||
|
// solution to the unbounded growth of the buffer.
|
||||||
|
// Alternative solutions such as compression, multiple
|
||||||
|
// buffers, channels and other similar pieces of code
|
||||||
|
// were reducing throughput, overall Docker performance
|
||||||
|
// or simply crashed Docker.
|
||||||
|
// This solution releases the buffer when specific
|
||||||
|
// conditions are met to avoid the continuous resizing
|
||||||
|
// of the buffer for long lived containers.
|
||||||
|
//
|
||||||
|
// Move data to the front of the buffer if it's
|
||||||
|
// smaller than what reuseBuf can store
|
||||||
|
if bufLen > 0 && reuseBufLen >= bufLen {
|
||||||
|
n, _ := r.buf.Read(r.reuseBuf)
|
||||||
|
r.buf.Write(r.reuseBuf[0:n])
|
||||||
|
// Take action if the buffer has been reused too many
|
||||||
|
// times and if there's data in the buffer.
|
||||||
|
// The timeout is also used as means to avoid doing
|
||||||
|
// these operations more often or less often than
|
||||||
|
// required.
|
||||||
|
// The various conditions try to detect heavy activity
|
||||||
|
// in the buffer which might be indicators of heavy
|
||||||
|
// growth of the buffer.
|
||||||
|
} else if reuseCount >= r.maxReuse && bufLen > 0 {
|
||||||
|
now = time.Now()
|
||||||
|
duration = now.Sub(lastReset)
|
||||||
|
timeoutReached := duration >= r.resetTimeout
|
||||||
|
|
||||||
|
// The timeout has been reached and the
|
||||||
|
// buffered data couldn't be moved to the front
|
||||||
|
// of the buffer, so the buffer gets reset.
|
||||||
|
if timeoutReached && bufLen > reuseBufLen {
|
||||||
|
reset = true
|
||||||
|
}
|
||||||
|
// The amount of buffered data is too high now,
|
||||||
|
// reset the buffer.
|
||||||
|
if timeoutReached && maxBufLen >= r.bufLenResetThreshold {
|
||||||
|
reset = true
|
||||||
|
}
|
||||||
|
// Reset the buffer if a certain amount of
|
||||||
|
// data has gone through the buffer since the
|
||||||
|
// last reset.
|
||||||
|
if timeoutReached && dataSinceReset >= r.maxReadDataReset {
|
||||||
|
reset = true
|
||||||
|
}
|
||||||
|
// The buffered data is moved to a fresh buffer,
|
||||||
|
// swap the old buffer with the new one and
|
||||||
|
// reset all counters.
|
||||||
|
if reset {
|
||||||
|
newbuf := &bytes.Buffer{}
|
||||||
|
newbuf.ReadFrom(r.buf)
|
||||||
|
r.buf = newbuf
|
||||||
|
lastReset = now
|
||||||
|
reset = false
|
||||||
|
dataSinceReset = 0
|
||||||
|
maxBufLen = 0
|
||||||
|
reuseCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
r.err = err
|
||||||
|
} else {
|
||||||
|
r.buf.Write(r.drainBuf[0:n])
|
||||||
|
}
|
||||||
|
reuseCount++
|
||||||
|
r.wait.Signal()
|
||||||
|
r.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *bufReader) Read(p []byte) (n int, err error) {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
for {
|
||||||
|
n, err = r.buf.Read(p)
|
||||||
|
if n > 0 {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if r.err != nil {
|
||||||
|
return 0, r.err
|
||||||
|
}
|
||||||
|
r.wait.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *bufReader) Close() error {
|
||||||
|
closer, ok := r.reader.(io.ReadCloser)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func HashData(src io.Reader) (string, error) {
|
||||||
|
h := sha256.New()
|
||||||
|
if _, err := io.Copy(h, src); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
|
||||||
|
}
|
||||||
+217
@@ -0,0 +1,217 @@
|
|||||||
|
package ioutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implement io.Reader
|
||||||
|
type errorReader struct{}
|
||||||
|
|
||||||
|
func (r *errorReader) Read(p []byte) (int, error) {
|
||||||
|
return 0, fmt.Errorf("Error reader always fail.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadCloserWrapperClose(t *testing.T) {
|
||||||
|
reader := strings.NewReader("A string reader")
|
||||||
|
wrapper := NewReadCloserWrapper(reader, func() error {
|
||||||
|
return fmt.Errorf("This will be called when closing")
|
||||||
|
})
|
||||||
|
err := wrapper.Close()
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "This will be called when closing") {
|
||||||
|
t.Fatalf("readCloserWrapper should have call the anonymous func and thus, fail.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaderErrWrapperReadOnError(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
reader := &errorReader{}
|
||||||
|
wrapper := NewReaderErrWrapper(reader, func() {
|
||||||
|
called = true
|
||||||
|
})
|
||||||
|
_, err := wrapper.Read([]byte{})
|
||||||
|
if err == nil || !strings.Contains(err.Error(), "Error reader always fail.") {
|
||||||
|
t.Fatalf("readErrWrapper should returned an error")
|
||||||
|
}
|
||||||
|
if !called {
|
||||||
|
t.Fatalf("readErrWrapper should have call the anonymous function on failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReaderErrWrapperRead(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
reader := strings.NewReader("a string reader.")
|
||||||
|
wrapper := NewReaderErrWrapper(reader, func() {
|
||||||
|
called = true // Should not be called
|
||||||
|
})
|
||||||
|
// Read 20 byte (should be ok with the string above)
|
||||||
|
num, err := wrapper.Read(make([]byte, 20))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if num != 16 {
|
||||||
|
t.Fatalf("readerErrWrapper should have read 16 byte, but read %d", num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewBufReaderWithDrainbufAndBuffer(t *testing.T) {
|
||||||
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
|
drainBuffer := make([]byte, 1024)
|
||||||
|
buffer := bytes.Buffer{}
|
||||||
|
bufreader := NewBufReaderWithDrainbufAndBuffer(reader, drainBuffer, &buffer)
|
||||||
|
|
||||||
|
// Write everything down to a Pipe
|
||||||
|
// Usually, a pipe should block but because of the buffered reader,
|
||||||
|
// the writes will go through
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
writer.Write([]byte("hello world"))
|
||||||
|
writer.Close()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Drain the reader *after* everything has been written, just to verify
|
||||||
|
// it is indeed buffering
|
||||||
|
<-done
|
||||||
|
|
||||||
|
output, err := ioutil.ReadAll(bufreader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(output, []byte("hello world")) {
|
||||||
|
t.Error(string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufReader(t *testing.T) {
|
||||||
|
reader, writer := io.Pipe()
|
||||||
|
bufreader := NewBufReader(reader)
|
||||||
|
|
||||||
|
// Write everything down to a Pipe
|
||||||
|
// Usually, a pipe should block but because of the buffered reader,
|
||||||
|
// the writes will go through
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
writer.Write([]byte("hello world"))
|
||||||
|
writer.Close()
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Drain the reader *after* everything has been written, just to verify
|
||||||
|
// it is indeed buffering
|
||||||
|
<-done
|
||||||
|
output, err := ioutil.ReadAll(bufreader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(output, []byte("hello world")) {
|
||||||
|
t.Error(string(output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufReaderCloseWithNonReaderCloser(t *testing.T) {
|
||||||
|
reader := strings.NewReader("buffer")
|
||||||
|
bufreader := NewBufReader(reader)
|
||||||
|
|
||||||
|
if err := bufreader.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements io.ReadCloser
|
||||||
|
type simpleReaderCloser struct{}
|
||||||
|
|
||||||
|
func (r *simpleReaderCloser) Read(p []byte) (n int, err error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *simpleReaderCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBufReaderCloseWithReaderCloser(t *testing.T) {
|
||||||
|
reader := &simpleReaderCloser{}
|
||||||
|
bufreader := NewBufReader(reader)
|
||||||
|
|
||||||
|
err := bufreader.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHashData(t *testing.T) {
|
||||||
|
reader := strings.NewReader("hash-me")
|
||||||
|
actual, err := HashData(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
expected := "sha256:4d11186aed035cc624d553e10db358492c84a7cd6b9670d92123c144930450aa"
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("Expecting %s, got %s", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type repeatedReader struct {
|
||||||
|
readCount int
|
||||||
|
maxReads int
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRepeatedReader(max int, data []byte) *repeatedReader {
|
||||||
|
return &repeatedReader{0, max, data}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *repeatedReader) Read(p []byte) (int, error) {
|
||||||
|
if r.readCount >= r.maxReads {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
r.readCount++
|
||||||
|
n := copy(p, r.data)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testWithData(data []byte, reads int) {
|
||||||
|
reader := newRepeatedReader(reads, data)
|
||||||
|
bufReader := NewBufReader(reader)
|
||||||
|
io.Copy(ioutil.Discard, bufReader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark1M10BytesReads(b *testing.B) {
|
||||||
|
reads := 1000000
|
||||||
|
readSize := int64(10)
|
||||||
|
data := make([]byte, readSize)
|
||||||
|
b.SetBytes(readSize * int64(reads))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testWithData(data, reads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark1M1024BytesReads(b *testing.B) {
|
||||||
|
reads := 1000000
|
||||||
|
readSize := int64(1024)
|
||||||
|
data := make([]byte, readSize)
|
||||||
|
b.SetBytes(readSize * int64(reads))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testWithData(data, reads)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark10k32KBytesReads(b *testing.B) {
|
||||||
|
reads := 10000
|
||||||
|
readSize := int64(32 * 1024)
|
||||||
|
data := make([]byte, readSize)
|
||||||
|
b.SetBytes(readSize * int64(reads))
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testWithData(data, reads)
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package ioutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WriteFlusher struct {
|
||||||
|
sync.Mutex
|
||||||
|
w io.Writer
|
||||||
|
flusher http.Flusher
|
||||||
|
flushed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
|
||||||
|
wf.Lock()
|
||||||
|
defer wf.Unlock()
|
||||||
|
n, err = wf.w.Write(b)
|
||||||
|
wf.flushed = true
|
||||||
|
wf.flusher.Flush()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the stream immediately.
|
||||||
|
func (wf *WriteFlusher) Flush() {
|
||||||
|
wf.Lock()
|
||||||
|
defer wf.Unlock()
|
||||||
|
wf.flushed = true
|
||||||
|
wf.flusher.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wf *WriteFlusher) Flushed() bool {
|
||||||
|
wf.Lock()
|
||||||
|
defer wf.Unlock()
|
||||||
|
return wf.flushed
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
||||||
|
var flusher http.Flusher
|
||||||
|
if f, ok := w.(http.Flusher); ok {
|
||||||
|
flusher = f
|
||||||
|
} else {
|
||||||
|
flusher = &NopFlusher{}
|
||||||
|
}
|
||||||
|
return &WriteFlusher{w: w, flusher: flusher}
|
||||||
|
}
|
||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
package ioutils
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type NopWriter struct{}
|
||||||
|
|
||||||
|
func (*NopWriter) Write(buf []byte) (int, error) {
|
||||||
|
return len(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopWriteCloser struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *nopWriteCloser) Close() error { return nil }
|
||||||
|
|
||||||
|
func NopWriteCloser(w io.Writer) io.WriteCloser {
|
||||||
|
return &nopWriteCloser{w}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NopFlusher struct{}
|
||||||
|
|
||||||
|
func (f *NopFlusher) Flush() {}
|
||||||
|
|
||||||
|
type writeCloserWrapper struct {
|
||||||
|
io.Writer
|
||||||
|
closer func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *writeCloserWrapper) Close() error {
|
||||||
|
return r.closer()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser {
|
||||||
|
return &writeCloserWrapper{
|
||||||
|
Writer: r,
|
||||||
|
closer: closer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap a concrete io.Writer and hold a count of the number
|
||||||
|
// of bytes written to the writer during a "session".
|
||||||
|
// This can be convenient when write return is masked
|
||||||
|
// (e.g., json.Encoder.Encode())
|
||||||
|
type WriteCounter struct {
|
||||||
|
Count int64
|
||||||
|
Writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWriteCounter(w io.Writer) *WriteCounter {
|
||||||
|
return &WriteCounter{
|
||||||
|
Writer: w,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wc *WriteCounter) Write(p []byte) (count int, err error) {
|
||||||
|
count, err = wc.Writer.Write(p)
|
||||||
|
wc.Count += int64(count)
|
||||||
|
return
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package ioutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriteCloserWrapperClose(t *testing.T) {
|
||||||
|
called := false
|
||||||
|
writer := bytes.NewBuffer([]byte{})
|
||||||
|
wrapper := NewWriteCloserWrapper(writer, func() error {
|
||||||
|
called = true
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err := wrapper.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !called {
|
||||||
|
t.Fatalf("writeCloserWrapper should have call the anonymous function.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNopWriteCloser(t *testing.T) {
|
||||||
|
writer := bytes.NewBuffer([]byte{})
|
||||||
|
wrapper := NopWriteCloser(writer)
|
||||||
|
if err := wrapper.Close(); err != nil {
|
||||||
|
t.Fatal("NopWriteCloser always return nil on Close.")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNopWriter(t *testing.T) {
|
||||||
|
nw := &NopWriter{}
|
||||||
|
l, err := nw.Write([]byte{'c'})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if l != 1 {
|
||||||
|
t.Fatalf("Expected 1 got %d", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteCounter(t *testing.T) {
|
||||||
|
dummy1 := "This is a dummy string."
|
||||||
|
dummy2 := "This is another dummy string."
|
||||||
|
totalLength := int64(len(dummy1) + len(dummy2))
|
||||||
|
|
||||||
|
reader1 := strings.NewReader(dummy1)
|
||||||
|
reader2 := strings.NewReader(dummy2)
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
wc := NewWriteCounter(&buffer)
|
||||||
|
|
||||||
|
reader1.WriteTo(wc)
|
||||||
|
reader2.WriteTo(wc)
|
||||||
|
|
||||||
|
if wc.Count != totalLength {
|
||||||
|
t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
if buffer.String() != dummy1+dummy2 {
|
||||||
|
t.Error("Wrong message written")
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2014-2015 The Docker & Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
Package mflag (aka multiple-flag) implements command-line flag parsing.
|
||||||
|
It's an **hacky** fork of the [official golang package](http://golang.org/pkg/flag/)
|
||||||
|
|
||||||
|
It adds:
|
||||||
|
|
||||||
|
* both short and long flag version
|
||||||
|
`./example -s red` `./example --string blue`
|
||||||
|
|
||||||
|
* multiple names for the same option
|
||||||
|
```
|
||||||
|
$>./example -h
|
||||||
|
Usage of example:
|
||||||
|
-s, --string="": a simple string
|
||||||
|
```
|
||||||
|
|
||||||
|
___
|
||||||
|
It is very flexible on purpose, so you can do things like:
|
||||||
|
```
|
||||||
|
$>./example -h
|
||||||
|
Usage of example:
|
||||||
|
-s, -string, --string="": a simple string
|
||||||
|
```
|
||||||
|
|
||||||
|
Or:
|
||||||
|
```
|
||||||
|
$>./example -h
|
||||||
|
Usage of example:
|
||||||
|
-oldflag, --newflag="": a simple string
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also hide some flags from the usage, so if we want only `--newflag`:
|
||||||
|
```
|
||||||
|
$>./example -h
|
||||||
|
Usage of example:
|
||||||
|
--newflag="": a simple string
|
||||||
|
$>./example -oldflag str
|
||||||
|
str
|
||||||
|
```
|
||||||
|
|
||||||
|
See [example.go](example/example.go) for more details.
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
i int
|
||||||
|
str string
|
||||||
|
b, b2, h bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Bool([]string{"#hp", "#-halp"}, false, "display the halp")
|
||||||
|
flag.BoolVar(&b, []string{"b", "#bal", "#bol", "-bal"}, false, "a simple bool")
|
||||||
|
flag.BoolVar(&b, []string{"g", "#gil"}, false, "a simple bool")
|
||||||
|
flag.BoolVar(&b2, []string{"#-bool"}, false, "a simple bool")
|
||||||
|
flag.IntVar(&i, []string{"-integer", "-number"}, -1, "a simple integer")
|
||||||
|
flag.StringVar(&str, []string{"s", "#hidden", "-string"}, "", "a simple string") //-s -hidden and --string will work, but -hidden won't be in the usage
|
||||||
|
flag.BoolVar(&h, []string{"h", "#help", "-help"}, false, "display the help")
|
||||||
|
flag.StringVar(&str, []string{"mode"}, "mode1", "set the mode\nmode1: use the mode1\nmode2: use the mode2\nmode3: use the mode3")
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
if h {
|
||||||
|
flag.PrintDefaults()
|
||||||
|
} else {
|
||||||
|
fmt.Printf("s/#hidden/-string: %s\n", str)
|
||||||
|
fmt.Printf("b: %t\n", b)
|
||||||
|
fmt.Printf("-bool: %t\n", b2)
|
||||||
|
fmt.Printf("s/#hidden/-string(via lookup): %s\n", flag.Lookup("s").Value.String())
|
||||||
|
fmt.Printf("ARGS: %v\n", flag.Args())
|
||||||
|
}
|
||||||
|
}
|
||||||
+1130
File diff suppressed because it is too large
Load Diff
+516
@@ -0,0 +1,516 @@
|
|||||||
|
// Copyright 2014-2015 The Docker & Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mflag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResetForTesting clears all flag state and sets the usage function as directed.
|
||||||
|
// After calling ResetForTesting, parse errors in flag handling will not
|
||||||
|
// exit the program.
|
||||||
|
func ResetForTesting(usage func()) {
|
||||||
|
CommandLine = NewFlagSet(os.Args[0], ContinueOnError)
|
||||||
|
Usage = usage
|
||||||
|
}
|
||||||
|
func boolString(s string) string {
|
||||||
|
if s == "0" {
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEverything(t *testing.T) {
|
||||||
|
ResetForTesting(nil)
|
||||||
|
Bool([]string{"test_bool"}, false, "bool value")
|
||||||
|
Int([]string{"test_int"}, 0, "int value")
|
||||||
|
Int64([]string{"test_int64"}, 0, "int64 value")
|
||||||
|
Uint([]string{"test_uint"}, 0, "uint value")
|
||||||
|
Uint64([]string{"test_uint64"}, 0, "uint64 value")
|
||||||
|
String([]string{"test_string"}, "0", "string value")
|
||||||
|
Float64([]string{"test_float64"}, 0, "float64 value")
|
||||||
|
Duration([]string{"test_duration"}, 0, "time.Duration value")
|
||||||
|
|
||||||
|
m := make(map[string]*Flag)
|
||||||
|
desired := "0"
|
||||||
|
visitor := func(f *Flag) {
|
||||||
|
for _, name := range f.Names {
|
||||||
|
if len(name) > 5 && name[0:5] == "test_" {
|
||||||
|
m[name] = f
|
||||||
|
ok := false
|
||||||
|
switch {
|
||||||
|
case f.Value.String() == desired:
|
||||||
|
ok = true
|
||||||
|
case name == "test_bool" && f.Value.String() == boolString(desired):
|
||||||
|
ok = true
|
||||||
|
case name == "test_duration" && f.Value.String() == desired+"s":
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Error("Visit: bad value", f.Value.String(), "for", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VisitAll(visitor)
|
||||||
|
if len(m) != 8 {
|
||||||
|
t.Error("VisitAll misses some flags")
|
||||||
|
for k, v := range m {
|
||||||
|
t.Log(k, *v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m = make(map[string]*Flag)
|
||||||
|
Visit(visitor)
|
||||||
|
if len(m) != 0 {
|
||||||
|
t.Errorf("Visit sees unset flags")
|
||||||
|
for k, v := range m {
|
||||||
|
t.Log(k, *v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now set all flags
|
||||||
|
Set("test_bool", "true")
|
||||||
|
Set("test_int", "1")
|
||||||
|
Set("test_int64", "1")
|
||||||
|
Set("test_uint", "1")
|
||||||
|
Set("test_uint64", "1")
|
||||||
|
Set("test_string", "1")
|
||||||
|
Set("test_float64", "1")
|
||||||
|
Set("test_duration", "1s")
|
||||||
|
desired = "1"
|
||||||
|
Visit(visitor)
|
||||||
|
if len(m) != 8 {
|
||||||
|
t.Error("Visit fails after set")
|
||||||
|
for k, v := range m {
|
||||||
|
t.Log(k, *v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Now test they're visited in sort order.
|
||||||
|
var flagNames []string
|
||||||
|
Visit(func(f *Flag) {
|
||||||
|
for _, name := range f.Names {
|
||||||
|
flagNames = append(flagNames, name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if !sort.StringsAreSorted(flagNames) {
|
||||||
|
t.Errorf("flag names not sorted: %v", flagNames)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
ResetForTesting(nil)
|
||||||
|
Bool([]string{"test_bool"}, true, "bool value")
|
||||||
|
Int([]string{"test_int"}, 1, "int value")
|
||||||
|
Int64([]string{"test_int64"}, 2, "int64 value")
|
||||||
|
Uint([]string{"test_uint"}, 3, "uint value")
|
||||||
|
Uint64([]string{"test_uint64"}, 4, "uint64 value")
|
||||||
|
String([]string{"test_string"}, "5", "string value")
|
||||||
|
Float64([]string{"test_float64"}, 6, "float64 value")
|
||||||
|
Duration([]string{"test_duration"}, 7, "time.Duration value")
|
||||||
|
|
||||||
|
visitor := func(f *Flag) {
|
||||||
|
for _, name := range f.Names {
|
||||||
|
if len(name) > 5 && name[0:5] == "test_" {
|
||||||
|
g, ok := f.Value.(Getter)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Visit: value does not satisfy Getter: %T", f.Value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch name {
|
||||||
|
case "test_bool":
|
||||||
|
ok = g.Get() == true
|
||||||
|
case "test_int":
|
||||||
|
ok = g.Get() == int(1)
|
||||||
|
case "test_int64":
|
||||||
|
ok = g.Get() == int64(2)
|
||||||
|
case "test_uint":
|
||||||
|
ok = g.Get() == uint(3)
|
||||||
|
case "test_uint64":
|
||||||
|
ok = g.Get() == uint64(4)
|
||||||
|
case "test_string":
|
||||||
|
ok = g.Get() == "5"
|
||||||
|
case "test_float64":
|
||||||
|
ok = g.Get() == float64(6)
|
||||||
|
case "test_duration":
|
||||||
|
ok = g.Get() == time.Duration(7)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Visit: bad value %T(%v) for %s", g.Get(), g.Get(), name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VisitAll(visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testParse(f *FlagSet, t *testing.T) {
|
||||||
|
if f.Parsed() {
|
||||||
|
t.Error("f.Parse() = true before Parse")
|
||||||
|
}
|
||||||
|
boolFlag := f.Bool([]string{"bool"}, false, "bool value")
|
||||||
|
bool2Flag := f.Bool([]string{"bool2"}, false, "bool2 value")
|
||||||
|
f.Bool([]string{"bool3"}, false, "bool3 value")
|
||||||
|
bool4Flag := f.Bool([]string{"bool4"}, false, "bool4 value")
|
||||||
|
intFlag := f.Int([]string{"-int"}, 0, "int value")
|
||||||
|
int64Flag := f.Int64([]string{"-int64"}, 0, "int64 value")
|
||||||
|
uintFlag := f.Uint([]string{"uint"}, 0, "uint value")
|
||||||
|
uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value")
|
||||||
|
stringFlag := f.String([]string{"string"}, "0", "string value")
|
||||||
|
f.String([]string{"string2"}, "0", "string2 value")
|
||||||
|
singleQuoteFlag := f.String([]string{"squote"}, "", "single quoted value")
|
||||||
|
doubleQuoteFlag := f.String([]string{"dquote"}, "", "double quoted value")
|
||||||
|
mixedQuoteFlag := f.String([]string{"mquote"}, "", "mixed quoted value")
|
||||||
|
mixed2QuoteFlag := f.String([]string{"mquote2"}, "", "mixed2 quoted value")
|
||||||
|
nestedQuoteFlag := f.String([]string{"nquote"}, "", "nested quoted value")
|
||||||
|
nested2QuoteFlag := f.String([]string{"nquote2"}, "", "nested2 quoted value")
|
||||||
|
float64Flag := f.Float64([]string{"float64"}, 0, "float64 value")
|
||||||
|
durationFlag := f.Duration([]string{"duration"}, 5*time.Second, "time.Duration value")
|
||||||
|
extra := "one-extra-argument"
|
||||||
|
args := []string{
|
||||||
|
"-bool",
|
||||||
|
"-bool2=true",
|
||||||
|
"-bool4=false",
|
||||||
|
"--int", "22",
|
||||||
|
"--int64", "0x23",
|
||||||
|
"-uint", "24",
|
||||||
|
"--uint64", "25",
|
||||||
|
"-string", "hello",
|
||||||
|
"-squote='single'",
|
||||||
|
`-dquote="double"`,
|
||||||
|
`-mquote='mixed"`,
|
||||||
|
`-mquote2="mixed2'`,
|
||||||
|
`-nquote="'single nested'"`,
|
||||||
|
`-nquote2='"double nested"'`,
|
||||||
|
"-float64", "2718e28",
|
||||||
|
"-duration", "2m",
|
||||||
|
extra,
|
||||||
|
}
|
||||||
|
if err := f.Parse(args); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !f.Parsed() {
|
||||||
|
t.Error("f.Parse() = false after Parse")
|
||||||
|
}
|
||||||
|
if *boolFlag != true {
|
||||||
|
t.Error("bool flag should be true, is ", *boolFlag)
|
||||||
|
}
|
||||||
|
if *bool2Flag != true {
|
||||||
|
t.Error("bool2 flag should be true, is ", *bool2Flag)
|
||||||
|
}
|
||||||
|
if !f.IsSet("bool2") {
|
||||||
|
t.Error("bool2 should be marked as set")
|
||||||
|
}
|
||||||
|
if f.IsSet("bool3") {
|
||||||
|
t.Error("bool3 should not be marked as set")
|
||||||
|
}
|
||||||
|
if !f.IsSet("bool4") {
|
||||||
|
t.Error("bool4 should be marked as set")
|
||||||
|
}
|
||||||
|
if *bool4Flag != false {
|
||||||
|
t.Error("bool4 flag should be false, is ", *bool4Flag)
|
||||||
|
}
|
||||||
|
if *intFlag != 22 {
|
||||||
|
t.Error("int flag should be 22, is ", *intFlag)
|
||||||
|
}
|
||||||
|
if *int64Flag != 0x23 {
|
||||||
|
t.Error("int64 flag should be 0x23, is ", *int64Flag)
|
||||||
|
}
|
||||||
|
if *uintFlag != 24 {
|
||||||
|
t.Error("uint flag should be 24, is ", *uintFlag)
|
||||||
|
}
|
||||||
|
if *uint64Flag != 25 {
|
||||||
|
t.Error("uint64 flag should be 25, is ", *uint64Flag)
|
||||||
|
}
|
||||||
|
if *stringFlag != "hello" {
|
||||||
|
t.Error("string flag should be `hello`, is ", *stringFlag)
|
||||||
|
}
|
||||||
|
if !f.IsSet("string") {
|
||||||
|
t.Error("string flag should be marked as set")
|
||||||
|
}
|
||||||
|
if f.IsSet("string2") {
|
||||||
|
t.Error("string2 flag should not be marked as set")
|
||||||
|
}
|
||||||
|
if *singleQuoteFlag != "single" {
|
||||||
|
t.Error("single quote string flag should be `single`, is ", *singleQuoteFlag)
|
||||||
|
}
|
||||||
|
if *doubleQuoteFlag != "double" {
|
||||||
|
t.Error("double quote string flag should be `double`, is ", *doubleQuoteFlag)
|
||||||
|
}
|
||||||
|
if *mixedQuoteFlag != `'mixed"` {
|
||||||
|
t.Error("mixed quote string flag should be `'mixed\"`, is ", *mixedQuoteFlag)
|
||||||
|
}
|
||||||
|
if *mixed2QuoteFlag != `"mixed2'` {
|
||||||
|
t.Error("mixed2 quote string flag should be `\"mixed2'`, is ", *mixed2QuoteFlag)
|
||||||
|
}
|
||||||
|
if *nestedQuoteFlag != "'single nested'" {
|
||||||
|
t.Error("nested quote string flag should be `'single nested'`, is ", *nestedQuoteFlag)
|
||||||
|
}
|
||||||
|
if *nested2QuoteFlag != `"double nested"` {
|
||||||
|
t.Error("double quote string flag should be `\"double nested\"`, is ", *nested2QuoteFlag)
|
||||||
|
}
|
||||||
|
if *float64Flag != 2718e28 {
|
||||||
|
t.Error("float64 flag should be 2718e28, is ", *float64Flag)
|
||||||
|
}
|
||||||
|
if *durationFlag != 2*time.Minute {
|
||||||
|
t.Error("duration flag should be 2m, is ", *durationFlag)
|
||||||
|
}
|
||||||
|
if len(f.Args()) != 1 {
|
||||||
|
t.Error("expected one argument, got", len(f.Args()))
|
||||||
|
} else if f.Args()[0] != extra {
|
||||||
|
t.Errorf("expected argument %q got %q", extra, f.Args()[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPanic(f *FlagSet, t *testing.T) {
|
||||||
|
f.Int([]string{"-int"}, 0, "int value")
|
||||||
|
if f.Parsed() {
|
||||||
|
t.Error("f.Parse() = true before Parse")
|
||||||
|
}
|
||||||
|
args := []string{
|
||||||
|
"-int", "21",
|
||||||
|
}
|
||||||
|
f.Parse(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePanic(t *testing.T) {
|
||||||
|
ResetForTesting(func() {})
|
||||||
|
testPanic(CommandLine, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
ResetForTesting(func() { t.Error("bad parse") })
|
||||||
|
testParse(CommandLine, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlagSetParse(t *testing.T) {
|
||||||
|
testParse(NewFlagSet("test", ContinueOnError), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare a user-defined flag type.
|
||||||
|
type flagVar []string
|
||||||
|
|
||||||
|
func (f *flagVar) String() string {
|
||||||
|
return fmt.Sprint([]string(*f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *flagVar) Set(value string) error {
|
||||||
|
*f = append(*f, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserDefined(t *testing.T) {
|
||||||
|
var flags FlagSet
|
||||||
|
flags.Init("test", ContinueOnError)
|
||||||
|
var v flagVar
|
||||||
|
flags.Var(&v, []string{"v"}, "usage")
|
||||||
|
if err := flags.Parse([]string{"-v", "1", "-v", "2", "-v=3"}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if len(v) != 3 {
|
||||||
|
t.Fatal("expected 3 args; got ", len(v))
|
||||||
|
}
|
||||||
|
expect := "[1 2 3]"
|
||||||
|
if v.String() != expect {
|
||||||
|
t.Errorf("expected value %q got %q", expect, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare a user-defined boolean flag type.
|
||||||
|
type boolFlagVar struct {
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolFlagVar) String() string {
|
||||||
|
return fmt.Sprintf("%d", b.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolFlagVar) Set(value string) error {
|
||||||
|
if value == "true" {
|
||||||
|
b.count++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *boolFlagVar) IsBoolFlag() bool {
|
||||||
|
return b.count < 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserDefinedBool(t *testing.T) {
|
||||||
|
var flags FlagSet
|
||||||
|
flags.Init("test", ContinueOnError)
|
||||||
|
var b boolFlagVar
|
||||||
|
var err error
|
||||||
|
flags.Var(&b, []string{"b"}, "usage")
|
||||||
|
if err = flags.Parse([]string{"-b", "-b", "-b", "-b=true", "-b=false", "-b", "barg", "-b"}); err != nil {
|
||||||
|
if b.count < 4 {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.count != 4 {
|
||||||
|
t.Errorf("want: %d; got: %d", 4, b.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error; got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetOutput(t *testing.T) {
|
||||||
|
var flags FlagSet
|
||||||
|
var buf bytes.Buffer
|
||||||
|
flags.SetOutput(&buf)
|
||||||
|
flags.Init("test", ContinueOnError)
|
||||||
|
flags.Parse([]string{"-unknown"})
|
||||||
|
if out := buf.String(); !strings.Contains(out, "-unknown") {
|
||||||
|
t.Logf("expected output mentioning unknown; got %q", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tests that one can reset the flags. This still works but not well, and is
|
||||||
|
// superseded by FlagSet.
|
||||||
|
func TestChangingArgs(t *testing.T) {
|
||||||
|
ResetForTesting(func() { t.Fatal("bad parse") })
|
||||||
|
oldArgs := os.Args
|
||||||
|
defer func() { os.Args = oldArgs }()
|
||||||
|
os.Args = []string{"cmd", "-before", "subcmd", "-after", "args"}
|
||||||
|
before := Bool([]string{"before"}, false, "")
|
||||||
|
if err := CommandLine.Parse(os.Args[1:]); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cmd := Arg(0)
|
||||||
|
os.Args = Args()
|
||||||
|
after := Bool([]string{"after"}, false, "")
|
||||||
|
Parse()
|
||||||
|
args := Args()
|
||||||
|
|
||||||
|
if !*before || cmd != "subcmd" || !*after || len(args) != 1 || args[0] != "args" {
|
||||||
|
t.Fatalf("expected true subcmd true [args] got %v %v %v %v", *before, cmd, *after, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that -help invokes the usage message and returns ErrHelp.
|
||||||
|
func TestHelp(t *testing.T) {
|
||||||
|
var helpCalled = false
|
||||||
|
fs := NewFlagSet("help test", ContinueOnError)
|
||||||
|
fs.Usage = func() { helpCalled = true }
|
||||||
|
var flag bool
|
||||||
|
fs.BoolVar(&flag, []string{"flag"}, false, "regular flag")
|
||||||
|
// Regular flag invocation should work
|
||||||
|
err := fs.Parse([]string{"-flag=true"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("expected no error; got ", err)
|
||||||
|
}
|
||||||
|
if !flag {
|
||||||
|
t.Error("flag was not set by -flag")
|
||||||
|
}
|
||||||
|
if helpCalled {
|
||||||
|
t.Error("help called for regular flag")
|
||||||
|
helpCalled = false // reset for next test
|
||||||
|
}
|
||||||
|
// Help flag should work as expected.
|
||||||
|
err = fs.Parse([]string{"-help"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("error expected")
|
||||||
|
}
|
||||||
|
if err != ErrHelp {
|
||||||
|
t.Fatal("expected ErrHelp; got ", err)
|
||||||
|
}
|
||||||
|
if !helpCalled {
|
||||||
|
t.Fatal("help was not called")
|
||||||
|
}
|
||||||
|
// If we define a help flag, that should override.
|
||||||
|
var help bool
|
||||||
|
fs.BoolVar(&help, []string{"help"}, false, "help flag")
|
||||||
|
helpCalled = false
|
||||||
|
err = fs.Parse([]string{"-help"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("expected no error for defined -help; got ", err)
|
||||||
|
}
|
||||||
|
if helpCalled {
|
||||||
|
t.Fatal("help was called; should not have been for defined help flag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the flag count functions.
|
||||||
|
func TestFlagCounts(t *testing.T) {
|
||||||
|
fs := NewFlagSet("help test", ContinueOnError)
|
||||||
|
var flag bool
|
||||||
|
fs.BoolVar(&flag, []string{"flag1"}, false, "regular flag")
|
||||||
|
fs.BoolVar(&flag, []string{"#deprecated1"}, false, "regular flag")
|
||||||
|
fs.BoolVar(&flag, []string{"f", "flag2"}, false, "regular flag")
|
||||||
|
fs.BoolVar(&flag, []string{"#d", "#deprecated2"}, false, "regular flag")
|
||||||
|
fs.BoolVar(&flag, []string{"flag3"}, false, "regular flag")
|
||||||
|
fs.BoolVar(&flag, []string{"g", "#flag4", "-flag4"}, false, "regular flag")
|
||||||
|
|
||||||
|
if fs.FlagCount() != 6 {
|
||||||
|
t.Fatal("FlagCount wrong. ", fs.FlagCount())
|
||||||
|
}
|
||||||
|
if fs.FlagCountUndeprecated() != 4 {
|
||||||
|
t.Fatal("FlagCountUndeprecated wrong. ", fs.FlagCountUndeprecated())
|
||||||
|
}
|
||||||
|
if fs.NFlag() != 0 {
|
||||||
|
t.Fatal("NFlag wrong. ", fs.NFlag())
|
||||||
|
}
|
||||||
|
err := fs.Parse([]string{"-fd", "-g", "-flag4"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("expected no error for defined -help; got ", err)
|
||||||
|
}
|
||||||
|
if fs.NFlag() != 4 {
|
||||||
|
t.Fatal("NFlag wrong. ", fs.NFlag())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show up bug in sortFlags
|
||||||
|
func TestSortFlags(t *testing.T) {
|
||||||
|
fs := NewFlagSet("help TestSortFlags", ContinueOnError)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var b bool
|
||||||
|
fs.BoolVar(&b, []string{"b", "-banana"}, false, "usage")
|
||||||
|
|
||||||
|
err = fs.Parse([]string{"--banana=true"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("expected no error; got ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
|
||||||
|
fs.VisitAll(func(flag *Flag) {
|
||||||
|
count++
|
||||||
|
if flag == nil {
|
||||||
|
t.Fatal("VisitAll should not return a nil flag")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
flagcount := fs.FlagCount()
|
||||||
|
if flagcount != count {
|
||||||
|
t.Fatalf("FlagCount (%d) != number (%d) of elements visited", flagcount, count)
|
||||||
|
}
|
||||||
|
// Make sure its idempotent
|
||||||
|
if flagcount != fs.FlagCount() {
|
||||||
|
t.Fatalf("FlagCount (%d) != fs.FlagCount() (%d) of elements visited", flagcount, fs.FlagCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
fs.Visit(func(flag *Flag) {
|
||||||
|
count++
|
||||||
|
if flag == nil {
|
||||||
|
t.Fatal("Visit should not return a nil flag")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
nflag := fs.NFlag()
|
||||||
|
if nflag != count {
|
||||||
|
t.Fatalf("NFlag (%d) != number (%d) of elements visited", nflag, count)
|
||||||
|
}
|
||||||
|
if nflag != fs.NFlag() {
|
||||||
|
t.Fatalf("NFlag (%d) != fs.NFlag() (%d) of elements visited", nflag, fs.NFlag())
|
||||||
|
}
|
||||||
|
}
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
package filters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Args map[string][]string
|
||||||
|
|
||||||
|
// Parse the argument to the filter flag. Like
|
||||||
|
//
|
||||||
|
// `docker ps -f 'created=today' -f 'image.name=ubuntu*'`
|
||||||
|
//
|
||||||
|
// If prev map is provided, then it is appended to, and returned. By default a new
|
||||||
|
// map is created.
|
||||||
|
func ParseFlag(arg string, prev Args) (Args, error) {
|
||||||
|
var filters Args = prev
|
||||||
|
if prev == nil {
|
||||||
|
filters = Args{}
|
||||||
|
}
|
||||||
|
if len(arg) == 0 {
|
||||||
|
return filters, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(arg, "=") {
|
||||||
|
return filters, ErrorBadFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
f := strings.SplitN(arg, "=", 2)
|
||||||
|
name := strings.ToLower(strings.TrimSpace(f[0]))
|
||||||
|
value := strings.TrimSpace(f[1])
|
||||||
|
filters[name] = append(filters[name], value)
|
||||||
|
|
||||||
|
return filters, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrorBadFormat = errors.New("bad format of filter (expected name=value)")
|
||||||
|
|
||||||
|
// packs the Args into an string for easy transport from client to server
|
||||||
|
func ToParam(a Args) (string, error) {
|
||||||
|
// this way we don't URL encode {}, just empty space
|
||||||
|
if len(a) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := json.Marshal(a)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpacks the filter Args
|
||||||
|
func FromParam(p string) (Args, error) {
|
||||||
|
args := Args{}
|
||||||
|
if len(p) == 0 {
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(strings.NewReader(p)).Decode(&args); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filters Args) MatchKVList(field string, sources map[string]string) bool {
|
||||||
|
fieldValues := filters[field]
|
||||||
|
|
||||||
|
//do not filter if there is no filter set or cannot determine filter
|
||||||
|
if len(fieldValues) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if sources == nil || len(sources) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
outer:
|
||||||
|
for _, name2match := range fieldValues {
|
||||||
|
testKV := strings.SplitN(name2match, "=", 2)
|
||||||
|
|
||||||
|
for k, v := range sources {
|
||||||
|
if len(testKV) == 1 {
|
||||||
|
if k == testKV[0] {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
} else if k == testKV[0] && v == testKV[1] {
|
||||||
|
continue outer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (filters Args) Match(field, source string) bool {
|
||||||
|
fieldValues := filters[field]
|
||||||
|
|
||||||
|
//do not filter if there is no filter set or cannot determine filter
|
||||||
|
if len(fieldValues) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, name2match := range fieldValues {
|
||||||
|
match, err := regexp.MatchString(name2match, source)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if match {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
package filters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseArgs(t *testing.T) {
|
||||||
|
// equivalent of `docker ps -f 'created=today' -f 'image.name=ubuntu*' -f 'image.name=*untu'`
|
||||||
|
flagArgs := []string{
|
||||||
|
"created=today",
|
||||||
|
"image.name=ubuntu*",
|
||||||
|
"image.name=*untu",
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
args = Args{}
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for i := range flagArgs {
|
||||||
|
args, err = ParseFlag(flagArgs[i], args)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to parse %s: %s", flagArgs[i], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(args["created"]) != 1 {
|
||||||
|
t.Errorf("failed to set this arg")
|
||||||
|
}
|
||||||
|
if len(args["image.name"]) != 2 {
|
||||||
|
t.Errorf("the args should have collapsed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParam(t *testing.T) {
|
||||||
|
a := Args{
|
||||||
|
"created": []string{"today"},
|
||||||
|
"image.name": []string{"ubuntu*", "*untu"},
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := ToParam(a)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to marshal the filters: %s", err)
|
||||||
|
}
|
||||||
|
v1, err := FromParam(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err)
|
||||||
|
}
|
||||||
|
for key, vals := range v1 {
|
||||||
|
if _, ok := a[key]; !ok {
|
||||||
|
t.Errorf("could not find key %s in original set", key)
|
||||||
|
}
|
||||||
|
sort.Strings(vals)
|
||||||
|
sort.Strings(a[key])
|
||||||
|
if len(vals) != len(a[key]) {
|
||||||
|
t.Errorf("value lengths ought to match")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := range vals {
|
||||||
|
if vals[i] != a[key][i] {
|
||||||
|
t.Errorf("expected %s, but got %s", a[key][i], vals[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmpty(t *testing.T) {
|
||||||
|
a := Args{}
|
||||||
|
v, err := ToParam(a)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to marshal the filters: %s", err)
|
||||||
|
}
|
||||||
|
v1, err := FromParam(v)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s", err)
|
||||||
|
}
|
||||||
|
if len(a) != len(v1) {
|
||||||
|
t.Errorf("these should both be empty sets")
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -1,3 +1,5 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
package kernel
|
package kernel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
Generated
Vendored
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package kernel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KernelVersionInfo struct {
|
||||||
|
kvi string
|
||||||
|
major int
|
||||||
|
minor int
|
||||||
|
build int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *KernelVersionInfo) String() string {
|
||||||
|
return fmt.Sprintf("%d.%d %d (%s)", k.major, k.minor, k.build, k.kvi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetKernelVersion() (*KernelVersionInfo, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
h syscall.Handle
|
||||||
|
dwVersion uint32
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
KVI := &KernelVersionInfo{"Unknown", 0, 0, 0}
|
||||||
|
|
||||||
|
if err = syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE,
|
||||||
|
syscall.StringToUTF16Ptr(`SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\`),
|
||||||
|
0,
|
||||||
|
syscall.KEY_READ,
|
||||||
|
&h); err != nil {
|
||||||
|
return KVI, err
|
||||||
|
}
|
||||||
|
defer syscall.RegCloseKey(h)
|
||||||
|
|
||||||
|
var buf [1 << 10]uint16
|
||||||
|
var typ uint32
|
||||||
|
n := uint32(len(buf) * 2) // api expects array of bytes, not uint16
|
||||||
|
|
||||||
|
if err = syscall.RegQueryValueEx(h,
|
||||||
|
syscall.StringToUTF16Ptr("BuildLabEx"),
|
||||||
|
nil,
|
||||||
|
&typ,
|
||||||
|
(*byte)(unsafe.Pointer(&buf[0])),
|
||||||
|
&n); err != nil {
|
||||||
|
return KVI, err
|
||||||
|
}
|
||||||
|
|
||||||
|
KVI.kvi = syscall.UTF16ToString(buf[:])
|
||||||
|
|
||||||
|
// Important - docker.exe MUST be manifested for this API to return
|
||||||
|
// the correct information.
|
||||||
|
if dwVersion, err = syscall.GetVersion(); err != nil {
|
||||||
|
return KVI, err
|
||||||
|
}
|
||||||
|
|
||||||
|
KVI.major = int(dwVersion & 0xFF)
|
||||||
|
KVI.minor = int((dwVersion & 0XFF00) >> 8)
|
||||||
|
KVI.build = int((dwVersion & 0xFFFF0000) >> 16)
|
||||||
|
|
||||||
|
return KVI, nil
|
||||||
|
}
|
||||||
Generated
Vendored
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package operatingsystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// file to use to detect if the daemon is running in a container
|
||||||
|
proc1Cgroup = "/proc/1/cgroup"
|
||||||
|
|
||||||
|
// file to check to determine Operating System
|
||||||
|
etcOsRelease = "/etc/os-release"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetOperatingSystem() (string, error) {
|
||||||
|
b, err := ioutil.ReadFile(etcOsRelease)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if i := bytes.Index(b, []byte("PRETTY_NAME")); i >= 0 {
|
||||||
|
b = b[i+13:]
|
||||||
|
return string(b[:bytes.IndexByte(b, '"')]), nil
|
||||||
|
}
|
||||||
|
return "", errors.New("PRETTY_NAME not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsContainerized() (bool, error) {
|
||||||
|
b, err := ioutil.ReadFile(proc1Cgroup)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, line := range bytes.Split(b, []byte{'\n'}) {
|
||||||
|
if len(line) > 0 && !bytes.HasSuffix(line, []byte{'/'}) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
Generated
Vendored
+124
@@ -0,0 +1,124 @@
|
|||||||
|
package operatingsystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetOperatingSystem(t *testing.T) {
|
||||||
|
var (
|
||||||
|
backup = etcOsRelease
|
||||||
|
ubuntuTrusty = []byte(`NAME="Ubuntu"
|
||||||
|
VERSION="14.04, Trusty Tahr"
|
||||||
|
ID=ubuntu
|
||||||
|
ID_LIKE=debian
|
||||||
|
PRETTY_NAME="Ubuntu 14.04 LTS"
|
||||||
|
VERSION_ID="14.04"
|
||||||
|
HOME_URL="http://www.ubuntu.com/"
|
||||||
|
SUPPORT_URL="http://help.ubuntu.com/"
|
||||||
|
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`)
|
||||||
|
gentoo = []byte(`NAME=Gentoo
|
||||||
|
ID=gentoo
|
||||||
|
PRETTY_NAME="Gentoo/Linux"
|
||||||
|
ANSI_COLOR="1;32"
|
||||||
|
HOME_URL="http://www.gentoo.org/"
|
||||||
|
SUPPORT_URL="http://www.gentoo.org/main/en/support.xml"
|
||||||
|
BUG_REPORT_URL="https://bugs.gentoo.org/"
|
||||||
|
`)
|
||||||
|
noPrettyName = []byte(`NAME="Ubuntu"
|
||||||
|
VERSION="14.04, Trusty Tahr"
|
||||||
|
ID=ubuntu
|
||||||
|
ID_LIKE=debian
|
||||||
|
VERSION_ID="14.04"
|
||||||
|
HOME_URL="http://www.ubuntu.com/"
|
||||||
|
SUPPORT_URL="http://help.ubuntu.com/"
|
||||||
|
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`)
|
||||||
|
)
|
||||||
|
|
||||||
|
dir := os.TempDir()
|
||||||
|
etcOsRelease = filepath.Join(dir, "etcOsRelease")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
os.Remove(etcOsRelease)
|
||||||
|
etcOsRelease = backup
|
||||||
|
}()
|
||||||
|
|
||||||
|
for expect, osRelease := range map[string][]byte{
|
||||||
|
"Ubuntu 14.04 LTS": ubuntuTrusty,
|
||||||
|
"Gentoo/Linux": gentoo,
|
||||||
|
"": noPrettyName,
|
||||||
|
} {
|
||||||
|
if err := ioutil.WriteFile(etcOsRelease, osRelease, 0600); err != nil {
|
||||||
|
t.Fatalf("failed to write to %s: %v", etcOsRelease, err)
|
||||||
|
}
|
||||||
|
s, err := GetOperatingSystem()
|
||||||
|
if s != expect {
|
||||||
|
if expect == "" {
|
||||||
|
t.Fatalf("Expected error 'PRETTY_NAME not found', but got %v", err)
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Expected '%s', but got '%s'. Err=%v", expect, s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsContainerized(t *testing.T) {
|
||||||
|
var (
|
||||||
|
backup = proc1Cgroup
|
||||||
|
nonContainerizedProc1Cgroup = []byte(`14:name=systemd:/
|
||||||
|
13:hugetlb:/
|
||||||
|
12:net_prio:/
|
||||||
|
11:perf_event:/
|
||||||
|
10:bfqio:/
|
||||||
|
9:blkio:/
|
||||||
|
8:net_cls:/
|
||||||
|
7:freezer:/
|
||||||
|
6:devices:/
|
||||||
|
5:memory:/
|
||||||
|
4:cpuacct:/
|
||||||
|
3:cpu:/
|
||||||
|
2:cpuset:/
|
||||||
|
`)
|
||||||
|
containerizedProc1Cgroup = []byte(`9:perf_event:/docker/3cef1b53c50b0fa357d994f8a1a8cd783c76bbf4f5dd08b226e38a8bd331338d
|
||||||
|
8:blkio:/docker/3cef1b53c50b0fa357d994f8a1a8cd783c76bbf4f5dd08b226e38a8bd331338d
|
||||||
|
7:net_cls:/
|
||||||
|
6:freezer:/docker/3cef1b53c50b0fa357d994f8a1a8cd783c76bbf4f5dd08b226e38a8bd331338d
|
||||||
|
5:devices:/docker/3cef1b53c50b0fa357d994f8a1a8cd783c76bbf4f5dd08b226e38a8bd331338d
|
||||||
|
4:memory:/docker/3cef1b53c50b0fa357d994f8a1a8cd783c76bbf4f5dd08b226e38a8bd331338d
|
||||||
|
3:cpuacct:/docker/3cef1b53c50b0fa357d994f8a1a8cd783c76bbf4f5dd08b226e38a8bd331338d
|
||||||
|
2:cpu:/docker/3cef1b53c50b0fa357d994f8a1a8cd783c76bbf4f5dd08b226e38a8bd331338d
|
||||||
|
1:cpuset:/`)
|
||||||
|
)
|
||||||
|
|
||||||
|
dir := os.TempDir()
|
||||||
|
proc1Cgroup = filepath.Join(dir, "proc1Cgroup")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
os.Remove(proc1Cgroup)
|
||||||
|
proc1Cgroup = backup
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(proc1Cgroup, nonContainerizedProc1Cgroup, 0600); err != nil {
|
||||||
|
t.Fatalf("failed to write to %s: %v", proc1Cgroup, err)
|
||||||
|
}
|
||||||
|
inContainer, err := IsContainerized()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if inContainer {
|
||||||
|
t.Fatal("Wrongly assuming containerized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ioutil.WriteFile(proc1Cgroup, containerizedProc1Cgroup, 0600); err != nil {
|
||||||
|
t.Fatalf("failed to write to %s: %v", proc1Cgroup, err)
|
||||||
|
}
|
||||||
|
inContainer, err = IsContainerized()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !inContainer {
|
||||||
|
t.Fatal("Wrongly assuming non-containerized")
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package operatingsystem
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// See https://code.google.com/p/go/source/browse/src/pkg/mime/type_windows.go?r=d14520ac25bf6940785aabb71f5be453a286f58c
|
||||||
|
// for a similar sample
|
||||||
|
|
||||||
|
func GetOperatingSystem() (string, error) {
|
||||||
|
|
||||||
|
var h syscall.Handle
|
||||||
|
|
||||||
|
// Default return value
|
||||||
|
ret := "Unknown Operating System"
|
||||||
|
|
||||||
|
if err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE,
|
||||||
|
syscall.StringToUTF16Ptr(`SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\`),
|
||||||
|
0,
|
||||||
|
syscall.KEY_READ,
|
||||||
|
&h); err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
defer syscall.RegCloseKey(h)
|
||||||
|
|
||||||
|
var buf [1 << 10]uint16
|
||||||
|
var typ uint32
|
||||||
|
n := uint32(len(buf) * 2) // api expects array of bytes, not uint16
|
||||||
|
|
||||||
|
if err := syscall.RegQueryValueEx(h,
|
||||||
|
syscall.StringToUTF16Ptr("ProductName"),
|
||||||
|
nil,
|
||||||
|
&typ,
|
||||||
|
(*byte)(unsafe.Pointer(&buf[0])),
|
||||||
|
&n); err != nil {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
ret = syscall.UTF16ToString(buf[:])
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// No-op on Windows
|
||||||
|
func IsContainerized() (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
package parsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FIXME: Change this not to receive default value as parameter
|
||||||
|
func ParseHost(defaultTCPAddr, defaultUnixAddr, addr string) (string, error) {
|
||||||
|
addr = strings.TrimSpace(addr)
|
||||||
|
if addr == "" {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
addr = fmt.Sprintf("unix://%s", defaultUnixAddr)
|
||||||
|
} else {
|
||||||
|
// Note - defaultTCPAddr already includes tcp:// prefix
|
||||||
|
addr = fmt.Sprintf("%s", defaultTCPAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addrParts := strings.Split(addr, "://")
|
||||||
|
if len(addrParts) == 1 {
|
||||||
|
addrParts = []string{"tcp", addrParts[0]}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch addrParts[0] {
|
||||||
|
case "tcp":
|
||||||
|
return ParseTCPAddr(addrParts[1], defaultTCPAddr)
|
||||||
|
case "unix":
|
||||||
|
return ParseUnixAddr(addrParts[1], defaultUnixAddr)
|
||||||
|
case "fd":
|
||||||
|
return addr, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("Invalid bind address format: %s", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseUnixAddr(addr string, defaultAddr string) (string, error) {
|
||||||
|
addr = strings.TrimPrefix(addr, "unix://")
|
||||||
|
if strings.Contains(addr, "://") {
|
||||||
|
return "", fmt.Errorf("Invalid proto, expected unix: %s", addr)
|
||||||
|
}
|
||||||
|
if addr == "" {
|
||||||
|
addr = defaultAddr
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unix://%s", addr), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseTCPAddr(addr string, defaultAddr string) (string, error) {
|
||||||
|
addr = strings.TrimPrefix(addr, "tcp://")
|
||||||
|
if strings.Contains(addr, "://") || addr == "" {
|
||||||
|
return "", fmt.Errorf("Invalid proto, expected tcp: %s", addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostParts := strings.Split(addr, ":")
|
||||||
|
if len(hostParts) != 2 {
|
||||||
|
return "", fmt.Errorf("Invalid bind address format: %s", addr)
|
||||||
|
}
|
||||||
|
host := hostParts[0]
|
||||||
|
if host == "" {
|
||||||
|
host = defaultAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := strconv.Atoi(hostParts[1])
|
||||||
|
if err != nil && p == 0 {
|
||||||
|
return "", fmt.Errorf("Invalid bind address format: %s", addr)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("tcp://%s:%d", host, p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a repos name and returns the right reposName + tag|digest
|
||||||
|
// The tag can be confusing because of a port in a repository name.
|
||||||
|
// Ex: localhost.localdomain:5000/samalba/hipache:latest
|
||||||
|
// Digest ex: localhost:5000/foo/bar@sha256:bc8813ea7b3603864987522f02a76101c17ad122e1c46d790efc0fca78ca7bfb
|
||||||
|
func ParseRepositoryTag(repos string) (string, string) {
|
||||||
|
n := strings.Index(repos, "@")
|
||||||
|
if n >= 0 {
|
||||||
|
parts := strings.Split(repos, "@")
|
||||||
|
return parts[0], parts[1]
|
||||||
|
}
|
||||||
|
n = strings.LastIndex(repos, ":")
|
||||||
|
if n < 0 {
|
||||||
|
return repos, ""
|
||||||
|
}
|
||||||
|
if tag := repos[n+1:]; !strings.Contains(tag, "/") {
|
||||||
|
return repos[:n], tag
|
||||||
|
}
|
||||||
|
return repos, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func PartParser(template, data string) (map[string]string, error) {
|
||||||
|
// ip:public:private
|
||||||
|
var (
|
||||||
|
templateParts = strings.Split(template, ":")
|
||||||
|
parts = strings.Split(data, ":")
|
||||||
|
out = make(map[string]string, len(templateParts))
|
||||||
|
)
|
||||||
|
if len(parts) != len(templateParts) {
|
||||||
|
return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, t := range templateParts {
|
||||||
|
value := ""
|
||||||
|
if len(parts) > i {
|
||||||
|
value = parts[i]
|
||||||
|
}
|
||||||
|
out[t] = value
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseKeyValueOpt(opt string) (string, string, error) {
|
||||||
|
parts := strings.SplitN(opt, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePortRange(ports string) (uint64, uint64, error) {
|
||||||
|
if ports == "" {
|
||||||
|
return 0, 0, fmt.Errorf("Empty string specified for ports.")
|
||||||
|
}
|
||||||
|
if !strings.Contains(ports, "-") {
|
||||||
|
start, err := strconv.ParseUint(ports, 10, 16)
|
||||||
|
end := start
|
||||||
|
return start, end, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(ports, "-")
|
||||||
|
start, err := strconv.ParseUint(parts[0], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
end, err := strconv.ParseUint(parts[1], 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
if end < start {
|
||||||
|
return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports)
|
||||||
|
}
|
||||||
|
return start, end, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseLink(val string) (string, string, error) {
|
||||||
|
if val == "" {
|
||||||
|
return "", "", fmt.Errorf("empty string specified for links")
|
||||||
|
}
|
||||||
|
arr := strings.Split(val, ":")
|
||||||
|
if len(arr) > 2 {
|
||||||
|
return "", "", fmt.Errorf("bad format for links: %s", val)
|
||||||
|
}
|
||||||
|
if len(arr) == 1 {
|
||||||
|
return val, val, nil
|
||||||
|
}
|
||||||
|
return arr[0], arr[1], nil
|
||||||
|
}
|
||||||
+157
@@ -0,0 +1,157 @@
|
|||||||
|
package parsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseHost(t *testing.T) {
|
||||||
|
var (
|
||||||
|
defaultHttpHost = "127.0.0.1"
|
||||||
|
defaultUnix = "/var/run/docker.sock"
|
||||||
|
)
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, "0.0.0.0"); err == nil {
|
||||||
|
t.Errorf("tcp 0.0.0.0 address expected error return, but err == nil, got %s", addr)
|
||||||
|
}
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, "tcp://"); err == nil {
|
||||||
|
t.Errorf("default tcp:// address expected error return, but err == nil, got %s", addr)
|
||||||
|
}
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, "0.0.0.1:5555"); err != nil || addr != "tcp://0.0.0.1:5555" {
|
||||||
|
t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
|
||||||
|
}
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, ":6666"); err != nil || addr != "tcp://127.0.0.1:6666" {
|
||||||
|
t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
|
||||||
|
}
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, "tcp://:7777"); err != nil || addr != "tcp://127.0.0.1:7777" {
|
||||||
|
t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
|
||||||
|
}
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, ""); err != nil || addr != "unix:///var/run/docker.sock" {
|
||||||
|
t.Errorf("empty argument -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||||
|
}
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, "unix:///var/run/docker.sock"); err != nil || addr != "unix:///var/run/docker.sock" {
|
||||||
|
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||||
|
}
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, "unix://"); err != nil || addr != "unix:///var/run/docker.sock" {
|
||||||
|
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||||
|
}
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, "udp://127.0.0.1"); err == nil {
|
||||||
|
t.Errorf("udp protocol address expected error return, but err == nil. Got %s", addr)
|
||||||
|
}
|
||||||
|
if addr, err := ParseHost(defaultHttpHost, defaultUnix, "udp://127.0.0.1:2375"); err == nil {
|
||||||
|
t.Errorf("udp protocol address expected error return, but err == nil. Got %s", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseRepositoryTag(t *testing.T) {
|
||||||
|
if repo, tag := ParseRepositoryTag("root"); repo != "root" || tag != "" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("root:tag"); repo != "root" || tag != "tag" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "tag", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, digest := ParseRepositoryTag("root@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); repo != "root" || digest != "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
|
||||||
|
t.Errorf("Expected repo: '%s' and digest: '%s', got '%s' and '%s'", "root", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", repo, digest)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("user/repo"); repo != "user/repo" || tag != "" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("user/repo:tag"); repo != "user/repo" || tag != "tag" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "tag", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, digest := ParseRepositoryTag("user/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); repo != "user/repo" || digest != "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
|
||||||
|
t.Errorf("Expected repo: '%s' and digest: '%s', got '%s' and '%s'", "user/repo", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", repo, digest)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("url:5000/repo"); repo != "url:5000/repo" || tag != "" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, tag := ParseRepositoryTag("url:5000/repo:tag"); repo != "url:5000/repo" || tag != "tag" {
|
||||||
|
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "tag", repo, tag)
|
||||||
|
}
|
||||||
|
if repo, digest := ParseRepositoryTag("url:5000/repo@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); repo != "url:5000/repo" || digest != "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
|
||||||
|
t.Errorf("Expected repo: '%s' and digest: '%s', got '%s' and '%s'", "url:5000/repo", "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", repo, digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePortMapping(t *testing.T) {
|
||||||
|
data, err := PartParser("ip:public:private", "192.168.1.1:80:8080")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) != 3 {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
if data["ip"] != "192.168.1.1" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if data["public"] != "80" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if data["private"] != "8080" {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePortRange(t *testing.T) {
|
||||||
|
if start, end, err := ParsePortRange("8000-8080"); err != nil || start != 8000 || end != 8080 {
|
||||||
|
t.Fatalf("Error: %s or Expecting {start,end} values {8000,8080} but found {%d,%d}.", err, start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePortRangeIncorrectRange(t *testing.T) {
|
||||||
|
if _, _, err := ParsePortRange("9000-8080"); err == nil || !strings.Contains(err.Error(), "Invalid range specified for the Port") {
|
||||||
|
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePortRangeIncorrectEndRange(t *testing.T) {
|
||||||
|
if _, _, err := ParsePortRange("8000-a"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
|
||||||
|
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := ParsePortRange("8000-30a"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
|
||||||
|
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePortRangeIncorrectStartRange(t *testing.T) {
|
||||||
|
if _, _, err := ParsePortRange("a-8000"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
|
||||||
|
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, _, err := ParsePortRange("30a-8000"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
|
||||||
|
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseLink(t *testing.T) {
|
||||||
|
name, alias, err := ParseLink("name:alias")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
|
||||||
|
}
|
||||||
|
if name != "name" {
|
||||||
|
t.Fatalf("Link name should have been name, got %s instead", name)
|
||||||
|
}
|
||||||
|
if alias != "alias" {
|
||||||
|
t.Fatalf("Link alias should have been alias, got %s instead", alias)
|
||||||
|
}
|
||||||
|
// short format definition
|
||||||
|
name, alias, err = ParseLink("name")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
|
||||||
|
}
|
||||||
|
if name != "name" {
|
||||||
|
t.Fatalf("Link name should have been name, got %s instead", name)
|
||||||
|
}
|
||||||
|
if alias != "name" {
|
||||||
|
t.Fatalf("Link alias should have been name, got %s instead", alias)
|
||||||
|
}
|
||||||
|
// empty string link definition is not allowed
|
||||||
|
if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
|
||||||
|
t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
|
||||||
|
}
|
||||||
|
// more than two colons are not allowed
|
||||||
|
if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
|
||||||
|
t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
versionMimetype = "application/vnd.docker.plugins.v1+json"
|
||||||
|
defaultTimeOut = 30
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewClient(addr string) *Client {
|
||||||
|
tr := &http.Transport{}
|
||||||
|
protoAndAddr := strings.Split(addr, "://")
|
||||||
|
configureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1])
|
||||||
|
return &Client{&http.Client{Transport: tr}, protoAndAddr[1]}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
http *http.Client
|
||||||
|
addr string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := json.NewEncoder(&buf).Encode(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "/"+serviceMethod, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Add("Accept", versionMimetype)
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
req.URL.Host = c.addr
|
||||||
|
|
||||||
|
var retries int
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, err := c.http.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
timeOff := backoff(retries)
|
||||||
|
if timeOff+time.Since(start) > defaultTimeOut {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
retries++
|
||||||
|
logrus.Warn("Unable to connect to plugin: %s, retrying in %ds\n", c.addr, timeOff)
|
||||||
|
time.Sleep(timeOff)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
remoteErr, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Plugin Error: %s", remoteErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.NewDecoder(resp.Body).Decode(&ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func backoff(retries int) time.Duration {
|
||||||
|
b, max := float64(1), float64(defaultTimeOut)
|
||||||
|
for b < max && retries > 0 {
|
||||||
|
b *= 2
|
||||||
|
retries--
|
||||||
|
}
|
||||||
|
if b > max {
|
||||||
|
b = max
|
||||||
|
}
|
||||||
|
return time.Duration(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func configureTCPTransport(tr *http.Transport, proto, addr string) {
|
||||||
|
// Why 32? See https://github.com/docker/docker/pull/8035.
|
||||||
|
timeout := 32 * time.Second
|
||||||
|
if proto == "unix" {
|
||||||
|
// No need for compression in local communications.
|
||||||
|
tr.DisableCompression = true
|
||||||
|
tr.Dial = func(_, _ string) (net.Conn, error) {
|
||||||
|
return net.DialTimeout(proto, addr, timeout)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tr.Proxy = http.ProxyFromEnvironment
|
||||||
|
tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mux *http.ServeMux
|
||||||
|
server *httptest.Server
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupRemotePluginServer() string {
|
||||||
|
mux = http.NewServeMux()
|
||||||
|
server = httptest.NewServer(mux)
|
||||||
|
return server.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func teardownRemotePluginServer() {
|
||||||
|
if server != nil {
|
||||||
|
server.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFailedConnection(t *testing.T) {
|
||||||
|
c := NewClient("tcp://127.0.0.1:1")
|
||||||
|
err := c.Call("Service.Method", nil, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Unexpected successful connection")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoInputOutput(t *testing.T) {
|
||||||
|
addr := setupRemotePluginServer()
|
||||||
|
defer teardownRemotePluginServer()
|
||||||
|
|
||||||
|
m := Manifest{[]string{"VolumeDriver", "NetworkDriver"}}
|
||||||
|
|
||||||
|
mux.HandleFunc("/Test.Echo", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != "POST" {
|
||||||
|
t.Fatalf("Expected POST, got %s\n", r.Method)
|
||||||
|
}
|
||||||
|
|
||||||
|
header := w.Header()
|
||||||
|
header.Set("Content-Type", versionMimetype)
|
||||||
|
|
||||||
|
io.Copy(w, r.Body)
|
||||||
|
})
|
||||||
|
|
||||||
|
c := NewClient(addr)
|
||||||
|
var output Manifest
|
||||||
|
err := c.Call("Test.Echo", m, &output)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(output, m) {
|
||||||
|
t.Fatalf("Expected %v, was %v\n", m, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultLocalRegistry = "/usr/share/docker/plugins"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("Plugin not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Registry interface {
|
||||||
|
Plugins() ([]*Plugin, error)
|
||||||
|
Plugin(name string) (*Plugin, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalRegistry struct {
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLocalRegistry(path string) *LocalRegistry {
|
||||||
|
if len(path) == 0 {
|
||||||
|
path = defaultLocalRegistry
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LocalRegistry{path}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LocalRegistry) Plugin(name string) (*Plugin, error) {
|
||||||
|
filepath := filepath.Join(l.path, name)
|
||||||
|
specpath := filepath + ".spec"
|
||||||
|
if fi, err := os.Stat(specpath); err == nil {
|
||||||
|
return readPluginInfo(specpath, fi)
|
||||||
|
}
|
||||||
|
socketpath := filepath + ".sock"
|
||||||
|
if fi, err := os.Stat(socketpath); err == nil {
|
||||||
|
return readPluginInfo(socketpath, fi)
|
||||||
|
}
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) {
|
||||||
|
name := strings.Split(fi.Name(), ".")[0]
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeSocket != 0 {
|
||||||
|
return &Plugin{
|
||||||
|
Name: name,
|
||||||
|
Addr: "unix://" + path,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addr := strings.TrimSpace(string(content))
|
||||||
|
|
||||||
|
u, err := url.Parse(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(u.Scheme) == 0 {
|
||||||
|
return nil, fmt.Errorf("Unknown protocol")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Plugin{
|
||||||
|
Name: name,
|
||||||
|
Addr: addr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnknownLocalPath(t *testing.T) {
|
||||||
|
tmpdir, err := ioutil.TempDir("", "docker-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
|
||||||
|
l := newLocalRegistry(filepath.Join(tmpdir, "unknown"))
|
||||||
|
_, err = l.Plugin("foo")
|
||||||
|
if err == nil || err != ErrNotFound {
|
||||||
|
t.Fatalf("Expected error for unknown directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalSocket(t *testing.T) {
|
||||||
|
tmpdir, err := ioutil.TempDir("", "docker-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpdir)
|
||||||
|
l, err := net.Listen("unix", filepath.Join(tmpdir, "echo.sock"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
r := newLocalRegistry(tmpdir)
|
||||||
|
p, err := r.Plugin("echo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pp, err := r.Plugin("echo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(p, pp) {
|
||||||
|
t.Fatalf("Expected %v, was %v\n", p, pp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Name != "echo" {
|
||||||
|
t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := fmt.Sprintf("unix://%s/echo.sock", tmpdir)
|
||||||
|
if p.Addr != addr {
|
||||||
|
t.Fatalf("Expected plugin addr `%s`, got %s\n", addr, p.Addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileSpecPlugin(t *testing.T) {
|
||||||
|
tmpdir, err := ioutil.TempDir("", "docker-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
path string
|
||||||
|
name string
|
||||||
|
addr string
|
||||||
|
fail bool
|
||||||
|
}{
|
||||||
|
{filepath.Join(tmpdir, "echo.spec"), "echo", "unix://var/lib/docker/plugins/echo.sock", false},
|
||||||
|
{filepath.Join(tmpdir, "foo.spec"), "foo", "tcp://localhost:8080", false},
|
||||||
|
{filepath.Join(tmpdir, "bar.spec"), "bar", "localhost:8080", true}, // unknown transport
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
if err = os.MkdirAll(path.Dir(c.path), 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = ioutil.WriteFile(c.path, []byte(c.addr), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
r := newLocalRegistry(tmpdir)
|
||||||
|
p, err := r.Plugin(c.name)
|
||||||
|
if c.fail && err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Name != c.name {
|
||||||
|
t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Addr != c.addr {
|
||||||
|
t.Fatalf("Expected plugin addr `%s`, got %s\n", c.addr, p.Addr)
|
||||||
|
}
|
||||||
|
os.Remove(c.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotImplements = errors.New("Plugin does not implement the requested driver")
|
||||||
|
)
|
||||||
|
|
||||||
|
type plugins struct {
|
||||||
|
sync.Mutex
|
||||||
|
plugins map[string]*Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
storage = plugins{plugins: make(map[string]*Plugin)}
|
||||||
|
extpointHandlers = make(map[string]func(string, *Client))
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manifest struct {
|
||||||
|
Implements []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Plugin struct {
|
||||||
|
Name string
|
||||||
|
Addr string
|
||||||
|
Client *Client
|
||||||
|
Manifest *Manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) activate() error {
|
||||||
|
m := new(Manifest)
|
||||||
|
p.Client = NewClient(p.Addr)
|
||||||
|
err := p.Client.Call("Plugin.Activate", nil, m)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("%s's manifest: %v", p.Name, m)
|
||||||
|
p.Manifest = m
|
||||||
|
for _, iface := range m.Implements {
|
||||||
|
handler, handled := extpointHandlers[iface]
|
||||||
|
if !handled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
handler(p.Name, p.Client)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func load(name string) (*Plugin, error) {
|
||||||
|
registry := newLocalRegistry("")
|
||||||
|
pl, err := registry.Plugin(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := pl.activate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func get(name string) (*Plugin, error) {
|
||||||
|
storage.Lock()
|
||||||
|
defer storage.Unlock()
|
||||||
|
pl, ok := storage.plugins[name]
|
||||||
|
if ok {
|
||||||
|
return pl, nil
|
||||||
|
}
|
||||||
|
pl, err := load(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("Plugin: %v", pl)
|
||||||
|
storage.plugins[name] = pl
|
||||||
|
return pl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(name, imp string) (*Plugin, error) {
|
||||||
|
pl, err := get(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, driver := range pl.Manifest.Implements {
|
||||||
|
logrus.Debugf("%s implements: %s", name, driver)
|
||||||
|
if driver == imp {
|
||||||
|
return pl, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, ErrNotImplements
|
||||||
|
}
|
||||||
|
|
||||||
|
func Handle(iface string, fn func(string, *Client)) {
|
||||||
|
extpointHandlers[iface] = fn
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// +build !linux
|
// +build !linux,!windows
|
||||||
|
|
||||||
package reexec
|
package reexec
|
||||||
|
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package reexec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Command(args ...string) *exec.Cmd {
|
||||||
|
return &exec.Cmd{
|
||||||
|
Path: Self(),
|
||||||
|
Args: args,
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
This package provides helper functions for dealing with string identifiers
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package stringid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const shortLen = 12
|
||||||
|
|
||||||
|
var validShortID = regexp.MustCompile("^[a-z0-9]{12}$")
|
||||||
|
|
||||||
|
// Determine if an arbitrary string *looks like* a short ID.
|
||||||
|
func IsShortID(id string) bool {
|
||||||
|
return validShortID.MatchString(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TruncateID returns a shorthand version of a string identifier for convenience.
|
||||||
|
// A collision with other shorthands is very unlikely, but possible.
|
||||||
|
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
|
||||||
|
// will need to use a langer prefix, or the full-length Id.
|
||||||
|
func TruncateID(id string) string {
|
||||||
|
trimTo := shortLen
|
||||||
|
if len(id) < shortLen {
|
||||||
|
trimTo = len(id)
|
||||||
|
}
|
||||||
|
return id[:trimTo]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateRandomID returns an unique id
|
||||||
|
func GenerateRandomID() string {
|
||||||
|
for {
|
||||||
|
id := make([]byte, 32)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, id); err != nil {
|
||||||
|
panic(err) // This shouldn't happen
|
||||||
|
}
|
||||||
|
value := hex.EncodeToString(id)
|
||||||
|
// if we try to parse the truncated for as an int and we don't have
|
||||||
|
// an error then the value is all numberic and causes issues when
|
||||||
|
// used as a hostname. ref #3869
|
||||||
|
if _, err := strconv.ParseInt(TruncateID(value), 10, 64); err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package stringid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateRandomID(t *testing.T) {
|
||||||
|
id := GenerateRandomID()
|
||||||
|
|
||||||
|
if len(id) != 64 {
|
||||||
|
t.Fatalf("Id returned is incorrect: %s", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortenId(t *testing.T) {
|
||||||
|
id := GenerateRandomID()
|
||||||
|
truncID := TruncateID(id)
|
||||||
|
if len(truncID) != 12 {
|
||||||
|
t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortenIdEmpty(t *testing.T) {
|
||||||
|
id := ""
|
||||||
|
truncID := TruncateID(id)
|
||||||
|
if len(truncID) > len(id) {
|
||||||
|
t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortenIdInvalid(t *testing.T) {
|
||||||
|
id := "1234"
|
||||||
|
truncID := TruncateID(id)
|
||||||
|
if len(truncID) != len(id) {
|
||||||
|
t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsShortIDNonHex(t *testing.T) {
|
||||||
|
id := "some non-hex value"
|
||||||
|
if IsShortID(id) {
|
||||||
|
t.Fatalf("%s is not a short ID", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsShortIDNotCorrectSize(t *testing.T) {
|
||||||
|
id := strings.Repeat("a", shortLen+1)
|
||||||
|
if IsShortID(id) {
|
||||||
|
t.Fatalf("%s is not a short ID", id)
|
||||||
|
}
|
||||||
|
id = strings.Repeat("a", shortLen-1)
|
||||||
|
if IsShortID(id) {
|
||||||
|
t.Fatalf("%s is not a short ID", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
// +build linux,cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// #include <termios.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
type Termios syscall.Termios
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if err := tcget(fd, &oldState.termios); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
|
||||||
|
C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&newState)))
|
||||||
|
newState.Oflag = newState.Oflag | C.OPOST
|
||||||
|
if err := tcset(fd, &newState); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p)))
|
||||||
|
if ret != 0 {
|
||||||
|
return err.(syscall.Errno)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p)))
|
||||||
|
if ret != 0 {
|
||||||
|
return err.(syscall.Errno)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
// +build !windows
|
||||||
|
// +build !linux !cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(p)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(p)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
+118
@@ -0,0 +1,118 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidState = errors.New("Invalid terminal state")
|
||||||
|
)
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
termios Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
type Winsize struct {
|
||||||
|
Height uint16
|
||||||
|
Width uint16
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||||
|
return os.Stdin, os.Stdout, os.Stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||||
|
var inFd uintptr
|
||||||
|
var isTerminalIn bool
|
||||||
|
if file, ok := in.(*os.File); ok {
|
||||||
|
inFd = file.Fd()
|
||||||
|
isTerminalIn = IsTerminal(inFd)
|
||||||
|
}
|
||||||
|
return inFd, isTerminalIn
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||||
|
ws := &Winsize{}
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||||
|
// Skipp errno = 0
|
||||||
|
if err == 0 {
|
||||||
|
return ws, nil
|
||||||
|
}
|
||||||
|
return ws, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
|
||||||
|
// Skipp errno = 0
|
||||||
|
if err == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
var termios Termios
|
||||||
|
return tcget(fd, &termios) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func RestoreTerminal(fd uintptr, state *State) error {
|
||||||
|
if state == nil {
|
||||||
|
return ErrInvalidState
|
||||||
|
}
|
||||||
|
if err := tcset(fd, &state.termios); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveState(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if err := tcget(fd, &oldState.termios); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisableEcho(fd uintptr, state *State) error {
|
||||||
|
newState := state.termios
|
||||||
|
newState.Lflag &^= syscall.ECHO
|
||||||
|
|
||||||
|
if err := tcset(fd, &newState); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
handleInterrupt(fd, state)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||||
|
oldState, err := MakeRaw(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
handleInterrupt(fd, oldState)
|
||||||
|
return oldState, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleInterrupt(fd uintptr, state *State) {
|
||||||
|
sigchan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigchan, os.Interrupt)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = <-sigchan
|
||||||
|
RestoreTerminal(fd, state)
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
}
|
||||||
+138
@@ -0,0 +1,138 @@
|
|||||||
|
// +build windows
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/pkg/term/winconsole"
|
||||||
|
)
|
||||||
|
|
||||||
|
// State holds the console mode for the terminal.
|
||||||
|
type State struct {
|
||||||
|
mode uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Winsize is used for window size.
|
||||||
|
type Winsize struct {
|
||||||
|
Height uint16
|
||||||
|
Width uint16
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||||
|
switch {
|
||||||
|
case os.Getenv("ConEmuANSI") == "ON":
|
||||||
|
// The ConEmu shell emulates ANSI well by default.
|
||||||
|
return os.Stdin, os.Stdout, os.Stderr
|
||||||
|
case os.Getenv("MSYSTEM") != "":
|
||||||
|
// MSYS (mingw) does not emulate ANSI well.
|
||||||
|
return winconsole.WinConsoleStreams()
|
||||||
|
default:
|
||||||
|
return winconsole.WinConsoleStreams()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal.
|
||||||
|
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||||
|
return winconsole.GetHandleInfo(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWinsize retrieves the window size of the terminal connected to the passed file descriptor.
|
||||||
|
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||||
|
info, err := winconsole.GetConsoleScreenBufferInfo(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(azlinux): Set the pixel width / height of the console (currently unused by any caller)
|
||||||
|
return &Winsize{
|
||||||
|
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
||||||
|
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
||||||
|
x: 0,
|
||||||
|
y: 0}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWinsize sets the size of the given terminal connected to the passed file descriptor.
|
||||||
|
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||||
|
// TODO(azlinux): Implement SetWinsize
|
||||||
|
logrus.Debugf("[windows] SetWinsize: WARNING -- Unsupported method invoked")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func IsTerminal(fd uintptr) bool {
|
||||||
|
return winconsole.IsConsole(fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreTerminal restores the terminal connected to the given file descriptor to a
|
||||||
|
// previous state.
|
||||||
|
func RestoreTerminal(fd uintptr, state *State) error {
|
||||||
|
return winconsole.SetConsoleMode(fd, state.mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveState saves the state of the terminal connected to the given file descriptor.
|
||||||
|
func SaveState(fd uintptr) (*State, error) {
|
||||||
|
mode, e := winconsole.GetConsoleMode(fd)
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
return &State{mode}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableEcho disables echo for the terminal connected to the given file descriptor.
|
||||||
|
// -- See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||||
|
func DisableEcho(fd uintptr, state *State) error {
|
||||||
|
mode := state.mode
|
||||||
|
mode &^= winconsole.ENABLE_ECHO_INPUT
|
||||||
|
mode |= winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT
|
||||||
|
// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
|
||||||
|
return winconsole.SetConsoleMode(fd, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawTerminal puts the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||||
|
state, err := MakeRaw(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
|
||||||
|
return state, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
state, err := SaveState(fd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// See
|
||||||
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||||
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||||
|
mode := state.mode
|
||||||
|
|
||||||
|
// Disable these modes
|
||||||
|
mode &^= winconsole.ENABLE_ECHO_INPUT
|
||||||
|
mode &^= winconsole.ENABLE_LINE_INPUT
|
||||||
|
mode &^= winconsole.ENABLE_MOUSE_INPUT
|
||||||
|
mode &^= winconsole.ENABLE_WINDOW_INPUT
|
||||||
|
mode &^= winconsole.ENABLE_PROCESSED_INPUT
|
||||||
|
|
||||||
|
// Enable these modes
|
||||||
|
mode |= winconsole.ENABLE_EXTENDED_FLAGS
|
||||||
|
mode |= winconsole.ENABLE_INSERT_MODE
|
||||||
|
mode |= winconsole.ENABLE_QUICK_EDIT_MODE
|
||||||
|
|
||||||
|
err = winconsole.SetConsoleMode(fd, mode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TIOCGETA
|
||||||
|
setTermios = syscall.TIOCSETA
|
||||||
|
|
||||||
|
IGNBRK = syscall.IGNBRK
|
||||||
|
PARMRK = syscall.PARMRK
|
||||||
|
INLCR = syscall.INLCR
|
||||||
|
IGNCR = syscall.IGNCR
|
||||||
|
ECHONL = syscall.ECHONL
|
||||||
|
CSIZE = syscall.CSIZE
|
||||||
|
ICRNL = syscall.ICRNL
|
||||||
|
ISTRIP = syscall.ISTRIP
|
||||||
|
PARENB = syscall.PARENB
|
||||||
|
ECHO = syscall.ECHO
|
||||||
|
ICANON = syscall.ICANON
|
||||||
|
ISIG = syscall.ISIG
|
||||||
|
IXON = syscall.IXON
|
||||||
|
BRKINT = syscall.BRKINT
|
||||||
|
INPCK = syscall.INPCK
|
||||||
|
OPOST = syscall.OPOST
|
||||||
|
CS8 = syscall.CS8
|
||||||
|
IEXTEN = syscall.IEXTEN
|
||||||
|
)
|
||||||
|
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint64
|
||||||
|
Oflag uint64
|
||||||
|
Cflag uint64
|
||||||
|
Lflag uint64
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint64
|
||||||
|
Ospeed uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
|
||||||
|
newState.Oflag &^= OPOST
|
||||||
|
newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN)
|
||||||
|
newState.Cflag &^= (CSIZE | PARENB)
|
||||||
|
newState.Cflag |= CS8
|
||||||
|
newState.Cc[syscall.VMIN] = 1
|
||||||
|
newState.Cc[syscall.VTIME] = 0
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TIOCGETA
|
||||||
|
setTermios = syscall.TIOCSETA
|
||||||
|
|
||||||
|
IGNBRK = syscall.IGNBRK
|
||||||
|
PARMRK = syscall.PARMRK
|
||||||
|
INLCR = syscall.INLCR
|
||||||
|
IGNCR = syscall.IGNCR
|
||||||
|
ECHONL = syscall.ECHONL
|
||||||
|
CSIZE = syscall.CSIZE
|
||||||
|
ICRNL = syscall.ICRNL
|
||||||
|
ISTRIP = syscall.ISTRIP
|
||||||
|
PARENB = syscall.PARENB
|
||||||
|
ECHO = syscall.ECHO
|
||||||
|
ICANON = syscall.ICANON
|
||||||
|
ISIG = syscall.ISIG
|
||||||
|
IXON = syscall.IXON
|
||||||
|
BRKINT = syscall.BRKINT
|
||||||
|
INPCK = syscall.INPCK
|
||||||
|
OPOST = syscall.OPOST
|
||||||
|
CS8 = syscall.CS8
|
||||||
|
IEXTEN = syscall.IEXTEN
|
||||||
|
)
|
||||||
|
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON)
|
||||||
|
newState.Oflag &^= OPOST
|
||||||
|
newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN)
|
||||||
|
newState.Cflag &^= (CSIZE | PARENB)
|
||||||
|
newState.Cflag |= CS8
|
||||||
|
newState.Cc[syscall.VMIN] = 1
|
||||||
|
newState.Cc[syscall.VTIME] = 0
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
// +build !cgo
|
||||||
|
|
||||||
|
package term
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
getTermios = syscall.TCGETS
|
||||||
|
setTermios = syscall.TCSETS
|
||||||
|
)
|
||||||
|
|
||||||
|
type Termios struct {
|
||||||
|
Iflag uint32
|
||||||
|
Oflag uint32
|
||||||
|
Cflag uint32
|
||||||
|
Lflag uint32
|
||||||
|
Cc [20]byte
|
||||||
|
Ispeed uint32
|
||||||
|
Ospeed uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||||
|
// mode and returns the previous state of the terminal so that it can be
|
||||||
|
// restored.
|
||||||
|
func MakeRaw(fd uintptr) (*State, error) {
|
||||||
|
var oldState State
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newState := oldState.termios
|
||||||
|
|
||||||
|
newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON)
|
||||||
|
newState.Oflag &^= syscall.OPOST
|
||||||
|
newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN)
|
||||||
|
newState.Cflag &^= (syscall.CSIZE | syscall.PARENB)
|
||||||
|
newState.Cflag |= syscall.CS8
|
||||||
|
|
||||||
|
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &oldState, nil
|
||||||
|
}
|
||||||
Generated
Vendored
+1053
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+232
@@ -0,0 +1,232 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winconsole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func helpsTestParseInt16OrDefault(t *testing.T, expectedValue int16, shouldFail bool, input string, defaultValue int16, format string, args ...string) {
|
||||||
|
value, err := parseInt16OrDefault(input, defaultValue)
|
||||||
|
if nil != err && !shouldFail {
|
||||||
|
t.Errorf("Unexpected error returned %v", err)
|
||||||
|
t.Errorf(format, args)
|
||||||
|
}
|
||||||
|
if nil == err && shouldFail {
|
||||||
|
t.Errorf("Should have failed as expected\n\tReturned value = %d", value)
|
||||||
|
t.Errorf(format, args)
|
||||||
|
}
|
||||||
|
if expectedValue != value {
|
||||||
|
t.Errorf("The value returned does not match expected\n\tExpected:%v\n\t:Actual%v", expectedValue, value)
|
||||||
|
t.Errorf(format, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseInt16OrDefault(t *testing.T) {
|
||||||
|
// empty string
|
||||||
|
helpsTestParseInt16OrDefault(t, 0, false, "", 0, "Empty string returns default")
|
||||||
|
helpsTestParseInt16OrDefault(t, 2, false, "", 2, "Empty string returns default")
|
||||||
|
|
||||||
|
// normal case
|
||||||
|
helpsTestParseInt16OrDefault(t, 0, false, "0", 0, "0 handled correctly")
|
||||||
|
helpsTestParseInt16OrDefault(t, 111, false, "111", 2, "Normal")
|
||||||
|
helpsTestParseInt16OrDefault(t, 111, false, "+111", 2, "+N")
|
||||||
|
helpsTestParseInt16OrDefault(t, -111, false, "-111", 2, "-N")
|
||||||
|
helpsTestParseInt16OrDefault(t, 0, false, "+0", 11, "+0")
|
||||||
|
helpsTestParseInt16OrDefault(t, 0, false, "-0", 12, "-0")
|
||||||
|
|
||||||
|
// ill formed strings
|
||||||
|
helpsTestParseInt16OrDefault(t, 0, true, "abc", 0, "Invalid string")
|
||||||
|
helpsTestParseInt16OrDefault(t, 42, true, "+= 23", 42, "Invalid string")
|
||||||
|
helpsTestParseInt16OrDefault(t, 42, true, "123.45", 42, "float like")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpsTestGetNumberOfChars(t *testing.T, expected uint32, fromCoord COORD, toCoord COORD, screenSize COORD, format string, args ...interface{}) {
|
||||||
|
actual := getNumberOfChars(fromCoord, toCoord, screenSize)
|
||||||
|
mesg := fmt.Sprintf(format, args)
|
||||||
|
assertTrue(t, expected == actual, fmt.Sprintf("%s Expected=%d, Actual=%d, Parameters = { fromCoord=%+v, toCoord=%+v, screenSize=%+v", mesg, expected, actual, fromCoord, toCoord, screenSize))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetNumberOfChars(t *testing.T) {
|
||||||
|
// Note: The columns and lines are 0 based
|
||||||
|
// Also that interval is "inclusive" means will have both start and end chars
|
||||||
|
// This test only tests the number opf characters being written
|
||||||
|
|
||||||
|
// all four corners
|
||||||
|
maxWindow := COORD{X: 80, Y: 50}
|
||||||
|
leftTop := COORD{X: 0, Y: 0}
|
||||||
|
rightTop := COORD{X: 79, Y: 0}
|
||||||
|
leftBottom := COORD{X: 0, Y: 49}
|
||||||
|
rightBottom := COORD{X: 79, Y: 49}
|
||||||
|
|
||||||
|
// same position
|
||||||
|
helpsTestGetNumberOfChars(t, 1, COORD{X: 1, Y: 14}, COORD{X: 1, Y: 14}, COORD{X: 80, Y: 50}, "Same position random line")
|
||||||
|
|
||||||
|
// four corners
|
||||||
|
helpsTestGetNumberOfChars(t, 1, leftTop, leftTop, maxWindow, "Same position- leftTop")
|
||||||
|
helpsTestGetNumberOfChars(t, 1, rightTop, rightTop, maxWindow, "Same position- rightTop")
|
||||||
|
helpsTestGetNumberOfChars(t, 1, leftBottom, leftBottom, maxWindow, "Same position- leftBottom")
|
||||||
|
helpsTestGetNumberOfChars(t, 1, rightBottom, rightBottom, maxWindow, "Same position- rightBottom")
|
||||||
|
|
||||||
|
// from this char to next char on same line
|
||||||
|
helpsTestGetNumberOfChars(t, 2, COORD{X: 0, Y: 0}, COORD{X: 1, Y: 0}, maxWindow, "Next position on same line")
|
||||||
|
helpsTestGetNumberOfChars(t, 2, COORD{X: 1, Y: 14}, COORD{X: 2, Y: 14}, maxWindow, "Next position on same line")
|
||||||
|
|
||||||
|
// from this char to next 10 chars on same line
|
||||||
|
helpsTestGetNumberOfChars(t, 11, COORD{X: 0, Y: 0}, COORD{X: 10, Y: 0}, maxWindow, "Next position on same line")
|
||||||
|
helpsTestGetNumberOfChars(t, 11, COORD{X: 1, Y: 14}, COORD{X: 11, Y: 14}, maxWindow, "Next position on same line")
|
||||||
|
|
||||||
|
helpsTestGetNumberOfChars(t, 5, COORD{X: 3, Y: 11}, COORD{X: 7, Y: 11}, maxWindow, "To and from on same line")
|
||||||
|
|
||||||
|
helpsTestGetNumberOfChars(t, 8, COORD{X: 0, Y: 34}, COORD{X: 7, Y: 34}, maxWindow, "Start of line to middle")
|
||||||
|
helpsTestGetNumberOfChars(t, 4, COORD{X: 76, Y: 34}, COORD{X: 79, Y: 34}, maxWindow, "Middle to end of line")
|
||||||
|
|
||||||
|
// multiple lines - 1
|
||||||
|
helpsTestGetNumberOfChars(t, 81, COORD{X: 0, Y: 0}, COORD{X: 0, Y: 1}, maxWindow, "one line below same X")
|
||||||
|
helpsTestGetNumberOfChars(t, 81, COORD{X: 10, Y: 10}, COORD{X: 10, Y: 11}, maxWindow, "one line below same X")
|
||||||
|
|
||||||
|
// multiple lines - 2
|
||||||
|
helpsTestGetNumberOfChars(t, 161, COORD{X: 0, Y: 0}, COORD{X: 0, Y: 2}, maxWindow, "one line below same X")
|
||||||
|
helpsTestGetNumberOfChars(t, 161, COORD{X: 10, Y: 10}, COORD{X: 10, Y: 12}, maxWindow, "one line below same X")
|
||||||
|
|
||||||
|
// multiple lines - 3
|
||||||
|
helpsTestGetNumberOfChars(t, 241, COORD{X: 0, Y: 0}, COORD{X: 0, Y: 3}, maxWindow, "one line below same X")
|
||||||
|
helpsTestGetNumberOfChars(t, 241, COORD{X: 10, Y: 10}, COORD{X: 10, Y: 13}, maxWindow, "one line below same X")
|
||||||
|
|
||||||
|
// full line
|
||||||
|
helpsTestGetNumberOfChars(t, 80, COORD{X: 0, Y: 0}, COORD{X: 79, Y: 0}, maxWindow, "Full line - first")
|
||||||
|
helpsTestGetNumberOfChars(t, 80, COORD{X: 0, Y: 23}, COORD{X: 79, Y: 23}, maxWindow, "Full line - random")
|
||||||
|
helpsTestGetNumberOfChars(t, 80, COORD{X: 0, Y: 49}, COORD{X: 79, Y: 49}, maxWindow, "Full line - last")
|
||||||
|
|
||||||
|
// full screen
|
||||||
|
helpsTestGetNumberOfChars(t, 80*50, leftTop, rightBottom, maxWindow, "full screen")
|
||||||
|
|
||||||
|
helpsTestGetNumberOfChars(t, 80*50-1, COORD{X: 1, Y: 0}, rightBottom, maxWindow, "dropping first char to, end of screen")
|
||||||
|
helpsTestGetNumberOfChars(t, 80*50-2, COORD{X: 2, Y: 0}, rightBottom, maxWindow, "dropping first two char to, end of screen")
|
||||||
|
|
||||||
|
helpsTestGetNumberOfChars(t, 80*50-1, leftTop, COORD{X: 78, Y: 49}, maxWindow, "from start of screen, till last char-1")
|
||||||
|
helpsTestGetNumberOfChars(t, 80*50-2, leftTop, COORD{X: 77, Y: 49}, maxWindow, "from start of screen, till last char-2")
|
||||||
|
|
||||||
|
helpsTestGetNumberOfChars(t, 80*50-5, COORD{X: 4, Y: 0}, COORD{X: 78, Y: 49}, COORD{X: 80, Y: 50}, "from start of screen+4, till last char-1")
|
||||||
|
helpsTestGetNumberOfChars(t, 80*50-6, COORD{X: 4, Y: 0}, COORD{X: 77, Y: 49}, COORD{X: 80, Y: 50}, "from start of screen+4, till last char-2")
|
||||||
|
}
|
||||||
|
|
||||||
|
var allForeground = []int16{
|
||||||
|
ANSI_FOREGROUND_BLACK,
|
||||||
|
ANSI_FOREGROUND_RED,
|
||||||
|
ANSI_FOREGROUND_GREEN,
|
||||||
|
ANSI_FOREGROUND_YELLOW,
|
||||||
|
ANSI_FOREGROUND_BLUE,
|
||||||
|
ANSI_FOREGROUND_MAGENTA,
|
||||||
|
ANSI_FOREGROUND_CYAN,
|
||||||
|
ANSI_FOREGROUND_WHITE,
|
||||||
|
ANSI_FOREGROUND_DEFAULT,
|
||||||
|
}
|
||||||
|
var allBackground = []int16{
|
||||||
|
ANSI_BACKGROUND_BLACK,
|
||||||
|
ANSI_BACKGROUND_RED,
|
||||||
|
ANSI_BACKGROUND_GREEN,
|
||||||
|
ANSI_BACKGROUND_YELLOW,
|
||||||
|
ANSI_BACKGROUND_BLUE,
|
||||||
|
ANSI_BACKGROUND_MAGENTA,
|
||||||
|
ANSI_BACKGROUND_CYAN,
|
||||||
|
ANSI_BACKGROUND_WHITE,
|
||||||
|
ANSI_BACKGROUND_DEFAULT,
|
||||||
|
}
|
||||||
|
|
||||||
|
func maskForeground(flag WORD) WORD {
|
||||||
|
return flag & FOREGROUND_MASK_UNSET
|
||||||
|
}
|
||||||
|
|
||||||
|
func onlyForeground(flag WORD) WORD {
|
||||||
|
return flag & FOREGROUND_MASK_SET
|
||||||
|
}
|
||||||
|
|
||||||
|
func maskBackground(flag WORD) WORD {
|
||||||
|
return flag & BACKGROUND_MASK_UNSET
|
||||||
|
}
|
||||||
|
|
||||||
|
func onlyBackground(flag WORD) WORD {
|
||||||
|
return flag & BACKGROUND_MASK_SET
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpsTestGetWindowsTextAttributeForAnsiValue(t *testing.T, oldValue WORD /*, expected WORD*/, ansi int16, onlyMask WORD, restMask WORD) WORD {
|
||||||
|
actual, err := getWindowsTextAttributeForAnsiValue(oldValue, FOREGROUND_MASK_SET, ansi)
|
||||||
|
assertTrue(t, nil == err, "Should be no error")
|
||||||
|
// assert that other bits are not affected
|
||||||
|
if 0 != oldValue {
|
||||||
|
assertTrue(t, (actual&restMask) == (oldValue&restMask), "The operation should not have affected other bits actual=%X oldValue=%X ansi=%d", actual, oldValue, ansi)
|
||||||
|
}
|
||||||
|
return actual
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackgroundForAnsiValue(t *testing.T) {
|
||||||
|
// Check that nothing else changes
|
||||||
|
// background changes
|
||||||
|
for _, state1 := range allBackground {
|
||||||
|
for _, state2 := range allBackground {
|
||||||
|
flag := WORD(0)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// cummulative bcakground changes
|
||||||
|
for _, state1 := range allBackground {
|
||||||
|
flag := WORD(0)
|
||||||
|
for _, state2 := range allBackground {
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// change background after foreground
|
||||||
|
for _, state1 := range allForeground {
|
||||||
|
for _, state2 := range allBackground {
|
||||||
|
flag := WORD(0)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// change background after change cumulative
|
||||||
|
for _, state1 := range allForeground {
|
||||||
|
flag := WORD(0)
|
||||||
|
for _, state2 := range allBackground {
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestForegroundForAnsiValue(t *testing.T) {
|
||||||
|
// Check that nothing else changes
|
||||||
|
for _, state1 := range allForeground {
|
||||||
|
for _, state2 := range allForeground {
|
||||||
|
flag := WORD(0)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state1 := range allForeground {
|
||||||
|
flag := WORD(0)
|
||||||
|
for _, state2 := range allForeground {
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, state1 := range allBackground {
|
||||||
|
for _, state2 := range allForeground {
|
||||||
|
flag := WORD(0)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, state1 := range allBackground {
|
||||||
|
flag := WORD(0)
|
||||||
|
for _, state2 := range allForeground {
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
|
||||||
|
flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
Vendored
+234
@@ -0,0 +1,234 @@
|
|||||||
|
package winconsole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
|
||||||
|
const (
|
||||||
|
ANSI_ESCAPE_PRIMARY = 0x1B
|
||||||
|
ANSI_ESCAPE_SECONDARY = 0x5B
|
||||||
|
ANSI_COMMAND_FIRST = 0x40
|
||||||
|
ANSI_COMMAND_LAST = 0x7E
|
||||||
|
ANSI_PARAMETER_SEP = ";"
|
||||||
|
ANSI_CMD_G0 = '('
|
||||||
|
ANSI_CMD_G1 = ')'
|
||||||
|
ANSI_CMD_G2 = '*'
|
||||||
|
ANSI_CMD_G3 = '+'
|
||||||
|
ANSI_CMD_DECPNM = '>'
|
||||||
|
ANSI_CMD_DECPAM = '='
|
||||||
|
ANSI_CMD_OSC = ']'
|
||||||
|
ANSI_CMD_STR_TERM = '\\'
|
||||||
|
ANSI_BEL = 0x07
|
||||||
|
KEY_EVENT = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface that implements terminal handling
|
||||||
|
type terminalEmulator interface {
|
||||||
|
HandleOutputCommand(fd uintptr, command []byte) (n int, err error)
|
||||||
|
HandleInputSequence(fd uintptr, command []byte) (n int, err error)
|
||||||
|
WriteChars(fd uintptr, w io.Writer, p []byte) (n int, err error)
|
||||||
|
ReadChars(fd uintptr, w io.Reader, p []byte) (n int, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type terminalWriter struct {
|
||||||
|
wrappedWriter io.Writer
|
||||||
|
emulator terminalEmulator
|
||||||
|
command []byte
|
||||||
|
inSequence bool
|
||||||
|
fd uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type terminalReader struct {
|
||||||
|
wrappedReader io.ReadCloser
|
||||||
|
emulator terminalEmulator
|
||||||
|
command []byte
|
||||||
|
inSequence bool
|
||||||
|
fd uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
|
||||||
|
func isAnsiCommandChar(b byte) bool {
|
||||||
|
switch {
|
||||||
|
case ANSI_COMMAND_FIRST <= b && b <= ANSI_COMMAND_LAST && b != ANSI_ESCAPE_SECONDARY:
|
||||||
|
return true
|
||||||
|
case b == ANSI_CMD_G1 || b == ANSI_CMD_OSC || b == ANSI_CMD_DECPAM || b == ANSI_CMD_DECPNM:
|
||||||
|
// non-CSI escape sequence terminator
|
||||||
|
return true
|
||||||
|
case b == ANSI_CMD_STR_TERM || b == ANSI_BEL:
|
||||||
|
// String escape sequence terminator
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCharacterSelectionCmdChar(b byte) bool {
|
||||||
|
return (b == ANSI_CMD_G0 || b == ANSI_CMD_G1 || b == ANSI_CMD_G2 || b == ANSI_CMD_G3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isXtermOscSequence(command []byte, current byte) bool {
|
||||||
|
return (len(command) >= 2 && command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_CMD_OSC && current != ANSI_BEL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes len(p) bytes from p to the underlying data stream.
|
||||||
|
// http://golang.org/pkg/io/#Writer
|
||||||
|
func (tw *terminalWriter) Write(p []byte) (n int, err error) {
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if tw.emulator == nil {
|
||||||
|
return tw.wrappedWriter.Write(p)
|
||||||
|
}
|
||||||
|
// Emulate terminal by extracting commands and executing them
|
||||||
|
totalWritten := 0
|
||||||
|
start := 0 // indicates start of the next chunk
|
||||||
|
end := len(p)
|
||||||
|
for current := 0; current < end; current++ {
|
||||||
|
if tw.inSequence {
|
||||||
|
// inside escape sequence
|
||||||
|
tw.command = append(tw.command, p[current])
|
||||||
|
if isAnsiCommandChar(p[current]) {
|
||||||
|
if !isXtermOscSequence(tw.command, p[current]) {
|
||||||
|
// found the last command character.
|
||||||
|
// Now we have a complete command.
|
||||||
|
nchar, err := tw.emulator.HandleOutputCommand(tw.fd, tw.command)
|
||||||
|
totalWritten += nchar
|
||||||
|
if err != nil {
|
||||||
|
return totalWritten, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear the command
|
||||||
|
// don't include current character again
|
||||||
|
tw.command = tw.command[:0]
|
||||||
|
start = current + 1
|
||||||
|
tw.inSequence = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if p[current] == ANSI_ESCAPE_PRIMARY {
|
||||||
|
// entering escape sequnce
|
||||||
|
tw.inSequence = true
|
||||||
|
// indicates end of "normal sequence", write whatever you have so far
|
||||||
|
if len(p[start:current]) > 0 {
|
||||||
|
nw, err := tw.emulator.WriteChars(tw.fd, tw.wrappedWriter, p[start:current])
|
||||||
|
totalWritten += nw
|
||||||
|
if err != nil {
|
||||||
|
return totalWritten, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// include the current character as part of the next sequence
|
||||||
|
tw.command = append(tw.command, p[current])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// note that so far, start of the escape sequence triggers writing out of bytes to console.
|
||||||
|
// For the part _after_ the end of last escape sequence, it is not written out yet. So write it out
|
||||||
|
if !tw.inSequence {
|
||||||
|
// assumption is that we can't be inside sequence and therefore command should be empty
|
||||||
|
if len(p[start:]) > 0 {
|
||||||
|
nw, err := tw.emulator.WriteChars(tw.fd, tw.wrappedWriter, p[start:])
|
||||||
|
totalWritten += nw
|
||||||
|
if err != nil {
|
||||||
|
return totalWritten, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalWritten, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads up to len(p) bytes into p.
|
||||||
|
// http://golang.org/pkg/io/#Reader
|
||||||
|
func (tr *terminalReader) Read(p []byte) (n int, err error) {
|
||||||
|
//Implementations of Read are discouraged from returning a zero byte count
|
||||||
|
// with a nil error, except when len(p) == 0.
|
||||||
|
if len(p) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if nil == tr.emulator {
|
||||||
|
return tr.readFromWrappedReader(p)
|
||||||
|
}
|
||||||
|
return tr.emulator.ReadChars(tr.fd, tr.wrappedReader, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the underlying stream
|
||||||
|
func (tr *terminalReader) Close() (err error) {
|
||||||
|
return tr.wrappedReader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *terminalReader) readFromWrappedReader(p []byte) (n int, err error) {
|
||||||
|
return tr.wrappedReader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ansiCommand struct {
|
||||||
|
CommandBytes []byte
|
||||||
|
Command string
|
||||||
|
Parameters []string
|
||||||
|
IsSpecial bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAnsiCommand(command []byte) *ansiCommand {
|
||||||
|
if isCharacterSelectionCmdChar(command[1]) {
|
||||||
|
// Is Character Set Selection commands
|
||||||
|
return &ansiCommand{
|
||||||
|
CommandBytes: command,
|
||||||
|
Command: string(command),
|
||||||
|
IsSpecial: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// last char is command character
|
||||||
|
lastCharIndex := len(command) - 1
|
||||||
|
|
||||||
|
retValue := &ansiCommand{
|
||||||
|
CommandBytes: command,
|
||||||
|
Command: string(command[lastCharIndex]),
|
||||||
|
IsSpecial: false,
|
||||||
|
}
|
||||||
|
// more than a single escape
|
||||||
|
if lastCharIndex != 0 {
|
||||||
|
start := 1
|
||||||
|
// skip if double char escape sequence
|
||||||
|
if command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_ESCAPE_SECONDARY {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
// convert this to GetNextParam method
|
||||||
|
retValue.Parameters = strings.Split(string(command[start:lastCharIndex]), ANSI_PARAMETER_SEP)
|
||||||
|
}
|
||||||
|
return retValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ansiCommand) getParam(index int) string {
|
||||||
|
if len(c.Parameters) > index {
|
||||||
|
return c.Parameters[index]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *ansiCommand) String() string {
|
||||||
|
return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
|
||||||
|
bytesToHex(ac.CommandBytes),
|
||||||
|
ac.Command,
|
||||||
|
strings.Join(ac.Parameters, "\",\""))
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytesToHex(b []byte) string {
|
||||||
|
hex := make([]string, len(b))
|
||||||
|
for i, ch := range b {
|
||||||
|
hex[i] = fmt.Sprintf("%X", ch)
|
||||||
|
}
|
||||||
|
return strings.Join(hex, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt16OrDefault(s string, defaultValue int16) (n int16, err error) {
|
||||||
|
if s == "" {
|
||||||
|
return defaultValue, nil
|
||||||
|
}
|
||||||
|
parsedValue, err := strconv.ParseInt(s, 10, 16)
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue, err
|
||||||
|
}
|
||||||
|
return int16(parsedValue), nil
|
||||||
|
}
|
||||||
Generated
Vendored
+388
@@ -0,0 +1,388 @@
|
|||||||
|
package winconsole
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WRITE_OPERATION = iota
|
||||||
|
COMMAND_OPERATION = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
var languages = []string{
|
||||||
|
"Български",
|
||||||
|
"Català",
|
||||||
|
"Čeština",
|
||||||
|
"Ελληνικά",
|
||||||
|
"Español",
|
||||||
|
"Esperanto",
|
||||||
|
"Euskara",
|
||||||
|
"Français",
|
||||||
|
"Galego",
|
||||||
|
"한국어",
|
||||||
|
"ქართული",
|
||||||
|
"Latviešu",
|
||||||
|
"Lietuvių",
|
||||||
|
"Magyar",
|
||||||
|
"Nederlands",
|
||||||
|
"日本語",
|
||||||
|
"Norsk bokmål",
|
||||||
|
"Norsk nynorsk",
|
||||||
|
"Polski",
|
||||||
|
"Português",
|
||||||
|
"Română",
|
||||||
|
"Русский",
|
||||||
|
"Slovenčina",
|
||||||
|
"Slovenščina",
|
||||||
|
"Српски",
|
||||||
|
"српскохрватски",
|
||||||
|
"Suomi",
|
||||||
|
"Svenska",
|
||||||
|
"ไทย",
|
||||||
|
"Tiếng Việt",
|
||||||
|
"Türkçe",
|
||||||
|
"Українська",
|
||||||
|
"中文",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock terminal handler object
|
||||||
|
type mockTerminal struct {
|
||||||
|
OutputCommandSequence []terminalOperation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used for recording the callback data
|
||||||
|
type terminalOperation struct {
|
||||||
|
Operation int
|
||||||
|
Data []byte
|
||||||
|
Str string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *mockTerminal) record(operation int, data []byte) {
|
||||||
|
op := terminalOperation{
|
||||||
|
Operation: operation,
|
||||||
|
Data: make([]byte, len(data)),
|
||||||
|
}
|
||||||
|
copy(op.Data, data)
|
||||||
|
op.Str = string(op.Data)
|
||||||
|
mt.OutputCommandSequence = append(mt.OutputCommandSequence, op)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *mockTerminal) HandleOutputCommand(fd uintptr, command []byte) (n int, err error) {
|
||||||
|
mt.record(COMMAND_OPERATION, command)
|
||||||
|
return len(command), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *mockTerminal) HandleInputSequence(fd uintptr, command []byte) (n int, err error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *mockTerminal) WriteChars(fd uintptr, w io.Writer, p []byte) (n int, err error) {
|
||||||
|
mt.record(WRITE_OPERATION, p)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *mockTerminal) ReadChars(fd uintptr, w io.Reader, p []byte) (n int, err error) {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertTrue(t *testing.T, cond bool, format string, args ...interface{}) {
|
||||||
|
if !cond {
|
||||||
|
t.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reflect.DeepEqual does not provide detailed information as to what excatly failed.
|
||||||
|
func assertBytesEqual(t *testing.T, expected, actual []byte, format string, args ...interface{}) {
|
||||||
|
match := true
|
||||||
|
mismatchIndex := 0
|
||||||
|
if len(expected) == len(actual) {
|
||||||
|
for i := 0; i < len(expected); i++ {
|
||||||
|
if expected[i] != actual[i] {
|
||||||
|
match = false
|
||||||
|
mismatchIndex = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match = false
|
||||||
|
t.Errorf("Lengths don't match Expected=%d Actual=%d", len(expected), len(actual))
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
t.Errorf("Mismatch at index %d ", mismatchIndex)
|
||||||
|
t.Errorf("\tActual String = %s", string(actual))
|
||||||
|
t.Errorf("\tExpected String = %s", string(expected))
|
||||||
|
t.Errorf("\tActual = %v", actual)
|
||||||
|
t.Errorf("\tExpected = %v", expected)
|
||||||
|
t.Errorf(format, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just to make sure :)
|
||||||
|
func TestAssertEqualBytes(t *testing.T) {
|
||||||
|
data := []byte{9, 9, 1, 1, 1, 9, 9}
|
||||||
|
assertBytesEqual(t, data, data, "Self")
|
||||||
|
assertBytesEqual(t, data[1:4], data[1:4], "Self")
|
||||||
|
assertBytesEqual(t, []byte{1, 1}, []byte{1, 1}, "Simple match")
|
||||||
|
assertBytesEqual(t, []byte{1, 2, 3}, []byte{1, 2, 3}, "content mismatch")
|
||||||
|
assertBytesEqual(t, []byte{1, 1, 1}, data[2:5], "slice match")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func TestAssertEqualBytesNegative(t *testing.T) {
|
||||||
|
AssertBytesEqual(t, []byte{1, 1}, []byte{1}, "Length mismatch")
|
||||||
|
AssertBytesEqual(t, []byte{1, 1}, []byte{1}, "Length mismatch")
|
||||||
|
AssertBytesEqual(t, []byte{1, 2, 3}, []byte{1, 1, 1}, "content mismatch")
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Checks that the calls received
|
||||||
|
func assertHandlerOutput(t *testing.T, mock *mockTerminal, plainText string, commands ...string) {
|
||||||
|
text := make([]byte, 0, 3*len(plainText))
|
||||||
|
cmdIndex := 0
|
||||||
|
for opIndex := 0; opIndex < len(mock.OutputCommandSequence); opIndex++ {
|
||||||
|
op := mock.OutputCommandSequence[opIndex]
|
||||||
|
if op.Operation == WRITE_OPERATION {
|
||||||
|
t.Logf("\nThe data is[%d] == %s", opIndex, string(op.Data))
|
||||||
|
text = append(text[:], op.Data...)
|
||||||
|
} else {
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[opIndex].Operation == COMMAND_OPERATION, "Operation should be command : %s", fmt.Sprintf("%+v", mock))
|
||||||
|
assertBytesEqual(t, StringToBytes(commands[cmdIndex]), mock.OutputCommandSequence[opIndex].Data, "Command data should match")
|
||||||
|
cmdIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertBytesEqual(t, StringToBytes(plainText), text, "Command data should match %#v", mock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringToBytes(str string) []byte {
|
||||||
|
bytes := make([]byte, len(str))
|
||||||
|
copy(bytes[:], str)
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseAnsiCommand(t *testing.T) {
|
||||||
|
// Note: if the parameter does not exist then the empty value is returned
|
||||||
|
|
||||||
|
c := parseAnsiCommand(StringToBytes("\x1Bm"))
|
||||||
|
assertTrue(t, c.Command == "m", "Command should be m")
|
||||||
|
assertTrue(t, "" == c.getParam(0), "should return empty string")
|
||||||
|
assertTrue(t, "" == c.getParam(1), "should return empty string")
|
||||||
|
|
||||||
|
// Escape sequence - ESC[
|
||||||
|
c = parseAnsiCommand(StringToBytes("\x1B[m"))
|
||||||
|
assertTrue(t, c.Command == "m", "Command should be m")
|
||||||
|
assertTrue(t, "" == c.getParam(0), "should return empty string")
|
||||||
|
assertTrue(t, "" == c.getParam(1), "should return empty string")
|
||||||
|
|
||||||
|
// Escape sequence With empty parameters- ESC[
|
||||||
|
c = parseAnsiCommand(StringToBytes("\x1B[;m"))
|
||||||
|
assertTrue(t, c.Command == "m", "Command should be m")
|
||||||
|
assertTrue(t, "" == c.getParam(0), "should return empty string")
|
||||||
|
assertTrue(t, "" == c.getParam(1), "should return empty string")
|
||||||
|
assertTrue(t, "" == c.getParam(2), "should return empty string")
|
||||||
|
|
||||||
|
// Escape sequence With empty muliple parameters- ESC[
|
||||||
|
c = parseAnsiCommand(StringToBytes("\x1B[;;m"))
|
||||||
|
assertTrue(t, c.Command == "m", "Command should be m")
|
||||||
|
assertTrue(t, "" == c.getParam(0), "")
|
||||||
|
assertTrue(t, "" == c.getParam(1), "")
|
||||||
|
assertTrue(t, "" == c.getParam(2), "")
|
||||||
|
|
||||||
|
// Escape sequence With muliple parameters- ESC[
|
||||||
|
c = parseAnsiCommand(StringToBytes("\x1B[1;2;3m"))
|
||||||
|
assertTrue(t, c.Command == "m", "Command should be m")
|
||||||
|
assertTrue(t, "1" == c.getParam(0), "")
|
||||||
|
assertTrue(t, "2" == c.getParam(1), "")
|
||||||
|
assertTrue(t, "3" == c.getParam(2), "")
|
||||||
|
|
||||||
|
// Escape sequence With muliple parameters- some missing
|
||||||
|
c = parseAnsiCommand(StringToBytes("\x1B[1;;3;;;6m"))
|
||||||
|
assertTrue(t, c.Command == "m", "Command should be m")
|
||||||
|
assertTrue(t, "1" == c.getParam(0), "")
|
||||||
|
assertTrue(t, "" == c.getParam(1), "")
|
||||||
|
assertTrue(t, "3" == c.getParam(2), "")
|
||||||
|
assertTrue(t, "" == c.getParam(3), "")
|
||||||
|
assertTrue(t, "" == c.getParam(4), "")
|
||||||
|
assertTrue(t, "6" == c.getParam(5), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBufferedMockTerm() (stdOut io.Writer, stdErr io.Writer, stdIn io.ReadCloser, mock *mockTerminal) {
|
||||||
|
var input bytes.Buffer
|
||||||
|
var output bytes.Buffer
|
||||||
|
var err bytes.Buffer
|
||||||
|
|
||||||
|
mock = &mockTerminal{
|
||||||
|
OutputCommandSequence: make([]terminalOperation, 0, 256),
|
||||||
|
}
|
||||||
|
|
||||||
|
stdOut = &terminalWriter{
|
||||||
|
wrappedWriter: &output,
|
||||||
|
emulator: mock,
|
||||||
|
command: make([]byte, 0, 256),
|
||||||
|
}
|
||||||
|
stdErr = &terminalWriter{
|
||||||
|
wrappedWriter: &err,
|
||||||
|
emulator: mock,
|
||||||
|
command: make([]byte, 0, 256),
|
||||||
|
}
|
||||||
|
stdIn = &terminalReader{
|
||||||
|
wrappedReader: ioutil.NopCloser(&input),
|
||||||
|
emulator: mock,
|
||||||
|
command: make([]byte, 0, 256),
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputSimple(t *testing.T) {
|
||||||
|
stdOut, _, _, mock := newBufferedMockTerm()
|
||||||
|
|
||||||
|
stdOut.Write(StringToBytes("Hello world"))
|
||||||
|
stdOut.Write(StringToBytes("\x1BmHello again"))
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("Hello world"), mock.OutputCommandSequence[0].Data, "Write data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[1].Operation == COMMAND_OPERATION, "Operation should be command : %+v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("\x1Bm"), mock.OutputCommandSequence[1].Data, "Command data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[2].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("Hello again"), mock.OutputCommandSequence[2].Data, "Write data should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputSplitCommand(t *testing.T) {
|
||||||
|
stdOut, _, _, mock := newBufferedMockTerm()
|
||||||
|
|
||||||
|
stdOut.Write(StringToBytes("Hello world\x1B[1;2;3"))
|
||||||
|
stdOut.Write(StringToBytes("mHello again"))
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("Hello world"), mock.OutputCommandSequence[0].Data, "Write data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[1].Operation == COMMAND_OPERATION, "Operation should be command : %+v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("\x1B[1;2;3m"), mock.OutputCommandSequence[1].Data, "Command data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[2].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("Hello again"), mock.OutputCommandSequence[2].Data, "Write data should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputMultipleCommands(t *testing.T) {
|
||||||
|
stdOut, _, _, mock := newBufferedMockTerm()
|
||||||
|
|
||||||
|
stdOut.Write(StringToBytes("Hello world"))
|
||||||
|
stdOut.Write(StringToBytes("\x1B[1;2;3m"))
|
||||||
|
stdOut.Write(StringToBytes("\x1B[J"))
|
||||||
|
stdOut.Write(StringToBytes("Hello again"))
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("Hello world"), mock.OutputCommandSequence[0].Data, "Write data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[1].Operation == COMMAND_OPERATION, "Operation should be command : %+v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("\x1B[1;2;3m"), mock.OutputCommandSequence[1].Data, "Command data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[2].Operation == COMMAND_OPERATION, "Operation should be command : %+v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("\x1B[J"), mock.OutputCommandSequence[2].Data, "Command data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[3].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, StringToBytes("Hello again"), mock.OutputCommandSequence[3].Data, "Write data should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits the given data in two chunks , makes two writes and checks the split data is parsed correctly
|
||||||
|
// checks output write/command is passed to handler correctly
|
||||||
|
func helpsTestOutputSplitChunksAtIndex(t *testing.T, i int, data []byte) {
|
||||||
|
t.Logf("\ni=%d", i)
|
||||||
|
stdOut, _, _, mock := newBufferedMockTerm()
|
||||||
|
|
||||||
|
t.Logf("\nWriting chunk[0] == %s", string(data[:i]))
|
||||||
|
t.Logf("\nWriting chunk[1] == %s", string(data[i:]))
|
||||||
|
stdOut.Write(data[:i])
|
||||||
|
stdOut.Write(data[i:])
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, data[:i], mock.OutputCommandSequence[0].Data, "Write data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[1].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, data[i:], mock.OutputCommandSequence[1].Data, "Write data should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits the given data in three chunks , makes three writes and checks the split data is parsed correctly
|
||||||
|
// checks output write/command is passed to handler correctly
|
||||||
|
func helpsTestOutputSplitThreeChunksAtIndex(t *testing.T, data []byte, i int, j int) {
|
||||||
|
stdOut, _, _, mock := newBufferedMockTerm()
|
||||||
|
|
||||||
|
t.Logf("\nWriting chunk[0] == %s", string(data[:i]))
|
||||||
|
t.Logf("\nWriting chunk[1] == %s", string(data[i:j]))
|
||||||
|
t.Logf("\nWriting chunk[2] == %s", string(data[j:]))
|
||||||
|
stdOut.Write(data[:i])
|
||||||
|
stdOut.Write(data[i:j])
|
||||||
|
stdOut.Write(data[j:])
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, data[:i], mock.OutputCommandSequence[0].Data, "Write data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[1].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, data[i:j], mock.OutputCommandSequence[1].Data, "Write data should match")
|
||||||
|
|
||||||
|
assertTrue(t, mock.OutputCommandSequence[2].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
|
||||||
|
assertBytesEqual(t, data[j:], mock.OutputCommandSequence[2].Data, "Write data should match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits the output into two parts and tests all such possible pairs
|
||||||
|
func helpsTestOutputSplitChunks(t *testing.T, data []byte) {
|
||||||
|
for i := 1; i < len(data)-1; i++ {
|
||||||
|
helpsTestOutputSplitChunksAtIndex(t, i, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits the output in three parts and tests all such possible triples
|
||||||
|
func helpsTestOutputSplitThreeChunks(t *testing.T, data []byte) {
|
||||||
|
for i := 1; i < len(data)-2; i++ {
|
||||||
|
for j := i + 1; j < len(data)-1; j++ {
|
||||||
|
helpsTestOutputSplitThreeChunksAtIndex(t, data, i, j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpsTestOutputSplitCommandsAtIndex(t *testing.T, data []byte, i int, plainText string, commands ...string) {
|
||||||
|
t.Logf("\ni=%d", i)
|
||||||
|
stdOut, _, _, mock := newBufferedMockTerm()
|
||||||
|
|
||||||
|
stdOut.Write(data[:i])
|
||||||
|
stdOut.Write(data[i:])
|
||||||
|
assertHandlerOutput(t, mock, plainText, commands...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpsTestOutputSplitCommands(t *testing.T, data []byte, plainText string, commands ...string) {
|
||||||
|
for i := 1; i < len(data)-1; i++ {
|
||||||
|
helpsTestOutputSplitCommandsAtIndex(t, data, i, plainText, commands...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func injectCommandAt(data string, i int, command string) string {
|
||||||
|
retValue := make([]byte, len(data)+len(command)+4)
|
||||||
|
retValue = append(retValue, data[:i]...)
|
||||||
|
retValue = append(retValue, data[i:]...)
|
||||||
|
return string(retValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputSplitChunks(t *testing.T) {
|
||||||
|
data := StringToBytes("qwertyuiopasdfghjklzxcvbnm")
|
||||||
|
helpsTestOutputSplitChunks(t, data)
|
||||||
|
helpsTestOutputSplitChunks(t, StringToBytes("BBBBB"))
|
||||||
|
helpsTestOutputSplitThreeChunks(t, StringToBytes("ABCDE"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOutputSplitChunksIncludingCommands(t *testing.T) {
|
||||||
|
helpsTestOutputSplitCommands(t, StringToBytes("Hello world.\x1B[mHello again."), "Hello world.Hello again.", "\x1B[m")
|
||||||
|
helpsTestOutputSplitCommandsAtIndex(t, StringToBytes("Hello world.\x1B[mHello again."), 2, "Hello world.Hello again.", "\x1B[m")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitChunkUnicode(t *testing.T) {
|
||||||
|
for _, l := range languages {
|
||||||
|
data := StringToBytes(l)
|
||||||
|
helpsTestOutputSplitChunks(t, data)
|
||||||
|
helpsTestOutputSplitThreeChunks(t, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||||
|
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// The current operating system does not provide the required data for user lookups.
|
||||||
|
ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data")
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupUser(filter func(u User) bool) (User, error) {
|
||||||
|
// Get operating system-specific passwd reader-closer.
|
||||||
|
passwd, err := GetPasswd()
|
||||||
|
if err != nil {
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
|
||||||
|
// Get the users.
|
||||||
|
users, err := ParsePasswdFilter(passwd, filter)
|
||||||
|
if err != nil {
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No user entries found.
|
||||||
|
if len(users) == 0 {
|
||||||
|
return User{}, fmt.Errorf("no matching entries in passwd file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the first entry is the "correct" one.
|
||||||
|
return users[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
|
||||||
|
// user cannot be found (or there is no /etc/passwd file on the filesystem),
|
||||||
|
// then CurrentUser returns an error.
|
||||||
|
func CurrentUser() (User, error) {
|
||||||
|
return LookupUid(syscall.Getuid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUser looks up a user by their username in /etc/passwd. If the user
|
||||||
|
// cannot be found (or there is no /etc/passwd file on the filesystem), then
|
||||||
|
// LookupUser returns an error.
|
||||||
|
func LookupUser(username string) (User, error) {
|
||||||
|
return lookupUser(func(u User) bool {
|
||||||
|
return u.Name == username
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot
|
||||||
|
// be found (or there is no /etc/passwd file on the filesystem), then LookupId
|
||||||
|
// returns an error.
|
||||||
|
func LookupUid(uid int) (User, error) {
|
||||||
|
return lookupUser(func(u User) bool {
|
||||||
|
return u.Uid == uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupGroup(filter func(g Group) bool) (Group, error) {
|
||||||
|
// Get operating system-specific group reader-closer.
|
||||||
|
group, err := GetGroup()
|
||||||
|
if err != nil {
|
||||||
|
return Group{}, err
|
||||||
|
}
|
||||||
|
defer group.Close()
|
||||||
|
|
||||||
|
// Get the users.
|
||||||
|
groups, err := ParseGroupFilter(group, filter)
|
||||||
|
if err != nil {
|
||||||
|
return Group{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No user entries found.
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return Group{}, fmt.Errorf("no matching entries in group file")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the first entry is the "correct" one.
|
||||||
|
return groups[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentGroup looks up the current user's group by their primary group id's
|
||||||
|
// entry in /etc/passwd. If the group cannot be found (or there is no
|
||||||
|
// /etc/group file on the filesystem), then CurrentGroup returns an error.
|
||||||
|
func CurrentGroup() (Group, error) {
|
||||||
|
return LookupGid(syscall.Getgid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGroup looks up a group by its name in /etc/group. If the group cannot
|
||||||
|
// be found (or there is no /etc/group file on the filesystem), then LookupGroup
|
||||||
|
// returns an error.
|
||||||
|
func LookupGroup(groupname string) (Group, error) {
|
||||||
|
return lookupGroup(func(g Group) bool {
|
||||||
|
return g.Name == groupname
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGid looks up a group by its group id in /etc/group. If the group cannot
|
||||||
|
// be found (or there is no /etc/group file on the filesystem), then LookupGid
|
||||||
|
// returns an error.
|
||||||
|
func LookupGid(gid int) (Group, error) {
|
||||||
|
return lookupGroup(func(g Group) bool {
|
||||||
|
return g.Gid == gid
|
||||||
|
})
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unix-specific path to the passwd and group formatted files.
|
||||||
|
const (
|
||||||
|
unixPasswdPath = "/etc/passwd"
|
||||||
|
unixGroupPath = "/etc/group"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPasswdPath() (string, error) {
|
||||||
|
return unixPasswdPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPasswd() (io.ReadCloser, error) {
|
||||||
|
return os.Open(unixPasswdPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroupPath() (string, error) {
|
||||||
|
return unixGroupPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroup() (io.ReadCloser, error) {
|
||||||
|
return os.Open(unixGroupPath)
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func GetPasswdPath() (string, error) {
|
||||||
|
return "", ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPasswd() (io.ReadCloser, error) {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroupPath() (string, error) {
|
||||||
|
return "", ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroup() (io.ReadCloser, error) {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
||||||
+350
@@ -0,0 +1,350 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minId = 0
|
||||||
|
maxId = 1<<31 - 1 //for 32-bit systems compatibility
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId)
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Pass string
|
||||||
|
Uid int
|
||||||
|
Gid int
|
||||||
|
Gecos string
|
||||||
|
Home string
|
||||||
|
Shell string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
Name string
|
||||||
|
Pass string
|
||||||
|
Gid int
|
||||||
|
List []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLine(line string, v ...interface{}) {
|
||||||
|
if line == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
for i, p := range parts {
|
||||||
|
if len(v) <= i {
|
||||||
|
// if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e := v[i].(type) {
|
||||||
|
case *string:
|
||||||
|
// "root", "adm", "/bin/bash"
|
||||||
|
*e = p
|
||||||
|
case *int:
|
||||||
|
// "0", "4", "1000"
|
||||||
|
// ignore string to int conversion errors, for great "tolerance" of naughty configuration files
|
||||||
|
*e, _ = strconv.Atoi(p)
|
||||||
|
case *[]string:
|
||||||
|
// "", "root", "root,adm,daemon"
|
||||||
|
if p != "" {
|
||||||
|
*e = strings.Split(p, ",")
|
||||||
|
} else {
|
||||||
|
*e = []string{}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// panic, because this is a programming/logic error, not a runtime one
|
||||||
|
panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFile(path string) ([]User, error) {
|
||||||
|
passwd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
return ParsePasswd(passwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswd(passwd io.Reader) ([]User, error) {
|
||||||
|
return ParsePasswdFilter(passwd, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
|
||||||
|
passwd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
return ParsePasswdFilter(passwd, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for passwd-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []User{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := strings.TrimSpace(s.Text())
|
||||||
|
if text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 5 passwd
|
||||||
|
// name:password:UID:GID:GECOS:directory:shell
|
||||||
|
// Name:Pass:Uid:Gid:Gecos:Home:Shell
|
||||||
|
// root:x:0:0:root:/root:/bin/bash
|
||||||
|
// adm:x:3:4:adm:/var/adm:/bin/false
|
||||||
|
p := User{}
|
||||||
|
parseLine(
|
||||||
|
text,
|
||||||
|
&p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell,
|
||||||
|
)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFile(path string) ([]Group, error) {
|
||||||
|
group, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer group.Close()
|
||||||
|
return ParseGroup(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroup(group io.Reader) ([]Group, error) {
|
||||||
|
return ParseGroupFilter(group, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
|
||||||
|
group, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer group.Close()
|
||||||
|
return ParseGroupFilter(group, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for group-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []Group{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := s.Text()
|
||||||
|
if text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 5 group
|
||||||
|
// group_name:password:GID:user_list
|
||||||
|
// Name:Pass:Gid:List
|
||||||
|
// root:x:0:root
|
||||||
|
// adm:x:4:root,adm,daemon
|
||||||
|
p := Group{}
|
||||||
|
parseLine(
|
||||||
|
text,
|
||||||
|
&p.Name, &p.Pass, &p.Gid, &p.List,
|
||||||
|
)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecUser struct {
|
||||||
|
Uid, Gid int
|
||||||
|
Sgids []int
|
||||||
|
Home string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
|
||||||
|
// given file paths and uses that data as the arguments to GetExecUser. If the
|
||||||
|
// files cannot be opened for any reason, the error is ignored and a nil
|
||||||
|
// io.Reader is passed instead.
|
||||||
|
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
|
||||||
|
passwd, err := os.Open(passwdPath)
|
||||||
|
if err != nil {
|
||||||
|
passwd = nil
|
||||||
|
} else {
|
||||||
|
defer passwd.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
group, err := os.Open(groupPath)
|
||||||
|
if err != nil {
|
||||||
|
group = nil
|
||||||
|
} else {
|
||||||
|
defer group.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetExecUser(userSpec, defaults, passwd, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExecUser parses a user specification string (using the passwd and group
|
||||||
|
// readers as sources for /etc/passwd and /etc/group data, respectively). In
|
||||||
|
// the case of blank fields or missing data from the sources, the values in
|
||||||
|
// defaults is used.
|
||||||
|
//
|
||||||
|
// GetExecUser will return an error if a user or group literal could not be
|
||||||
|
// found in any entry in passwd and group respectively.
|
||||||
|
//
|
||||||
|
// Examples of valid user specifications are:
|
||||||
|
// * ""
|
||||||
|
// * "user"
|
||||||
|
// * "uid"
|
||||||
|
// * "user:group"
|
||||||
|
// * "uid:gid
|
||||||
|
// * "user:gid"
|
||||||
|
// * "uid:group"
|
||||||
|
func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
|
||||||
|
var (
|
||||||
|
userArg, groupArg string
|
||||||
|
name string
|
||||||
|
)
|
||||||
|
|
||||||
|
if defaults == nil {
|
||||||
|
defaults = new(ExecUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy over defaults.
|
||||||
|
user := &ExecUser{
|
||||||
|
Uid: defaults.Uid,
|
||||||
|
Gid: defaults.Gid,
|
||||||
|
Sgids: defaults.Sgids,
|
||||||
|
Home: defaults.Home,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sgids slice *cannot* be nil.
|
||||||
|
if user.Sgids == nil {
|
||||||
|
user.Sgids = []int{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow for userArg to have either "user" syntax, or optionally "user:group" syntax
|
||||||
|
parseLine(userSpec, &userArg, &groupArg)
|
||||||
|
|
||||||
|
users, err := ParsePasswdFilter(passwd, func(u User) bool {
|
||||||
|
if userArg == "" {
|
||||||
|
return u.Uid == user.Uid
|
||||||
|
}
|
||||||
|
return u.Name == userArg || strconv.Itoa(u.Uid) == userArg
|
||||||
|
})
|
||||||
|
if err != nil && passwd != nil {
|
||||||
|
if userArg == "" {
|
||||||
|
userArg = strconv.Itoa(user.Uid)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("Unable to find user %v: %v", userArg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
haveUser := users != nil && len(users) > 0
|
||||||
|
if haveUser {
|
||||||
|
// if we found any user entries that matched our filter, let's take the first one as "correct"
|
||||||
|
name = users[0].Name
|
||||||
|
user.Uid = users[0].Uid
|
||||||
|
user.Gid = users[0].Gid
|
||||||
|
user.Home = users[0].Home
|
||||||
|
} else if userArg != "" {
|
||||||
|
// we asked for a user but didn't find them... let's check to see if we wanted a numeric user
|
||||||
|
user.Uid, err = strconv.Atoi(userArg)
|
||||||
|
if err != nil {
|
||||||
|
// not numeric - we have to bail
|
||||||
|
return nil, fmt.Errorf("Unable to find user %v", userArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be inside valid uid range.
|
||||||
|
if user.Uid < minId || user.Uid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupArg != "" || name != "" {
|
||||||
|
groups, err := ParseGroupFilter(group, func(g Group) bool {
|
||||||
|
// Explicit group format takes precedence.
|
||||||
|
if groupArg != "" {
|
||||||
|
return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is a member.
|
||||||
|
for _, u := range g.List {
|
||||||
|
if u == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if err != nil && group != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
haveGroup := groups != nil && len(groups) > 0
|
||||||
|
if groupArg != "" {
|
||||||
|
if haveGroup {
|
||||||
|
// if we found any group entries that matched our filter, let's take the first one as "correct"
|
||||||
|
user.Gid = groups[0].Gid
|
||||||
|
} else {
|
||||||
|
// we asked for a group but didn't find id... let's check to see if we wanted a numeric group
|
||||||
|
user.Gid, err = strconv.Atoi(groupArg)
|
||||||
|
if err != nil {
|
||||||
|
// not numeric - we have to bail
|
||||||
|
return nil, fmt.Errorf("Unable to find group %v", groupArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure gid is inside gid range.
|
||||||
|
if user.Gid < minId || user.Gid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit
|
||||||
|
}
|
||||||
|
} else if haveGroup {
|
||||||
|
// If implicit group format, fill supplementary gids.
|
||||||
|
user.Sgids = make([]int, len(groups))
|
||||||
|
for i, group := range groups {
|
||||||
|
user.Sgids[i] = group.Gid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
+352
@@ -0,0 +1,352 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserParseLine(t *testing.T) {
|
||||||
|
var (
|
||||||
|
a, b string
|
||||||
|
c []string
|
||||||
|
d int
|
||||||
|
)
|
||||||
|
|
||||||
|
parseLine("", &a, &b)
|
||||||
|
if a != "" || b != "" {
|
||||||
|
t.Fatalf("a and b should be empty ('%v', '%v')", a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("a", &a, &b)
|
||||||
|
if a != "a" || b != "" {
|
||||||
|
t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("bad boys:corny cows", &a, &b)
|
||||||
|
if a != "bad boys" || b != "corny cows" {
|
||||||
|
t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("", &c)
|
||||||
|
if len(c) != 0 {
|
||||||
|
t.Fatalf("c should be empty (%#v)", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c)
|
||||||
|
if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" {
|
||||||
|
t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("::::::::::", &a, &b, &c)
|
||||||
|
if a != "" || b != "" || len(c) != 0 {
|
||||||
|
t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("not a number", &d)
|
||||||
|
if d != 0 {
|
||||||
|
t.Fatalf("d should be 0 (%v)", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseLine("b:12:c", &a, &d, &b)
|
||||||
|
if a != "b" || b != "c" || d != 12 {
|
||||||
|
t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserParsePasswd(t *testing.T) {
|
||||||
|
users, err := ParsePasswdFilter(strings.NewReader(`
|
||||||
|
root:x:0:0:root:/root:/bin/bash
|
||||||
|
adm:x:3:4:adm:/var/adm:/bin/false
|
||||||
|
this is just some garbage data
|
||||||
|
`), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(users) != 3 {
|
||||||
|
t.Fatalf("Expected 3 users, got %v", len(users))
|
||||||
|
}
|
||||||
|
if users[0].Uid != 0 || users[0].Name != "root" {
|
||||||
|
t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name)
|
||||||
|
}
|
||||||
|
if users[1].Uid != 3 || users[1].Name != "adm" {
|
||||||
|
t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUserParseGroup(t *testing.T) {
|
||||||
|
groups, err := ParseGroupFilter(strings.NewReader(`
|
||||||
|
root:x:0:root
|
||||||
|
adm:x:4:root,adm,daemon
|
||||||
|
this is just some garbage data
|
||||||
|
`), nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(groups) != 3 {
|
||||||
|
t.Fatalf("Expected 3 groups, got %v", len(groups))
|
||||||
|
}
|
||||||
|
if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 {
|
||||||
|
t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List))
|
||||||
|
}
|
||||||
|
if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 {
|
||||||
|
t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidGetExecUser(t *testing.T) {
|
||||||
|
const passwdContent = `
|
||||||
|
root:x:0:0:root user:/root:/bin/bash
|
||||||
|
adm:x:42:43:adm:/var/adm:/bin/false
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
const groupContent = `
|
||||||
|
root:x:0:root
|
||||||
|
adm:x:43:
|
||||||
|
grp:x:1234:root,adm
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
defaultExecUser := ExecUser{
|
||||||
|
Uid: 8888,
|
||||||
|
Gid: 8888,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
ref string
|
||||||
|
expected ExecUser
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ref: "root",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Sgids: []int{0, 1234},
|
||||||
|
Home: "/root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "adm",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 42,
|
||||||
|
Gid: 43,
|
||||||
|
Sgids: []int{1234},
|
||||||
|
Home: "/var/adm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "root:adm",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 43,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: "/root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "adm:1234",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 42,
|
||||||
|
Gid: 1234,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: "/var/adm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "42:1234",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 42,
|
||||||
|
Gid: 1234,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: "/var/adm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "1337:1234",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 1337,
|
||||||
|
Gid: 1234,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: defaultExecUser.Home,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "1337",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 1337,
|
||||||
|
Gid: defaultExecUser.Gid,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: defaultExecUser.Home,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "",
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: defaultExecUser.Uid,
|
||||||
|
Gid: defaultExecUser.Gid,
|
||||||
|
Sgids: defaultExecUser.Sgids,
|
||||||
|
Home: defaultExecUser.Home,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
passwd := strings.NewReader(passwdContent)
|
||||||
|
group := strings.NewReader(groupContent)
|
||||||
|
|
||||||
|
execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(test.expected, *execUser) {
|
||||||
|
t.Logf("got: %#v", execUser)
|
||||||
|
t.Logf("expected: %#v", test.expected)
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidGetExecUser(t *testing.T) {
|
||||||
|
const passwdContent = `
|
||||||
|
root:x:0:0:root user:/root:/bin/bash
|
||||||
|
adm:x:42:43:adm:/var/adm:/bin/false
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
const groupContent = `
|
||||||
|
root:x:0:root
|
||||||
|
adm:x:43:
|
||||||
|
grp:x:1234:root,adm
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
|
||||||
|
tests := []string{
|
||||||
|
// No such user/group.
|
||||||
|
"notuser",
|
||||||
|
"notuser:notgroup",
|
||||||
|
"root:notgroup",
|
||||||
|
"notuser:adm",
|
||||||
|
"8888:notgroup",
|
||||||
|
"notuser:8888",
|
||||||
|
|
||||||
|
// Invalid user/group values.
|
||||||
|
"-1:0",
|
||||||
|
"0:-3",
|
||||||
|
"-5:-2",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
passwd := strings.NewReader(passwdContent)
|
||||||
|
group := strings.NewReader(groupContent)
|
||||||
|
|
||||||
|
execUser, err := GetExecUser(test, nil, passwd, group)
|
||||||
|
if err == nil {
|
||||||
|
t.Logf("got unexpected success when parsing '%s': %#v", test, execUser)
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetExecUserNilSources(t *testing.T) {
|
||||||
|
const passwdContent = `
|
||||||
|
root:x:0:0:root user:/root:/bin/bash
|
||||||
|
adm:x:42:43:adm:/var/adm:/bin/false
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
const groupContent = `
|
||||||
|
root:x:0:root
|
||||||
|
adm:x:43:
|
||||||
|
grp:x:1234:root,adm
|
||||||
|
this is just some garbage data
|
||||||
|
`
|
||||||
|
|
||||||
|
defaultExecUser := ExecUser{
|
||||||
|
Uid: 8888,
|
||||||
|
Gid: 8888,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
ref string
|
||||||
|
passwd, group bool
|
||||||
|
expected ExecUser
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
ref: "",
|
||||||
|
passwd: false,
|
||||||
|
group: false,
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 8888,
|
||||||
|
Gid: 8888,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "root",
|
||||||
|
passwd: true,
|
||||||
|
group: false,
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "0",
|
||||||
|
passwd: false,
|
||||||
|
group: false,
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 8888,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "0:0",
|
||||||
|
passwd: false,
|
||||||
|
group: false,
|
||||||
|
expected: ExecUser{
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Sgids: []int{8888},
|
||||||
|
Home: "/8888",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var passwd, group io.Reader
|
||||||
|
|
||||||
|
if test.passwd {
|
||||||
|
passwd = strings.NewReader(passwdContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.group {
|
||||||
|
group = strings.NewReader(groupContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error())
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(test.expected, *execUser) {
|
||||||
|
t.Logf("got: %#v", execUser)
|
||||||
|
t.Logf("expected: %#v", test.expected)
|
||||||
|
t.Fail()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
# 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.
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2013, Georg Reinke (<guelfey at gmail dot com>), Google
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||||
|
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
Brandon Philips <brandon@ifup.org> (@philips)
|
||||||
|
Brian Waldon <brian@waldon.cc> (@bcwaldon)
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
dbus
|
||||||
|
----
|
||||||
|
|
||||||
|
dbus is a simple library that implements native Go client bindings for the
|
||||||
|
D-Bus message bus system.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Complete native implementation of the D-Bus message protocol
|
||||||
|
* Go-like API (channels for signals / asynchronous method calls, Goroutine-safe connections)
|
||||||
|
* Subpackages that help with the introspection / property interfaces
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
This packages requires Go 1.1. If you installed it and set up your GOPATH, just run:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/godbus/dbus
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to use the subpackages, you can install them the same way.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
The complete package documentation and some simple examples are available at
|
||||||
|
[godoc.org](http://godoc.org/github.com/godbus/dbus). Also, the
|
||||||
|
[_examples](https://github.com/godbus/dbus/tree/master/_examples) directory
|
||||||
|
gives a short overview over the basic usage.
|
||||||
|
|
||||||
|
Please note that the API is considered unstable for now and may change without
|
||||||
|
further notice.
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
go.dbus is available under the Simplified BSD License; see LICENSE for the full
|
||||||
|
text.
|
||||||
|
|
||||||
|
Nearly all of the credit for this library goes to github.com/guelfey/go.dbus.
|
||||||
+253
@@ -0,0 +1,253 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthStatus represents the Status of an authentication mechanism.
|
||||||
|
type AuthStatus byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AuthOk signals that authentication is finished; the next command
|
||||||
|
// from the server should be an OK.
|
||||||
|
AuthOk AuthStatus = iota
|
||||||
|
|
||||||
|
// AuthContinue signals that additional data is needed; the next command
|
||||||
|
// from the server should be a DATA.
|
||||||
|
AuthContinue
|
||||||
|
|
||||||
|
// AuthError signals an error; the server sent invalid data or some
|
||||||
|
// other unexpected thing happened and the current authentication
|
||||||
|
// process should be aborted.
|
||||||
|
AuthError
|
||||||
|
)
|
||||||
|
|
||||||
|
type authState byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
waitingForData authState = iota
|
||||||
|
waitingForOk
|
||||||
|
waitingForReject
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auth defines the behaviour of an authentication mechanism.
|
||||||
|
type Auth interface {
|
||||||
|
// Return the name of the mechnism, the argument to the first AUTH command
|
||||||
|
// and the next status.
|
||||||
|
FirstData() (name, resp []byte, status AuthStatus)
|
||||||
|
|
||||||
|
// Process the given DATA command, and return the argument to the DATA
|
||||||
|
// command and the next status. If len(resp) == 0, no DATA command is sent.
|
||||||
|
HandleData(data []byte) (resp []byte, status AuthStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth authenticates the connection, trying the given list of authentication
|
||||||
|
// mechanisms (in that order). If nil is passed, the EXTERNAL and
|
||||||
|
// DBUS_COOKIE_SHA1 mechanisms are tried for the current user. For private
|
||||||
|
// connections, this method must be called before sending any messages to the
|
||||||
|
// bus. Auth must not be called on shared connections.
|
||||||
|
func (conn *Conn) Auth(methods []Auth) error {
|
||||||
|
if methods == nil {
|
||||||
|
uid := strconv.Itoa(os.Getuid())
|
||||||
|
methods = []Auth{AuthExternal(uid), AuthCookieSha1(uid, getHomeDir())}
|
||||||
|
}
|
||||||
|
in := bufio.NewReader(conn.transport)
|
||||||
|
err := conn.transport.SendNullByte()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = authWriteLine(conn.transport, []byte("AUTH"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s, err := authReadLine(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(s) < 2 || !bytes.Equal(s[0], []byte("REJECTED")) {
|
||||||
|
return errors.New("dbus: authentication protocol error")
|
||||||
|
}
|
||||||
|
s = s[1:]
|
||||||
|
for _, v := range s {
|
||||||
|
for _, m := range methods {
|
||||||
|
if name, data, status := m.FirstData(); bytes.Equal(v, name) {
|
||||||
|
var ok bool
|
||||||
|
err = authWriteLine(conn.transport, []byte("AUTH"), []byte(v), data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch status {
|
||||||
|
case AuthOk:
|
||||||
|
err, ok = conn.tryAuth(m, waitingForOk, in)
|
||||||
|
case AuthContinue:
|
||||||
|
err, ok = conn.tryAuth(m, waitingForData, in)
|
||||||
|
default:
|
||||||
|
panic("dbus: invalid authentication status")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
if conn.transport.SupportsUnixFDs() {
|
||||||
|
err = authWriteLine(conn, []byte("NEGOTIATE_UNIX_FD"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
line, err := authReadLine(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case bytes.Equal(line[0], []byte("AGREE_UNIX_FD")):
|
||||||
|
conn.EnableUnixFDs()
|
||||||
|
conn.unixFD = true
|
||||||
|
case bytes.Equal(line[0], []byte("ERROR")):
|
||||||
|
default:
|
||||||
|
return errors.New("dbus: authentication protocol error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = authWriteLine(conn.transport, []byte("BEGIN"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go conn.inWorker()
|
||||||
|
go conn.outWorker()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.New("dbus: authentication failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryAuth tries to authenticate with m as the mechanism, using state as the
|
||||||
|
// initial authState and in for reading input. It returns (nil, true) on
|
||||||
|
// success, (nil, false) on a REJECTED and (someErr, false) if some other
|
||||||
|
// error occured.
|
||||||
|
func (conn *Conn) tryAuth(m Auth, state authState, in *bufio.Reader) (error, bool) {
|
||||||
|
for {
|
||||||
|
s, err := authReadLine(in)
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case state == waitingForData && string(s[0]) == "DATA":
|
||||||
|
if len(s) != 2 {
|
||||||
|
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, status := m.HandleData(s[1])
|
||||||
|
switch status {
|
||||||
|
case AuthOk, AuthContinue:
|
||||||
|
if len(data) != 0 {
|
||||||
|
err = authWriteLine(conn.transport, []byte("DATA"), data)
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if status == AuthOk {
|
||||||
|
state = waitingForOk
|
||||||
|
}
|
||||||
|
case AuthError:
|
||||||
|
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case state == waitingForData && string(s[0]) == "REJECTED":
|
||||||
|
return nil, false
|
||||||
|
case state == waitingForData && string(s[0]) == "ERROR":
|
||||||
|
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
state = waitingForReject
|
||||||
|
case state == waitingForData && string(s[0]) == "OK":
|
||||||
|
if len(s) != 2 {
|
||||||
|
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
state = waitingForReject
|
||||||
|
}
|
||||||
|
conn.uuid = string(s[1])
|
||||||
|
return nil, true
|
||||||
|
case state == waitingForData:
|
||||||
|
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
case state == waitingForOk && string(s[0]) == "OK":
|
||||||
|
if len(s) != 2 {
|
||||||
|
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
state = waitingForReject
|
||||||
|
}
|
||||||
|
conn.uuid = string(s[1])
|
||||||
|
return nil, true
|
||||||
|
case state == waitingForOk && string(s[0]) == "REJECTED":
|
||||||
|
return nil, false
|
||||||
|
case state == waitingForOk && (string(s[0]) == "DATA" ||
|
||||||
|
string(s[0]) == "ERROR"):
|
||||||
|
|
||||||
|
err = authWriteLine(conn.transport, []byte("CANCEL"))
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
state = waitingForReject
|
||||||
|
case state == waitingForOk:
|
||||||
|
err = authWriteLine(conn.transport, []byte("ERROR"))
|
||||||
|
if err != nil {
|
||||||
|
return err, false
|
||||||
|
}
|
||||||
|
case state == waitingForReject && string(s[0]) == "REJECTED":
|
||||||
|
return nil, false
|
||||||
|
case state == waitingForReject:
|
||||||
|
return errors.New("dbus: authentication protocol error"), false
|
||||||
|
default:
|
||||||
|
panic("dbus: invalid auth state")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// authReadLine reads a line and separates it into its fields.
|
||||||
|
func authReadLine(in *bufio.Reader) ([][]byte, error) {
|
||||||
|
data, err := in.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data = bytes.TrimSuffix(data, []byte("\r\n"))
|
||||||
|
return bytes.Split(data, []byte{' '}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// authWriteLine writes the given line in the authentication protocol format
|
||||||
|
// (elements of data separated by a " " and terminated by "\r\n").
|
||||||
|
func authWriteLine(out io.Writer, data ...[]byte) error {
|
||||||
|
buf := make([]byte, 0)
|
||||||
|
for i, v := range data {
|
||||||
|
buf = append(buf, v...)
|
||||||
|
if i != len(data)-1 {
|
||||||
|
buf = append(buf, ' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf = append(buf, '\r')
|
||||||
|
buf = append(buf, '\n')
|
||||||
|
n, err := out.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != len(buf) {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthExternal returns an Auth that authenticates as the given user with the
|
||||||
|
// EXTERNAL mechanism.
|
||||||
|
func AuthExternal(user string) Auth {
|
||||||
|
return authExternal{user}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthExternal implements the EXTERNAL authentication mechanism.
|
||||||
|
type authExternal struct {
|
||||||
|
user string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a authExternal) FirstData() ([]byte, []byte, AuthStatus) {
|
||||||
|
b := make([]byte, 2*len(a.user))
|
||||||
|
hex.Encode(b, []byte(a.user))
|
||||||
|
return []byte("EXTERNAL"), b, AuthOk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a authExternal) HandleData(b []byte) ([]byte, AuthStatus) {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthCookieSha1 returns an Auth that authenticates as the given user with the
|
||||||
|
// DBUS_COOKIE_SHA1 mechanism. The home parameter should specify the home
|
||||||
|
// directory of the user.
|
||||||
|
func AuthCookieSha1(user, home string) Auth {
|
||||||
|
return authCookieSha1{user, home}
|
||||||
|
}
|
||||||
|
|
||||||
|
type authCookieSha1 struct {
|
||||||
|
user, home string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a authCookieSha1) FirstData() ([]byte, []byte, AuthStatus) {
|
||||||
|
b := make([]byte, 2*len(a.user))
|
||||||
|
hex.Encode(b, []byte(a.user))
|
||||||
|
return []byte("DBUS_COOKIE_SHA1"), b, AuthContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a authCookieSha1) HandleData(data []byte) ([]byte, AuthStatus) {
|
||||||
|
challenge := make([]byte, len(data)/2)
|
||||||
|
_, err := hex.Decode(challenge, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
|
b := bytes.Split(challenge, []byte{' '})
|
||||||
|
if len(b) != 3 {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
|
context := b[0]
|
||||||
|
id := b[1]
|
||||||
|
svchallenge := b[2]
|
||||||
|
cookie := a.getCookie(context, id)
|
||||||
|
if cookie == nil {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
|
clchallenge := a.generateChallenge()
|
||||||
|
if clchallenge == nil {
|
||||||
|
return nil, AuthError
|
||||||
|
}
|
||||||
|
hash := sha1.New()
|
||||||
|
hash.Write(bytes.Join([][]byte{svchallenge, clchallenge, cookie}, []byte{':'}))
|
||||||
|
hexhash := make([]byte, 2*hash.Size())
|
||||||
|
hex.Encode(hexhash, hash.Sum(nil))
|
||||||
|
data = append(clchallenge, ' ')
|
||||||
|
data = append(data, hexhash...)
|
||||||
|
resp := make([]byte, 2*len(data))
|
||||||
|
hex.Encode(resp, data)
|
||||||
|
return resp, AuthOk
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCookie searches for the cookie identified by id in context and returns
|
||||||
|
// the cookie content or nil. (Since HandleData can't return a specific error,
|
||||||
|
// but only whether an error occured, this function also doesn't bother to
|
||||||
|
// return an error.)
|
||||||
|
func (a authCookieSha1) getCookie(context, id []byte) []byte {
|
||||||
|
file, err := os.Open(a.home + "/.dbus-keyrings/" + string(context))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
rd := bufio.NewReader(file)
|
||||||
|
for {
|
||||||
|
line, err := rd.ReadBytes('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
line = line[:len(line)-1]
|
||||||
|
b := bytes.Split(line, []byte{' '})
|
||||||
|
if len(b) != 3 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if bytes.Equal(b[0], id) {
|
||||||
|
return b[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateChallenge returns a random, hex-encoded challenge, or nil on error
|
||||||
|
// (see above).
|
||||||
|
func (a authCookieSha1) generateChallenge() []byte {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
n, err := rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if n != 16 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
enc := make([]byte, 32)
|
||||||
|
hex.Encode(enc, b)
|
||||||
|
return enc
|
||||||
|
}
|
||||||
+147
@@ -0,0 +1,147 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Call represents a pending or completed method call.
|
||||||
|
type Call struct {
|
||||||
|
Destination string
|
||||||
|
Path ObjectPath
|
||||||
|
Method string
|
||||||
|
Args []interface{}
|
||||||
|
|
||||||
|
// Strobes when the call is complete.
|
||||||
|
Done chan *Call
|
||||||
|
|
||||||
|
// After completion, the error status. If this is non-nil, it may be an
|
||||||
|
// error message from the peer (with Error as its type) or some other error.
|
||||||
|
Err error
|
||||||
|
|
||||||
|
// Holds the response once the call is done.
|
||||||
|
Body []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errSignature = errors.New("dbus: mismatched signature")
|
||||||
|
|
||||||
|
// Store stores the body of the reply into the provided pointers. It returns
|
||||||
|
// an error if the signatures of the body and retvalues don't match, or if
|
||||||
|
// the error status is not nil.
|
||||||
|
func (c *Call) Store(retvalues ...interface{}) error {
|
||||||
|
if c.Err != nil {
|
||||||
|
return c.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
return Store(c.Body, retvalues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object represents a remote object on which methods can be invoked.
|
||||||
|
type Object struct {
|
||||||
|
conn *Conn
|
||||||
|
dest string
|
||||||
|
path ObjectPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call calls a method with (*Object).Go and waits for its reply.
|
||||||
|
func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call {
|
||||||
|
return <-o.Go(method, flags, make(chan *Call, 1), args...).Done
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProperty calls org.freedesktop.DBus.Properties.GetProperty on the given
|
||||||
|
// object. The property name must be given in interface.member notation.
|
||||||
|
func (o *Object) GetProperty(p string) (Variant, error) {
|
||||||
|
idx := strings.LastIndex(p, ".")
|
||||||
|
if idx == -1 || idx+1 == len(p) {
|
||||||
|
return Variant{}, errors.New("dbus: invalid property " + p)
|
||||||
|
}
|
||||||
|
|
||||||
|
iface := p[:idx]
|
||||||
|
prop := p[idx+1:]
|
||||||
|
|
||||||
|
result := Variant{}
|
||||||
|
err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return Variant{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go calls a method with the given arguments asynchronously. It returns a
|
||||||
|
// Call structure representing this method call. The passed channel will
|
||||||
|
// return the same value once the call is done. If ch is nil, a new channel
|
||||||
|
// will be allocated. Otherwise, ch has to be buffered or Go will panic.
|
||||||
|
//
|
||||||
|
// If the flags include FlagNoReplyExpected, ch is ignored and a Call structure
|
||||||
|
// is returned of which only the Err member is valid.
|
||||||
|
//
|
||||||
|
// If the method parameter contains a dot ('.'), the part before the last dot
|
||||||
|
// specifies the interface on which the method is called.
|
||||||
|
func (o *Object) Go(method string, flags Flags, ch chan *Call, args ...interface{}) *Call {
|
||||||
|
iface := ""
|
||||||
|
i := strings.LastIndex(method, ".")
|
||||||
|
if i != -1 {
|
||||||
|
iface = method[:i]
|
||||||
|
}
|
||||||
|
method = method[i+1:]
|
||||||
|
msg := new(Message)
|
||||||
|
msg.Type = TypeMethodCall
|
||||||
|
msg.serial = o.conn.getSerial()
|
||||||
|
msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected)
|
||||||
|
msg.Headers = make(map[HeaderField]Variant)
|
||||||
|
msg.Headers[FieldPath] = MakeVariant(o.path)
|
||||||
|
msg.Headers[FieldDestination] = MakeVariant(o.dest)
|
||||||
|
msg.Headers[FieldMember] = MakeVariant(method)
|
||||||
|
if iface != "" {
|
||||||
|
msg.Headers[FieldInterface] = MakeVariant(iface)
|
||||||
|
}
|
||||||
|
msg.Body = args
|
||||||
|
if len(args) > 0 {
|
||||||
|
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...))
|
||||||
|
}
|
||||||
|
if msg.Flags&FlagNoReplyExpected == 0 {
|
||||||
|
if ch == nil {
|
||||||
|
ch = make(chan *Call, 10)
|
||||||
|
} else if cap(ch) == 0 {
|
||||||
|
panic("dbus: unbuffered channel passed to (*Object).Go")
|
||||||
|
}
|
||||||
|
call := &Call{
|
||||||
|
Destination: o.dest,
|
||||||
|
Path: o.path,
|
||||||
|
Method: method,
|
||||||
|
Args: args,
|
||||||
|
Done: ch,
|
||||||
|
}
|
||||||
|
o.conn.callsLck.Lock()
|
||||||
|
o.conn.calls[msg.serial] = call
|
||||||
|
o.conn.callsLck.Unlock()
|
||||||
|
o.conn.outLck.RLock()
|
||||||
|
if o.conn.closed {
|
||||||
|
call.Err = ErrClosed
|
||||||
|
call.Done <- call
|
||||||
|
} else {
|
||||||
|
o.conn.out <- msg
|
||||||
|
}
|
||||||
|
o.conn.outLck.RUnlock()
|
||||||
|
return call
|
||||||
|
}
|
||||||
|
o.conn.outLck.RLock()
|
||||||
|
defer o.conn.outLck.RUnlock()
|
||||||
|
if o.conn.closed {
|
||||||
|
return &Call{Err: ErrClosed}
|
||||||
|
}
|
||||||
|
o.conn.out <- msg
|
||||||
|
return &Call{Err: nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destination returns the destination that calls on o are sent to.
|
||||||
|
func (o *Object) Destination() string {
|
||||||
|
return o.dest
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the path that calls on o are sent to.
|
||||||
|
func (o *Object) Path() ObjectPath {
|
||||||
|
return o.path
|
||||||
|
}
|
||||||
+609
@@ -0,0 +1,609 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultSystemBusAddress = "unix:path=/var/run/dbus/system_bus_socket"
|
||||||
|
|
||||||
|
var (
|
||||||
|
systemBus *Conn
|
||||||
|
systemBusLck sync.Mutex
|
||||||
|
sessionBus *Conn
|
||||||
|
sessionBusLck sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrClosed is the error returned by calls on a closed connection.
|
||||||
|
var ErrClosed = errors.New("dbus: connection closed by user")
|
||||||
|
|
||||||
|
// Conn represents a connection to a message bus (usually, the system or
|
||||||
|
// session bus).
|
||||||
|
//
|
||||||
|
// Connections are either shared or private. Shared connections
|
||||||
|
// are shared between calls to the functions that return them. As a result,
|
||||||
|
// the methods Close, Auth and Hello must not be called on them.
|
||||||
|
//
|
||||||
|
// Multiple goroutines may invoke methods on a connection simultaneously.
|
||||||
|
type Conn struct {
|
||||||
|
transport
|
||||||
|
|
||||||
|
busObj *Object
|
||||||
|
unixFD bool
|
||||||
|
uuid string
|
||||||
|
|
||||||
|
names []string
|
||||||
|
namesLck sync.RWMutex
|
||||||
|
|
||||||
|
serialLck sync.Mutex
|
||||||
|
nextSerial uint32
|
||||||
|
serialUsed map[uint32]bool
|
||||||
|
|
||||||
|
calls map[uint32]*Call
|
||||||
|
callsLck sync.RWMutex
|
||||||
|
|
||||||
|
handlers map[ObjectPath]map[string]interface{}
|
||||||
|
handlersLck sync.RWMutex
|
||||||
|
|
||||||
|
out chan *Message
|
||||||
|
closed bool
|
||||||
|
outLck sync.RWMutex
|
||||||
|
|
||||||
|
signals []chan<- *Signal
|
||||||
|
signalsLck sync.Mutex
|
||||||
|
|
||||||
|
eavesdropped chan<- *Message
|
||||||
|
eavesdroppedLck sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionBus returns a shared connection to the session bus, connecting to it
|
||||||
|
// if not already done.
|
||||||
|
func SessionBus() (conn *Conn, err error) {
|
||||||
|
sessionBusLck.Lock()
|
||||||
|
defer sessionBusLck.Unlock()
|
||||||
|
if sessionBus != nil {
|
||||||
|
return sessionBus, nil
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if conn != nil {
|
||||||
|
sessionBus = conn
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
conn, err = SessionBusPrivate()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = conn.Auth(nil); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
conn = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = conn.Hello(); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
conn = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionBusPrivate returns a new private connection to the session bus.
|
||||||
|
func SessionBusPrivate() (*Conn, error) {
|
||||||
|
address := os.Getenv("DBUS_SESSION_BUS_ADDRESS")
|
||||||
|
if address != "" && address != "autolaunch:" {
|
||||||
|
return Dial(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionBusPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemBus returns a shared connection to the system bus, connecting to it if
|
||||||
|
// not already done.
|
||||||
|
func SystemBus() (conn *Conn, err error) {
|
||||||
|
systemBusLck.Lock()
|
||||||
|
defer systemBusLck.Unlock()
|
||||||
|
if systemBus != nil {
|
||||||
|
return systemBus, nil
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if conn != nil {
|
||||||
|
systemBus = conn
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
conn, err = SystemBusPrivate()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = conn.Auth(nil); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
conn = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = conn.Hello(); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
conn = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemBusPrivate returns a new private connection to the system bus.
|
||||||
|
func SystemBusPrivate() (*Conn, error) {
|
||||||
|
address := os.Getenv("DBUS_SYSTEM_BUS_ADDRESS")
|
||||||
|
if address != "" {
|
||||||
|
return Dial(address)
|
||||||
|
}
|
||||||
|
return Dial(defaultSystemBusAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial establishes a new private connection to the message bus specified by address.
|
||||||
|
func Dial(address string) (*Conn, error) {
|
||||||
|
tr, err := getTransport(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newConn(tr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConn creates a new private *Conn from an already established connection.
|
||||||
|
func NewConn(conn io.ReadWriteCloser) (*Conn, error) {
|
||||||
|
return newConn(genericTransport{conn})
|
||||||
|
}
|
||||||
|
|
||||||
|
// newConn creates a new *Conn from a transport.
|
||||||
|
func newConn(tr transport) (*Conn, error) {
|
||||||
|
conn := new(Conn)
|
||||||
|
conn.transport = tr
|
||||||
|
conn.calls = make(map[uint32]*Call)
|
||||||
|
conn.out = make(chan *Message, 10)
|
||||||
|
conn.handlers = make(map[ObjectPath]map[string]interface{})
|
||||||
|
conn.nextSerial = 1
|
||||||
|
conn.serialUsed = map[uint32]bool{0: true}
|
||||||
|
conn.busObj = conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BusObject returns the object owned by the bus daemon which handles
|
||||||
|
// administrative requests.
|
||||||
|
func (conn *Conn) BusObject() *Object {
|
||||||
|
return conn.busObj
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the connection. Any blocked operations will return with errors
|
||||||
|
// and the channels passed to Eavesdrop and Signal are closed. This method must
|
||||||
|
// not be called on shared connections.
|
||||||
|
func (conn *Conn) Close() error {
|
||||||
|
conn.outLck.Lock()
|
||||||
|
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" && member == "NameLost" &&
|
||||||
|
sender == "org.freedesktop.DBus" {
|
||||||
|
|
||||||
|
name, _ := msg.Body[0].(string)
|
||||||
|
conn.namesLck.Lock()
|
||||||
|
for i, v := range conn.names {
|
||||||
|
if v == name {
|
||||||
|
copy(conn.names[i:], conn.names[i+1:])
|
||||||
|
conn.names = conn.names[:len(conn.names)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.namesLck.Unlock()
|
||||||
|
}
|
||||||
|
signal := &Signal{
|
||||||
|
Sender: sender,
|
||||||
|
Path: msg.Headers[FieldPath].value.(ObjectPath),
|
||||||
|
Name: iface + "." + member,
|
||||||
|
Body: msg.Body,
|
||||||
|
}
|
||||||
|
conn.signalsLck.Lock()
|
||||||
|
for _, ch := range conn.signals {
|
||||||
|
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) *Object {
|
||||||
|
return &Object{conn, dest, path}
|
||||||
|
}
|
||||||
|
|
||||||
|
// outWorker runs in an own goroutine, encoding and sending messages that are
|
||||||
|
// sent to conn.out.
|
||||||
|
func (conn *Conn) outWorker() {
|
||||||
|
for msg := range conn.out {
|
||||||
|
err := conn.SendMessage(msg)
|
||||||
|
conn.callsLck.RLock()
|
||||||
|
if err != nil {
|
||||||
|
if c := conn.calls[msg.serial]; c != nil {
|
||||||
|
c.Err = err
|
||||||
|
c.Done <- c
|
||||||
|
}
|
||||||
|
conn.serialLck.Lock()
|
||||||
|
delete(conn.serialUsed, msg.serial)
|
||||||
|
conn.serialLck.Unlock()
|
||||||
|
} else if msg.Type != TypeMethodCall {
|
||||||
|
conn.serialLck.Lock()
|
||||||
|
delete(conn.serialUsed, msg.serial)
|
||||||
|
conn.serialLck.Unlock()
|
||||||
|
}
|
||||||
|
conn.callsLck.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send sends the given message to the message bus. You usually don't need to
|
||||||
|
// use this; use the higher-level equivalents (Call / Go, Emit and Export)
|
||||||
|
// instead. If msg is a method call and NoReplyExpected is not set, a non-nil
|
||||||
|
// call is returned and the same value is sent to ch (which must be buffered)
|
||||||
|
// once the call is complete. Otherwise, ch is ignored and a Call structure is
|
||||||
|
// returned of which only the Err member is valid.
|
||||||
|
func (conn *Conn) Send(msg *Message, ch chan *Call) *Call {
|
||||||
|
var call *Call
|
||||||
|
|
||||||
|
msg.serial = conn.getSerial()
|
||||||
|
if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 {
|
||||||
|
if ch == nil {
|
||||||
|
ch = make(chan *Call, 5)
|
||||||
|
} else if cap(ch) == 0 {
|
||||||
|
panic("dbus: unbuffered channel passed to (*Conn).Send")
|
||||||
|
}
|
||||||
|
call = new(Call)
|
||||||
|
call.Destination, _ = msg.Headers[FieldDestination].value.(string)
|
||||||
|
call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath)
|
||||||
|
iface, _ := msg.Headers[FieldInterface].value.(string)
|
||||||
|
member, _ := msg.Headers[FieldMember].value.(string)
|
||||||
|
call.Method = iface + "." + member
|
||||||
|
call.Args = msg.Body
|
||||||
|
call.Done = ch
|
||||||
|
conn.callsLck.Lock()
|
||||||
|
conn.calls[msg.serial] = call
|
||||||
|
conn.callsLck.Unlock()
|
||||||
|
conn.outLck.RLock()
|
||||||
|
if conn.closed {
|
||||||
|
call.Err = ErrClosed
|
||||||
|
call.Done <- call
|
||||||
|
} else {
|
||||||
|
conn.out <- msg
|
||||||
|
}
|
||||||
|
conn.outLck.RUnlock()
|
||||||
|
} else {
|
||||||
|
conn.outLck.RLock()
|
||||||
|
if conn.closed {
|
||||||
|
call = &Call{Err: ErrClosed}
|
||||||
|
} else {
|
||||||
|
conn.out <- msg
|
||||||
|
call = &Call{Err: nil}
|
||||||
|
}
|
||||||
|
conn.outLck.RUnlock()
|
||||||
|
}
|
||||||
|
return call
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendError creates an error message corresponding to the parameters and sends
|
||||||
|
// it to conn.out.
|
||||||
|
func (conn *Conn) sendError(e Error, dest string, serial uint32) {
|
||||||
|
msg := new(Message)
|
||||||
|
msg.Type = TypeError
|
||||||
|
msg.serial = conn.getSerial()
|
||||||
|
msg.Headers = make(map[HeaderField]Variant)
|
||||||
|
if dest != "" {
|
||||||
|
msg.Headers[FieldDestination] = MakeVariant(dest)
|
||||||
|
}
|
||||||
|
msg.Headers[FieldErrorName] = MakeVariant(e.Name)
|
||||||
|
msg.Headers[FieldReplySerial] = MakeVariant(serial)
|
||||||
|
msg.Body = e.Body
|
||||||
|
if len(e.Body) > 0 {
|
||||||
|
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...))
|
||||||
|
}
|
||||||
|
conn.outLck.RLock()
|
||||||
|
if !conn.closed {
|
||||||
|
conn.out <- msg
|
||||||
|
}
|
||||||
|
conn.outLck.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendReply creates a method reply message corresponding to the parameters and
|
||||||
|
// sends it to conn.out.
|
||||||
|
func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
|
||||||
|
msg := new(Message)
|
||||||
|
msg.Type = TypeMethodReply
|
||||||
|
msg.serial = conn.getSerial()
|
||||||
|
msg.Headers = make(map[HeaderField]Variant)
|
||||||
|
if dest != "" {
|
||||||
|
msg.Headers[FieldDestination] = MakeVariant(dest)
|
||||||
|
}
|
||||||
|
msg.Headers[FieldReplySerial] = MakeVariant(serial)
|
||||||
|
msg.Body = values
|
||||||
|
if len(values) > 0 {
|
||||||
|
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
|
||||||
|
}
|
||||||
|
conn.outLck.RLock()
|
||||||
|
if !conn.closed {
|
||||||
|
conn.out <- msg
|
||||||
|
}
|
||||||
|
conn.outLck.RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal registers the given channel to be passed all received signal messages.
|
||||||
|
// The caller has to make sure that ch is sufficiently buffered; if a message
|
||||||
|
// arrives when a write to c is not possible, it is discarded.
|
||||||
|
//
|
||||||
|
// Multiple of these channels can be registered at the same time. Passing a
|
||||||
|
// channel that already is registered will remove it from the list of the
|
||||||
|
// registered channels.
|
||||||
|
//
|
||||||
|
// These channels are "overwritten" by Eavesdrop; i.e., if there currently is a
|
||||||
|
// channel for eavesdropped messages, this channel receives all signals, and
|
||||||
|
// none of the channels passed to Signal will receive any signals.
|
||||||
|
func (conn *Conn) Signal(ch chan<- *Signal) {
|
||||||
|
conn.signalsLck.Lock()
|
||||||
|
conn.signals = append(conn.signals, ch)
|
||||||
|
conn.signalsLck.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportsUnixFDs returns whether the underlying transport supports passing of
|
||||||
|
// unix file descriptors. If this is false, method calls containing unix file
|
||||||
|
// descriptors will return an error and emitted signals containing them will
|
||||||
|
// not be sent.
|
||||||
|
func (conn *Conn) SupportsUnixFDs() bool {
|
||||||
|
return conn.unixFD
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error represents a D-Bus message of type Error.
|
||||||
|
type Error struct {
|
||||||
|
Name string
|
||||||
|
Body []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func 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 map[string]func(string) (transport, error) = 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)")
|
||||||
|
}
|
||||||
|
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]
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sessionBusPlatform() (*Conn, error) {
|
||||||
|
cmd := exec.Command("launchctl", "getenv", "DBUS_LAUNCHD_SESSION_BUS_SOCKET")
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil, errors.New("dbus: couldn't determine address of session bus")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Dial("unix:path=" + string(b[:len(b)-1]))
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
// +build !darwin
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sessionBusPlatform() (*Conn, error) {
|
||||||
|
cmd := exec.Command("dbus-launch")
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
i := bytes.IndexByte(b, '=')
|
||||||
|
j := bytes.IndexByte(b, '\n')
|
||||||
|
|
||||||
|
if i == -1 || j == -1 {
|
||||||
|
return nil, errors.New("dbus: couldn't determine address of session bus")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Dial(string(b[i+1 : j]))
|
||||||
|
}
|
||||||
+199
@@ -0,0 +1,199 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSessionBus(t *testing.T) {
|
||||||
|
_, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSystemBus(t *testing.T) {
|
||||||
|
_, err := SystemBus()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSend(t *testing.T) {
|
||||||
|
bus, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
ch := make(chan *Call, 1)
|
||||||
|
msg := &Message{
|
||||||
|
Type: TypeMethodCall,
|
||||||
|
Flags: 0,
|
||||||
|
Headers: map[HeaderField]Variant{
|
||||||
|
FieldDestination: MakeVariant(bus.Names()[0]),
|
||||||
|
FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")),
|
||||||
|
FieldInterface: MakeVariant("org.freedesktop.DBus.Peer"),
|
||||||
|
FieldMember: MakeVariant("Ping"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
call := bus.Send(msg, ch)
|
||||||
|
<-ch
|
||||||
|
if call.Err != nil {
|
||||||
|
t.Error(call.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type server struct{}
|
||||||
|
|
||||||
|
func (server) Double(i int64) (int64, *Error) {
|
||||||
|
return 2 * i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCall(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
var s string
|
||||||
|
bus, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
name := bus.Names()[0]
|
||||||
|
obj := bus.BusObject()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := obj.Call("org.freedesktop.DBus.GetNameOwner", 0, name).Store(&s)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if s != name {
|
||||||
|
b.Errorf("got %s, wanted %s", s, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCallAsync(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
bus, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
name := bus.Names()[0]
|
||||||
|
obj := bus.BusObject()
|
||||||
|
c := make(chan *Call, 50)
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := <-c
|
||||||
|
if v.Err != nil {
|
||||||
|
b.Error(v.Err)
|
||||||
|
}
|
||||||
|
s := v.Body[0].(string)
|
||||||
|
if s != name {
|
||||||
|
b.Errorf("got %s, wanted %s", s, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
obj.Go("org.freedesktop.DBus.GetNameOwner", 0, c, name)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkServe(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
srv, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
cli, err := SessionBusPrivate()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = cli.Auth(nil); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = cli.Hello(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
benchmarkServe(b, srv, cli)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkServeAsync(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
srv, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
cli, err := SessionBusPrivate()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = cli.Auth(nil); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = cli.Hello(); err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
benchmarkServeAsync(b, srv, cli)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkServeSameConn(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
bus, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmarkServe(b, bus, bus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkServeSameConnAsync(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
bus, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmarkServeAsync(b, bus, bus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkServe(b *testing.B, srv, cli *Conn) {
|
||||||
|
var r int64
|
||||||
|
var err error
|
||||||
|
dest := srv.Names()[0]
|
||||||
|
srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
|
||||||
|
obj := cli.Object(dest, "/org/guelfey/DBus/Test")
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err = obj.Call("org.guelfey.DBus.Test.Double", 0, int64(i)).Store(&r)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if r != 2*int64(i) {
|
||||||
|
b.Errorf("got %d, wanted %d", r, 2*int64(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkServeAsync(b *testing.B, srv, cli *Conn) {
|
||||||
|
dest := srv.Names()[0]
|
||||||
|
srv.Export(server{}, "/org/guelfey/DBus/Test", "org.guelfey.DBus.Test")
|
||||||
|
obj := cli.Object(dest, "/org/guelfey/DBus/Test")
|
||||||
|
c := make(chan *Call, 50)
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v := <-c
|
||||||
|
if v.Err != nil {
|
||||||
|
b.Fatal(v.Err)
|
||||||
|
}
|
||||||
|
i, r := v.Args[0].(int64), v.Body[0].(int64)
|
||||||
|
if 2*i != r {
|
||||||
|
b.Errorf("got %d, wanted %d", r, 2*i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
obj.Go("org.guelfey.DBus.Test.Double", 0, c, int64(i))
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
+258
@@ -0,0 +1,258 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
byteType = reflect.TypeOf(byte(0))
|
||||||
|
boolType = reflect.TypeOf(false)
|
||||||
|
uint8Type = reflect.TypeOf(uint8(0))
|
||||||
|
int16Type = reflect.TypeOf(int16(0))
|
||||||
|
uint16Type = reflect.TypeOf(uint16(0))
|
||||||
|
int32Type = reflect.TypeOf(int32(0))
|
||||||
|
uint32Type = reflect.TypeOf(uint32(0))
|
||||||
|
int64Type = reflect.TypeOf(int64(0))
|
||||||
|
uint64Type = reflect.TypeOf(uint64(0))
|
||||||
|
float64Type = reflect.TypeOf(float64(0))
|
||||||
|
stringType = reflect.TypeOf("")
|
||||||
|
signatureType = reflect.TypeOf(Signature{""})
|
||||||
|
objectPathType = reflect.TypeOf(ObjectPath(""))
|
||||||
|
variantType = reflect.TypeOf(Variant{Signature{""}, nil})
|
||||||
|
interfacesType = reflect.TypeOf([]interface{}{})
|
||||||
|
unixFDType = reflect.TypeOf(UnixFD(0))
|
||||||
|
unixFDIndexType = reflect.TypeOf(UnixFDIndex(0))
|
||||||
|
)
|
||||||
|
|
||||||
|
// An InvalidTypeError signals that a value which cannot be represented in the
|
||||||
|
// D-Bus wire format was passed to a function.
|
||||||
|
type InvalidTypeError struct {
|
||||||
|
Type reflect.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e InvalidTypeError) Error() string {
|
||||||
|
return "dbus: invalid type " + e.Type.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store copies the values contained in src to dest, which must be a slice of
|
||||||
|
// pointers. It converts slices of interfaces from src to corresponding structs
|
||||||
|
// in dest. An error is returned if the lengths of src and dest or the types of
|
||||||
|
// their elements don't match.
|
||||||
|
func Store(src []interface{}, dest ...interface{}) error {
|
||||||
|
if len(src) != len(dest) {
|
||||||
|
return errors.New("dbus.Store: length mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range src {
|
||||||
|
if err := store(src[i], dest[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func store(src, dest interface{}) error {
|
||||||
|
if reflect.TypeOf(dest).Elem() == reflect.TypeOf(src) {
|
||||||
|
reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(src))
|
||||||
|
return nil
|
||||||
|
} else if hasStruct(dest) {
|
||||||
|
rv := reflect.ValueOf(dest).Elem()
|
||||||
|
switch rv.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
vs, ok := src.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return errors.New("dbus.Store: type mismatch")
|
||||||
|
}
|
||||||
|
t := rv.Type()
|
||||||
|
ndest := make([]interface{}, 0, rv.NumField())
|
||||||
|
for i := 0; i < rv.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
|
||||||
|
ndest = append(ndest, rv.Field(i).Addr().Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(vs) != len(ndest) {
|
||||||
|
return errors.New("dbus.Store: type mismatch")
|
||||||
|
}
|
||||||
|
err := Store(vs, ndest...)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("dbus.Store: type mismatch")
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
sv := reflect.ValueOf(src)
|
||||||
|
if sv.Kind() != reflect.Slice {
|
||||||
|
return errors.New("dbus.Store: type mismatch")
|
||||||
|
}
|
||||||
|
rv.Set(reflect.MakeSlice(rv.Type(), sv.Len(), sv.Len()))
|
||||||
|
for i := 0; i < sv.Len(); i++ {
|
||||||
|
if err := store(sv.Index(i).Interface(), rv.Index(i).Addr().Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
sv := reflect.ValueOf(src)
|
||||||
|
if sv.Kind() != reflect.Map {
|
||||||
|
return errors.New("dbus.Store: type mismatch")
|
||||||
|
}
|
||||||
|
keys := sv.MapKeys()
|
||||||
|
rv.Set(reflect.MakeMap(sv.Type()))
|
||||||
|
for _, key := range keys {
|
||||||
|
v := reflect.New(sv.Type().Elem())
|
||||||
|
if err := store(v, sv.MapIndex(key).Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rv.SetMapIndex(key, v.Elem())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("dbus.Store: type mismatch")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.New("dbus.Store: type mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasStruct(v interface{}) bool {
|
||||||
|
t := reflect.TypeOf(v)
|
||||||
|
for {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
return true
|
||||||
|
case reflect.Slice, reflect.Ptr, reflect.Map:
|
||||||
|
t = t.Elem()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An ObjectPath is an object path as defined by the D-Bus spec.
|
||||||
|
type ObjectPath string
|
||||||
|
|
||||||
|
// IsValid returns whether the object path is valid.
|
||||||
|
func (o ObjectPath) IsValid() bool {
|
||||||
|
s := string(o)
|
||||||
|
if len(s) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[0] != '/' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[len(s)-1] == '/' && len(s) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// probably not used, but technically possible
|
||||||
|
if s == "/" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
split := strings.Split(s[1:], "/")
|
||||||
|
for _, v := range split {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range v {
|
||||||
|
if !isMemberChar(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// A UnixFD is a Unix file descriptor sent over the wire. See the package-level
|
||||||
|
// documentation for more information about Unix file descriptor passsing.
|
||||||
|
type UnixFD int32
|
||||||
|
|
||||||
|
// A UnixFDIndex is the representation of a Unix file descriptor in a message.
|
||||||
|
type UnixFDIndex uint32
|
||||||
|
|
||||||
|
// alignment returns the alignment of values of type t.
|
||||||
|
func alignment(t reflect.Type) int {
|
||||||
|
switch t {
|
||||||
|
case variantType:
|
||||||
|
return 1
|
||||||
|
case objectPathType:
|
||||||
|
return 4
|
||||||
|
case signatureType:
|
||||||
|
return 1
|
||||||
|
case interfacesType: // sometimes used for structs
|
||||||
|
return 8
|
||||||
|
}
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Uint8:
|
||||||
|
return 1
|
||||||
|
case reflect.Uint16, reflect.Int16:
|
||||||
|
return 2
|
||||||
|
case reflect.Uint32, reflect.Int32, reflect.String, reflect.Array, reflect.Slice, reflect.Map:
|
||||||
|
return 4
|
||||||
|
case reflect.Uint64, reflect.Int64, reflect.Float64, reflect.Struct:
|
||||||
|
return 8
|
||||||
|
case reflect.Ptr:
|
||||||
|
return alignment(t.Elem())
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// isKeyType returns whether t is a valid type for a D-Bus dict.
|
||||||
|
func isKeyType(t reflect.Type) bool {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||||
|
reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float64,
|
||||||
|
reflect.String:
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidInterface returns whether s is a valid name for an interface.
|
||||||
|
func isValidInterface(s string) bool {
|
||||||
|
if len(s) == 0 || len(s) > 255 || s[0] == '.' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
elem := strings.Split(s, ".")
|
||||||
|
if len(elem) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, v := range elem {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v[0] >= '0' && v[0] <= '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range v {
|
||||||
|
if !isMemberChar(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidMember returns whether s is a valid name for a member.
|
||||||
|
func isValidMember(s string) bool {
|
||||||
|
if len(s) == 0 || len(s) > 255 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i := strings.Index(s, ".")
|
||||||
|
if i != -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s[0] >= '0' && s[0] <= '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range s {
|
||||||
|
if !isMemberChar(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMemberChar(c rune) bool {
|
||||||
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
|
||||||
|
(c >= 'a' && c <= 'z') || c == '_'
|
||||||
|
}
|
||||||
+228
@@ -0,0 +1,228 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
in io.Reader
|
||||||
|
order binary.ByteOrder
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDecoder returns a new decoder that reads values from in. The input is
|
||||||
|
// expected to be in the given byte order.
|
||||||
|
func newDecoder(in io.Reader, order binary.ByteOrder) *decoder {
|
||||||
|
dec := new(decoder)
|
||||||
|
dec.in = in
|
||||||
|
dec.order = order
|
||||||
|
return dec
|
||||||
|
}
|
||||||
|
|
||||||
|
// align aligns the input to the given boundary and panics on error.
|
||||||
|
func (dec *decoder) align(n int) {
|
||||||
|
if dec.pos%n != 0 {
|
||||||
|
newpos := (dec.pos + n - 1) & ^(n - 1)
|
||||||
|
empty := make([]byte, newpos-dec.pos)
|
||||||
|
if _, err := io.ReadFull(dec.in, empty); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dec.pos = newpos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calls binary.Read(dec.in, dec.order, v) and panics on read errors.
|
||||||
|
func (dec *decoder) binread(v interface{}) {
|
||||||
|
if err := binary.Read(dec.in, dec.order, v); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *decoder) Decode(sig Signature) (vs []interface{}, err error) {
|
||||||
|
defer func() {
|
||||||
|
var ok bool
|
||||||
|
v := recover()
|
||||||
|
if err, ok = v.(error); ok {
|
||||||
|
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||||
|
err = FormatError("unexpected EOF")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
vs = make([]interface{}, 0)
|
||||||
|
s := sig.str
|
||||||
|
for s != "" {
|
||||||
|
err, rem := validSingle(s, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v := dec.decode(s[:len(s)-len(rem)], 0)
|
||||||
|
vs = append(vs, v)
|
||||||
|
s = rem
|
||||||
|
}
|
||||||
|
return vs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dec *decoder) decode(s string, depth int) interface{} {
|
||||||
|
dec.align(alignment(typeFor(s)))
|
||||||
|
switch s[0] {
|
||||||
|
case 'y':
|
||||||
|
var b [1]byte
|
||||||
|
if _, err := dec.in.Read(b[:]); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dec.pos++
|
||||||
|
return b[0]
|
||||||
|
case 'b':
|
||||||
|
i := dec.decode("u", depth).(uint32)
|
||||||
|
switch {
|
||||||
|
case i == 0:
|
||||||
|
return false
|
||||||
|
case i == 1:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
panic(FormatError("invalid value for boolean"))
|
||||||
|
}
|
||||||
|
case 'n':
|
||||||
|
var i int16
|
||||||
|
dec.binread(&i)
|
||||||
|
dec.pos += 2
|
||||||
|
return i
|
||||||
|
case 'i':
|
||||||
|
var i int32
|
||||||
|
dec.binread(&i)
|
||||||
|
dec.pos += 4
|
||||||
|
return i
|
||||||
|
case 'x':
|
||||||
|
var i int64
|
||||||
|
dec.binread(&i)
|
||||||
|
dec.pos += 8
|
||||||
|
return i
|
||||||
|
case 'q':
|
||||||
|
var i uint16
|
||||||
|
dec.binread(&i)
|
||||||
|
dec.pos += 2
|
||||||
|
return i
|
||||||
|
case 'u':
|
||||||
|
var i uint32
|
||||||
|
dec.binread(&i)
|
||||||
|
dec.pos += 4
|
||||||
|
return i
|
||||||
|
case 't':
|
||||||
|
var i uint64
|
||||||
|
dec.binread(&i)
|
||||||
|
dec.pos += 8
|
||||||
|
return i
|
||||||
|
case 'd':
|
||||||
|
var f float64
|
||||||
|
dec.binread(&f)
|
||||||
|
dec.pos += 8
|
||||||
|
return f
|
||||||
|
case 's':
|
||||||
|
length := dec.decode("u", depth).(uint32)
|
||||||
|
b := make([]byte, int(length)+1)
|
||||||
|
if _, err := io.ReadFull(dec.in, b); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dec.pos += int(length) + 1
|
||||||
|
return string(b[:len(b)-1])
|
||||||
|
case 'o':
|
||||||
|
return ObjectPath(dec.decode("s", depth).(string))
|
||||||
|
case 'g':
|
||||||
|
length := dec.decode("y", depth).(byte)
|
||||||
|
b := make([]byte, int(length)+1)
|
||||||
|
if _, err := io.ReadFull(dec.in, b); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
dec.pos += int(length) + 1
|
||||||
|
sig, err := ParseSignature(string(b[:len(b)-1]))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return sig
|
||||||
|
case 'v':
|
||||||
|
if depth >= 64 {
|
||||||
|
panic(FormatError("input exceeds container depth limit"))
|
||||||
|
}
|
||||||
|
var variant Variant
|
||||||
|
sig := dec.decode("g", depth).(Signature)
|
||||||
|
if len(sig.str) == 0 {
|
||||||
|
panic(FormatError("variant signature is empty"))
|
||||||
|
}
|
||||||
|
err, rem := validSingle(sig.str, 0)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if rem != "" {
|
||||||
|
panic(FormatError("variant signature has multiple types"))
|
||||||
|
}
|
||||||
|
variant.sig = sig
|
||||||
|
variant.value = dec.decode(sig.str, depth+1)
|
||||||
|
return variant
|
||||||
|
case 'h':
|
||||||
|
return UnixFDIndex(dec.decode("u", depth).(uint32))
|
||||||
|
case 'a':
|
||||||
|
if len(s) > 1 && s[1] == '{' {
|
||||||
|
ksig := s[2:3]
|
||||||
|
vsig := s[3 : len(s)-1]
|
||||||
|
v := reflect.MakeMap(reflect.MapOf(typeFor(ksig), typeFor(vsig)))
|
||||||
|
if depth >= 63 {
|
||||||
|
panic(FormatError("input exceeds container depth limit"))
|
||||||
|
}
|
||||||
|
length := dec.decode("u", depth).(uint32)
|
||||||
|
// Even for empty maps, the correct padding must be included
|
||||||
|
dec.align(8)
|
||||||
|
spos := dec.pos
|
||||||
|
for dec.pos < spos+int(length) {
|
||||||
|
dec.align(8)
|
||||||
|
if !isKeyType(v.Type().Key()) {
|
||||||
|
panic(InvalidTypeError{v.Type()})
|
||||||
|
}
|
||||||
|
kv := dec.decode(ksig, depth+2)
|
||||||
|
vv := dec.decode(vsig, depth+2)
|
||||||
|
v.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
|
||||||
|
}
|
||||||
|
return v.Interface()
|
||||||
|
}
|
||||||
|
if depth >= 64 {
|
||||||
|
panic(FormatError("input exceeds container depth limit"))
|
||||||
|
}
|
||||||
|
length := dec.decode("u", depth).(uint32)
|
||||||
|
v := reflect.MakeSlice(reflect.SliceOf(typeFor(s[1:])), 0, int(length))
|
||||||
|
// Even for empty arrays, the correct padding must be included
|
||||||
|
dec.align(alignment(typeFor(s[1:])))
|
||||||
|
spos := dec.pos
|
||||||
|
for dec.pos < spos+int(length) {
|
||||||
|
ev := dec.decode(s[1:], depth+1)
|
||||||
|
v = reflect.Append(v, reflect.ValueOf(ev))
|
||||||
|
}
|
||||||
|
return v.Interface()
|
||||||
|
case '(':
|
||||||
|
if depth >= 64 {
|
||||||
|
panic(FormatError("input exceeds container depth limit"))
|
||||||
|
}
|
||||||
|
dec.align(8)
|
||||||
|
v := make([]interface{}, 0)
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
for s != "" {
|
||||||
|
err, rem := validSingle(s, 0)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ev := dec.decode(s[:len(s)-len(rem)], depth+1)
|
||||||
|
v = append(v, ev)
|
||||||
|
s = rem
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
panic(SignatureError{Sig: s})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A FormatError is an error in the wire format.
|
||||||
|
type FormatError string
|
||||||
|
|
||||||
|
func (e FormatError) Error() string {
|
||||||
|
return "dbus: wire format error: " + string(e)
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
Package dbus implements bindings to the D-Bus message bus system.
|
||||||
|
|
||||||
|
To use the message bus API, you first need to connect to a bus (usually the
|
||||||
|
session or system bus). The acquired connection then can be used to call methods
|
||||||
|
on remote objects and emit or receive signals. Using the Export method, you can
|
||||||
|
arrange D-Bus methods calls to be directly translated to method calls on a Go
|
||||||
|
value.
|
||||||
|
|
||||||
|
Conversion Rules
|
||||||
|
|
||||||
|
For outgoing messages, Go types are automatically converted to the
|
||||||
|
corresponding D-Bus types. The following types are directly encoded as their
|
||||||
|
respective D-Bus equivalents:
|
||||||
|
|
||||||
|
Go type | D-Bus type
|
||||||
|
------------+-----------
|
||||||
|
byte | BYTE
|
||||||
|
bool | BOOLEAN
|
||||||
|
int16 | INT16
|
||||||
|
uint16 | UINT16
|
||||||
|
int32 | INT32
|
||||||
|
uint32 | UINT32
|
||||||
|
int64 | INT64
|
||||||
|
uint64 | UINT64
|
||||||
|
float64 | DOUBLE
|
||||||
|
string | STRING
|
||||||
|
ObjectPath | OBJECT_PATH
|
||||||
|
Signature | SIGNATURE
|
||||||
|
Variant | VARIANT
|
||||||
|
UnixFDIndex | UNIX_FD
|
||||||
|
|
||||||
|
Slices and arrays encode as ARRAYs of their element type.
|
||||||
|
|
||||||
|
Maps encode as DICTs, provided that their key type can be used as a key for
|
||||||
|
a DICT.
|
||||||
|
|
||||||
|
Structs other than Variant and Signature encode as a STRUCT containing their
|
||||||
|
exported fields. Fields whose tags contain `dbus:"-"` and unexported fields will
|
||||||
|
be skipped.
|
||||||
|
|
||||||
|
Pointers encode as the value they're pointed to.
|
||||||
|
|
||||||
|
Trying to encode any other type or a slice, map or struct containing an
|
||||||
|
unsupported type will result in an InvalidTypeError.
|
||||||
|
|
||||||
|
For incoming messages, the inverse of these rules are used, with the exception
|
||||||
|
of STRUCTs. Incoming STRUCTS are represented as a slice of empty interfaces
|
||||||
|
containing the struct fields in the correct order. The Store function can be
|
||||||
|
used to convert such values to Go structs.
|
||||||
|
|
||||||
|
Unix FD passing
|
||||||
|
|
||||||
|
Handling Unix file descriptors deserves special mention. To use them, you should
|
||||||
|
first check that they are supported on a connection by calling SupportsUnixFDs.
|
||||||
|
If it returns true, all method of Connection will translate messages containing
|
||||||
|
UnixFD's to messages that are accompanied by the given file descriptors with the
|
||||||
|
UnixFD values being substituted by the correct indices. Similarily, the indices
|
||||||
|
of incoming messages are automatically resolved. It shouldn't be necessary to use
|
||||||
|
UnixFDIndex.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package dbus
|
||||||
+208
@@ -0,0 +1,208 @@
|
|||||||
|
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()})
|
||||||
|
}
|
||||||
|
}
|
||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodeArrayOfMaps(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
vs []interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"aligned at 8 at start of array",
|
||||||
|
[]interface{}{
|
||||||
|
"12345",
|
||||||
|
[]map[string]Variant{
|
||||||
|
{
|
||||||
|
"abcdefg": MakeVariant("foo"),
|
||||||
|
"cdef": MakeVariant(uint32(2)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"not aligned at 8 for start of array",
|
||||||
|
[]interface{}{
|
||||||
|
"1234567890",
|
||||||
|
[]map[string]Variant{
|
||||||
|
{
|
||||||
|
"abcdefg": MakeVariant("foo"),
|
||||||
|
"cdef": MakeVariant(uint32(2)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, order := range []binary.ByteOrder{binary.LittleEndian, binary.BigEndian} {
|
||||||
|
for _, tt := range tests {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
enc := newEncoder(buf, order)
|
||||||
|
enc.Encode(tt.vs...)
|
||||||
|
|
||||||
|
dec := newDecoder(buf, order)
|
||||||
|
v, err := dec.Decode(SignatureOf(tt.vs...))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%q: decode (%v) failed: %v", tt.name, order, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(v, tt.vs) {
|
||||||
|
t.Errorf("%q: (%v) not equal: got '%v', want '%v'", tt.name, order, v, tt.vs)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func ExampleConn_Emit() {
|
||||||
|
conn, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Emit("/foo/bar", "foo.bar.Baz", uint32(0xDAEDBEEF))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleObject_Call() {
|
||||||
|
var list []string
|
||||||
|
|
||||||
|
conn, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&list)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, v := range list {
|
||||||
|
fmt.Println(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleObject_Go() {
|
||||||
|
conn, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan *Call, 10)
|
||||||
|
conn.BusObject().Go("org.freedesktop.DBus.ListActivatableNames", 0, ch)
|
||||||
|
select {
|
||||||
|
case call := <-ch:
|
||||||
|
if call.Err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
list := call.Body[0].([]string)
|
||||||
|
for _, v := range list {
|
||||||
|
fmt.Println(v)
|
||||||
|
}
|
||||||
|
// put some other cases here
|
||||||
|
}
|
||||||
|
}
|
||||||
+302
@@ -0,0 +1,302 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errmsgInvalidArg = Error{
|
||||||
|
"org.freedesktop.DBus.Error.InvalidArgs",
|
||||||
|
[]interface{}{"Invalid type / number of args"},
|
||||||
|
}
|
||||||
|
errmsgNoObject = Error{
|
||||||
|
"org.freedesktop.DBus.Error.NoSuchObject",
|
||||||
|
[]interface{}{"No such object"},
|
||||||
|
}
|
||||||
|
errmsgUnknownMethod = Error{
|
||||||
|
"org.freedesktop.DBus.Error.UnknownMethod",
|
||||||
|
[]interface{}{"Unknown / invalid method"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sender is a type which can be used in exported methods to receive the message
|
||||||
|
// sender.
|
||||||
|
type Sender string
|
||||||
|
|
||||||
|
func exportedMethod(v interface{}, name string) reflect.Value {
|
||||||
|
if v == nil {
|
||||||
|
return reflect.Value{}
|
||||||
|
}
|
||||||
|
m := reflect.ValueOf(v).MethodByName(name)
|
||||||
|
if !m.IsValid() {
|
||||||
|
return reflect.Value{}
|
||||||
|
}
|
||||||
|
t := m.Type()
|
||||||
|
if t.NumOut() == 0 ||
|
||||||
|
t.Out(t.NumOut()-1) != reflect.TypeOf(&errmsgInvalidArg) {
|
||||||
|
|
||||||
|
return reflect.Value{}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCall handles the given method call (i.e. looks if it's one of the
|
||||||
|
// pre-implemented ones and searches for a corresponding handler if not).
|
||||||
|
func (conn *Conn) handleCall(msg *Message) {
|
||||||
|
name := msg.Headers[FieldMember].value.(string)
|
||||||
|
path := msg.Headers[FieldPath].value.(ObjectPath)
|
||||||
|
ifaceName, hasIface := msg.Headers[FieldInterface].value.(string)
|
||||||
|
sender, hasSender := msg.Headers[FieldSender].value.(string)
|
||||||
|
serial := msg.serial
|
||||||
|
if ifaceName == "org.freedesktop.DBus.Peer" {
|
||||||
|
switch name {
|
||||||
|
case "Ping":
|
||||||
|
conn.sendReply(sender, serial)
|
||||||
|
case "GetMachineId":
|
||||||
|
conn.sendReply(sender, serial, conn.uuid)
|
||||||
|
default:
|
||||||
|
conn.sendError(errmsgUnknownMethod, sender, serial)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(name) == 0 || unicode.IsLower([]rune(name)[0]) {
|
||||||
|
conn.sendError(errmsgUnknownMethod, sender, serial)
|
||||||
|
}
|
||||||
|
var m reflect.Value
|
||||||
|
if hasIface {
|
||||||
|
conn.handlersLck.RLock()
|
||||||
|
obj, ok := conn.handlers[path]
|
||||||
|
if !ok {
|
||||||
|
conn.sendError(errmsgNoObject, sender, serial)
|
||||||
|
conn.handlersLck.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iface := obj[ifaceName]
|
||||||
|
conn.handlersLck.RUnlock()
|
||||||
|
m = exportedMethod(iface, name)
|
||||||
|
} else {
|
||||||
|
conn.handlersLck.RLock()
|
||||||
|
if _, ok := conn.handlers[path]; !ok {
|
||||||
|
conn.sendError(errmsgNoObject, sender, serial)
|
||||||
|
conn.handlersLck.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range conn.handlers[path] {
|
||||||
|
m = exportedMethod(v, name)
|
||||||
|
if m.IsValid() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.handlersLck.RUnlock()
|
||||||
|
}
|
||||||
|
if !m.IsValid() {
|
||||||
|
conn.sendError(errmsgUnknownMethod, sender, serial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t := m.Type()
|
||||||
|
vs := msg.Body
|
||||||
|
pointers := make([]interface{}, t.NumIn())
|
||||||
|
decode := make([]interface{}, 0, len(vs))
|
||||||
|
for i := 0; i < t.NumIn(); i++ {
|
||||||
|
tp := t.In(i)
|
||||||
|
val := reflect.New(tp)
|
||||||
|
pointers[i] = val.Interface()
|
||||||
|
if tp == reflect.TypeOf((*Sender)(nil)).Elem() {
|
||||||
|
val.Elem().SetString(sender)
|
||||||
|
} else {
|
||||||
|
decode = append(decode, pointers[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(decode) != len(vs) {
|
||||||
|
conn.sendError(errmsgInvalidArg, sender, serial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := Store(vs, decode...); err != nil {
|
||||||
|
conn.sendError(errmsgInvalidArg, sender, serial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
params := make([]reflect.Value, len(pointers))
|
||||||
|
for i := 0; i < len(pointers); i++ {
|
||||||
|
params[i] = reflect.ValueOf(pointers[i]).Elem()
|
||||||
|
}
|
||||||
|
ret := m.Call(params)
|
||||||
|
if em := ret[t.NumOut()-1].Interface().(*Error); em != nil {
|
||||||
|
conn.sendError(*em, sender, serial)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if msg.Flags&FlagNoReplyExpected == 0 {
|
||||||
|
reply := new(Message)
|
||||||
|
reply.Type = TypeMethodReply
|
||||||
|
reply.serial = conn.getSerial()
|
||||||
|
reply.Headers = make(map[HeaderField]Variant)
|
||||||
|
if hasSender {
|
||||||
|
reply.Headers[FieldDestination] = msg.Headers[FieldSender]
|
||||||
|
}
|
||||||
|
reply.Headers[FieldReplySerial] = MakeVariant(msg.serial)
|
||||||
|
reply.Body = make([]interface{}, len(ret)-1)
|
||||||
|
for i := 0; i < len(ret)-1; i++ {
|
||||||
|
reply.Body[i] = ret[i].Interface()
|
||||||
|
}
|
||||||
|
if len(ret) != 1 {
|
||||||
|
reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
|
||||||
|
}
|
||||||
|
conn.outLck.RLock()
|
||||||
|
if !conn.closed {
|
||||||
|
conn.out <- reply
|
||||||
|
}
|
||||||
|
conn.outLck.RUnlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit emits the given signal on the message bus. The name parameter must be
|
||||||
|
// formatted as "interface.member", e.g., "org.freedesktop.DBus.NameLost".
|
||||||
|
func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) error {
|
||||||
|
if !path.IsValid() {
|
||||||
|
return errors.New("dbus: invalid object path")
|
||||||
|
}
|
||||||
|
i := strings.LastIndex(name, ".")
|
||||||
|
if i == -1 {
|
||||||
|
return errors.New("dbus: invalid method name")
|
||||||
|
}
|
||||||
|
iface := name[:i]
|
||||||
|
member := name[i+1:]
|
||||||
|
if !isValidMember(member) {
|
||||||
|
return errors.New("dbus: invalid method name")
|
||||||
|
}
|
||||||
|
if !isValidInterface(iface) {
|
||||||
|
return errors.New("dbus: invalid interface name")
|
||||||
|
}
|
||||||
|
msg := new(Message)
|
||||||
|
msg.Type = TypeSignal
|
||||||
|
msg.serial = conn.getSerial()
|
||||||
|
msg.Headers = make(map[HeaderField]Variant)
|
||||||
|
msg.Headers[FieldInterface] = MakeVariant(iface)
|
||||||
|
msg.Headers[FieldMember] = MakeVariant(member)
|
||||||
|
msg.Headers[FieldPath] = MakeVariant(path)
|
||||||
|
msg.Body = values
|
||||||
|
if len(values) > 0 {
|
||||||
|
msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
|
||||||
|
}
|
||||||
|
conn.outLck.RLock()
|
||||||
|
defer conn.outLck.RUnlock()
|
||||||
|
if conn.closed {
|
||||||
|
return ErrClosed
|
||||||
|
}
|
||||||
|
conn.out <- msg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export registers the given value to be exported as an object on the
|
||||||
|
// message bus.
|
||||||
|
//
|
||||||
|
// If a method call on the given path and interface is received, an exported
|
||||||
|
// method with the same name is called with v as the receiver if the
|
||||||
|
// parameters match and the last return value is of type *Error. If this
|
||||||
|
// *Error is not nil, it is sent back to the caller as an error.
|
||||||
|
// Otherwise, a method reply is sent with the other return values as its body.
|
||||||
|
//
|
||||||
|
// Any parameters with the special type Sender are set to the sender of the
|
||||||
|
// dbus message when the method is called. Parameters of this type do not
|
||||||
|
// contribute to the dbus signature of the method (i.e. the method is exposed
|
||||||
|
// as if the parameters of type Sender were not there).
|
||||||
|
//
|
||||||
|
// Every method call is executed in a new goroutine, so the method may be called
|
||||||
|
// in multiple goroutines at once.
|
||||||
|
//
|
||||||
|
// Method calls on the interface org.freedesktop.DBus.Peer will be automatically
|
||||||
|
// handled for every object.
|
||||||
|
//
|
||||||
|
// Passing nil as the first parameter will cause conn to cease handling calls on
|
||||||
|
// the given combination of path and interface.
|
||||||
|
//
|
||||||
|
// Export returns an error if path is not a valid path name.
|
||||||
|
func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error {
|
||||||
|
if !path.IsValid() {
|
||||||
|
return errors.New("dbus: invalid path name")
|
||||||
|
}
|
||||||
|
conn.handlersLck.Lock()
|
||||||
|
if v == nil {
|
||||||
|
if _, ok := conn.handlers[path]; ok {
|
||||||
|
delete(conn.handlers[path], iface)
|
||||||
|
if len(conn.handlers[path]) == 0 {
|
||||||
|
delete(conn.handlers, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, ok := conn.handlers[path]; !ok {
|
||||||
|
conn.handlers[path] = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
conn.handlers[path][iface] = v
|
||||||
|
conn.handlersLck.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseName calls org.freedesktop.DBus.ReleaseName. You should use only this
|
||||||
|
// method to release a name (see below).
|
||||||
|
func (conn *Conn) ReleaseName(name string) (ReleaseNameReply, error) {
|
||||||
|
var r uint32
|
||||||
|
err := conn.busObj.Call("org.freedesktop.DBus.ReleaseName", 0, name).Store(&r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if r == uint32(ReleaseNameReplyReleased) {
|
||||||
|
conn.namesLck.Lock()
|
||||||
|
for i, v := range conn.names {
|
||||||
|
if v == name {
|
||||||
|
copy(conn.names[i:], conn.names[i+1:])
|
||||||
|
conn.names = conn.names[:len(conn.names)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conn.namesLck.Unlock()
|
||||||
|
}
|
||||||
|
return ReleaseNameReply(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestName calls org.freedesktop.DBus.RequestName. You should use only this
|
||||||
|
// method to request a name because package dbus needs to keep track of all
|
||||||
|
// names that the connection has.
|
||||||
|
func (conn *Conn) RequestName(name string, flags RequestNameFlags) (RequestNameReply, error) {
|
||||||
|
var r uint32
|
||||||
|
err := conn.busObj.Call("org.freedesktop.DBus.RequestName", 0, name, flags).Store(&r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if r == uint32(RequestNameReplyPrimaryOwner) {
|
||||||
|
conn.namesLck.Lock()
|
||||||
|
conn.names = append(conn.names, name)
|
||||||
|
conn.namesLck.Unlock()
|
||||||
|
}
|
||||||
|
return RequestNameReply(r), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseNameReply is the reply to a ReleaseName call.
|
||||||
|
type ReleaseNameReply uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReleaseNameReplyReleased ReleaseNameReply = 1 + iota
|
||||||
|
ReleaseNameReplyNonExistent
|
||||||
|
ReleaseNameReplyNotOwner
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestNameFlags represents the possible flags for a RequestName call.
|
||||||
|
type RequestNameFlags uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
NameFlagAllowReplacement RequestNameFlags = 1 << iota
|
||||||
|
NameFlagReplaceExisting
|
||||||
|
NameFlagDoNotQueue
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestNameReply is the reply to a RequestName call.
|
||||||
|
type RequestNameReply uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
RequestNameReplyPrimaryOwner RequestNameReply = 1 + iota
|
||||||
|
RequestNameReplyInQueue
|
||||||
|
RequestNameReplyExists
|
||||||
|
RequestNameReplyAlreadyOwner
|
||||||
|
)
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
homeDir string
|
||||||
|
homeDirLock sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
func getHomeDir() string {
|
||||||
|
homeDirLock.Lock()
|
||||||
|
defer homeDirLock.Unlock()
|
||||||
|
|
||||||
|
if homeDir != "" {
|
||||||
|
return homeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
homeDir = os.Getenv("HOME")
|
||||||
|
if homeDir != "" {
|
||||||
|
return homeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
homeDir = lookupHomeDir()
|
||||||
|
return homeDir
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
// +build !static_build
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupHomeDir() string {
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
return u.HomeDir
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
// +build static_build
|
||||||
|
|
||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupHomeDir() string {
|
||||||
|
myUid := os.Getuid()
|
||||||
|
|
||||||
|
f, err := os.Open("/etc/passwd")
|
||||||
|
if err != nil {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
s := bufio.NewScanner(f)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
line := strings.TrimSpace(s.Text())
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
|
||||||
|
if len(parts) >= 6 {
|
||||||
|
uid, err := strconv.Atoi(parts[2])
|
||||||
|
if err == nil && uid == myUid {
|
||||||
|
return parts[5]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to / if we can't get a better value
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package introspect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/godbus/dbus"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Call calls org.freedesktop.Introspectable.Introspect on a remote object
|
||||||
|
// and returns the introspection data.
|
||||||
|
func Call(o *dbus.Object) (*Node, error) {
|
||||||
|
var xmldata string
|
||||||
|
var node Node
|
||||||
|
|
||||||
|
err := o.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xmldata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = xml.NewDecoder(strings.NewReader(xmldata)).Decode(&node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if node.Name == "" {
|
||||||
|
node.Name = string(o.Path())
|
||||||
|
}
|
||||||
|
return &node, nil
|
||||||
|
}
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
// Package introspect provides some utilities for dealing with the DBus
|
||||||
|
// introspection format.
|
||||||
|
package introspect
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
// The introspection data for the org.freedesktop.DBus.Introspectable interface.
|
||||||
|
var IntrospectData = Interface{
|
||||||
|
Name: "org.freedesktop.DBus.Introspectable",
|
||||||
|
Methods: []Method{
|
||||||
|
{
|
||||||
|
Name: "Introspect",
|
||||||
|
Args: []Arg{
|
||||||
|
{"out", "s", "out"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// XML document type declaration of the introspection format version 1.0
|
||||||
|
const IntrospectDeclarationString = `
|
||||||
|
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||||
|
`
|
||||||
|
|
||||||
|
// The introspection data for the org.freedesktop.DBus.Introspectable interface,
|
||||||
|
// as a string.
|
||||||
|
const IntrospectDataString = `
|
||||||
|
<interface name="org.freedesktop.DBus.Introspectable">
|
||||||
|
<method name="Introspect">
|
||||||
|
<arg name="out" direction="out" type="s"/>
|
||||||
|
</method>
|
||||||
|
</interface>
|
||||||
|
`
|
||||||
|
|
||||||
|
// Node is the root element of an introspection.
|
||||||
|
type Node struct {
|
||||||
|
XMLName xml.Name `xml:"node"`
|
||||||
|
Name string `xml:"name,attr,omitempty"`
|
||||||
|
Interfaces []Interface `xml:"interface"`
|
||||||
|
Children []Node `xml:"node,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface describes a DBus interface that is available on the message bus.
|
||||||
|
type Interface struct {
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Methods []Method `xml:"method"`
|
||||||
|
Signals []Signal `xml:"signal"`
|
||||||
|
Properties []Property `xml:"property"`
|
||||||
|
Annotations []Annotation `xml:"annotation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method describes a Method on an Interface as retured by an introspection.
|
||||||
|
type Method struct {
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Args []Arg `xml:"arg"`
|
||||||
|
Annotations []Annotation `xml:"annotation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signal describes a Signal emitted on an Interface.
|
||||||
|
type Signal struct {
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Args []Arg `xml:"arg"`
|
||||||
|
Annotations []Annotation `xml:"annotation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Property describes a property of an Interface.
|
||||||
|
type Property struct {
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
Access string `xml:"access,attr"`
|
||||||
|
Annotations []Annotation `xml:"annotation"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arg represents an argument of a method or a signal.
|
||||||
|
type Arg struct {
|
||||||
|
Name string `xml:"name,attr,omitempty"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
Direction string `xml:"direction,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotation is an annotation in the introspection format.
|
||||||
|
type Annotation struct {
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
Value string `xml:"value,attr"`
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
package introspect
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"github.com/godbus/dbus"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Introspectable implements org.freedesktop.Introspectable.
|
||||||
|
//
|
||||||
|
// You can create it by converting the XML-formatted introspection data from a
|
||||||
|
// string to an Introspectable or call NewIntrospectable with a Node. Then,
|
||||||
|
// export it as org.freedesktop.Introspectable on you object.
|
||||||
|
type Introspectable string
|
||||||
|
|
||||||
|
// NewIntrospectable returns an Introspectable that returns the introspection
|
||||||
|
// data that corresponds to the given Node. If n.Interfaces doesn't contain the
|
||||||
|
// data for org.freedesktop.DBus.Introspectable, it is added automatically.
|
||||||
|
func NewIntrospectable(n *Node) Introspectable {
|
||||||
|
found := false
|
||||||
|
for _, v := range n.Interfaces {
|
||||||
|
if v.Name == "org.freedesktop.DBus.Introspectable" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
n.Interfaces = append(n.Interfaces, IntrospectData)
|
||||||
|
}
|
||||||
|
b, err := xml.Marshal(n)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return Introspectable(strings.TrimSpace(IntrospectDeclarationString) + string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Introspect implements org.freedesktop.Introspectable.Introspect.
|
||||||
|
func (i Introspectable) Introspect() (string, *dbus.Error) {
|
||||||
|
return string(i), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods returns the description of the methods of v. This can be used to
|
||||||
|
// create a Node which can be passed to NewIntrospectable.
|
||||||
|
func Methods(v interface{}) []Method {
|
||||||
|
t := reflect.TypeOf(v)
|
||||||
|
ms := make([]Method, 0, t.NumMethod())
|
||||||
|
for i := 0; i < t.NumMethod(); i++ {
|
||||||
|
if t.Method(i).PkgPath != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mt := t.Method(i).Type
|
||||||
|
if mt.NumOut() == 0 ||
|
||||||
|
mt.Out(mt.NumOut()-1) != reflect.TypeOf(&dbus.Error{}) {
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var m Method
|
||||||
|
m.Name = t.Method(i).Name
|
||||||
|
m.Args = make([]Arg, 0, mt.NumIn()+mt.NumOut()-2)
|
||||||
|
for j := 1; j < mt.NumIn(); j++ {
|
||||||
|
if mt.In(j) != reflect.TypeOf((*dbus.Sender)(nil)).Elem() {
|
||||||
|
arg := Arg{"", dbus.SignatureOfType(mt.In(j)).String(), "in"}
|
||||||
|
m.Args = append(m.Args, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for j := 0; j < mt.NumOut()-1; j++ {
|
||||||
|
arg := Arg{"", dbus.SignatureOfType(mt.Out(j)).String(), "out"}
|
||||||
|
m.Args = append(m.Args, arg)
|
||||||
|
}
|
||||||
|
m.Annotations = make([]Annotation, 0)
|
||||||
|
ms = append(ms, m)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
+346
@@ -0,0 +1,346 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const protoVersion byte = 1
|
||||||
|
|
||||||
|
// Flags represents the possible flags of a D-Bus message.
|
||||||
|
type Flags byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// FlagNoReplyExpected signals that the message is not expected to generate
|
||||||
|
// a reply. If this flag is set on outgoing messages, any possible reply
|
||||||
|
// will be discarded.
|
||||||
|
FlagNoReplyExpected Flags = 1 << iota
|
||||||
|
// FlagNoAutoStart signals that the message bus should not automatically
|
||||||
|
// start an application when handling this message.
|
||||||
|
FlagNoAutoStart
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type represents the possible types of a D-Bus message.
|
||||||
|
type Type byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeMethodCall Type = 1 + iota
|
||||||
|
TypeMethodReply
|
||||||
|
TypeError
|
||||||
|
TypeSignal
|
||||||
|
typeMax
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t Type) String() string {
|
||||||
|
switch t {
|
||||||
|
case TypeMethodCall:
|
||||||
|
return "method call"
|
||||||
|
case TypeMethodReply:
|
||||||
|
return "reply"
|
||||||
|
case TypeError:
|
||||||
|
return "error"
|
||||||
|
case TypeSignal:
|
||||||
|
return "signal"
|
||||||
|
}
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderField represents the possible byte codes for the headers
|
||||||
|
// of a D-Bus message.
|
||||||
|
type HeaderField byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
FieldPath HeaderField = 1 + iota
|
||||||
|
FieldInterface
|
||||||
|
FieldMember
|
||||||
|
FieldErrorName
|
||||||
|
FieldReplySerial
|
||||||
|
FieldDestination
|
||||||
|
FieldSender
|
||||||
|
FieldSignature
|
||||||
|
FieldUnixFDs
|
||||||
|
fieldMax
|
||||||
|
)
|
||||||
|
|
||||||
|
// An InvalidMessageError describes the reason why a D-Bus message is regarded as
|
||||||
|
// invalid.
|
||||||
|
type InvalidMessageError string
|
||||||
|
|
||||||
|
func (e InvalidMessageError) Error() string {
|
||||||
|
return "dbus: invalid message: " + string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldType are the types of the various header fields.
|
||||||
|
var fieldTypes = [fieldMax]reflect.Type{
|
||||||
|
FieldPath: objectPathType,
|
||||||
|
FieldInterface: stringType,
|
||||||
|
FieldMember: stringType,
|
||||||
|
FieldErrorName: stringType,
|
||||||
|
FieldReplySerial: uint32Type,
|
||||||
|
FieldDestination: stringType,
|
||||||
|
FieldSender: stringType,
|
||||||
|
FieldSignature: signatureType,
|
||||||
|
FieldUnixFDs: uint32Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// requiredFields lists the header fields that are required by the different
|
||||||
|
// message types.
|
||||||
|
var requiredFields = [typeMax][]HeaderField{
|
||||||
|
TypeMethodCall: {FieldPath, FieldMember},
|
||||||
|
TypeMethodReply: {FieldReplySerial},
|
||||||
|
TypeError: {FieldErrorName, FieldReplySerial},
|
||||||
|
TypeSignal: {FieldPath, FieldInterface, FieldMember},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message represents a single D-Bus message.
|
||||||
|
type Message struct {
|
||||||
|
Type
|
||||||
|
Flags
|
||||||
|
Headers map[HeaderField]Variant
|
||||||
|
Body []interface{}
|
||||||
|
|
||||||
|
serial uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type header struct {
|
||||||
|
Field byte
|
||||||
|
Variant
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMessage tries to decode a single message in the D-Bus wire format
|
||||||
|
// from the given reader. The byte order is figured out from the first byte.
|
||||||
|
// The possibly returned error can be an error of the underlying reader, an
|
||||||
|
// InvalidMessageError or a FormatError.
|
||||||
|
func DecodeMessage(rd io.Reader) (msg *Message, err error) {
|
||||||
|
var order binary.ByteOrder
|
||||||
|
var hlength, length uint32
|
||||||
|
var typ, flags, proto byte
|
||||||
|
var headers []header
|
||||||
|
|
||||||
|
b := make([]byte, 1)
|
||||||
|
_, err = rd.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch b[0] {
|
||||||
|
case 'l':
|
||||||
|
order = binary.LittleEndian
|
||||||
|
case 'B':
|
||||||
|
order = binary.BigEndian
|
||||||
|
default:
|
||||||
|
return nil, InvalidMessageError("invalid byte order")
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := newDecoder(rd, order)
|
||||||
|
dec.pos = 1
|
||||||
|
|
||||||
|
msg = new(Message)
|
||||||
|
vs, err := dec.Decode(Signature{"yyyuu"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = Store(vs, &typ, &flags, &proto, &length, &msg.serial); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg.Type = Type(typ)
|
||||||
|
msg.Flags = Flags(flags)
|
||||||
|
|
||||||
|
// get the header length separately because we need it later
|
||||||
|
b = make([]byte, 4)
|
||||||
|
_, err = io.ReadFull(rd, b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
binary.Read(bytes.NewBuffer(b), order, &hlength)
|
||||||
|
if hlength+length+16 > 1<<27 {
|
||||||
|
return nil, InvalidMessageError("message is too long")
|
||||||
|
}
|
||||||
|
dec = newDecoder(io.MultiReader(bytes.NewBuffer(b), rd), order)
|
||||||
|
dec.pos = 12
|
||||||
|
vs, err = dec.Decode(Signature{"a(yv)"})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err = Store(vs, &headers); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Headers = make(map[HeaderField]Variant)
|
||||||
|
for _, v := range headers {
|
||||||
|
msg.Headers[HeaderField(v.Field)] = v.Variant
|
||||||
|
}
|
||||||
|
|
||||||
|
dec.align(8)
|
||||||
|
body := make([]byte, int(length))
|
||||||
|
if length != 0 {
|
||||||
|
_, err := io.ReadFull(rd, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = msg.IsValid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sig, _ := msg.Headers[FieldSignature].value.(Signature)
|
||||||
|
if sig.str != "" {
|
||||||
|
buf := bytes.NewBuffer(body)
|
||||||
|
dec = newDecoder(buf, order)
|
||||||
|
vs, err := dec.Decode(sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msg.Body = vs
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeTo encodes and sends a message to the given writer. The byte order must
|
||||||
|
// be either binary.LittleEndian or binary.BigEndian. If the message is not
|
||||||
|
// valid or an error occurs when writing, an error is returned.
|
||||||
|
func (msg *Message) EncodeTo(out io.Writer, order binary.ByteOrder) error {
|
||||||
|
if err := msg.IsValid(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var vs [7]interface{}
|
||||||
|
switch order {
|
||||||
|
case binary.LittleEndian:
|
||||||
|
vs[0] = byte('l')
|
||||||
|
case binary.BigEndian:
|
||||||
|
vs[0] = byte('B')
|
||||||
|
default:
|
||||||
|
return errors.New("dbus: invalid byte order")
|
||||||
|
}
|
||||||
|
body := new(bytes.Buffer)
|
||||||
|
enc := newEncoder(body, order)
|
||||||
|
if len(msg.Body) != 0 {
|
||||||
|
enc.Encode(msg.Body...)
|
||||||
|
}
|
||||||
|
vs[1] = msg.Type
|
||||||
|
vs[2] = msg.Flags
|
||||||
|
vs[3] = protoVersion
|
||||||
|
vs[4] = uint32(len(body.Bytes()))
|
||||||
|
vs[5] = msg.serial
|
||||||
|
headers := make([]header, 0, len(msg.Headers))
|
||||||
|
for k, v := range msg.Headers {
|
||||||
|
headers = append(headers, header{byte(k), v})
|
||||||
|
}
|
||||||
|
vs[6] = headers
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc = newEncoder(&buf, order)
|
||||||
|
enc.Encode(vs[:]...)
|
||||||
|
enc.align(8)
|
||||||
|
body.WriteTo(&buf)
|
||||||
|
if buf.Len() > 1<<27 {
|
||||||
|
return InvalidMessageError("message is too long")
|
||||||
|
}
|
||||||
|
if _, err := buf.WriteTo(out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValid checks whether msg is a valid message and returns an
|
||||||
|
// InvalidMessageError if it is not.
|
||||||
|
func (msg *Message) IsValid() error {
|
||||||
|
if msg.Flags & ^(FlagNoAutoStart|FlagNoReplyExpected) != 0 {
|
||||||
|
return InvalidMessageError("invalid flags")
|
||||||
|
}
|
||||||
|
if msg.Type == 0 || msg.Type >= typeMax {
|
||||||
|
return InvalidMessageError("invalid message type")
|
||||||
|
}
|
||||||
|
for k, v := range msg.Headers {
|
||||||
|
if k == 0 || k >= fieldMax {
|
||||||
|
return InvalidMessageError("invalid header")
|
||||||
|
}
|
||||||
|
if reflect.TypeOf(v.value) != fieldTypes[k] {
|
||||||
|
return InvalidMessageError("invalid type of header field")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, v := range requiredFields[msg.Type] {
|
||||||
|
if _, ok := msg.Headers[v]; !ok {
|
||||||
|
return InvalidMessageError("missing required header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if path, ok := msg.Headers[FieldPath]; ok {
|
||||||
|
if !path.value.(ObjectPath).IsValid() {
|
||||||
|
return InvalidMessageError("invalid path name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iface, ok := msg.Headers[FieldInterface]; ok {
|
||||||
|
if !isValidInterface(iface.value.(string)) {
|
||||||
|
return InvalidMessageError("invalid interface name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if member, ok := msg.Headers[FieldMember]; ok {
|
||||||
|
if !isValidMember(member.value.(string)) {
|
||||||
|
return InvalidMessageError("invalid member name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errname, ok := msg.Headers[FieldErrorName]; ok {
|
||||||
|
if !isValidInterface(errname.value.(string)) {
|
||||||
|
return InvalidMessageError("invalid error name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(msg.Body) != 0 {
|
||||||
|
if _, ok := msg.Headers[FieldSignature]; !ok {
|
||||||
|
return InvalidMessageError("missing signature")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serial returns the message's serial number. The returned value is only valid
|
||||||
|
// for messages received by eavesdropping.
|
||||||
|
func (msg *Message) Serial() uint32 {
|
||||||
|
return msg.serial
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of a message similar to the format of
|
||||||
|
// dbus-monitor.
|
||||||
|
func (msg *Message) String() string {
|
||||||
|
if err := msg.IsValid(); err != nil {
|
||||||
|
return "<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
|
||||||
|
}
|
||||||
+264
@@ -0,0 +1,264 @@
|
|||||||
|
// Package prop provides the Properties struct which can be used to implement
|
||||||
|
// org.freedesktop.DBus.Properties.
|
||||||
|
package prop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/godbus/dbus"
|
||||||
|
"github.com/godbus/dbus/introspect"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmitType controls how org.freedesktop.DBus.Properties.PropertiesChanged is
|
||||||
|
// emitted for a property. If it is EmitTrue, the signal is emitted. If it is
|
||||||
|
// EmitInvalidates, the signal is also emitted, but the new value of the property
|
||||||
|
// is not disclosed.
|
||||||
|
type EmitType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
EmitFalse EmitType = iota
|
||||||
|
EmitTrue
|
||||||
|
EmitInvalidates
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrIfaceNotFound is the error returned to peers who try to access properties
|
||||||
|
// on interfaces that aren't found.
|
||||||
|
var ErrIfaceNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.InterfaceNotFound", nil)
|
||||||
|
|
||||||
|
// ErrPropNotFound is the error returned to peers trying to access properties
|
||||||
|
// that aren't found.
|
||||||
|
var ErrPropNotFound = dbus.NewError("org.freedesktop.DBus.Properties.Error.PropertyNotFound", nil)
|
||||||
|
|
||||||
|
// ErrReadOnly is the error returned to peers trying to set a read-only
|
||||||
|
// property.
|
||||||
|
var ErrReadOnly = dbus.NewError("org.freedesktop.DBus.Properties.Error.ReadOnly", nil)
|
||||||
|
|
||||||
|
// ErrInvalidArg is returned to peers if the type of the property that is being
|
||||||
|
// changed and the argument don't match.
|
||||||
|
var ErrInvalidArg = dbus.NewError("org.freedesktop.DBus.Properties.Error.InvalidArg", nil)
|
||||||
|
|
||||||
|
// The introspection data for the org.freedesktop.DBus.Properties interface.
|
||||||
|
var IntrospectData = introspect.Interface{
|
||||||
|
Name: "org.freedesktop.DBus.Properties",
|
||||||
|
Methods: []introspect.Method{
|
||||||
|
{
|
||||||
|
Name: "Get",
|
||||||
|
Args: []introspect.Arg{
|
||||||
|
{"interface", "s", "in"},
|
||||||
|
{"property", "s", "in"},
|
||||||
|
{"value", "v", "out"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "GetAll",
|
||||||
|
Args: []introspect.Arg{
|
||||||
|
{"interface", "s", "in"},
|
||||||
|
{"props", "a{sv}", "out"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Set",
|
||||||
|
Args: []introspect.Arg{
|
||||||
|
{"interface", "s", "in"},
|
||||||
|
{"property", "s", "in"},
|
||||||
|
{"value", "v", "in"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Signals: []introspect.Signal{
|
||||||
|
{
|
||||||
|
Name: "PropertiesChanged",
|
||||||
|
Args: []introspect.Arg{
|
||||||
|
{"interface", "s", "out"},
|
||||||
|
{"changed_properties", "a{sv}", "out"},
|
||||||
|
{"invalidates_properties", "as", "out"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// The introspection data for the org.freedesktop.DBus.Properties interface, as
|
||||||
|
// a string.
|
||||||
|
const IntrospectDataString = `
|
||||||
|
<interface name="org.freedesktop.DBus.Introspectable">
|
||||||
|
<method name="Get">
|
||||||
|
<arg name="interface" direction="in" type="s"/>
|
||||||
|
<arg name="property" direction="in" type="s"/>
|
||||||
|
<arg name="value" direction="out" type="v"/>
|
||||||
|
</method>
|
||||||
|
<method name="GetAll">
|
||||||
|
<arg name="interface" direction="in" type="s"/>
|
||||||
|
<arg name="props" direction="out" type="a{sv}"/>
|
||||||
|
</method>
|
||||||
|
<method name="Set">
|
||||||
|
<arg name="interface" direction="in" type="s"/>
|
||||||
|
<arg name="property" direction="in" type="s"/>
|
||||||
|
<arg name="value" direction="in" type="v"/>
|
||||||
|
</method>
|
||||||
|
<signal name="PropertiesChanged">
|
||||||
|
<arg name="interface" type="s"/>
|
||||||
|
<arg name="changed_properties" type="a{sv}"/>
|
||||||
|
<arg name="invalidates_properties" type="as"/>
|
||||||
|
</signal>
|
||||||
|
</interface>
|
||||||
|
`
|
||||||
|
|
||||||
|
// Prop represents a single property. It is used for creating a Properties
|
||||||
|
// value.
|
||||||
|
type Prop struct {
|
||||||
|
// Initial value. Must be a DBus-representable type.
|
||||||
|
Value interface{}
|
||||||
|
|
||||||
|
// If true, the value can be modified by calls to Set.
|
||||||
|
Writable bool
|
||||||
|
|
||||||
|
// Controls how org.freedesktop.DBus.Properties.PropertiesChanged is
|
||||||
|
// emitted if this property changes.
|
||||||
|
Emit EmitType
|
||||||
|
|
||||||
|
// If not nil, anytime this property is changed by Set, this function is
|
||||||
|
// called with an appropiate Change as its argument. If the returned error
|
||||||
|
// is not nil, it is sent back to the caller of Set and the property is not
|
||||||
|
// changed.
|
||||||
|
Callback func(*Change) *dbus.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change represents a change of a property by a call to Set.
|
||||||
|
type Change struct {
|
||||||
|
Props *Properties
|
||||||
|
Iface string
|
||||||
|
Name string
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Properties is a set of values that can be made available to the message bus
|
||||||
|
// using the org.freedesktop.DBus.Properties interface. It is safe for
|
||||||
|
// concurrent use by multiple goroutines.
|
||||||
|
type Properties struct {
|
||||||
|
m map[string]map[string]*Prop
|
||||||
|
mut sync.RWMutex
|
||||||
|
conn *dbus.Conn
|
||||||
|
path dbus.ObjectPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new Properties structure that manages the given properties.
|
||||||
|
// The key for the first-level map of props is the name of the interface; the
|
||||||
|
// second-level key is the name of the property. The returned structure will be
|
||||||
|
// exported as org.freedesktop.DBus.Properties on path.
|
||||||
|
func New(conn *dbus.Conn, path dbus.ObjectPath, props map[string]map[string]*Prop) *Properties {
|
||||||
|
p := &Properties{m: props, conn: conn, path: path}
|
||||||
|
conn.Export(p, path, "org.freedesktop.DBus.Properties")
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get implements org.freedesktop.DBus.Properties.Get.
|
||||||
|
func (p *Properties) Get(iface, property string) (dbus.Variant, *dbus.Error) {
|
||||||
|
p.mut.RLock()
|
||||||
|
defer p.mut.RUnlock()
|
||||||
|
m, ok := p.m[iface]
|
||||||
|
if !ok {
|
||||||
|
return dbus.Variant{}, ErrIfaceNotFound
|
||||||
|
}
|
||||||
|
prop, ok := m[property]
|
||||||
|
if !ok {
|
||||||
|
return dbus.Variant{}, ErrPropNotFound
|
||||||
|
}
|
||||||
|
return dbus.MakeVariant(prop.Value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll implements org.freedesktop.DBus.Properties.GetAll.
|
||||||
|
func (p *Properties) GetAll(iface string) (map[string]dbus.Variant, *dbus.Error) {
|
||||||
|
p.mut.RLock()
|
||||||
|
defer p.mut.RUnlock()
|
||||||
|
m, ok := p.m[iface]
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrIfaceNotFound
|
||||||
|
}
|
||||||
|
rm := make(map[string]dbus.Variant, len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
rm[k] = dbus.MakeVariant(v.Value)
|
||||||
|
}
|
||||||
|
return rm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMust returns the value of the given property and panics if either the
|
||||||
|
// interface or the property name are invalid.
|
||||||
|
func (p *Properties) GetMust(iface, property string) interface{} {
|
||||||
|
p.mut.RLock()
|
||||||
|
defer p.mut.RUnlock()
|
||||||
|
return p.m[iface][property].Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Introspection returns the introspection data that represents the properties
|
||||||
|
// of iface.
|
||||||
|
func (p *Properties) Introspection(iface string) []introspect.Property {
|
||||||
|
p.mut.RLock()
|
||||||
|
defer p.mut.RUnlock()
|
||||||
|
m := p.m[iface]
|
||||||
|
s := make([]introspect.Property, 0, len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
p := introspect.Property{Name: k, Type: dbus.SignatureOf(v.Value).String()}
|
||||||
|
if v.Writable {
|
||||||
|
p.Access = "readwrite"
|
||||||
|
} else {
|
||||||
|
p.Access = "read"
|
||||||
|
}
|
||||||
|
s = append(s, p)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// set sets the given property and emits PropertyChanged if appropiate. p.mut
|
||||||
|
// must already be locked.
|
||||||
|
func (p *Properties) set(iface, property string, v interface{}) {
|
||||||
|
prop := p.m[iface][property]
|
||||||
|
prop.Value = v
|
||||||
|
switch prop.Emit {
|
||||||
|
case EmitFalse:
|
||||||
|
// do nothing
|
||||||
|
case EmitInvalidates:
|
||||||
|
p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
|
||||||
|
iface, map[string]dbus.Variant{}, []string{property})
|
||||||
|
case EmitTrue:
|
||||||
|
p.conn.Emit(p.path, "org.freedesktop.DBus.Properties.PropertiesChanged",
|
||||||
|
iface, map[string]dbus.Variant{property: dbus.MakeVariant(v)},
|
||||||
|
[]string{})
|
||||||
|
default:
|
||||||
|
panic("invalid value for EmitType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set implements org.freedesktop.Properties.Set.
|
||||||
|
func (p *Properties) Set(iface, property string, newv dbus.Variant) *dbus.Error {
|
||||||
|
p.mut.Lock()
|
||||||
|
defer p.mut.Unlock()
|
||||||
|
m, ok := p.m[iface]
|
||||||
|
if !ok {
|
||||||
|
return ErrIfaceNotFound
|
||||||
|
}
|
||||||
|
prop, ok := m[property]
|
||||||
|
if !ok {
|
||||||
|
return ErrPropNotFound
|
||||||
|
}
|
||||||
|
if !prop.Writable {
|
||||||
|
return ErrReadOnly
|
||||||
|
}
|
||||||
|
if newv.Signature() != dbus.SignatureOf(prop.Value) {
|
||||||
|
return ErrInvalidArg
|
||||||
|
}
|
||||||
|
if prop.Callback != nil {
|
||||||
|
err := prop.Callback(&Change{p, iface, property, newv.Value()})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.set(iface, property, newv.Value())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMust sets the value of the given property and panics if the interface or
|
||||||
|
// the property name are invalid.
|
||||||
|
func (p *Properties) SetMust(iface, property string, v interface{}) {
|
||||||
|
p.mut.Lock()
|
||||||
|
p.set(iface, property, v)
|
||||||
|
p.mut.Unlock()
|
||||||
|
}
|
||||||
+369
@@ -0,0 +1,369 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var protoTests = []struct {
|
||||||
|
vs []interface{}
|
||||||
|
bigEndian []byte
|
||||||
|
littleEndian []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
[]interface{}{int32(0)},
|
||||||
|
[]byte{0, 0, 0, 0},
|
||||||
|
[]byte{0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{true, false},
|
||||||
|
[]byte{0, 0, 0, 1, 0, 0, 0, 0},
|
||||||
|
[]byte{1, 0, 0, 0, 0, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{byte(0), uint16(12), int16(32), uint32(43)},
|
||||||
|
[]byte{0, 0, 0, 12, 0, 32, 0, 0, 0, 0, 0, 43},
|
||||||
|
[]byte{0, 0, 12, 0, 32, 0, 0, 0, 43, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{int64(-1), uint64(1<<64 - 1)},
|
||||||
|
bytes.Repeat([]byte{255}, 16),
|
||||||
|
bytes.Repeat([]byte{255}, 16),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{math.Inf(+1)},
|
||||||
|
[]byte{0x7f, 0xf0, 0, 0, 0, 0, 0, 0},
|
||||||
|
[]byte{0, 0, 0, 0, 0, 0, 0xf0, 0x7f},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{"foo"},
|
||||||
|
[]byte{0, 0, 0, 3, 'f', 'o', 'o', 0},
|
||||||
|
[]byte{3, 0, 0, 0, 'f', 'o', 'o', 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{Signature{"ai"}},
|
||||||
|
[]byte{2, 'a', 'i', 0},
|
||||||
|
[]byte{2, 'a', 'i', 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{[]int16{42, 256}},
|
||||||
|
[]byte{0, 0, 0, 4, 0, 42, 1, 0},
|
||||||
|
[]byte{4, 0, 0, 0, 42, 0, 0, 1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{MakeVariant("foo")},
|
||||||
|
[]byte{1, 's', 0, 0, 0, 0, 0, 3, 'f', 'o', 'o', 0},
|
||||||
|
[]byte{1, 's', 0, 0, 3, 0, 0, 0, 'f', 'o', 'o', 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{MakeVariant(MakeVariant(Signature{"v"}))},
|
||||||
|
[]byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0},
|
||||||
|
[]byte{1, 'v', 0, 1, 'g', 0, 1, 'v', 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{map[int32]bool{42: true}},
|
||||||
|
[]byte{0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1},
|
||||||
|
[]byte{8, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 1, 0, 0, 0},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{map[string]Variant{}, byte(42)},
|
||||||
|
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
|
||||||
|
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{[]uint64{}, byte(42)},
|
||||||
|
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
|
||||||
|
[]byte{0, 0, 0, 0, 0, 0, 0, 0, 42},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProto(t *testing.T) {
|
||||||
|
for i, v := range protoTests {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
bigEnc := newEncoder(buf, binary.BigEndian)
|
||||||
|
bigEnc.Encode(v.vs...)
|
||||||
|
marshalled := buf.Bytes()
|
||||||
|
if bytes.Compare(marshalled, v.bigEndian) != 0 {
|
||||||
|
t.Errorf("test %d (marshal be): got '%v', but expected '%v'\n", i+1, marshalled,
|
||||||
|
v.bigEndian)
|
||||||
|
}
|
||||||
|
buf.Reset()
|
||||||
|
litEnc := newEncoder(buf, binary.LittleEndian)
|
||||||
|
litEnc.Encode(v.vs...)
|
||||||
|
marshalled = buf.Bytes()
|
||||||
|
if bytes.Compare(marshalled, v.littleEndian) != 0 {
|
||||||
|
t.Errorf("test %d (marshal le): got '%v', but expected '%v'\n", i+1, marshalled,
|
||||||
|
v.littleEndian)
|
||||||
|
}
|
||||||
|
unmarshalled := reflect.MakeSlice(reflect.TypeOf(v.vs),
|
||||||
|
0, 0)
|
||||||
|
for i := range v.vs {
|
||||||
|
unmarshalled = reflect.Append(unmarshalled,
|
||||||
|
reflect.New(reflect.TypeOf(v.vs[i])))
|
||||||
|
}
|
||||||
|
bigDec := newDecoder(bytes.NewReader(v.bigEndian), binary.BigEndian)
|
||||||
|
vs, err := bigDec.Decode(SignatureOf(v.vs...))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d (unmarshal be): %s\n", i+1, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(vs, v.vs) {
|
||||||
|
t.Errorf("test %d (unmarshal be): got %#v, but expected %#v\n", i+1, vs, v.vs)
|
||||||
|
}
|
||||||
|
litDec := newDecoder(bytes.NewReader(v.littleEndian), binary.LittleEndian)
|
||||||
|
vs, err = litDec.Decode(SignatureOf(v.vs...))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d (unmarshal le): %s\n", i+1, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(vs, v.vs) {
|
||||||
|
t.Errorf("test %d (unmarshal le): got %#v, but expected %#v\n", i+1, vs, v.vs)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProtoMap(t *testing.T) {
|
||||||
|
m := map[string]uint8{
|
||||||
|
"foo": 23,
|
||||||
|
"bar": 2,
|
||||||
|
}
|
||||||
|
var n map[string]uint8
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
enc := newEncoder(buf, binary.LittleEndian)
|
||||||
|
enc.Encode(m)
|
||||||
|
dec := newDecoder(buf, binary.LittleEndian)
|
||||||
|
vs, err := dec.Decode(Signature{"a{sy}"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = Store(vs, &n); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(n) != 2 || n["foo"] != 23 || n["bar"] != 2 {
|
||||||
|
t.Error("got", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProtoVariantStruct(t *testing.T) {
|
||||||
|
var variant Variant
|
||||||
|
v := MakeVariant(struct {
|
||||||
|
A int32
|
||||||
|
B int16
|
||||||
|
}{1, 2})
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
enc := newEncoder(buf, binary.LittleEndian)
|
||||||
|
enc.Encode(v)
|
||||||
|
dec := newDecoder(buf, binary.LittleEndian)
|
||||||
|
vs, err := dec.Decode(Signature{"v"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = Store(vs, &variant); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sl := variant.Value().([]interface{})
|
||||||
|
v1, v2 := sl[0].(int32), sl[1].(int16)
|
||||||
|
if v1 != int32(1) {
|
||||||
|
t.Error("got", v1, "as first int")
|
||||||
|
}
|
||||||
|
if v2 != int16(2) {
|
||||||
|
t.Error("got", v2, "as second int")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProtoStructTag(t *testing.T) {
|
||||||
|
type Bar struct {
|
||||||
|
A int32
|
||||||
|
B chan interface{} `dbus:"-"`
|
||||||
|
C int32
|
||||||
|
}
|
||||||
|
var bar1, bar2 Bar
|
||||||
|
bar1.A = 234
|
||||||
|
bar2.C = 345
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
enc := newEncoder(buf, binary.LittleEndian)
|
||||||
|
enc.Encode(bar1)
|
||||||
|
dec := newDecoder(buf, binary.LittleEndian)
|
||||||
|
vs, err := dec.Decode(Signature{"(ii)"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err = Store(vs, &bar2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if bar1 != bar2 {
|
||||||
|
t.Error("struct tag test: got", bar2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProtoStoreStruct(t *testing.T) {
|
||||||
|
var foo struct {
|
||||||
|
A int32
|
||||||
|
B string
|
||||||
|
c chan interface{}
|
||||||
|
D interface{} `dbus:"-"`
|
||||||
|
}
|
||||||
|
src := []interface{}{[]interface{}{int32(42), "foo"}}
|
||||||
|
err := Store(src, &foo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProtoStoreNestedStruct(t *testing.T) {
|
||||||
|
var foo struct {
|
||||||
|
A int32
|
||||||
|
B struct {
|
||||||
|
C string
|
||||||
|
D float64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src := []interface{}{
|
||||||
|
[]interface{}{
|
||||||
|
int32(42),
|
||||||
|
[]interface{}{
|
||||||
|
"foo",
|
||||||
|
3.14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := Store(src, &foo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMessage(t *testing.T) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
message := new(Message)
|
||||||
|
message.Type = TypeMethodCall
|
||||||
|
message.serial = 32
|
||||||
|
message.Headers = map[HeaderField]Variant{
|
||||||
|
FieldPath: MakeVariant(ObjectPath("/org/foo/bar")),
|
||||||
|
FieldMember: MakeVariant("baz"),
|
||||||
|
}
|
||||||
|
message.Body = make([]interface{}, 0)
|
||||||
|
err := message.EncodeTo(buf, binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = DecodeMessage(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProtoStructInterfaces(t *testing.T) {
|
||||||
|
b := []byte{42}
|
||||||
|
vs, err := newDecoder(bytes.NewReader(b), binary.LittleEndian).Decode(Signature{"(y)"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if vs[0].([]interface{})[0].(byte) != 42 {
|
||||||
|
t.Errorf("wrongs results (got %v)", vs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ordinary org.freedesktop.DBus.Hello call
|
||||||
|
var smallMessage = &Message{
|
||||||
|
Type: TypeMethodCall,
|
||||||
|
serial: 1,
|
||||||
|
Headers: map[HeaderField]Variant{
|
||||||
|
FieldDestination: MakeVariant("org.freedesktop.DBus"),
|
||||||
|
FieldPath: MakeVariant(ObjectPath("/org/freedesktop/DBus")),
|
||||||
|
FieldInterface: MakeVariant("org.freedesktop.DBus"),
|
||||||
|
FieldMember: MakeVariant("Hello"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// org.freedesktop.Notifications.Notify
|
||||||
|
var bigMessage = &Message{
|
||||||
|
Type: TypeMethodCall,
|
||||||
|
serial: 2,
|
||||||
|
Headers: map[HeaderField]Variant{
|
||||||
|
FieldDestination: MakeVariant("org.freedesktop.Notifications"),
|
||||||
|
FieldPath: MakeVariant(ObjectPath("/org/freedesktop/Notifications")),
|
||||||
|
FieldInterface: MakeVariant("org.freedesktop.Notifications"),
|
||||||
|
FieldMember: MakeVariant("Notify"),
|
||||||
|
FieldSignature: MakeVariant(Signature{"susssasa{sv}i"}),
|
||||||
|
},
|
||||||
|
Body: []interface{}{
|
||||||
|
"app_name",
|
||||||
|
uint32(0),
|
||||||
|
"dialog-information",
|
||||||
|
"Notification",
|
||||||
|
"This is the body of a notification",
|
||||||
|
[]string{"ok", "Ok"},
|
||||||
|
map[string]Variant{
|
||||||
|
"sound-name": MakeVariant("dialog-information"),
|
||||||
|
},
|
||||||
|
int32(-1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeMessageSmall(b *testing.B) {
|
||||||
|
var err error
|
||||||
|
var rd *bytes.Reader
|
||||||
|
|
||||||
|
b.StopTimer()
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = smallMessage.EncodeTo(buf, binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
decoded := buf.Bytes()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
rd = bytes.NewReader(decoded)
|
||||||
|
_, err = DecodeMessage(rd)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeMessageBig(b *testing.B) {
|
||||||
|
var err error
|
||||||
|
var rd *bytes.Reader
|
||||||
|
|
||||||
|
b.StopTimer()
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err = bigMessage.EncodeTo(buf, binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
decoded := buf.Bytes()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
rd = bytes.NewReader(decoded)
|
||||||
|
_, err = DecodeMessage(rd)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeMessageSmall(b *testing.B) {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err = smallMessage.EncodeTo(ioutil.Discard, binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEncodeMessageBig(b *testing.B) {
|
||||||
|
var err error
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err = bigMessage.EncodeTo(ioutil.Discard, binary.LittleEndian)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+257
@@ -0,0 +1,257 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sigToType = map[byte]reflect.Type{
|
||||||
|
'y': byteType,
|
||||||
|
'b': boolType,
|
||||||
|
'n': int16Type,
|
||||||
|
'q': uint16Type,
|
||||||
|
'i': int32Type,
|
||||||
|
'u': uint32Type,
|
||||||
|
'x': int64Type,
|
||||||
|
't': uint64Type,
|
||||||
|
'd': float64Type,
|
||||||
|
's': stringType,
|
||||||
|
'g': signatureType,
|
||||||
|
'o': objectPathType,
|
||||||
|
'v': variantType,
|
||||||
|
'h': unixFDIndexType,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature represents a correct type signature as specified by the D-Bus
|
||||||
|
// specification. The zero value represents the empty signature, "".
|
||||||
|
type Signature struct {
|
||||||
|
str string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureOf returns the concatenation of all the signatures of the given
|
||||||
|
// values. It panics if one of them is not representable in D-Bus.
|
||||||
|
func SignatureOf(vs ...interface{}) Signature {
|
||||||
|
var s string
|
||||||
|
for _, v := range vs {
|
||||||
|
s += getSignature(reflect.TypeOf(v))
|
||||||
|
}
|
||||||
|
return Signature{s}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignatureOfType returns the signature of the given type. It panics if the
|
||||||
|
// type is not representable in D-Bus.
|
||||||
|
func SignatureOfType(t reflect.Type) Signature {
|
||||||
|
return Signature{getSignature(t)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSignature returns the signature of the given type and panics on unknown types.
|
||||||
|
func getSignature(t reflect.Type) string {
|
||||||
|
// handle simple types first
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Uint8:
|
||||||
|
return "y"
|
||||||
|
case reflect.Bool:
|
||||||
|
return "b"
|
||||||
|
case reflect.Int16:
|
||||||
|
return "n"
|
||||||
|
case reflect.Uint16:
|
||||||
|
return "q"
|
||||||
|
case reflect.Int32:
|
||||||
|
if t == unixFDType {
|
||||||
|
return "h"
|
||||||
|
}
|
||||||
|
return "i"
|
||||||
|
case reflect.Uint32:
|
||||||
|
if t == unixFDIndexType {
|
||||||
|
return "h"
|
||||||
|
}
|
||||||
|
return "u"
|
||||||
|
case reflect.Int64:
|
||||||
|
return "x"
|
||||||
|
case reflect.Uint64:
|
||||||
|
return "t"
|
||||||
|
case reflect.Float64:
|
||||||
|
return "d"
|
||||||
|
case reflect.Ptr:
|
||||||
|
return getSignature(t.Elem())
|
||||||
|
case reflect.String:
|
||||||
|
if t == objectPathType {
|
||||||
|
return "o"
|
||||||
|
}
|
||||||
|
return "s"
|
||||||
|
case reflect.Struct:
|
||||||
|
if t == variantType {
|
||||||
|
return "v"
|
||||||
|
} else if t == signatureType {
|
||||||
|
return "g"
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
if field.PkgPath == "" && field.Tag.Get("dbus") != "-" {
|
||||||
|
s += getSignature(t.Field(i).Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "(" + s + ")"
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
return "a" + getSignature(t.Elem())
|
||||||
|
case reflect.Map:
|
||||||
|
if !isKeyType(t.Key()) {
|
||||||
|
panic(InvalidTypeError{t})
|
||||||
|
}
|
||||||
|
return "a{" + getSignature(t.Key()) + getSignature(t.Elem()) + "}"
|
||||||
|
}
|
||||||
|
panic(InvalidTypeError{t})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSignature returns the signature represented by this string, or a
|
||||||
|
// SignatureError if the string is not a valid signature.
|
||||||
|
func ParseSignature(s string) (sig Signature, err error) {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(s) > 255 {
|
||||||
|
return Signature{""}, SignatureError{s, "too long"}
|
||||||
|
}
|
||||||
|
sig.str = s
|
||||||
|
for err == nil && len(s) != 0 {
|
||||||
|
err, s = validSingle(s, 0)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
sig = Signature{""}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSignatureMust behaves like ParseSignature, except that it panics if s
|
||||||
|
// is not valid.
|
||||||
|
func ParseSignatureMust(s string) Signature {
|
||||||
|
sig, err := ParseSignature(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return sig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty retruns whether the signature is the empty signature.
|
||||||
|
func (s Signature) Empty() bool {
|
||||||
|
return s.str == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single returns whether the signature represents a single, complete type.
|
||||||
|
func (s Signature) Single() bool {
|
||||||
|
err, r := validSingle(s.str, 0)
|
||||||
|
return err != nil && r == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the signature's string representation.
|
||||||
|
func (s Signature) String() string {
|
||||||
|
return s.str
|
||||||
|
}
|
||||||
|
|
||||||
|
// A SignatureError indicates that a signature passed to a function or received
|
||||||
|
// on a connection is not a valid signature.
|
||||||
|
type SignatureError struct {
|
||||||
|
Sig string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e SignatureError) Error() string {
|
||||||
|
return fmt.Sprintf("dbus: invalid signature: %q (%s)", e.Sig, e.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to read a single type from this string. If it was successfull, err is nil
|
||||||
|
// and rem is the remaining unparsed part. Otherwise, err is a non-nil
|
||||||
|
// SignatureError and rem is "". depth is the current recursion depth which may
|
||||||
|
// not be greater than 64 and should be given as 0 on the first call.
|
||||||
|
func validSingle(s string, depth int) (err error, rem string) {
|
||||||
|
if s == "" {
|
||||||
|
return SignatureError{Sig: s, Reason: "empty signature"}, ""
|
||||||
|
}
|
||||||
|
if depth > 64 {
|
||||||
|
return SignatureError{Sig: s, Reason: "container nesting too deep"}, ""
|
||||||
|
}
|
||||||
|
switch s[0] {
|
||||||
|
case 'y', 'b', 'n', 'q', 'i', 'u', 'x', 't', 'd', 's', 'g', 'o', 'v', 'h':
|
||||||
|
return nil, s[1:]
|
||||||
|
case 'a':
|
||||||
|
if len(s) > 1 && s[1] == '{' {
|
||||||
|
i := findMatching(s[1:], '{', '}')
|
||||||
|
if i == -1 {
|
||||||
|
return SignatureError{Sig: s, Reason: "unmatched '{'"}, ""
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
rem = s[i+1:]
|
||||||
|
s = s[2:i]
|
||||||
|
if err, _ = validSingle(s[:1], depth+1); err != nil {
|
||||||
|
return err, ""
|
||||||
|
}
|
||||||
|
err, nr := validSingle(s[1:], depth+1)
|
||||||
|
if err != nil {
|
||||||
|
return err, ""
|
||||||
|
}
|
||||||
|
if nr != "" {
|
||||||
|
return SignatureError{Sig: s, Reason: "too many types in dict"}, ""
|
||||||
|
}
|
||||||
|
return nil, rem
|
||||||
|
}
|
||||||
|
return validSingle(s[1:], depth+1)
|
||||||
|
case '(':
|
||||||
|
i := findMatching(s, '(', ')')
|
||||||
|
if i == -1 {
|
||||||
|
return SignatureError{Sig: s, Reason: "unmatched ')'"}, ""
|
||||||
|
}
|
||||||
|
rem = s[i+1:]
|
||||||
|
s = s[1:i]
|
||||||
|
for err == nil && s != "" {
|
||||||
|
err, s = validSingle(s, depth+1)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
rem = ""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return SignatureError{Sig: s, Reason: "invalid type character"}, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func findMatching(s string, left, right rune) int {
|
||||||
|
n := 0
|
||||||
|
for i, v := range s {
|
||||||
|
if v == left {
|
||||||
|
n++
|
||||||
|
} else if v == right {
|
||||||
|
n--
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeFor returns the type of the given signature. It ignores any left over
|
||||||
|
// characters and panics if s doesn't start with a valid type signature.
|
||||||
|
func typeFor(s string) (t reflect.Type) {
|
||||||
|
err, _ := validSingle(s, 0)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := sigToType[s[0]]; ok {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
switch s[0] {
|
||||||
|
case 'a':
|
||||||
|
if s[1] == '{' {
|
||||||
|
i := strings.LastIndex(s, "}")
|
||||||
|
t = reflect.MapOf(sigToType[s[2]], typeFor(s[3:i]))
|
||||||
|
} else {
|
||||||
|
t = reflect.SliceOf(typeFor(s[1:]))
|
||||||
|
}
|
||||||
|
case '(':
|
||||||
|
t = interfacesType
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var sigTests = []struct {
|
||||||
|
vs []interface{}
|
||||||
|
sig Signature
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
[]interface{}{new(int32)},
|
||||||
|
Signature{"i"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{new(string)},
|
||||||
|
Signature{"s"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{new(Signature)},
|
||||||
|
Signature{"g"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{new([]int16)},
|
||||||
|
Signature{"an"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{new(int16), new(uint32)},
|
||||||
|
Signature{"nu"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{new(map[byte]Variant)},
|
||||||
|
Signature{"a{yv}"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]interface{}{new(Variant), new([]map[int32]string)},
|
||||||
|
Signature{"vaa{is}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSig(t *testing.T) {
|
||||||
|
for i, v := range sigTests {
|
||||||
|
sig := SignatureOf(v.vs...)
|
||||||
|
if sig != v.sig {
|
||||||
|
t.Errorf("test %d: got %q, expected %q", i+1, sig.str, v.sig.str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var getSigTest = []interface{}{
|
||||||
|
[]struct {
|
||||||
|
b byte
|
||||||
|
i int32
|
||||||
|
t uint64
|
||||||
|
s string
|
||||||
|
}{},
|
||||||
|
map[string]Variant{},
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetSignatureSimple(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
SignatureOf("", int32(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetSignatureLong(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
SignatureOf(getSigTest...)
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
func (t *unixTransport) SendNullByte() error {
|
||||||
|
_, err := t.Write([]byte{0})
|
||||||
|
return err
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type genericTransport struct {
|
||||||
|
io.ReadWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t genericTransport) SendNullByte() error {
|
||||||
|
_, err := t.Write([]byte{0})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t genericTransport) SupportsUnixFDs() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t genericTransport) EnableUnixFDs() {}
|
||||||
|
|
||||||
|
func (t genericTransport) ReadMessage() (*Message, error) {
|
||||||
|
return DecodeMessage(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t genericTransport) SendMessage(msg *Message) error {
|
||||||
|
for _, v := range msg.Body {
|
||||||
|
if _, ok := v.(UnixFD); ok {
|
||||||
|
return errors.New("dbus: unix fd passing not enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msg.EncodeTo(t, binary.LittleEndian)
|
||||||
|
}
|
||||||
+196
@@ -0,0 +1,196 @@
|
|||||||
|
//+build !windows
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const testString = `This is a test!
|
||||||
|
This text should be read from the file that is created by this test.`
|
||||||
|
|
||||||
|
type unixFDTest struct{}
|
||||||
|
|
||||||
|
func (t unixFDTest) Test(fd UnixFD) (string, *Error) {
|
||||||
|
var b [4096]byte
|
||||||
|
file := os.NewFile(uintptr(fd), "testfile")
|
||||||
|
defer file.Close()
|
||||||
|
n, err := file.Read(b[:])
|
||||||
|
if err != nil {
|
||||||
|
return "", &Error{"com.github.guelfey.test.Error", nil}
|
||||||
|
}
|
||||||
|
return string(b[:n]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnixFDs(t *testing.T) {
|
||||||
|
conn, err := SessionBus()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
r, w, err := os.Pipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
if _, err := w.Write([]byte(testString)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
name := conn.Names()[0]
|
||||||
|
test := unixFDTest{}
|
||||||
|
conn.Export(test, "/com/github/guelfey/test", "com.github.guelfey.test")
|
||||||
|
var s string
|
||||||
|
obj := conn.Object(name, "/com/github/guelfey/test")
|
||||||
|
err = obj.Call("com.github.guelfey.test.Test", 0, UnixFD(r.Fd())).Store(&s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if s != testString {
|
||||||
|
t.Fatal("got", s, "wanted", testString)
|
||||||
|
}
|
||||||
|
}
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
+284
@@ -0,0 +1,284 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Heavily inspired by the lexer from text/template.
|
||||||
|
|
||||||
|
type varToken struct {
|
||||||
|
typ varTokenType
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
type varTokenType byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
tokEOF varTokenType = iota
|
||||||
|
tokError
|
||||||
|
tokNumber
|
||||||
|
tokString
|
||||||
|
tokBool
|
||||||
|
tokArrayStart
|
||||||
|
tokArrayEnd
|
||||||
|
tokDictStart
|
||||||
|
tokDictEnd
|
||||||
|
tokVariantStart
|
||||||
|
tokVariantEnd
|
||||||
|
tokComma
|
||||||
|
tokColon
|
||||||
|
tokType
|
||||||
|
tokByteString
|
||||||
|
)
|
||||||
|
|
||||||
|
type varLexer struct {
|
||||||
|
input string
|
||||||
|
start int
|
||||||
|
pos int
|
||||||
|
width int
|
||||||
|
tokens []varToken
|
||||||
|
}
|
||||||
|
|
||||||
|
type lexState func(*varLexer) lexState
|
||||||
|
|
||||||
|
func varLex(s string) []varToken {
|
||||||
|
l := &varLexer{input: s}
|
||||||
|
l.run()
|
||||||
|
return l.tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) accept(valid string) bool {
|
||||||
|
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) backup() {
|
||||||
|
l.pos -= l.width
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) emit(t varTokenType) {
|
||||||
|
l.tokens = append(l.tokens, varToken{t, l.input[l.start:l.pos]})
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) errorf(format string, v ...interface{}) lexState {
|
||||||
|
l.tokens = append(l.tokens, varToken{
|
||||||
|
tokError,
|
||||||
|
fmt.Sprintf(format, v...),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) ignore() {
|
||||||
|
l.start = l.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) next() rune {
|
||||||
|
var r rune
|
||||||
|
|
||||||
|
if l.pos >= len(l.input) {
|
||||||
|
l.width = 0
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
|
||||||
|
l.pos += l.width
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) run() {
|
||||||
|
for state := varLexNormal; state != nil; {
|
||||||
|
state = state(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *varLexer) peek() rune {
|
||||||
|
r := l.next()
|
||||||
|
l.backup()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexNormal(l *varLexer) lexState {
|
||||||
|
for {
|
||||||
|
r := l.next()
|
||||||
|
switch {
|
||||||
|
case r == -1:
|
||||||
|
l.emit(tokEOF)
|
||||||
|
return nil
|
||||||
|
case r == '[':
|
||||||
|
l.emit(tokArrayStart)
|
||||||
|
case r == ']':
|
||||||
|
l.emit(tokArrayEnd)
|
||||||
|
case r == '{':
|
||||||
|
l.emit(tokDictStart)
|
||||||
|
case r == '}':
|
||||||
|
l.emit(tokDictEnd)
|
||||||
|
case r == '<':
|
||||||
|
l.emit(tokVariantStart)
|
||||||
|
case r == '>':
|
||||||
|
l.emit(tokVariantEnd)
|
||||||
|
case r == ':':
|
||||||
|
l.emit(tokColon)
|
||||||
|
case r == ',':
|
||||||
|
l.emit(tokComma)
|
||||||
|
case r == '\'' || r == '"':
|
||||||
|
l.backup()
|
||||||
|
return varLexString
|
||||||
|
case r == '@':
|
||||||
|
l.backup()
|
||||||
|
return varLexType
|
||||||
|
case unicode.IsSpace(r):
|
||||||
|
l.ignore()
|
||||||
|
case unicode.IsNumber(r) || r == '+' || r == '-':
|
||||||
|
l.backup()
|
||||||
|
return varLexNumber
|
||||||
|
case r == 'b':
|
||||||
|
pos := l.start
|
||||||
|
if n := l.peek(); n == '"' || n == '\'' {
|
||||||
|
return varLexByteString
|
||||||
|
}
|
||||||
|
// not a byte string; try to parse it as a type or bool below
|
||||||
|
l.pos = pos + 1
|
||||||
|
l.width = 1
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
// either a bool or a type. Try bools first.
|
||||||
|
l.backup()
|
||||||
|
if l.pos+4 <= len(l.input) {
|
||||||
|
if l.input[l.pos:l.pos+4] == "true" {
|
||||||
|
l.pos += 4
|
||||||
|
l.emit(tokBool)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if l.pos+5 <= len(l.input) {
|
||||||
|
if l.input[l.pos:l.pos+5] == "false" {
|
||||||
|
l.pos += 5
|
||||||
|
l.emit(tokBool)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// must be a type.
|
||||||
|
return varLexType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var varTypeMap = map[string]string{
|
||||||
|
"boolean": "b",
|
||||||
|
"byte": "y",
|
||||||
|
"int16": "n",
|
||||||
|
"uint16": "q",
|
||||||
|
"int32": "i",
|
||||||
|
"uint32": "u",
|
||||||
|
"int64": "x",
|
||||||
|
"uint64": "t",
|
||||||
|
"double": "f",
|
||||||
|
"string": "s",
|
||||||
|
"objectpath": "o",
|
||||||
|
"signature": "g",
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexByteString(l *varLexer) lexState {
|
||||||
|
q := l.next()
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case -1:
|
||||||
|
return l.errorf("unterminated bytestring")
|
||||||
|
case q:
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(tokByteString)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexNumber(l *varLexer) lexState {
|
||||||
|
l.accept("+-")
|
||||||
|
digits := "0123456789"
|
||||||
|
if l.accept("0") {
|
||||||
|
if l.accept("x") {
|
||||||
|
digits = "0123456789abcdefABCDEF"
|
||||||
|
} else {
|
||||||
|
digits = "01234567"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for strings.IndexRune(digits, l.next()) >= 0 {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
if l.accept(".") {
|
||||||
|
for strings.IndexRune(digits, l.next()) >= 0 {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
if l.accept("eE") {
|
||||||
|
l.accept("+-")
|
||||||
|
for strings.IndexRune("0123456789", l.next()) >= 0 {
|
||||||
|
}
|
||||||
|
l.backup()
|
||||||
|
}
|
||||||
|
if r := l.peek(); unicode.IsLetter(r) {
|
||||||
|
l.next()
|
||||||
|
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
l.emit(tokNumber)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexString(l *varLexer) lexState {
|
||||||
|
q := l.next()
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
switch l.next() {
|
||||||
|
case '\\':
|
||||||
|
if r := l.next(); r != -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
case -1:
|
||||||
|
return l.errorf("unterminated string")
|
||||||
|
case q:
|
||||||
|
break Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.emit(tokString)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
|
|
||||||
|
func varLexType(l *varLexer) lexState {
|
||||||
|
at := l.accept("@")
|
||||||
|
for {
|
||||||
|
r := l.next()
|
||||||
|
if r == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if unicode.IsSpace(r) {
|
||||||
|
l.backup()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if at {
|
||||||
|
if _, err := ParseSignature(l.input[l.start+1 : l.pos]); err != nil {
|
||||||
|
return l.errorf("%s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := varTypeMap[l.input[l.start:l.pos]]; ok {
|
||||||
|
l.emit(tokType)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
|
return l.errorf("unrecognized type %q", l.input[l.start:l.pos])
|
||||||
|
}
|
||||||
|
l.emit(tokType)
|
||||||
|
return varLexNormal
|
||||||
|
}
|
||||||
+817
@@ -0,0 +1,817 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type varParser struct {
|
||||||
|
tokens []varToken
|
||||||
|
i int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *varParser) backup() {
|
||||||
|
p.i--
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *varParser) next() varToken {
|
||||||
|
if p.i < len(p.tokens) {
|
||||||
|
t := p.tokens[p.i]
|
||||||
|
p.i++
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return varToken{typ: tokEOF}
|
||||||
|
}
|
||||||
|
|
||||||
|
type varNode interface {
|
||||||
|
Infer() (Signature, error)
|
||||||
|
String() string
|
||||||
|
Sigs() sigSet
|
||||||
|
Value(Signature) (interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeNode(p *varParser) (varNode, error) {
|
||||||
|
var sig Signature
|
||||||
|
|
||||||
|
for {
|
||||||
|
t := p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
case tokNumber:
|
||||||
|
return varMakeNumNode(t, sig)
|
||||||
|
case tokString:
|
||||||
|
return varMakeStringNode(t, sig)
|
||||||
|
case tokBool:
|
||||||
|
if sig.str != "" && sig.str != "b" {
|
||||||
|
return nil, varTypeError{t.val, sig}
|
||||||
|
}
|
||||||
|
b, err := strconv.ParseBool(t.val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return boolNode(b), nil
|
||||||
|
case tokArrayStart:
|
||||||
|
return varMakeArrayNode(p, sig)
|
||||||
|
case tokVariantStart:
|
||||||
|
return varMakeVariantNode(p, sig)
|
||||||
|
case tokDictStart:
|
||||||
|
return varMakeDictNode(p, sig)
|
||||||
|
case tokType:
|
||||||
|
if sig.str != "" {
|
||||||
|
return nil, errors.New("unexpected type annotation")
|
||||||
|
}
|
||||||
|
if t.val[0] == '@' {
|
||||||
|
sig.str = t.val[1:]
|
||||||
|
} else {
|
||||||
|
sig.str = varTypeMap[t.val]
|
||||||
|
}
|
||||||
|
case tokByteString:
|
||||||
|
if sig.str != "" && sig.str != "ay" {
|
||||||
|
return nil, varTypeError{t.val, sig}
|
||||||
|
}
|
||||||
|
b, err := varParseByteString(t.val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return byteStringNode(b), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type varTypeError struct {
|
||||||
|
val string
|
||||||
|
sig Signature
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e varTypeError) Error() string {
|
||||||
|
return fmt.Sprintf("dbus: can't parse %q as type %q", e.val, e.sig.str)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sigSet map[Signature]bool
|
||||||
|
|
||||||
|
func (s sigSet) Empty() bool {
|
||||||
|
return len(s) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sigSet) Intersect(s2 sigSet) sigSet {
|
||||||
|
r := make(sigSet)
|
||||||
|
for k := range s {
|
||||||
|
if s2[k] {
|
||||||
|
r[k] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sigSet) Single() (Signature, bool) {
|
||||||
|
if len(s) == 1 {
|
||||||
|
for k := range s {
|
||||||
|
return k, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Signature{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sigSet) ToArray() sigSet {
|
||||||
|
r := make(sigSet, len(s))
|
||||||
|
for k := range s {
|
||||||
|
r[Signature{"a" + k.str}] = true
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type numNode struct {
|
||||||
|
sig Signature
|
||||||
|
str string
|
||||||
|
val interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var numSigSet = sigSet{
|
||||||
|
Signature{"y"}: true,
|
||||||
|
Signature{"n"}: true,
|
||||||
|
Signature{"q"}: true,
|
||||||
|
Signature{"i"}: true,
|
||||||
|
Signature{"u"}: true,
|
||||||
|
Signature{"x"}: true,
|
||||||
|
Signature{"t"}: true,
|
||||||
|
Signature{"d"}: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n numNode) Infer() (Signature, error) {
|
||||||
|
if strings.ContainsAny(n.str, ".e") {
|
||||||
|
return Signature{"d"}, nil
|
||||||
|
}
|
||||||
|
return Signature{"i"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n numNode) String() string {
|
||||||
|
return n.str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n numNode) Sigs() sigSet {
|
||||||
|
if n.sig.str != "" {
|
||||||
|
return sigSet{n.sig: true}
|
||||||
|
}
|
||||||
|
if strings.ContainsAny(n.str, ".e") {
|
||||||
|
return sigSet{Signature{"d"}: true}
|
||||||
|
}
|
||||||
|
return numSigSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n numNode) Value(sig Signature) (interface{}, error) {
|
||||||
|
if n.sig.str != "" && n.sig != sig {
|
||||||
|
return nil, varTypeError{n.str, sig}
|
||||||
|
}
|
||||||
|
if n.val != nil {
|
||||||
|
return n.val, nil
|
||||||
|
}
|
||||||
|
return varNumAs(n.str, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeNumNode(tok varToken, sig Signature) (varNode, error) {
|
||||||
|
if sig.str == "" {
|
||||||
|
return numNode{str: tok.val}, nil
|
||||||
|
}
|
||||||
|
num, err := varNumAs(tok.val, sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return numNode{sig: sig, val: num}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varNumAs(s string, sig Signature) (interface{}, error) {
|
||||||
|
isUnsigned := false
|
||||||
|
size := 32
|
||||||
|
switch sig.str {
|
||||||
|
case "n":
|
||||||
|
size = 16
|
||||||
|
case "i":
|
||||||
|
case "x":
|
||||||
|
size = 64
|
||||||
|
case "y":
|
||||||
|
size = 8
|
||||||
|
isUnsigned = true
|
||||||
|
case "q":
|
||||||
|
size = 16
|
||||||
|
isUnsigned = true
|
||||||
|
case "u":
|
||||||
|
isUnsigned = true
|
||||||
|
case "t":
|
||||||
|
size = 64
|
||||||
|
isUnsigned = true
|
||||||
|
case "d":
|
||||||
|
d, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
|
default:
|
||||||
|
return nil, varTypeError{s, sig}
|
||||||
|
}
|
||||||
|
base := 10
|
||||||
|
if strings.HasPrefix(s, "0x") {
|
||||||
|
base = 16
|
||||||
|
s = s[2:]
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(s, "0") && len(s) != 1 {
|
||||||
|
base = 8
|
||||||
|
s = s[1:]
|
||||||
|
}
|
||||||
|
if isUnsigned {
|
||||||
|
i, err := strconv.ParseUint(s, base, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var v interface{} = i
|
||||||
|
switch sig.str {
|
||||||
|
case "y":
|
||||||
|
v = byte(i)
|
||||||
|
case "q":
|
||||||
|
v = uint16(i)
|
||||||
|
case "u":
|
||||||
|
v = uint32(i)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
i, err := strconv.ParseInt(s, base, size)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var v interface{} = i
|
||||||
|
switch sig.str {
|
||||||
|
case "n":
|
||||||
|
v = int16(i)
|
||||||
|
case "i":
|
||||||
|
v = int32(i)
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringNode struct {
|
||||||
|
sig Signature
|
||||||
|
str string // parsed
|
||||||
|
val interface{} // has correct type
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringSigSet = sigSet{
|
||||||
|
Signature{"s"}: true,
|
||||||
|
Signature{"g"}: true,
|
||||||
|
Signature{"o"}: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stringNode) Infer() (Signature, error) {
|
||||||
|
return Signature{"s"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stringNode) String() string {
|
||||||
|
return n.str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stringNode) Sigs() sigSet {
|
||||||
|
if n.sig.str != "" {
|
||||||
|
return sigSet{n.sig: true}
|
||||||
|
}
|
||||||
|
return stringSigSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n stringNode) Value(sig Signature) (interface{}, error) {
|
||||||
|
if n.sig.str != "" && n.sig != sig {
|
||||||
|
return nil, varTypeError{n.str, sig}
|
||||||
|
}
|
||||||
|
if n.val != nil {
|
||||||
|
return n.val, nil
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case sig.str == "g":
|
||||||
|
return Signature{n.str}, nil
|
||||||
|
case sig.str == "o":
|
||||||
|
return ObjectPath(n.str), nil
|
||||||
|
case sig.str == "s":
|
||||||
|
return n.str, nil
|
||||||
|
default:
|
||||||
|
return nil, varTypeError{n.str, sig}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeStringNode(tok varToken, sig Signature) (varNode, error) {
|
||||||
|
if sig.str != "" && sig.str != "s" && sig.str != "g" && sig.str != "o" {
|
||||||
|
return nil, fmt.Errorf("invalid type %q for string", sig.str)
|
||||||
|
}
|
||||||
|
s, err := varParseString(tok.val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n := stringNode{str: s}
|
||||||
|
if sig.str == "" {
|
||||||
|
return stringNode{str: s}, nil
|
||||||
|
}
|
||||||
|
n.sig = sig
|
||||||
|
switch sig.str {
|
||||||
|
case "o":
|
||||||
|
n.val = ObjectPath(s)
|
||||||
|
case "g":
|
||||||
|
n.val = Signature{s}
|
||||||
|
case "s":
|
||||||
|
n.val = s
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varParseString(s string) (string, error) {
|
||||||
|
// quotes are guaranteed to be there
|
||||||
|
s = s[1 : len(s)-1]
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
for len(s) != 0 {
|
||||||
|
r, size := utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && size == 1 {
|
||||||
|
return "", errors.New("invalid UTF-8")
|
||||||
|
}
|
||||||
|
s = s[size:]
|
||||||
|
if r != '\\' {
|
||||||
|
buf.WriteRune(r)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r, size = utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && size == 1 {
|
||||||
|
return "", errors.New("invalid UTF-8")
|
||||||
|
}
|
||||||
|
s = s[size:]
|
||||||
|
switch r {
|
||||||
|
case 'a':
|
||||||
|
buf.WriteRune(0x7)
|
||||||
|
case 'b':
|
||||||
|
buf.WriteRune(0x8)
|
||||||
|
case 'f':
|
||||||
|
buf.WriteRune(0xc)
|
||||||
|
case 'n':
|
||||||
|
buf.WriteRune('\n')
|
||||||
|
case 'r':
|
||||||
|
buf.WriteRune('\r')
|
||||||
|
case 't':
|
||||||
|
buf.WriteRune('\t')
|
||||||
|
case '\n':
|
||||||
|
case 'u':
|
||||||
|
if len(s) < 4 {
|
||||||
|
return "", errors.New("short unicode escape")
|
||||||
|
}
|
||||||
|
r, err := strconv.ParseUint(s[:4], 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buf.WriteRune(rune(r))
|
||||||
|
s = s[4:]
|
||||||
|
case 'U':
|
||||||
|
if len(s) < 8 {
|
||||||
|
return "", errors.New("short unicode escape")
|
||||||
|
}
|
||||||
|
r, err := strconv.ParseUint(s[:8], 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buf.WriteRune(rune(r))
|
||||||
|
s = s[8:]
|
||||||
|
default:
|
||||||
|
buf.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var boolSigSet = sigSet{Signature{"b"}: true}
|
||||||
|
|
||||||
|
type boolNode bool
|
||||||
|
|
||||||
|
func (boolNode) Infer() (Signature, error) {
|
||||||
|
return Signature{"b"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b boolNode) String() string {
|
||||||
|
if b {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (boolNode) Sigs() sigSet {
|
||||||
|
return boolSigSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b boolNode) Value(sig Signature) (interface{}, error) {
|
||||||
|
if sig.str != "b" {
|
||||||
|
return nil, varTypeError{b.String(), sig}
|
||||||
|
}
|
||||||
|
return bool(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type arrayNode struct {
|
||||||
|
set sigSet
|
||||||
|
children []varNode
|
||||||
|
val interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n arrayNode) Infer() (Signature, error) {
|
||||||
|
for _, v := range n.children {
|
||||||
|
csig, err := varInfer(v)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return Signature{"a" + csig.str}, nil
|
||||||
|
}
|
||||||
|
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n arrayNode) String() string {
|
||||||
|
s := "["
|
||||||
|
for i, v := range n.children {
|
||||||
|
s += v.String()
|
||||||
|
if i != len(n.children)-1 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s + "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n arrayNode) Sigs() sigSet {
|
||||||
|
return n.set
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n arrayNode) Value(sig Signature) (interface{}, error) {
|
||||||
|
if n.set.Empty() {
|
||||||
|
// no type information whatsoever, so this must be an empty slice
|
||||||
|
return reflect.MakeSlice(typeFor(sig.str), 0, 0).Interface(), nil
|
||||||
|
}
|
||||||
|
if !n.set[sig] {
|
||||||
|
return nil, varTypeError{n.String(), sig}
|
||||||
|
}
|
||||||
|
s := reflect.MakeSlice(typeFor(sig.str), len(n.children), len(n.children))
|
||||||
|
for i, v := range n.children {
|
||||||
|
rv, err := v.Value(Signature{sig.str[1:]})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.Index(i).Set(reflect.ValueOf(rv))
|
||||||
|
}
|
||||||
|
return s.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeArrayNode(p *varParser, sig Signature) (varNode, error) {
|
||||||
|
var n arrayNode
|
||||||
|
if sig.str != "" {
|
||||||
|
n.set = sigSet{sig: true}
|
||||||
|
}
|
||||||
|
if t := p.next(); t.typ == tokArrayEnd {
|
||||||
|
return n, nil
|
||||||
|
} else {
|
||||||
|
p.backup()
|
||||||
|
}
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
t := p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
}
|
||||||
|
p.backup()
|
||||||
|
cn, err := varMakeNode(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cset := cn.Sigs(); !cset.Empty() {
|
||||||
|
if n.set.Empty() {
|
||||||
|
n.set = cset.ToArray()
|
||||||
|
} else {
|
||||||
|
nset := cset.ToArray().Intersect(n.set)
|
||||||
|
if nset.Empty() {
|
||||||
|
return nil, fmt.Errorf("can't parse %q with given type information", cn.String())
|
||||||
|
}
|
||||||
|
n.set = nset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.children = append(n.children, cn)
|
||||||
|
switch t := p.next(); t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
case tokArrayEnd:
|
||||||
|
break Loop
|
||||||
|
case tokComma:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type variantNode struct {
|
||||||
|
n varNode
|
||||||
|
}
|
||||||
|
|
||||||
|
var variantSet = sigSet{
|
||||||
|
Signature{"v"}: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (variantNode) Infer() (Signature, error) {
|
||||||
|
return Signature{"v"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n variantNode) String() string {
|
||||||
|
return "<" + n.n.String() + ">"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (variantNode) Sigs() sigSet {
|
||||||
|
return variantSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n variantNode) Value(sig Signature) (interface{}, error) {
|
||||||
|
if sig.str != "v" {
|
||||||
|
return nil, varTypeError{n.String(), sig}
|
||||||
|
}
|
||||||
|
sig, err := varInfer(n.n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v, err := n.n.Value(sig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return MakeVariant(v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeVariantNode(p *varParser, sig Signature) (varNode, error) {
|
||||||
|
n, err := varMakeNode(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t := p.next(); t.typ != tokVariantEnd {
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
vn := variantNode{n}
|
||||||
|
if sig.str != "" && sig.str != "v" {
|
||||||
|
return nil, varTypeError{vn.String(), sig}
|
||||||
|
}
|
||||||
|
return variantNode{n}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dictEntry struct {
|
||||||
|
key, val varNode
|
||||||
|
}
|
||||||
|
|
||||||
|
type dictNode struct {
|
||||||
|
kset, vset sigSet
|
||||||
|
children []dictEntry
|
||||||
|
val interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n dictNode) Infer() (Signature, error) {
|
||||||
|
for _, v := range n.children {
|
||||||
|
ksig, err := varInfer(v.key)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vsig, err := varInfer(v.val)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return Signature{"a{" + ksig.str + vsig.str + "}"}, nil
|
||||||
|
}
|
||||||
|
return Signature{}, fmt.Errorf("can't infer type for %q", n.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n dictNode) String() string {
|
||||||
|
s := "{"
|
||||||
|
for i, v := range n.children {
|
||||||
|
s += v.key.String() + ": " + v.val.String()
|
||||||
|
if i != len(n.children)-1 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n dictNode) Sigs() sigSet {
|
||||||
|
r := sigSet{}
|
||||||
|
for k := range n.kset {
|
||||||
|
for v := range n.vset {
|
||||||
|
sig := "a{" + k.str + v.str + "}"
|
||||||
|
r[Signature{sig}] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n dictNode) Value(sig Signature) (interface{}, error) {
|
||||||
|
set := n.Sigs()
|
||||||
|
if set.Empty() {
|
||||||
|
// no type information -> empty dict
|
||||||
|
return reflect.MakeMap(typeFor(sig.str)).Interface(), nil
|
||||||
|
}
|
||||||
|
if !set[sig] {
|
||||||
|
return nil, varTypeError{n.String(), sig}
|
||||||
|
}
|
||||||
|
m := reflect.MakeMap(typeFor(sig.str))
|
||||||
|
ksig := Signature{sig.str[2:3]}
|
||||||
|
vsig := Signature{sig.str[3 : len(sig.str)-1]}
|
||||||
|
for _, v := range n.children {
|
||||||
|
kv, err := v.key.Value(ksig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
vv, err := v.val.Value(vsig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.SetMapIndex(reflect.ValueOf(kv), reflect.ValueOf(vv))
|
||||||
|
}
|
||||||
|
return m.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varMakeDictNode(p *varParser, sig Signature) (varNode, error) {
|
||||||
|
var n dictNode
|
||||||
|
|
||||||
|
if sig.str != "" {
|
||||||
|
if len(sig.str) < 5 {
|
||||||
|
return nil, fmt.Errorf("invalid signature %q for dict type", sig)
|
||||||
|
}
|
||||||
|
ksig := Signature{string(sig.str[2])}
|
||||||
|
vsig := Signature{sig.str[3 : len(sig.str)-1]}
|
||||||
|
n.kset = sigSet{ksig: true}
|
||||||
|
n.vset = sigSet{vsig: true}
|
||||||
|
}
|
||||||
|
if t := p.next(); t.typ == tokDictEnd {
|
||||||
|
return n, nil
|
||||||
|
} else {
|
||||||
|
p.backup()
|
||||||
|
}
|
||||||
|
Loop:
|
||||||
|
for {
|
||||||
|
t := p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
}
|
||||||
|
p.backup()
|
||||||
|
kn, err := varMakeNode(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if kset := kn.Sigs(); !kset.Empty() {
|
||||||
|
if n.kset.Empty() {
|
||||||
|
n.kset = kset
|
||||||
|
} else {
|
||||||
|
n.kset = kset.Intersect(n.kset)
|
||||||
|
if n.kset.Empty() {
|
||||||
|
return nil, fmt.Errorf("can't parse %q with given type information", kn.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t = p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
case tokColon:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
t = p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
}
|
||||||
|
p.backup()
|
||||||
|
vn, err := varMakeNode(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if vset := vn.Sigs(); !vset.Empty() {
|
||||||
|
if n.vset.Empty() {
|
||||||
|
n.vset = vset
|
||||||
|
} else {
|
||||||
|
n.vset = n.vset.Intersect(vset)
|
||||||
|
if n.vset.Empty() {
|
||||||
|
return nil, fmt.Errorf("can't parse %q with given type information", vn.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n.children = append(n.children, dictEntry{kn, vn})
|
||||||
|
t = p.next()
|
||||||
|
switch t.typ {
|
||||||
|
case tokEOF:
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
case tokError:
|
||||||
|
return nil, errors.New(t.val)
|
||||||
|
case tokDictEnd:
|
||||||
|
break Loop
|
||||||
|
case tokComma:
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected %q", t.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type byteStringNode []byte
|
||||||
|
|
||||||
|
var byteStringSet = sigSet{
|
||||||
|
Signature{"ay"}: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (byteStringNode) Infer() (Signature, error) {
|
||||||
|
return Signature{"ay"}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byteStringNode) String() string {
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byteStringNode) Sigs() sigSet {
|
||||||
|
return byteStringSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b byteStringNode) Value(sig Signature) (interface{}, error) {
|
||||||
|
if sig.str != "ay" {
|
||||||
|
return nil, varTypeError{b.String(), sig}
|
||||||
|
}
|
||||||
|
return []byte(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varParseByteString(s string) ([]byte, error) {
|
||||||
|
// quotes and b at start are guaranteed to be there
|
||||||
|
b := make([]byte, 0, 1)
|
||||||
|
s = s[2 : len(s)-1]
|
||||||
|
for len(s) != 0 {
|
||||||
|
c := s[0]
|
||||||
|
s = s[1:]
|
||||||
|
if c != '\\' {
|
||||||
|
b = append(b, c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c = s[0]
|
||||||
|
s = s[1:]
|
||||||
|
switch c {
|
||||||
|
case 'a':
|
||||||
|
b = append(b, 0x7)
|
||||||
|
case 'b':
|
||||||
|
b = append(b, 0x8)
|
||||||
|
case 'f':
|
||||||
|
b = append(b, 0xc)
|
||||||
|
case 'n':
|
||||||
|
b = append(b, '\n')
|
||||||
|
case 'r':
|
||||||
|
b = append(b, '\r')
|
||||||
|
case 't':
|
||||||
|
b = append(b, '\t')
|
||||||
|
case 'x':
|
||||||
|
if len(s) < 2 {
|
||||||
|
return nil, errors.New("short escape")
|
||||||
|
}
|
||||||
|
n, err := strconv.ParseUint(s[:2], 16, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b = append(b, byte(n))
|
||||||
|
s = s[2:]
|
||||||
|
case '0':
|
||||||
|
if len(s) < 3 {
|
||||||
|
return nil, errors.New("short escape")
|
||||||
|
}
|
||||||
|
n, err := strconv.ParseUint(s[:3], 8, 8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b = append(b, byte(n))
|
||||||
|
s = s[3:]
|
||||||
|
default:
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(b, 0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func varInfer(n varNode) (Signature, error) {
|
||||||
|
if sig, ok := n.Sigs().Single(); ok {
|
||||||
|
return sig, nil
|
||||||
|
}
|
||||||
|
return n.Infer()
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
package dbus
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var variantFormatTests = []struct {
|
||||||
|
v interface{}
|
||||||
|
s string
|
||||||
|
}{
|
||||||
|
{int32(1), `1`},
|
||||||
|
{"foo", `"foo"`},
|
||||||
|
{ObjectPath("/org/foo"), `@o "/org/foo"`},
|
||||||
|
{Signature{"i"}, `@g "i"`},
|
||||||
|
{[]byte{}, `@ay []`},
|
||||||
|
{[]int32{1, 2}, `[1, 2]`},
|
||||||
|
{[]int64{1, 2}, `@ax [1, 2]`},
|
||||||
|
{[][]int32{{3, 4}, {5, 6}}, `[[3, 4], [5, 6]]`},
|
||||||
|
{[]Variant{MakeVariant(int32(1)), MakeVariant(1.0)}, `[<1>, <@d 1>]`},
|
||||||
|
{map[string]int32{"one": 1, "two": 2}, `{"one": 1, "two": 2}`},
|
||||||
|
{map[int32]ObjectPath{1: "/org/foo"}, `@a{io} {1: "/org/foo"}`},
|
||||||
|
{map[string]Variant{}, `@a{sv} {}`},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatVariant(t *testing.T) {
|
||||||
|
for i, v := range variantFormatTests {
|
||||||
|
if s := MakeVariant(v.v).String(); s != v.s {
|
||||||
|
t.Errorf("test %d: got %q, wanted %q", i+1, s, v.s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var variantParseTests = []struct {
|
||||||
|
s string
|
||||||
|
v interface{}
|
||||||
|
}{
|
||||||
|
{"1", int32(1)},
|
||||||
|
{"true", true},
|
||||||
|
{"false", false},
|
||||||
|
{"1.0", float64(1.0)},
|
||||||
|
{"0x10", int32(16)},
|
||||||
|
{"1e1", float64(10)},
|
||||||
|
{`"foo"`, "foo"},
|
||||||
|
{`"\a\b\f\n\r\t"`, "\x07\x08\x0c\n\r\t"},
|
||||||
|
{`"\u00e4\U0001f603"`, "\u00e4\U0001f603"},
|
||||||
|
{"[1]", []int32{1}},
|
||||||
|
{"[1, 2, 3]", []int32{1, 2, 3}},
|
||||||
|
{"@ai []", []int32{}},
|
||||||
|
{"[1, 5.0]", []float64{1, 5.0}},
|
||||||
|
{"[[1, 2], [3, 4.0]]", [][]float64{{1, 2}, {3, 4}}},
|
||||||
|
{`[@o "/org/foo", "/org/bar"]`, []ObjectPath{"/org/foo", "/org/bar"}},
|
||||||
|
{"<1>", MakeVariant(int32(1))},
|
||||||
|
{"[<1>, <2.0>]", []Variant{MakeVariant(int32(1)), MakeVariant(2.0)}},
|
||||||
|
{`[[], [""]]`, [][]string{{}, {""}}},
|
||||||
|
{`@a{ss} {}`, map[string]string{}},
|
||||||
|
{`{"foo": 1}`, map[string]int32{"foo": 1}},
|
||||||
|
{`[{}, {"foo": "bar"}]`, []map[string]string{{}, {"foo": "bar"}}},
|
||||||
|
{`{"a": <1>, "b": <"foo">}`,
|
||||||
|
map[string]Variant{"a": MakeVariant(int32(1)), "b": MakeVariant("foo")}},
|
||||||
|
{`b''`, []byte{0}},
|
||||||
|
{`b"abc"`, []byte{'a', 'b', 'c', 0}},
|
||||||
|
{`b"\x01\0002\a\b\f\n\r\t"`, []byte{1, 2, 0x7, 0x8, 0xc, '\n', '\r', '\t', 0}},
|
||||||
|
{`[[0], b""]`, [][]byte{{0}, {0}}},
|
||||||
|
{"int16 0", int16(0)},
|
||||||
|
{"byte 0", byte(0)},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseVariant(t *testing.T) {
|
||||||
|
for i, v := range variantParseTests {
|
||||||
|
nv, err := ParseVariant(v.s, Signature{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d: parsing failed: %s", i+1, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(nv.value, v.v) {
|
||||||
|
t.Errorf("test %d: got %q, wanted %q", i+1, nv, v.v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.0
|
||||||
|
- 1.1
|
||||||
|
- 1.2
|
||||||
|
- 1.3
|
||||||
|
- 1.4
|
||||||
|
- tip
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
context
|
||||||
|
=======
|
||||||
|
[](https://travis-ci.org/gorilla/context)
|
||||||
|
|
||||||
|
gorilla/context is a general purpose registry for global request variables.
|
||||||
|
|
||||||
|
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
||||||
+143
@@ -0,0 +1,143 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
mutex sync.RWMutex
|
||||||
|
data = make(map[*http.Request]map[interface{}]interface{})
|
||||||
|
datat = make(map[*http.Request]int64)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set stores a value for a given key in a given request.
|
||||||
|
func Set(r *http.Request, key, val interface{}) {
|
||||||
|
mutex.Lock()
|
||||||
|
if data[r] == nil {
|
||||||
|
data[r] = make(map[interface{}]interface{})
|
||||||
|
datat[r] = time.Now().Unix()
|
||||||
|
}
|
||||||
|
data[r][key] = val
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a value stored for a given key in a given request.
|
||||||
|
func Get(r *http.Request, key interface{}) interface{} {
|
||||||
|
mutex.RLock()
|
||||||
|
if ctx := data[r]; ctx != nil {
|
||||||
|
value := ctx[key]
|
||||||
|
mutex.RUnlock()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOk returns stored value and presence state like multi-value return of map access.
|
||||||
|
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
||||||
|
mutex.RLock()
|
||||||
|
if _, ok := data[r]; ok {
|
||||||
|
value, ok := data[r][key]
|
||||||
|
mutex.RUnlock()
|
||||||
|
return value, ok
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
||||||
|
func GetAll(r *http.Request) map[interface{}]interface{} {
|
||||||
|
mutex.RLock()
|
||||||
|
if context, ok := data[r]; ok {
|
||||||
|
result := make(map[interface{}]interface{}, len(context))
|
||||||
|
for k, v := range context {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
||||||
|
// the request was registered.
|
||||||
|
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
||||||
|
mutex.RLock()
|
||||||
|
context, ok := data[r]
|
||||||
|
result := make(map[interface{}]interface{}, len(context))
|
||||||
|
for k, v := range context {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
mutex.RUnlock()
|
||||||
|
return result, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes a value stored for a given key in a given request.
|
||||||
|
func Delete(r *http.Request, key interface{}) {
|
||||||
|
mutex.Lock()
|
||||||
|
if data[r] != nil {
|
||||||
|
delete(data[r], key)
|
||||||
|
}
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes all values stored for a given request.
|
||||||
|
//
|
||||||
|
// This is usually called by a handler wrapper to clean up request
|
||||||
|
// variables at the end of a request lifetime. See ClearHandler().
|
||||||
|
func Clear(r *http.Request) {
|
||||||
|
mutex.Lock()
|
||||||
|
clear(r)
|
||||||
|
mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear is Clear without the lock.
|
||||||
|
func clear(r *http.Request) {
|
||||||
|
delete(data, r)
|
||||||
|
delete(datat, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge removes request data stored for longer than maxAge, in seconds.
|
||||||
|
// It returns the amount of requests removed.
|
||||||
|
//
|
||||||
|
// If maxAge <= 0, all request data is removed.
|
||||||
|
//
|
||||||
|
// This is only used for sanity check: in case context cleaning was not
|
||||||
|
// properly set some request data can be kept forever, consuming an increasing
|
||||||
|
// amount of memory. In case this is detected, Purge() must be called
|
||||||
|
// periodically until the problem is fixed.
|
||||||
|
func Purge(maxAge int) int {
|
||||||
|
mutex.Lock()
|
||||||
|
count := 0
|
||||||
|
if maxAge <= 0 {
|
||||||
|
count = len(data)
|
||||||
|
data = make(map[*http.Request]map[interface{}]interface{})
|
||||||
|
datat = make(map[*http.Request]int64)
|
||||||
|
} else {
|
||||||
|
min := time.Now().Unix() - int64(maxAge)
|
||||||
|
for r := range data {
|
||||||
|
if datat[r] < min {
|
||||||
|
clear(r)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.Unlock()
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearHandler wraps an http.Handler and clears request values at the end
|
||||||
|
// of a request lifetime.
|
||||||
|
func ClearHandler(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer Clear(r)
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
+161
@@ -0,0 +1,161 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type keyType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
key1 keyType = iota
|
||||||
|
key2
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContext(t *testing.T) {
|
||||||
|
assertEqual := func(val interface{}, exp interface{}) {
|
||||||
|
if val != exp {
|
||||||
|
t.Errorf("Expected %v, got %v.", exp, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||||
|
emptyR, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||||
|
|
||||||
|
// Get()
|
||||||
|
assertEqual(Get(r, key1), nil)
|
||||||
|
|
||||||
|
// Set()
|
||||||
|
Set(r, key1, "1")
|
||||||
|
assertEqual(Get(r, key1), "1")
|
||||||
|
assertEqual(len(data[r]), 1)
|
||||||
|
|
||||||
|
Set(r, key2, "2")
|
||||||
|
assertEqual(Get(r, key2), "2")
|
||||||
|
assertEqual(len(data[r]), 2)
|
||||||
|
|
||||||
|
//GetOk
|
||||||
|
value, ok := GetOk(r, key1)
|
||||||
|
assertEqual(value, "1")
|
||||||
|
assertEqual(ok, true)
|
||||||
|
|
||||||
|
value, ok = GetOk(r, "not exists")
|
||||||
|
assertEqual(value, nil)
|
||||||
|
assertEqual(ok, false)
|
||||||
|
|
||||||
|
Set(r, "nil value", nil)
|
||||||
|
value, ok = GetOk(r, "nil value")
|
||||||
|
assertEqual(value, nil)
|
||||||
|
assertEqual(ok, true)
|
||||||
|
|
||||||
|
// GetAll()
|
||||||
|
values := GetAll(r)
|
||||||
|
assertEqual(len(values), 3)
|
||||||
|
|
||||||
|
// GetAll() for empty request
|
||||||
|
values = GetAll(emptyR)
|
||||||
|
if values != nil {
|
||||||
|
t.Error("GetAll didn't return nil value for invalid request")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllOk()
|
||||||
|
values, ok = GetAllOk(r)
|
||||||
|
assertEqual(len(values), 3)
|
||||||
|
assertEqual(ok, true)
|
||||||
|
|
||||||
|
// GetAllOk() for empty request
|
||||||
|
values, ok = GetAllOk(emptyR)
|
||||||
|
assertEqual(value, nil)
|
||||||
|
assertEqual(ok, false)
|
||||||
|
|
||||||
|
// Delete()
|
||||||
|
Delete(r, key1)
|
||||||
|
assertEqual(Get(r, key1), nil)
|
||||||
|
assertEqual(len(data[r]), 2)
|
||||||
|
|
||||||
|
Delete(r, key2)
|
||||||
|
assertEqual(Get(r, key2), nil)
|
||||||
|
assertEqual(len(data[r]), 1)
|
||||||
|
|
||||||
|
// Clear()
|
||||||
|
Clear(r)
|
||||||
|
assertEqual(len(data), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parallelReader(r *http.Request, key string, iterations int, wait, done chan struct{}) {
|
||||||
|
<-wait
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
Get(r, key)
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func parallelWriter(r *http.Request, key, value string, iterations int, wait, done chan struct{}) {
|
||||||
|
<-wait
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
Set(r, key, value)
|
||||||
|
}
|
||||||
|
done <- struct{}{}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkMutex(b *testing.B, numReaders, numWriters, iterations int) {
|
||||||
|
|
||||||
|
b.StopTimer()
|
||||||
|
r, _ := http.NewRequest("GET", "http://localhost:8080/", nil)
|
||||||
|
done := make(chan struct{})
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
wait := make(chan struct{})
|
||||||
|
|
||||||
|
for i := 0; i < numReaders; i++ {
|
||||||
|
go parallelReader(r, "test", iterations, wait, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < numWriters; i++ {
|
||||||
|
go parallelWriter(r, "test", "123", iterations, wait, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
close(wait)
|
||||||
|
|
||||||
|
for i := 0; i < numReaders+numWriters; i++ {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMutexSameReadWrite1(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 1, 1, 32)
|
||||||
|
}
|
||||||
|
func BenchmarkMutexSameReadWrite2(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 2, 2, 32)
|
||||||
|
}
|
||||||
|
func BenchmarkMutexSameReadWrite4(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 4, 4, 32)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex1(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 2, 8, 32)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex2(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 16, 4, 64)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex3(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 1, 2, 128)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex4(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 128, 32, 256)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex5(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 1024, 2048, 64)
|
||||||
|
}
|
||||||
|
func BenchmarkMutex6(b *testing.B) {
|
||||||
|
benchmarkMutex(b, 2048, 1024, 512)
|
||||||
|
}
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package context stores values shared during a request lifetime.
|
||||||
|
|
||||||
|
For example, a router can set variables extracted from the URL and later
|
||||||
|
application handlers can access those values, or it can be used to store
|
||||||
|
sessions values to be saved at the end of a request. There are several
|
||||||
|
others common uses.
|
||||||
|
|
||||||
|
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
||||||
|
|
||||||
|
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
||||||
|
|
||||||
|
Here's the basic usage: first define the keys that you will need. The key
|
||||||
|
type is interface{} so a key can be of any type that supports equality.
|
||||||
|
Here we define a key using a custom int type to avoid name collisions:
|
||||||
|
|
||||||
|
package foo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type key int
|
||||||
|
|
||||||
|
const MyKey key = 0
|
||||||
|
|
||||||
|
Then set a variable. Variables are bound to an http.Request object, so you
|
||||||
|
need a request instance to set a value:
|
||||||
|
|
||||||
|
context.Set(r, MyKey, "bar")
|
||||||
|
|
||||||
|
The application can later access the variable using the same key you provided:
|
||||||
|
|
||||||
|
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// val is "bar".
|
||||||
|
val := context.Get(r, foo.MyKey)
|
||||||
|
|
||||||
|
// returns ("bar", true)
|
||||||
|
val, ok := context.GetOk(r, foo.MyKey)
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
And that's all about the basic usage. We discuss some other ideas below.
|
||||||
|
|
||||||
|
Any type can be stored in the context. To enforce a given type, make the key
|
||||||
|
private and wrap Get() and Set() to accept and return values of a specific
|
||||||
|
type:
|
||||||
|
|
||||||
|
type key int
|
||||||
|
|
||||||
|
const mykey key = 0
|
||||||
|
|
||||||
|
// GetMyKey returns a value for this package from the request values.
|
||||||
|
func GetMyKey(r *http.Request) SomeType {
|
||||||
|
if rv := context.Get(r, mykey); rv != nil {
|
||||||
|
return rv.(SomeType)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMyKey sets a value for this package in the request values.
|
||||||
|
func SetMyKey(r *http.Request, val SomeType) {
|
||||||
|
context.Set(r, mykey, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
Variables must be cleared at the end of a request, to remove all values
|
||||||
|
that were stored. This can be done in an http.Handler, after a request was
|
||||||
|
served. Just call Clear() passing the request:
|
||||||
|
|
||||||
|
context.Clear(r)
|
||||||
|
|
||||||
|
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
||||||
|
variables at the end of a request lifetime.
|
||||||
|
|
||||||
|
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
||||||
|
so if you are using either of them you don't need to clear the context manually.
|
||||||
|
*/
|
||||||
|
package context
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.0
|
||||||
|
- 1.1
|
||||||
|
- 1.2
|
||||||
|
- tip
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
mux
|
||||||
|
===
|
||||||
|
[](https://travis-ci.org/gorilla/mux)
|
||||||
|
|
||||||
|
gorilla/mux is a powerful URL router and dispatcher.
|
||||||
|
|
||||||
|
Read the full documentation here: http://www.gorillatoolkit.org/pkg/mux
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BenchmarkMux(b *testing.B) {
|
||||||
|
router := new(Router)
|
||||||
|
handler := func(w http.ResponseWriter, r *http.Request) {}
|
||||||
|
router.HandleFunc("/v1/{v1}", handler)
|
||||||
|
|
||||||
|
request, _ := http.NewRequest("GET", "/v1/anything", nil)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
router.ServeHTTP(nil, request)
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user