diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 9b229fe026..b37fc5b6cb 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -1,6 +1,6 @@ { "ImportPath": "github.com/docker/libnetwork", - "GoVersion": "go1.4.2", + "GoVersion": "go1.4.1", "Packages": [ "./..." ], @@ -19,6 +19,11 @@ "ImportPath": "github.com/armon/go-metrics", "Rev": "eb0af217e5e9747e41dd5303755356b62d28e3ec" }, + { + "ImportPath": "github.com/boltdb/bolt", + "Comment": "v1.0-117-g0f053fa", + "Rev": "0f053fabc06119583d61937a0a06ef0ba0f1b301" + }, { "ImportPath": "github.com/codegangsta/cli", "Comment": "1.2.0-143-ga65b733", @@ -39,70 +44,105 @@ "Comment": "v3", "Rev": "be94bc700879ae8217780e9d141789a2defa302b" }, + { + "ImportPath": "github.com/deckarep/golang-set", + "Comment": "v1-26-gef32fa3", + "Rev": "ef32fa3046d9f249d399f98ebaf9be944430fd1d" + }, + { + "ImportPath": "github.com/docker/docker/pkg/discovery", + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" + }, { "ImportPath": "github.com/docker/docker/pkg/homedir", - "Comment": "v1.4.1-4106-g637023a", - "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/docker/pkg/ioutils", - "Comment": "v1.4.1-4106-g637023a", - "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" + }, + { + "ImportPath": "github.com/docker/docker/pkg/listenbuffer", + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/docker/pkg/mflag", - "Comment": "v1.4.1-4106-g637023a", - "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/docker/pkg/mount", - "Comment": "v1.4.1-4127-gb81f2ee", - "Rev": "b81f2ee5f20d094c13893f565ce716595c539d22" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/docker/pkg/parsers", - "Comment": "v1.4.1-4106-g637023a", - "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" + "Comment": "v1.4.1-4106-g637023a", + "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" }, { "ImportPath": "github.com/docker/docker/pkg/plugins", - "Comment": "v1.4.1-4106-g637023a", - "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/docker/pkg/proxy", - "Comment": "v1.4.1-4106-g637023a", - "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" + }, + { + "ImportPath": "github.com/docker/docker/pkg/random", + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/docker/pkg/reexec", - "Comment": "v1.4.1-4106-g637023a", - "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" + }, + { + "ImportPath": "github.com/docker/docker/pkg/signal", + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" + }, + { + "ImportPath": "github.com/docker/docker/pkg/sockets", + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/docker/pkg/stringid", - "Comment": "v1.4.1-4106-g637023a", - "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/docker/pkg/symlink", - "Comment": "v1.4.1-4127-gb81f2ee", - "Rev": "b81f2ee5f20d094c13893f565ce716595c539d22" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" + }, + { + "ImportPath": "github.com/docker/docker/pkg/system", + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/docker/pkg/term", - "Comment": "v1.4.1-4106-g637023a", - "Rev": "637023a5f8d8347a0e271c09d5c9bc84fbc97693" + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { - "ImportPath": "github.com/docker/docker/pkg/units", - "Comment": "v1.4.1-4127-gb81f2ee", - "Rev": "b81f2ee5f20d094c13893f565ce716595c539d22" + "ImportPath": "github.com/docker/docker/pkg/tlsconfig", + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { - "ImportPath": "github.com/docker/libcontainer/user", - "Comment": "v1.4.0-495-g3e66118", - "Rev": "3e661186ba24f259d3860f067df052c7f6904bee" + "ImportPath": "github.com/docker/docker/pkg/units", + "Comment": "v1.4.1-6485-g949270b", + "Rev": "949270ba7ca3c93f25a13abf68d5e0e4c05be08a" }, { "ImportPath": "github.com/docker/libkv", @@ -167,11 +207,6 @@ { "ImportPath": "github.com/vishvananda/netns", "Rev": "493029407eeb434d0c2d44e02ea072ff2488d322" - }, - { - "ImportPath": "github.com/boltdb/bolt", - "Comment": "v1.0-117-g0f053fa", - "Rev": "0f053fabc06119583d61937a0a06ef0ba0f1b301" } ] } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/README.md b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/README.md new file mode 100644 index 0000000000..78173c87d9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/README.md @@ -0,0 +1,41 @@ +--- +page_title: Docker discovery +page_description: discovery +page_keywords: docker, clustering, discovery +--- + +# Discovery + +Docker comes with multiple Discovery backends. + +## Backends + +### Using etcd + +Point your Docker Engine instances to a common etcd instance. You can specify +the address Docker uses to advertise the node using the `--discovery-address` +flag. + +```bash +$ docker daemon -H= --discovery-address= --discovery-backend etcd:/// +``` + +### Using consul + +Point your Docker Engine instances to a common Consul instance. You can specify +the address Docker uses to advertise the node using the `--discovery-address` +flag. + +```bash +$ docker daemon -H= --discovery-address= --discovery-backend consul:/// +``` + +### Using zookeeper + +Point your Docker Engine instances to a common Zookeeper instance. You can specify +the address Docker uses to advertise the node using the `--discovery-address` +flag. + +```bash +$ docker daemon -H= --discovery-address= --discovery-backend zk://,>/ +``` diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/backends.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/backends.go new file mode 100644 index 0000000000..3da2e510a5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/backends.go @@ -0,0 +1,53 @@ +package discovery + +import ( + "fmt" + "strings" + "time" + + log "github.com/Sirupsen/logrus" +) + +var ( + // Backends is a global map of discovery backends indexed by their + // associated scheme. + backends map[string]Backend +) + +func init() { + backends = make(map[string]Backend) +} + +// Register makes a discovery backend available by the provided scheme. +// If Register is called twice with the same scheme an error is returned. +func Register(scheme string, d Backend) error { + if _, exists := backends[scheme]; exists { + return fmt.Errorf("scheme already registered %s", scheme) + } + log.WithField("name", scheme).Debug("Registering discovery service") + backends[scheme] = d + return nil +} + +func parse(rawurl string) (string, string) { + parts := strings.SplitN(rawurl, "://", 2) + + // nodes:port,node2:port => nodes://node1:port,node2:port + if len(parts) == 1 { + return "nodes", parts[0] + } + return parts[0], parts[1] +} + +// New returns a new Discovery given a URL, heartbeat and ttl settings. +// Returns an error if the URL scheme is not supported. +func New(rawurl string, heartbeat time.Duration, ttl time.Duration) (Backend, error) { + scheme, uri := parse(rawurl) + if backend, exists := backends[scheme]; exists { + log.WithFields(log.Fields{"name": scheme, "uri": uri}).Debug("Initializing discovery service") + err := backend.Initialize(uri, heartbeat, ttl) + return backend, err + } + + return nil, ErrNotSupported +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery.go new file mode 100644 index 0000000000..11de2ca486 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery.go @@ -0,0 +1,35 @@ +package discovery + +import ( + "errors" + "time" +) + +var ( + // ErrNotSupported is returned when a discovery service is not supported. + ErrNotSupported = errors.New("discovery service not supported") + + // ErrNotImplemented is returned when discovery feature is not implemented + // by discovery backend. + ErrNotImplemented = errors.New("not implemented in this discovery service") +) + +// Watcher provides watching over a cluster for nodes joining and leaving. +type Watcher interface { + // Watch the discovery for entry changes. + // Returns a channel that will receive changes or an error. + // Providing a non-nil stopCh can be used to stop watching. + Watch(stopCh <-chan struct{}) (<-chan Entries, <-chan error) +} + +// Backend is implemented by discovery backends which manage cluster entries. +type Backend interface { + // Watcher must be provided by every backend. + Watcher + + // Initialize the discovery with URIs, a heartbeat and a ttl. + Initialize(string, time.Duration, time.Duration) error + + // Register to the discovery. + Register(string) error +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery_test.go new file mode 100644 index 0000000000..b7128ff258 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/discovery_test.go @@ -0,0 +1,120 @@ +package discovery + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewEntry(t *testing.T) { + entry, err := NewEntry("127.0.0.1:2375") + assert.NoError(t, err) + assert.True(t, entry.Equals(&Entry{Host: "127.0.0.1", Port: "2375"})) + assert.Equal(t, entry.String(), "127.0.0.1:2375") + + _, err = NewEntry("127.0.0.1") + assert.Error(t, err) +} + +func TestParse(t *testing.T) { + scheme, uri := parse("127.0.0.1:2375") + assert.Equal(t, scheme, "nodes") + assert.Equal(t, uri, "127.0.0.1:2375") + + scheme, uri = parse("localhost:2375") + assert.Equal(t, scheme, "nodes") + assert.Equal(t, uri, "localhost:2375") + + scheme, uri = parse("scheme://127.0.0.1:2375") + assert.Equal(t, scheme, "scheme") + assert.Equal(t, uri, "127.0.0.1:2375") + + scheme, uri = parse("scheme://localhost:2375") + assert.Equal(t, scheme, "scheme") + assert.Equal(t, uri, "localhost:2375") + + scheme, uri = parse("") + assert.Equal(t, scheme, "nodes") + assert.Equal(t, uri, "") +} + +func TestCreateEntries(t *testing.T) { + entries, err := CreateEntries(nil) + assert.Equal(t, entries, Entries{}) + assert.NoError(t, err) + + entries, err = CreateEntries([]string{"127.0.0.1:2375", "127.0.0.2:2375", ""}) + assert.NoError(t, err) + expected := Entries{ + &Entry{Host: "127.0.0.1", Port: "2375"}, + &Entry{Host: "127.0.0.2", Port: "2375"}, + } + assert.True(t, entries.Equals(expected)) + + _, err = CreateEntries([]string{"127.0.0.1", "127.0.0.2"}) + assert.Error(t, err) +} + +func TestContainsEntry(t *testing.T) { + entries, err := CreateEntries([]string{"127.0.0.1:2375", "127.0.0.2:2375", ""}) + assert.NoError(t, err) + assert.True(t, entries.Contains(&Entry{Host: "127.0.0.1", Port: "2375"})) + assert.False(t, entries.Contains(&Entry{Host: "127.0.0.3", Port: "2375"})) +} + +func TestEntriesEquality(t *testing.T) { + entries := Entries{ + &Entry{Host: "127.0.0.1", Port: "2375"}, + &Entry{Host: "127.0.0.2", Port: "2375"}, + } + + // Same + assert.True(t, entries.Equals(Entries{ + &Entry{Host: "127.0.0.1", Port: "2375"}, + &Entry{Host: "127.0.0.2", Port: "2375"}, + })) + + // Different size + assert.False(t, entries.Equals(Entries{ + &Entry{Host: "127.0.0.1", Port: "2375"}, + &Entry{Host: "127.0.0.2", Port: "2375"}, + &Entry{Host: "127.0.0.3", Port: "2375"}, + })) + + // Different content + assert.False(t, entries.Equals(Entries{ + &Entry{Host: "127.0.0.1", Port: "2375"}, + &Entry{Host: "127.0.0.42", Port: "2375"}, + })) +} + +func TestEntriesDiff(t *testing.T) { + entry1 := &Entry{Host: "1.1.1.1", Port: "1111"} + entry2 := &Entry{Host: "2.2.2.2", Port: "2222"} + entry3 := &Entry{Host: "3.3.3.3", Port: "3333"} + entries := Entries{entry1, entry2} + + // No diff + added, removed := entries.Diff(Entries{entry2, entry1}) + assert.Empty(t, added) + assert.Empty(t, removed) + + // Add + added, removed = entries.Diff(Entries{entry2, entry3, entry1}) + assert.Len(t, added, 1) + assert.True(t, added.Contains(entry3)) + assert.Empty(t, removed) + + // Remove + added, removed = entries.Diff(Entries{entry2}) + assert.Empty(t, added) + assert.Len(t, removed, 1) + assert.True(t, removed.Contains(entry1)) + + // Add and remove + added, removed = entries.Diff(Entries{entry1, entry3}) + assert.Len(t, added, 1) + assert.True(t, added.Contains(entry3)) + assert.Len(t, removed, 1) + assert.True(t, removed.Contains(entry2)) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/entry.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/entry.go new file mode 100644 index 0000000000..e9cee26ee1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/entry.go @@ -0,0 +1,97 @@ +package discovery + +import ( + "fmt" + "net" +) + +// NewEntry creates a new entry. +func NewEntry(url string) (*Entry, error) { + host, port, err := net.SplitHostPort(url) + if err != nil { + return nil, err + } + return &Entry{host, port}, nil +} + +// An Entry represents a host. +type Entry struct { + Host string + Port string +} + +// Equals returns true if cmp contains the same data. +func (e *Entry) Equals(cmp *Entry) bool { + return e.Host == cmp.Host && e.Port == cmp.Port +} + +// String returns the string form of an entry. +func (e *Entry) String() string { + return fmt.Sprintf("%s:%s", e.Host, e.Port) +} + +// Entries is a list of *Entry with some helpers. +type Entries []*Entry + +// Equals returns true if cmp contains the same data. +func (e Entries) Equals(cmp Entries) bool { + // Check if the file has really changed. + if len(e) != len(cmp) { + return false + } + for i := range e { + if !e[i].Equals(cmp[i]) { + return false + } + } + return true +} + +// Contains returns true if the Entries contain a given Entry. +func (e Entries) Contains(entry *Entry) bool { + for _, curr := range e { + if curr.Equals(entry) { + return true + } + } + return false +} + +// Diff compares two entries and returns the added and removed entries. +func (e Entries) Diff(cmp Entries) (Entries, Entries) { + added := Entries{} + for _, entry := range cmp { + if !e.Contains(entry) { + added = append(added, entry) + } + } + + removed := Entries{} + for _, entry := range e { + if !cmp.Contains(entry) { + removed = append(removed, entry) + } + } + + return added, removed +} + +// CreateEntries returns an array of entries based on the given addresses. +func CreateEntries(addrs []string) (Entries, error) { + entries := Entries{} + if addrs == nil { + return entries, nil + } + + for _, addr := range addrs { + if len(addr) == 0 { + continue + } + entry, err := NewEntry(addr) + if err != nil { + return nil, err + } + entries = append(entries, entry) + } + return entries, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file.go new file mode 100644 index 0000000000..1af930503a --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file.go @@ -0,0 +1,109 @@ +package file + +import ( + "fmt" + "io/ioutil" + "strings" + "time" + + "github.com/docker/docker/pkg/discovery" +) + +// Discovery is exported +type Discovery struct { + heartbeat time.Duration + path string +} + +func init() { + Init() +} + +// Init is exported +func Init() { + discovery.Register("file", &Discovery{}) +} + +// Initialize is exported +func (s *Discovery) Initialize(path string, heartbeat time.Duration, ttl time.Duration) error { + s.path = path + s.heartbeat = heartbeat + return nil +} + +func parseFileContent(content []byte) []string { + var result []string + for _, line := range strings.Split(strings.TrimSpace(string(content)), "\n") { + line = strings.TrimSpace(line) + // Ignoring line starts with # + if strings.HasPrefix(line, "#") { + continue + } + // Inlined # comment also ignored. + if strings.Contains(line, "#") { + line = line[0:strings.Index(line, "#")] + // Trim additional spaces caused by above stripping. + line = strings.TrimSpace(line) + } + for _, ip := range discovery.Generate(line) { + result = append(result, ip) + } + } + return result +} + +func (s *Discovery) fetch() (discovery.Entries, error) { + fileContent, err := ioutil.ReadFile(s.path) + if err != nil { + return nil, fmt.Errorf("failed to read '%s': %v", s.path, err) + } + return discovery.CreateEntries(parseFileContent(fileContent)) +} + +// Watch is exported +func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { + ch := make(chan discovery.Entries) + errCh := make(chan error) + ticker := time.NewTicker(s.heartbeat) + + go func() { + defer close(errCh) + defer close(ch) + + // Send the initial entries if available. + currentEntries, err := s.fetch() + if err != nil { + errCh <- err + } else { + ch <- currentEntries + } + + // Periodically send updates. + for { + select { + case <-ticker.C: + newEntries, err := s.fetch() + if err != nil { + errCh <- err + continue + } + + // Check if the file has really changed. + if !newEntries.Equals(currentEntries) { + ch <- newEntries + } + currentEntries = newEntries + case <-stopCh: + ticker.Stop() + return + } + } + }() + + return ch, errCh +} + +// Register is exported +func (s *Discovery) Register(addr string) error { + return discovery.ErrNotImplemented +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file_test.go new file mode 100644 index 0000000000..e9e2848015 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/file/file_test.go @@ -0,0 +1,106 @@ +package file + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/docker/docker/pkg/discovery" + "github.com/stretchr/testify/assert" +) + +func TestInitialize(t *testing.T) { + d := &Discovery{} + d.Initialize("/path/to/file", 1000, 0) + assert.Equal(t, d.path, "/path/to/file") +} + +func TestNew(t *testing.T) { + d, err := discovery.New("file:///path/to/file", 0, 0) + assert.NoError(t, err) + assert.Equal(t, d.(*Discovery).path, "/path/to/file") +} + +func TestContent(t *testing.T) { + data := ` +1.1.1.[1:2]:1111 +2.2.2.[2:4]:2222 +` + ips := parseFileContent([]byte(data)) + assert.Len(t, ips, 5) + assert.Equal(t, ips[0], "1.1.1.1:1111") + assert.Equal(t, ips[1], "1.1.1.2:1111") + assert.Equal(t, ips[2], "2.2.2.2:2222") + assert.Equal(t, ips[3], "2.2.2.3:2222") + assert.Equal(t, ips[4], "2.2.2.4:2222") +} + +func TestRegister(t *testing.T) { + discovery := &Discovery{path: "/path/to/file"} + assert.Error(t, discovery.Register("0.0.0.0")) +} + +func TestParsingContentsWithComments(t *testing.T) { + data := ` +### test ### +1.1.1.1:1111 # inline comment +# 2.2.2.2:2222 + ### empty line with comment + 3.3.3.3:3333 +### test ### +` + ips := parseFileContent([]byte(data)) + assert.Len(t, ips, 2) + assert.Equal(t, "1.1.1.1:1111", ips[0]) + assert.Equal(t, "3.3.3.3:3333", ips[1]) +} + +func TestWatch(t *testing.T) { + data := ` +1.1.1.1:1111 +2.2.2.2:2222 +` + expected := discovery.Entries{ + &discovery.Entry{Host: "1.1.1.1", Port: "1111"}, + &discovery.Entry{Host: "2.2.2.2", Port: "2222"}, + } + + // Create a temporary file and remove it. + tmp, err := ioutil.TempFile(os.TempDir(), "discovery-file-test") + assert.NoError(t, err) + assert.NoError(t, tmp.Close()) + assert.NoError(t, os.Remove(tmp.Name())) + + // Set up file discovery. + d := &Discovery{} + d.Initialize(tmp.Name(), 1000, 0) + stopCh := make(chan struct{}) + ch, errCh := d.Watch(stopCh) + + // Make sure it fires errors since the file doesn't exist. + assert.Error(t, <-errCh) + // We have to drain the error channel otherwise Watch will get stuck. + go func() { + for range errCh { + } + }() + + // Write the file and make sure we get the expected value back. + assert.NoError(t, ioutil.WriteFile(tmp.Name(), []byte(data), 0600)) + assert.Equal(t, expected, <-ch) + + // Add a new entry and look it up. + expected = append(expected, &discovery.Entry{Host: "3.3.3.3", Port: "3333"}) + f, err := os.OpenFile(tmp.Name(), os.O_APPEND|os.O_WRONLY, 0600) + assert.NoError(t, err) + assert.NotNil(t, f) + _, err = f.WriteString("\n3.3.3.3:3333\n") + assert.NoError(t, err) + f.Close() + assert.Equal(t, expected, <-ch) + + // Stop and make sure it closes all channels. + close(stopCh) + assert.Nil(t, <-ch) + assert.Nil(t, <-errCh) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator.go new file mode 100644 index 0000000000..d22298298f --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator.go @@ -0,0 +1,35 @@ +package discovery + +import ( + "fmt" + "regexp" + "strconv" +) + +// Generate takes care of IP generation +func Generate(pattern string) []string { + re, _ := regexp.Compile(`\[(.+):(.+)\]`) + submatch := re.FindStringSubmatch(pattern) + if submatch == nil { + return []string{pattern} + } + + from, err := strconv.Atoi(submatch[1]) + if err != nil { + return []string{pattern} + } + to, err := strconv.Atoi(submatch[2]) + if err != nil { + return []string{pattern} + } + + template := re.ReplaceAllString(pattern, "%d") + + var result []string + for val := from; val <= to; val++ { + entry := fmt.Sprintf(template, val) + result = append(result, entry) + } + + return result +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator_test.go new file mode 100644 index 0000000000..747334452f --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/generator_test.go @@ -0,0 +1,55 @@ +package discovery + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGeneratorNotGenerate(t *testing.T) { + ips := Generate("127.0.0.1") + assert.Equal(t, len(ips), 1) + assert.Equal(t, ips[0], "127.0.0.1") +} + +func TestGeneratorWithPortNotGenerate(t *testing.T) { + ips := Generate("127.0.0.1:8080") + assert.Equal(t, len(ips), 1) + assert.Equal(t, ips[0], "127.0.0.1:8080") +} + +func TestGeneratorMatchFailedNotGenerate(t *testing.T) { + ips := Generate("127.0.0.[1]") + assert.Equal(t, len(ips), 1) + assert.Equal(t, ips[0], "127.0.0.[1]") +} + +func TestGeneratorWithPort(t *testing.T) { + ips := Generate("127.0.0.[1:11]:2375") + assert.Equal(t, len(ips), 11) + assert.Equal(t, ips[0], "127.0.0.1:2375") + assert.Equal(t, ips[1], "127.0.0.2:2375") + assert.Equal(t, ips[2], "127.0.0.3:2375") + assert.Equal(t, ips[3], "127.0.0.4:2375") + assert.Equal(t, ips[4], "127.0.0.5:2375") + assert.Equal(t, ips[5], "127.0.0.6:2375") + assert.Equal(t, ips[6], "127.0.0.7:2375") + assert.Equal(t, ips[7], "127.0.0.8:2375") + assert.Equal(t, ips[8], "127.0.0.9:2375") + assert.Equal(t, ips[9], "127.0.0.10:2375") + assert.Equal(t, ips[10], "127.0.0.11:2375") +} + +func TestGenerateWithMalformedInputAtRangeStart(t *testing.T) { + malformedInput := "127.0.0.[x:11]:2375" + ips := Generate(malformedInput) + assert.Equal(t, len(ips), 1) + assert.Equal(t, ips[0], malformedInput) +} + +func TestGenerateWithMalformedInputAtRangeEnd(t *testing.T) { + malformedInput := "127.0.0.[1:x]:2375" + ips := Generate(malformedInput) + assert.Equal(t, len(ips), 1) + assert.Equal(t, ips[0], malformedInput) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv.go new file mode 100644 index 0000000000..347e0418a5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv.go @@ -0,0 +1,148 @@ +package kv + +import ( + "fmt" + "path" + "strings" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/discovery" + "github.com/docker/libkv" + "github.com/docker/libkv/store" + "github.com/docker/libkv/store/consul" + "github.com/docker/libkv/store/etcd" + "github.com/docker/libkv/store/zookeeper" +) + +const ( + discoveryPath = "docker/nodes" +) + +// Discovery is exported +type Discovery struct { + backend store.Backend + store store.Store + heartbeat time.Duration + ttl time.Duration + prefix string + path string +} + +func init() { + Init() +} + +// Init is exported +func Init() { + // Register to libkv + zookeeper.Register() + consul.Register() + etcd.Register() + + // Register to internal discovery service + discovery.Register("zk", &Discovery{backend: store.ZK}) + discovery.Register("consul", &Discovery{backend: store.CONSUL}) + discovery.Register("etcd", &Discovery{backend: store.ETCD}) +} + +// Initialize is exported +func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Duration) error { + var ( + parts = strings.SplitN(uris, "/", 2) + addrs = strings.Split(parts[0], ",") + err error + ) + + // A custom prefix to the path can be optionally used. + if len(parts) == 2 { + s.prefix = parts[1] + } + + s.heartbeat = heartbeat + s.ttl = ttl + s.path = path.Join(s.prefix, discoveryPath) + + // Creates a new store, will ignore options given + // if not supported by the chosen store + s.store, err = libkv.NewStore(s.backend, addrs, nil) + return err +} + +// Watch the store until either there's a store error or we receive a stop request. +// Returns false if we shouldn't attempt watching the store anymore (stop request received). +func (s *Discovery) watchOnce(stopCh <-chan struct{}, watchCh <-chan []*store.KVPair, discoveryCh chan discovery.Entries, errCh chan error) bool { + for { + select { + case pairs := <-watchCh: + if pairs == nil { + return true + } + + log.WithField("discovery", s.backend).Debugf("Watch triggered with %d nodes", len(pairs)) + + // Convert `KVPair` into `discovery.Entry`. + addrs := make([]string, len(pairs)) + for _, pair := range pairs { + addrs = append(addrs, string(pair.Value)) + } + + entries, err := discovery.CreateEntries(addrs) + if err != nil { + errCh <- err + } else { + discoveryCh <- entries + } + case <-stopCh: + // We were requested to stop watching. + return false + } + } +} + +// Watch is exported +func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { + ch := make(chan discovery.Entries) + errCh := make(chan error) + + go func() { + defer close(ch) + defer close(errCh) + + // Forever: Create a store watch, watch until we get an error and then try again. + // Will only stop if we receive a stopCh request. + for { + // Set up a watch. + watchCh, err := s.store.WatchTree(s.path, stopCh) + if err != nil { + errCh <- err + } else { + if !s.watchOnce(stopCh, watchCh, ch, errCh) { + return + } + } + + // If we get here it means the store watch channel was closed. This + // is unexpected so let's retry later. + errCh <- fmt.Errorf("Unexpected watch error") + time.Sleep(s.heartbeat) + } + }() + return ch, errCh +} + +// Register is exported +func (s *Discovery) Register(addr string) error { + opts := &store.WriteOptions{TTL: s.ttl} + return s.store.Put(path.Join(s.path, addr), []byte(addr), opts) +} + +// Store returns the underlying store used by KV discovery. +func (s *Discovery) Store() store.Store { + return s.store +} + +// Prefix returns the store prefix +func (s *Discovery) Prefix() string { + return s.prefix +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv_test.go new file mode 100644 index 0000000000..dea34d685a --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/kv/kv_test.go @@ -0,0 +1,119 @@ +package kv + +import ( + "errors" + "path" + "testing" + "time" + + "github.com/docker/docker/pkg/discovery" + "github.com/docker/libkv/store" + libkvmock "github.com/docker/libkv/store/mock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +func TestInitialize(t *testing.T) { + storeMock, err := libkvmock.New([]string{"127.0.0.1"}, nil) + assert.NotNil(t, storeMock) + assert.NoError(t, err) + + d := &Discovery{backend: store.CONSUL} + d.Initialize("127.0.0.1", 0, 0) + d.store = storeMock + + s := d.store.(*libkvmock.Mock) + assert.Len(t, s.Endpoints, 1) + assert.Equal(t, s.Endpoints[0], "127.0.0.1") + assert.Equal(t, d.path, discoveryPath) + + storeMock, err = libkvmock.New([]string{"127.0.0.1:1234"}, nil) + assert.NotNil(t, storeMock) + assert.NoError(t, err) + + d = &Discovery{backend: store.CONSUL} + d.Initialize("127.0.0.1:1234/path", 0, 0) + d.store = storeMock + + s = d.store.(*libkvmock.Mock) + assert.Len(t, s.Endpoints, 1) + assert.Equal(t, s.Endpoints[0], "127.0.0.1:1234") + assert.Equal(t, d.path, "path/"+discoveryPath) + + storeMock, err = libkvmock.New([]string{"127.0.0.1:1234", "127.0.0.2:1234", "127.0.0.3:1234"}, nil) + assert.NotNil(t, storeMock) + assert.NoError(t, err) + + d = &Discovery{backend: store.CONSUL} + d.Initialize("127.0.0.1:1234,127.0.0.2:1234,127.0.0.3:1234/path", 0, 0) + d.store = storeMock + + s = d.store.(*libkvmock.Mock) + if assert.Len(t, s.Endpoints, 3) { + assert.Equal(t, s.Endpoints[0], "127.0.0.1:1234") + assert.Equal(t, s.Endpoints[1], "127.0.0.2:1234") + assert.Equal(t, s.Endpoints[2], "127.0.0.3:1234") + } + assert.Equal(t, d.path, "path/"+discoveryPath) +} + +func TestWatch(t *testing.T) { + storeMock, err := libkvmock.New([]string{"127.0.0.1:1234"}, nil) + assert.NotNil(t, storeMock) + assert.NoError(t, err) + + d := &Discovery{backend: store.CONSUL} + d.Initialize("127.0.0.1:1234/path", 0, 0) + d.store = storeMock + + s := d.store.(*libkvmock.Mock) + mockCh := make(chan []*store.KVPair) + + // The first watch will fail. + s.On("WatchTree", "path/"+discoveryPath, mock.Anything).Return(mockCh, errors.New("test error")).Once() + // The second one will succeed. + s.On("WatchTree", "path/"+discoveryPath, mock.Anything).Return(mockCh, nil).Once() + expected := discovery.Entries{ + &discovery.Entry{Host: "1.1.1.1", Port: "1111"}, + &discovery.Entry{Host: "2.2.2.2", Port: "2222"}, + } + kvs := []*store.KVPair{ + {Key: path.Join("path", discoveryPath, "1.1.1.1"), Value: []byte("1.1.1.1:1111")}, + {Key: path.Join("path", discoveryPath, "2.2.2.2"), Value: []byte("2.2.2.2:2222")}, + } + + stopCh := make(chan struct{}) + ch, errCh := d.Watch(stopCh) + + // It should fire an error since the first WatchTree call failed. + assert.EqualError(t, <-errCh, "test error") + // We have to drain the error channel otherwise Watch will get stuck. + go func() { + for range errCh { + } + }() + + // Push the entries into the store channel and make sure discovery emits. + mockCh <- kvs + assert.Equal(t, <-ch, expected) + + // Add a new entry. + expected = append(expected, &discovery.Entry{Host: "3.3.3.3", Port: "3333"}) + kvs = append(kvs, &store.KVPair{Key: path.Join("path", discoveryPath, "3.3.3.3"), Value: []byte("3.3.3.3:3333")}) + mockCh <- kvs + assert.Equal(t, <-ch, expected) + + // Make sure that if an error occurs it retries. + // This third call to WatchTree will be checked later by AssertExpectations. + s.On("WatchTree", "path/"+discoveryPath, mock.Anything).Return(mockCh, nil) + close(mockCh) + // Give it enough time to call WatchTree. + time.Sleep(3) + + // Stop and make sure it closes all channels. + close(stopCh) + assert.Nil(t, <-ch) + assert.Nil(t, <-errCh) + + s.AssertExpectations(t) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes.go new file mode 100644 index 0000000000..9da00518e8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes.go @@ -0,0 +1,54 @@ +package nodes + +import ( + "fmt" + "strings" + "time" + + "github.com/docker/docker/pkg/discovery" +) + +// Discovery is exported +type Discovery struct { + entries discovery.Entries +} + +func init() { + Init() +} + +// Init is exported +func Init() { + discovery.Register("nodes", &Discovery{}) +} + +// Initialize is exported +func (s *Discovery) Initialize(uris string, _ time.Duration, _ time.Duration) error { + for _, input := range strings.Split(uris, ",") { + for _, ip := range discovery.Generate(input) { + entry, err := discovery.NewEntry(ip) + if err != nil { + return fmt.Errorf("%s, please check you are using the correct discovery (missing token:// ?)", err.Error()) + } + s.entries = append(s.entries, entry) + } + } + + return nil +} + +// Watch is exported +func (s *Discovery) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { + ch := make(chan discovery.Entries) + go func() { + defer close(ch) + ch <- s.entries + <-stopCh + }() + return ch, nil +} + +// Register is exported +func (s *Discovery) Register(addr string) error { + return discovery.ErrNotImplemented +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes_test.go new file mode 100644 index 0000000000..4563551386 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/discovery/nodes/nodes_test.go @@ -0,0 +1,43 @@ +package nodes + +import ( + "testing" + + "github.com/docker/docker/pkg/discovery" + "github.com/stretchr/testify/assert" +) + +func TestInitialize(t *testing.T) { + d := &Discovery{} + d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0) + assert.Equal(t, len(d.entries), 2) + assert.Equal(t, d.entries[0].String(), "1.1.1.1:1111") + assert.Equal(t, d.entries[1].String(), "2.2.2.2:2222") +} + +func TestInitializeWithPattern(t *testing.T) { + d := &Discovery{} + d.Initialize("1.1.1.[1:2]:1111,2.2.2.[2:4]:2222", 0, 0) + assert.Equal(t, len(d.entries), 5) + assert.Equal(t, d.entries[0].String(), "1.1.1.1:1111") + assert.Equal(t, d.entries[1].String(), "1.1.1.2:1111") + assert.Equal(t, d.entries[2].String(), "2.2.2.2:2222") + assert.Equal(t, d.entries[3].String(), "2.2.2.3:2222") + assert.Equal(t, d.entries[4].String(), "2.2.2.4:2222") +} + +func TestWatch(t *testing.T) { + d := &Discovery{} + d.Initialize("1.1.1.1:1111,2.2.2.2:2222", 0, 0) + expected := discovery.Entries{ + &discovery.Entry{Host: "1.1.1.1", Port: "1111"}, + &discovery.Entry{Host: "2.2.2.2", Port: "2222"}, + } + ch, _ := d.Watch(nil) + assert.True(t, expected.Equals(<-ch)) +} + +func TestRegister(t *testing.T) { + d := &Discovery{} + assert.Error(t, d.Register("0.0.0.0")) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go index 61137a8f5d..8154e83f0c 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go @@ -4,7 +4,7 @@ import ( "os" "runtime" - "github.com/docker/libcontainer/user" + "github.com/opencontainers/runc/libcontainer/user" ) // Key returns the env var name for the user's home dir based on diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe.go new file mode 100644 index 0000000000..932e1d1bcc --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe.go @@ -0,0 +1,89 @@ +package ioutils + +const maxCap = 1e6 + +// BytesPipe is io.ReadWriter which works similarly to pipe(queue). +// All written data could be read only once. Also BytesPipe is allocating +// and releasing new byte slices to adjust to current needs, so there won't be +// overgrown buffer after high load peak. +// BytesPipe isn't goroutine-safe, caller must synchronize it if needed. +type BytesPipe struct { + buf [][]byte // slice of byte-slices of buffered data + lastRead int // index in the first slice to a read point + bufLen int // length of data buffered over the slices +} + +// NewBytesPipe creates new BytesPipe, initialized by specified slice. +// If buf is nil, then it will be initialized with slice which cap is 64. +// buf will be adjusted in a way that len(buf) == 0, cap(buf) == cap(buf). +func NewBytesPipe(buf []byte) *BytesPipe { + if cap(buf) == 0 { + buf = make([]byte, 0, 64) + } + return &BytesPipe{ + buf: [][]byte{buf[:0]}, + } +} + +// Write writes p to BytesPipe. +// It can allocate new []byte slices in a process of writing. +func (bp *BytesPipe) Write(p []byte) (n int, err error) { + for { + // write data to the last buffer + b := bp.buf[len(bp.buf)-1] + // copy data to the current empty allocated area + n := copy(b[len(b):cap(b)], p) + // increment buffered data length + bp.bufLen += n + // include written data in last buffer + bp.buf[len(bp.buf)-1] = b[:len(b)+n] + + // if there was enough room to write all then break + if len(p) == n { + break + } + + // more data: write to the next slice + p = p[n:] + // allocate slice that has twice the size of the last unless maximum reached + nextCap := 2 * cap(bp.buf[len(bp.buf)-1]) + if maxCap < nextCap { + nextCap = maxCap + } + // add new byte slice to the buffers slice and continue writing + bp.buf = append(bp.buf, make([]byte, 0, nextCap)) + } + return +} + +func (bp *BytesPipe) len() int { + return bp.bufLen - bp.lastRead +} + +// Read reads bytes from BytesPipe. +// Data could be read only once. +func (bp *BytesPipe) Read(p []byte) (n int, err error) { + for { + read := copy(p, bp.buf[0][bp.lastRead:]) + n += read + bp.lastRead += read + if bp.len() == 0 { + // we have read everything. reset to the beginning. + bp.lastRead = 0 + bp.bufLen -= len(bp.buf[0]) + bp.buf[0] = bp.buf[0][:0] + break + } + // break if everything was read + if len(p) == read { + break + } + // more buffered data and more asked. read from next slice. + p = p[read:] + bp.lastRead = 0 + bp.bufLen -= len(bp.buf[0]) + bp.buf[0] = nil // throw away old slice + bp.buf = bp.buf[1:] // switch to next + } + return +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe_test.go new file mode 100644 index 0000000000..62b1a186fe --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe_test.go @@ -0,0 +1,141 @@ +package ioutils + +import ( + "crypto/sha1" + "encoding/hex" + "testing" +) + +func TestBytesPipeRead(t *testing.T) { + buf := NewBytesPipe(nil) + buf.Write([]byte("12")) + buf.Write([]byte("34")) + buf.Write([]byte("56")) + buf.Write([]byte("78")) + buf.Write([]byte("90")) + rd := make([]byte, 4) + n, err := buf.Read(rd) + if err != nil { + t.Fatal(err) + } + if n != 4 { + t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4) + } + if string(rd) != "1234" { + t.Fatalf("Read %s, but must be %s", rd, "1234") + } + n, err = buf.Read(rd) + if err != nil { + t.Fatal(err) + } + if n != 4 { + t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4) + } + if string(rd) != "5678" { + t.Fatalf("Read %s, but must be %s", rd, "5679") + } + n, err = buf.Read(rd) + if err != nil { + t.Fatal(err) + } + if n != 2 { + t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 2) + } + if string(rd[:n]) != "90" { + t.Fatalf("Read %s, but must be %s", rd, "90") + } +} + +func TestBytesPipeWrite(t *testing.T) { + buf := NewBytesPipe(nil) + buf.Write([]byte("12")) + buf.Write([]byte("34")) + buf.Write([]byte("56")) + buf.Write([]byte("78")) + buf.Write([]byte("90")) + if string(buf.buf[0]) != "1234567890" { + t.Fatalf("Buffer %s, must be %s", buf.buf, "1234567890") + } +} + +// Write and read in different speeds/chunk sizes and check valid data is read. +func TestBytesPipeWriteRandomChunks(t *testing.T) { + cases := []struct{ iterations, writesPerLoop, readsPerLoop int }{ + {100, 10, 1}, + {1000, 10, 5}, + {1000, 100, 0}, + {1000, 5, 6}, + {10000, 50, 25}, + } + + testMessage := []byte("this is a random string for testing") + // random slice sizes to read and write + writeChunks := []int{25, 35, 15, 20} + readChunks := []int{5, 45, 20, 25} + + for _, c := range cases { + // first pass: write directly to hash + hash := sha1.New() + for i := 0; i < c.iterations*c.writesPerLoop; i++ { + if _, err := hash.Write(testMessage[:writeChunks[i%len(writeChunks)]]); err != nil { + t.Fatal(err) + } + } + expected := hex.EncodeToString(hash.Sum(nil)) + + // write/read through buffer + buf := NewBytesPipe(nil) + hash.Reset() + for i := 0; i < c.iterations; i++ { + for w := 0; w < c.writesPerLoop; w++ { + buf.Write(testMessage[:writeChunks[(i*c.writesPerLoop+w)%len(writeChunks)]]) + } + for r := 0; r < c.readsPerLoop; r++ { + p := make([]byte, readChunks[(i*c.readsPerLoop+r)%len(readChunks)]) + n, _ := buf.Read(p) + hash.Write(p[:n]) + } + } + // read rest of the data from buffer + for i := 0; ; i++ { + p := make([]byte, readChunks[(c.iterations*c.readsPerLoop+i)%len(readChunks)]) + n, _ := buf.Read(p) + if n == 0 { + break + } + hash.Write(p[:n]) + } + actual := hex.EncodeToString(hash.Sum(nil)) + + if expected != actual { + t.Fatalf("BytesPipe returned invalid data. Expected checksum %v, got %v", expected, actual) + } + + } +} + +func BenchmarkBytesPipeWrite(b *testing.B) { + for i := 0; i < b.N; i++ { + buf := NewBytesPipe(nil) + for j := 0; j < 1000; j++ { + buf.Write([]byte("pretty short line, because why not?")) + } + } +} + +func BenchmarkBytesPipeRead(b *testing.B) { + rd := make([]byte, 1024) + for i := 0; i < b.N; i++ { + b.StopTimer() + buf := NewBytesPipe(nil) + for j := 0; j < 1000; j++ { + buf.Write(make([]byte, 1024)) + } + b.StartTimer() + for j := 0; j < 1000; j++ { + if n, _ := buf.Read(rd); n != 1024 { + b.Fatalf("Wrong number of bytes: %d", n) + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/fmt.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/fmt.go index 801132ff3d..0b04b0ba3e 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/fmt.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/fmt.go @@ -12,3 +12,11 @@ func FprintfIfNotEmpty(w io.Writer, format, value string) (int, error) { } return 0, nil } + +// FprintfIfTrue prints the boolean value if it's true +func FprintfIfTrue(w io.Writer, format string, ok bool) (int, error) { + if ok { + return fmt.Fprintf(w, format, ok) + } + return 0, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader.go new file mode 100644 index 0000000000..0d2d76b479 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader.go @@ -0,0 +1,226 @@ +package ioutils + +import ( + "bytes" + "fmt" + "io" + "os" +) + +type pos struct { + idx int + offset int64 +} + +type multiReadSeeker struct { + readers []io.ReadSeeker + pos *pos + posIdx map[io.ReadSeeker]int +} + +func (r *multiReadSeeker) Seek(offset int64, whence int) (int64, error) { + var tmpOffset int64 + switch whence { + case os.SEEK_SET: + for i, rdr := range r.readers { + // get size of the current reader + s, err := rdr.Seek(0, os.SEEK_END) + if err != nil { + return -1, err + } + + if offset > tmpOffset+s { + if i == len(r.readers)-1 { + rdrOffset := s + (offset - tmpOffset) + if _, err := rdr.Seek(rdrOffset, os.SEEK_SET); err != nil { + return -1, err + } + r.pos = &pos{i, rdrOffset} + return offset, nil + } + + tmpOffset += s + continue + } + + rdrOffset := offset - tmpOffset + idx := i + + rdr.Seek(rdrOffset, os.SEEK_SET) + // make sure all following readers are at 0 + for _, rdr := range r.readers[i+1:] { + rdr.Seek(0, os.SEEK_SET) + } + + if rdrOffset == s && i != len(r.readers)-1 { + idx++ + rdrOffset = 0 + } + r.pos = &pos{idx, rdrOffset} + return offset, nil + } + case os.SEEK_END: + for _, rdr := range r.readers { + s, err := rdr.Seek(0, os.SEEK_END) + if err != nil { + return -1, err + } + tmpOffset += s + } + r.Seek(tmpOffset+offset, os.SEEK_SET) + return tmpOffset + offset, nil + case os.SEEK_CUR: + if r.pos == nil { + return r.Seek(offset, os.SEEK_SET) + } + // Just return the current offset + if offset == 0 { + return r.getCurOffset() + } + + curOffset, err := r.getCurOffset() + if err != nil { + return -1, err + } + rdr, rdrOffset, err := r.getReaderForOffset(curOffset + offset) + if err != nil { + return -1, err + } + + r.pos = &pos{r.posIdx[rdr], rdrOffset} + return curOffset + offset, nil + default: + return -1, fmt.Errorf("Invalid whence: %d", whence) + } + + return -1, fmt.Errorf("Error seeking for whence: %d, offset: %d", whence, offset) +} + +func (r *multiReadSeeker) getReaderForOffset(offset int64) (io.ReadSeeker, int64, error) { + var rdr io.ReadSeeker + var rdrOffset int64 + + for i, rdr := range r.readers { + offsetTo, err := r.getOffsetToReader(rdr) + if err != nil { + return nil, -1, err + } + if offsetTo > offset { + rdr = r.readers[i-1] + rdrOffset = offsetTo - offset + break + } + + if rdr == r.readers[len(r.readers)-1] { + rdrOffset = offsetTo + offset + break + } + } + + return rdr, rdrOffset, nil +} + +func (r *multiReadSeeker) getCurOffset() (int64, error) { + var totalSize int64 + for _, rdr := range r.readers[:r.pos.idx+1] { + if r.posIdx[rdr] == r.pos.idx { + totalSize += r.pos.offset + break + } + + size, err := getReadSeekerSize(rdr) + if err != nil { + return -1, fmt.Errorf("error getting seeker size: %v", err) + } + totalSize += size + } + return totalSize, nil +} + +func (r *multiReadSeeker) getOffsetToReader(rdr io.ReadSeeker) (int64, error) { + var offset int64 + for _, r := range r.readers { + if r == rdr { + break + } + + size, err := getReadSeekerSize(rdr) + if err != nil { + return -1, err + } + offset += size + } + return offset, nil +} + +func (r *multiReadSeeker) Read(b []byte) (int, error) { + if r.pos == nil { + r.pos = &pos{0, 0} + } + + bCap := int64(cap(b)) + buf := bytes.NewBuffer(nil) + var rdr io.ReadSeeker + + for _, rdr = range r.readers[r.pos.idx:] { + readBytes, err := io.CopyN(buf, rdr, bCap) + if err != nil && err != io.EOF { + return -1, err + } + bCap -= readBytes + + if bCap == 0 { + break + } + } + + rdrPos, err := rdr.Seek(0, os.SEEK_CUR) + if err != nil { + return -1, err + } + r.pos = &pos{r.posIdx[rdr], rdrPos} + return buf.Read(b) +} + +func getReadSeekerSize(rdr io.ReadSeeker) (int64, error) { + // save the current position + pos, err := rdr.Seek(0, os.SEEK_CUR) + if err != nil { + return -1, err + } + + // get the size + size, err := rdr.Seek(0, os.SEEK_END) + if err != nil { + return -1, err + } + + // reset the position + if _, err := rdr.Seek(pos, os.SEEK_SET); err != nil { + return -1, err + } + return size, nil +} + +// MultiReadSeeker returns a ReadSeeker that's the logical concatenation of the provided +// input readseekers. After calling this method the initial position is set to the +// beginning of the first ReadSeeker. At the end of a ReadSeeker, Read always advances +// to the beginning of the next ReadSeeker and returns EOF at the end of the last ReadSeeker. +// Seek can be used over the sum of lengths of all readseekers. +// +// When a MultiReadSeeker is used, no Read and Seek operations should be made on +// its ReadSeeker components. Also, users should make no assumption on the state +// of individual readseekers while the MultiReadSeeker is used. +func MultiReadSeeker(readers ...io.ReadSeeker) io.ReadSeeker { + if len(readers) == 1 { + return readers[0] + } + idx := make(map[io.ReadSeeker]int) + for i, rdr := range readers { + idx[rdr] = i + } + return &multiReadSeeker{ + readers: readers, + posIdx: idx, + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader_test.go new file mode 100644 index 0000000000..de495b56da --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader_test.go @@ -0,0 +1,149 @@ +package ioutils + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + "testing" +) + +func TestMultiReadSeekerReadAll(t *testing.T) { + str := "hello world" + s1 := strings.NewReader(str + " 1") + s2 := strings.NewReader(str + " 2") + s3 := strings.NewReader(str + " 3") + mr := MultiReadSeeker(s1, s2, s3) + + expectedSize := int64(s1.Len() + s2.Len() + s3.Len()) + + b, err := ioutil.ReadAll(mr) + if err != nil { + t.Fatal(err) + } + + expected := "hello world 1hello world 2hello world 3" + if string(b) != expected { + t.Fatalf("ReadAll failed, got: %q, expected %q", string(b), expected) + } + + size, err := mr.Seek(0, os.SEEK_END) + if err != nil { + t.Fatal(err) + } + if size != expectedSize { + t.Fatalf("reader size does not match, got %d, expected %d", size, expectedSize) + } + + // Reset the position and read again + pos, err := mr.Seek(0, os.SEEK_SET) + if err != nil { + t.Fatal(err) + } + if pos != 0 { + t.Fatalf("expected position to be set to 0, got %d", pos) + } + + b, err = ioutil.ReadAll(mr) + if err != nil { + t.Fatal(err) + } + + if string(b) != expected { + t.Fatalf("ReadAll failed, got: %q, expected %q", string(b), expected) + } +} + +func TestMultiReadSeekerReadEach(t *testing.T) { + str := "hello world" + s1 := strings.NewReader(str + " 1") + s2 := strings.NewReader(str + " 2") + s3 := strings.NewReader(str + " 3") + mr := MultiReadSeeker(s1, s2, s3) + + var totalBytes int64 + for i, s := range []*strings.Reader{s1, s2, s3} { + sLen := int64(s.Len()) + buf := make([]byte, s.Len()) + expected := []byte(fmt.Sprintf("%s %d", str, i+1)) + + if _, err := mr.Read(buf); err != nil && err != io.EOF { + t.Fatal(err) + } + + if !bytes.Equal(buf, expected) { + t.Fatalf("expected %q to be %q", string(buf), string(expected)) + } + + pos, err := mr.Seek(0, os.SEEK_CUR) + if err != nil { + t.Fatalf("iteration: %d, error: %v", i+1, err) + } + + // check that the total bytes read is the current position of the seeker + totalBytes += sLen + if pos != totalBytes { + t.Fatalf("expected current position to be: %d, got: %d, iteration: %d", totalBytes, pos, i+1) + } + + // This tests not only that SEEK_SET and SEEK_CUR give the same values, but that the next iteration is in the expected position as well + newPos, err := mr.Seek(pos, os.SEEK_SET) + if err != nil { + t.Fatal(err) + } + if newPos != pos { + t.Fatalf("expected to get same position when calling SEEK_SET with value from SEEK_CUR, cur: %d, set: %d", pos, newPos) + } + } +} + +func TestMultiReadSeekerReadSpanningChunks(t *testing.T) { + str := "hello world" + s1 := strings.NewReader(str + " 1") + s2 := strings.NewReader(str + " 2") + s3 := strings.NewReader(str + " 3") + mr := MultiReadSeeker(s1, s2, s3) + + buf := make([]byte, s1.Len()+3) + _, err := mr.Read(buf) + if err != nil { + t.Fatal(err) + } + + // expected is the contents of s1 + 3 bytes from s2, ie, the `hel` at the end of this string + expected := "hello world 1hel" + if string(buf) != expected { + t.Fatalf("expected %s to be %s", string(buf), expected) + } +} + +func TestMultiReadSeekerNegativeSeek(t *testing.T) { + str := "hello world" + s1 := strings.NewReader(str + " 1") + s2 := strings.NewReader(str + " 2") + s3 := strings.NewReader(str + " 3") + mr := MultiReadSeeker(s1, s2, s3) + + s1Len := s1.Len() + s2Len := s2.Len() + s3Len := s3.Len() + + s, err := mr.Seek(int64(-1*s3.Len()), os.SEEK_END) + if err != nil { + t.Fatal(err) + } + if s != int64(s1Len+s2Len) { + t.Fatalf("expected %d to be %d", s, s1.Len()+s2.Len()) + } + + buf := make([]byte, s3Len) + if _, err := mr.Read(buf); err != nil && err != io.EOF { + t.Fatal(err) + } + expected := fmt.Sprintf("%s %d", str, 3) + if string(buf) != fmt.Sprintf("%s %d", str, 3) { + t.Fatalf("expected %q to be %q", string(buf), expected) + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers.go index 0e542cbad3..54dd312bb7 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers.go @@ -1,14 +1,10 @@ package ioutils import ( - "bytes" - "crypto/rand" "crypto/sha256" "encoding/hex" "io" - "math/big" "sync" - "time" ) type readCloserWrapper struct { @@ -20,6 +16,7 @@ func (r *readCloserWrapper) Close() error { return r.closer() } +// NewReadCloserWrapper returns a new io.ReadCloser. func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser { return &readCloserWrapper{ Reader: r, @@ -40,6 +37,7 @@ func (r *readerErrWrapper) Read(p []byte) (int, error) { return n, err } +// NewReaderErrWrapper returns a new io.Reader. func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader { return &readerErrWrapper{ reader: r, @@ -53,41 +51,27 @@ func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader { // 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 - } + buf io.ReadWriter + reader io.Reader + err error + wait sync.Cond + drainBuf []byte +} + +// NewBufReader returns a new bufReader. +func NewBufReader(r io.Reader) io.ReadCloser { 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, + buf: NewBytesPipe(nil), + reader: r, + drainBuf: make([]byte, 1024), } reader.wait.L = &reader.Mutex go reader.drain() return reader } -func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer *bytes.Buffer) *bufReader { +// NewBufReaderWithDrainbufAndBuffer returns a BufReader with drainBuffer and buffer. +func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer io.ReadWriter) io.ReadCloser { reader := &bufReader{ buf: buffer, drainBuf: drainBuffer, @@ -99,94 +83,22 @@ func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer * } 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 { + //Call to scheduler is made to yield from this goroutine. + //This avoids goroutine looping here when n=0,err=nil, fixes code hangs when run with GCC Go. + callSchedulerIfNecessary() 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]) + if n == 0 { + // nothing written, no need to signal + r.Unlock() + continue + } + r.buf.Write(r.drainBuf[:n]) } - reuseCount++ r.wait.Signal() r.Unlock() if err != nil { @@ -210,6 +122,7 @@ func (r *bufReader) Read(p []byte) (n int, err error) { } } +// Close closes the bufReader func (r *bufReader) Close() error { closer, ok := r.reader.(io.ReadCloser) if !ok { @@ -218,6 +131,7 @@ func (r *bufReader) Close() error { return closer.Close() } +// HashData returns the sha256 sum of src. func HashData(src io.Reader) (string, error) { h := sha256.New() if _, err := io.Copy(h, src); err != nil { @@ -225,3 +139,32 @@ func HashData(src io.Reader) (string, error) { } return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil } + +// OnEOFReader wraps a io.ReadCloser and a function +// the function will run at the end of file or close the file. +type OnEOFReader struct { + Rc io.ReadCloser + Fn func() +} + +func (r *OnEOFReader) Read(p []byte) (n int, err error) { + n, err = r.Rc.Read(p) + if err == io.EOF { + r.runFunc() + } + return +} + +// Close closes the file and run the function. +func (r *OnEOFReader) Close() error { + err := r.Rc.Close() + r.runFunc() + return err +} + +func (r *OnEOFReader) runFunc() { + if fn := r.Fn; fn != nil { + fn() + r.Fn = nil + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go index d220487ad5..5c26a2a147 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "strings" "testing" + "time" ) // Implement io.Reader @@ -45,7 +46,7 @@ func TestReaderErrWrapperReadOnError(t *testing.T) { func TestReaderErrWrapperRead(t *testing.T) { reader := strings.NewReader("a string reader.") wrapper := NewReaderErrWrapper(reader, func() { - t.Fatalf("readErrWrapper should not have called the anonymous function on failure") + t.Fatalf("readErrWrapper should not have called the anonymous function") }) // Read 20 byte (should be ok with the string above) num, err := wrapper.Read(make([]byte, 20)) @@ -61,8 +62,8 @@ func TestNewBufReaderWithDrainbufAndBuffer(t *testing.T) { reader, writer := io.Pipe() drainBuffer := make([]byte, 1024) - buffer := bytes.Buffer{} - bufreader := NewBufReaderWithDrainbufAndBuffer(reader, drainBuffer, &buffer) + buffer := NewBytesPipe(nil) + bufreader := NewBufReaderWithDrainbufAndBuffer(reader, drainBuffer, buffer) // Write everything down to a Pipe // Usually, a pipe should block but because of the buffered reader, @@ -76,7 +77,11 @@ func TestNewBufReaderWithDrainbufAndBuffer(t *testing.T) { // Drain the reader *after* everything has been written, just to verify // it is indeed buffering - <-done + select { + case <-done: + case <-time.After(1 * time.Second): + t.Fatal("timeout") + } output, err := ioutil.ReadAll(bufreader) if err != nil { @@ -124,13 +129,16 @@ func TestBufReaderCloseWithNonReaderCloser(t *testing.T) { } // implements io.ReadCloser -type simpleReaderCloser struct{} +type simpleReaderCloser struct { + err error +} func (r *simpleReaderCloser) Read(p []byte) (n int, err error) { - return 0, nil + return 0, r.err } func (r *simpleReaderCloser) Close() error { + r.err = io.EOF return nil } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler.go new file mode 100644 index 0000000000..3c88f29e35 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler.go @@ -0,0 +1,6 @@ +// +build !gccgo + +package ioutils + +func callSchedulerIfNecessary() { +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler_gccgo.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler_gccgo.go new file mode 100644 index 0000000000..c11d02b947 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler_gccgo.go @@ -0,0 +1,13 @@ +// +build gccgo + +package ioutils + +import ( + "runtime" +) + +func callSchedulerIfNecessary() { + //allow or force Go scheduler to switch context, without explicitly + //forcing this will make it hang when using gccgo implementation + runtime.Gosched() +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go index 25095474df..cedb9a0dde 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go @@ -6,6 +6,7 @@ import ( "sync" ) +// WriteFlusher wraps the Write and Flush operation. type WriteFlusher struct { sync.Mutex w io.Writer @@ -30,12 +31,15 @@ func (wf *WriteFlusher) Flush() { wf.flusher.Flush() } +// Flushed returns the state of flushed. +// If it's flushed, return true, or else it return false. func (wf *WriteFlusher) Flushed() bool { wf.Lock() defer wf.Unlock() return wf.flushed } +// NewWriteFlusher returns a new WriteFlusher. func NewWriteFlusher(w io.Writer) *WriteFlusher { var flusher http.Flusher if f, ok := w.(http.Flusher); ok { diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers.go index 43fdc44ea9..7a3249f3a0 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers.go @@ -2,6 +2,7 @@ package ioutils import "io" +// NopWriter represents a type which write operation is nop. type NopWriter struct{} func (*NopWriter) Write(buf []byte) (int, error) { @@ -14,12 +15,15 @@ type nopWriteCloser struct { func (w *nopWriteCloser) Close() error { return nil } +// NopWriteCloser returns a nopWriteCloser. func NopWriteCloser(w io.Writer) io.WriteCloser { return &nopWriteCloser{w} } +// NopFlusher represents a type which flush opetatin is nop. type NopFlusher struct{} +// Flush is a nop operation. func (f *NopFlusher) Flush() {} type writeCloserWrapper struct { @@ -31,6 +35,7 @@ func (r *writeCloserWrapper) Close() error { return r.closer() } +// NewWriteCloserWrapper returns a new io.WriteCloser. func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser { return &writeCloserWrapper{ Writer: r, @@ -38,7 +43,7 @@ func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser { } } -// Wrap a concrete io.Writer and hold a count of the number +// WriteCounter wraps 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()) @@ -47,6 +52,7 @@ type WriteCounter struct { Writer io.Writer } +// NewWriteCounter returns a new WriteCounter. func NewWriteCounter(w io.Writer) *WriteCounter { return &WriteCounter{ Writer: w, diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/listenbuffer/README.md b/Godeps/_workspace/src/github.com/docker/docker/pkg/listenbuffer/README.md new file mode 100644 index 0000000000..2273509817 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/listenbuffer/README.md @@ -0,0 +1,27 @@ +# listenbuffer + +listenbuffer uses the kernel's listening backlog functionality to queue +connections, allowing applications to start listening immediately and handle +connections later. This is signaled by closing the activation channel passed to +the constructor. + +The maximum amount of queued connections depends on the configuration of your +kernel (typically called SOMAXXCON) and cannot be configured in Go with the +net package. See `src/net/sock_platform.go` in the Go tree or consult your +kernel's manual. + + activator := make(chan struct{}) + buffer, err := NewListenBuffer("tcp", "localhost:4000", activator) + if err != nil { + panic(err) + } + + // will block until activator has been closed or is sent an event + client, err := buffer.Accept() + +Somewhere else in your application once it's been booted: + + close(activator) + +`buffer.Accept()` will return the first client in the kernel listening queue, or +continue to block until a client connects or an error occurs. diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/listenbuffer/buffer.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/listenbuffer/buffer.go new file mode 100644 index 0000000000..aa47471c40 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/listenbuffer/buffer.go @@ -0,0 +1,76 @@ +/* +Package listenbuffer uses the kernel's listening backlog functionality to queue +connections, allowing applications to start listening immediately and handle +connections later. This is signaled by closing the activation channel passed to +the constructor. + +The maximum amount of queued connections depends on the configuration of your +kernel (typically called SOMAXXCON) and cannot be configured in Go with the +net package. See `src/net/sock_platform.go` in the Go tree or consult your +kernel's manual. + + activator := make(chan struct{}) + buffer, err := NewListenBuffer("tcp", "localhost:4000", activator) + if err != nil { + panic(err) + } + + // will block until activator has been closed or is sent an event + client, err := buffer.Accept() + +Somewhere else in your application once it's been booted: + + close(activator) + +`buffer.Accept()` will return the first client in the kernel listening queue, or +continue to block until a client connects or an error occurs. +*/ +package listenbuffer + +import "net" + +// NewListenBuffer returns a net.Listener listening on addr with the protocol +// passed. The channel passed is used to activate the listenbuffer when the +// caller is ready to accept connections. +func NewListenBuffer(proto, addr string, activate <-chan struct{}) (net.Listener, error) { + wrapped, err := net.Listen(proto, addr) + if err != nil { + return nil, err + } + + return &defaultListener{ + wrapped: wrapped, + activate: activate, + }, nil +} + +// defaultListener is the buffered wrapper around the net.Listener +type defaultListener struct { + wrapped net.Listener // The net.Listener wrapped by listenbuffer + ready bool // Whether the listenbuffer has been activated + activate <-chan struct{} // Channel to control activation of the listenbuffer +} + +// Close closes the wrapped socket. +func (l *defaultListener) Close() error { + return l.wrapped.Close() +} + +// Addr returns the listening address of the wrapped socket. +func (l *defaultListener) Addr() net.Addr { + return l.wrapped.Addr() +} + +// Accept returns a client connection on the wrapped socket if the listen buffer +// has been activated. To active the listenbuffer the activation channel passed +// to NewListenBuffer must have been closed or sent an event. +func (l *defaultListener) Accept() (net.Conn, error) { + // if the listen has been told it is ready then we can go ahead and + // start returning connections + if l.ready { + return l.wrapped.Accept() + } + <-l.activate + l.ready = true + return l.Accept() +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/listenbuffer/listen_buffer_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/listenbuffer/listen_buffer_test.go new file mode 100644 index 0000000000..6ffd2f7984 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/listenbuffer/listen_buffer_test.go @@ -0,0 +1,41 @@ +package listenbuffer + +import ( + "io/ioutil" + "net" + "testing" +) + +func TestListenBufferAllowsAcceptingWhenActivated(t *testing.T) { + lock := make(chan struct{}) + buffer, err := NewListenBuffer("tcp", "", lock) + if err != nil { + t.Fatal("Unable to create listen buffer: ", err) + } + + go func() { + conn, err := net.Dial("tcp", buffer.Addr().String()) + if err != nil { + t.Fatal("Client failed to establish connection to server: ", err) + } + + conn.Write([]byte("ping")) + conn.Close() + }() + + close(lock) + + client, err := buffer.Accept() + if err != nil { + t.Fatal("Failed to accept client: ", err) + } + + response, err := ioutil.ReadAll(client) + if err != nil { + t.Fatal("Failed to read from client: ", err) + } + + if string(response) != "ping" { + t.Fatal("Expected to receive ping from client, received: ", string(response)) + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/mflag/flag.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/mflag/flag.go index 9626a2f6b7..dd3188462c 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/mflag/flag.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/mflag/flag.go @@ -2,83 +2,82 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -/* - Package flag implements command-line flag parsing. - - Usage: - - Define flags using flag.String(), Bool(), Int(), etc. +// Package mflag implements command-line flag parsing. +// +// Usage: +// +// Define flags using flag.String(), Bool(), Int(), etc. +// +// This declares an integer flag, -f or --flagname, stored in the pointer ip, with type *int. +// import "flag /github.com/docker/docker/pkg/mflag" +// var ip = flag.Int([]string{"f", "-flagname"}, 1234, "help message for flagname") +// If you like, you can bind the flag to a variable using the Var() functions. +// var flagvar int +// func init() { +// // -flaghidden will work, but will be hidden from the usage +// flag.IntVar(&flagvar, []string{"f", "#flaghidden", "-flagname"}, 1234, "help message for flagname") +// } +// Or you can create custom flags that satisfy the Value interface (with +// pointer receivers) and couple them to flag parsing by +// flag.Var(&flagVal, []string{"name"}, "help message for flagname") +// For such flags, the default value is just the initial value of the variable. +// +// You can also add "deprecated" flags, they are still usable, but are not shown +// in the usage and will display a warning when you try to use them. `#` before +// an option means this option is deprecated, if there is an following option +// without `#` ahead, then that's the replacement, if not, it will just be removed: +// var ip = flag.Int([]string{"#f", "#flagname", "-flagname"}, 1234, "help message for flagname") +// this will display: `Warning: '-f' is deprecated, it will be replaced by '--flagname' soon. See usage.` or +// this will display: `Warning: '-flagname' is deprecated, it will be replaced by '--flagname' soon. See usage.` +// var ip = flag.Int([]string{"f", "#flagname"}, 1234, "help message for flagname") +// will display: `Warning: '-flagname' is deprecated, it will be removed soon. See usage.` +// so you can only use `-f`. +// +// You can also group one letter flags, bif you declare +// var v = flag.Bool([]string{"v", "-verbose"}, false, "help message for verbose") +// var s = flag.Bool([]string{"s", "-slow"}, false, "help message for slow") +// you will be able to use the -vs or -sv +// +// After all flags are defined, call +// flag.Parse() +// to parse the command line into the defined flags. +// +// Flags may then be used directly. If you're using the flags themselves, +// they are all pointers; if you bind to variables, they're values. +// fmt.Println("ip has value ", *ip) +// fmt.Println("flagvar has value ", flagvar) +// +// After parsing, the arguments after the flag are available as the +// slice flag.Args() or individually as flag.Arg(i). +// The arguments are indexed from 0 through flag.NArg()-1. +// +// Command line flag syntax: +// -flag +// -flag=x +// -flag="x" +// -flag='x' +// -flag x // non-boolean flags only +// One or two minus signs may be used; they are equivalent. +// The last form is not permitted for boolean flags because the +// meaning of the command +// cmd -x * +// will change if there is a file called 0, false, etc. You must +// use the -flag=false form to turn off a boolean flag. +// +// Flag parsing stops just before the first non-flag argument +// ("-" is a non-flag argument) or after the terminator "--". +// +// Integer flags accept 1234, 0664, 0x1234 and may be negative. +// Boolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False. +// Duration flags accept any input valid for time.ParseDuration. +// +// The default set of command-line flags is controlled by +// top-level functions. The FlagSet type allows one to define +// independent sets of flags, such as to implement subcommands +// in a command-line interface. The methods of FlagSet are +// analogous to the top-level functions for the command-line +// flag set. - This declares an integer flag, -f or --flagname, stored in the pointer ip, with type *int. - import "flag /github.com/docker/docker/pkg/mflag" - var ip = flag.Int([]string{"f", "-flagname"}, 1234, "help message for flagname") - If you like, you can bind the flag to a variable using the Var() functions. - var flagvar int - func init() { - // -flaghidden will work, but will be hidden from the usage - flag.IntVar(&flagvar, []string{"f", "#flaghidden", "-flagname"}, 1234, "help message for flagname") - } - Or you can create custom flags that satisfy the Value interface (with - pointer receivers) and couple them to flag parsing by - flag.Var(&flagVal, []string{"name"}, "help message for flagname") - For such flags, the default value is just the initial value of the variable. - - You can also add "deprecated" flags, they are still usable, but are not shown - in the usage and will display a warning when you try to use them. `#` before - an option means this option is deprecated, if there is an following option - without `#` ahead, then that's the replacement, if not, it will just be removed: - var ip = flag.Int([]string{"#f", "#flagname", "-flagname"}, 1234, "help message for flagname") - this will display: `Warning: '-f' is deprecated, it will be replaced by '--flagname' soon. See usage.` or - this will display: `Warning: '-flagname' is deprecated, it will be replaced by '--flagname' soon. See usage.` - var ip = flag.Int([]string{"f", "#flagname"}, 1234, "help message for flagname") - will display: `Warning: '-flagname' is deprecated, it will be removed soon. See usage.` - so you can only use `-f`. - - You can also group one letter flags, bif you declare - var v = flag.Bool([]string{"v", "-verbose"}, false, "help message for verbose") - var s = flag.Bool([]string{"s", "-slow"}, false, "help message for slow") - you will be able to use the -vs or -sv - - After all flags are defined, call - flag.Parse() - to parse the command line into the defined flags. - - Flags may then be used directly. If you're using the flags themselves, - they are all pointers; if you bind to variables, they're values. - fmt.Println("ip has value ", *ip) - fmt.Println("flagvar has value ", flagvar) - - After parsing, the arguments after the flag are available as the - slice flag.Args() or individually as flag.Arg(i). - The arguments are indexed from 0 through flag.NArg()-1. - - Command line flag syntax: - -flag - -flag=x - -flag="x" - -flag='x' - -flag x // non-boolean flags only - One or two minus signs may be used; they are equivalent. - The last form is not permitted for boolean flags because the - meaning of the command - cmd -x * - will change if there is a file called 0, false, etc. You must - use the -flag=false form to turn off a boolean flag. - - Flag parsing stops just before the first non-flag argument - ("-" is a non-flag argument) or after the terminator "--". - - Integer flags accept 1234, 0664, 0x1234 and may be negative. - Boolean flags may be 1, 0, t, f, true, false, TRUE, FALSE, True, False. - Duration flags accept any input valid for time.ParseDuration. - - The default set of command-line flags is controlled by - top-level functions. The FlagSet type allows one to define - independent sets of flags, such as to implement subcommands - in a command-line interface. The methods of FlagSet are - analogous to the top-level functions for the command-line - flag set. -*/ package mflag import ( @@ -277,6 +276,7 @@ type Getter interface { // ErrorHandling defines how to handle flag parsing errors. type ErrorHandling int +// ErrorHandling strategies available when a flag parsing error occurs const ( ContinueOnError ErrorHandling = iota ExitOnError @@ -358,28 +358,28 @@ func sortFlags(flags map[string]*Flag) []*Flag { } // Name returns the name of the FlagSet. -func (f *FlagSet) Name() string { - return f.name +func (fs *FlagSet) Name() string { + return fs.name } // Out returns the destination for usage and error messages. -func (f *FlagSet) Out() io.Writer { - if f.output == nil { +func (fs *FlagSet) Out() io.Writer { + if fs.output == nil { return os.Stderr } - return f.output + return fs.output } // SetOutput sets the destination for usage and error messages. // If output is nil, os.Stderr is used. -func (f *FlagSet) SetOutput(output io.Writer) { - f.output = output +func (fs *FlagSet) SetOutput(output io.Writer) { + fs.output = output } // VisitAll visits the flags in lexicographical order, calling fn for each. // It visits all flags, even those not set. -func (f *FlagSet) VisitAll(fn func(*Flag)) { - for _, flag := range sortFlags(f.formal) { +func (fs *FlagSet) VisitAll(fn func(*Flag)) { + for _, flag := range sortFlags(fs.formal) { fn(flag) } } @@ -392,8 +392,8 @@ func VisitAll(fn func(*Flag)) { // Visit visits the flags in lexicographical order, calling fn for each. // It visits only those flags that have been set. -func (f *FlagSet) Visit(fn func(*Flag)) { - for _, flag := range sortFlags(f.actual) { +func (fs *FlagSet) Visit(fn func(*Flag)) { + for _, flag := range sortFlags(fs.actual) { fn(flag) } } @@ -405,13 +405,13 @@ func Visit(fn func(*Flag)) { } // Lookup returns the Flag structure of the named flag, returning nil if none exists. -func (f *FlagSet) Lookup(name string) *Flag { - return f.formal[name] +func (fs *FlagSet) Lookup(name string) *Flag { + return fs.formal[name] } -// Indicates whether the specified flag was specified at all on the cmd line -func (f *FlagSet) IsSet(name string) bool { - return f.actual[name] != nil +// IsSet indicates whether the specified flag is set in the given FlagSet +func (fs *FlagSet) IsSet(name string) bool { + return fs.actual[name] != nil } // Lookup returns the Flag structure of the named command-line flag, @@ -420,7 +420,7 @@ func Lookup(name string) *Flag { return CommandLine.formal[name] } -// Indicates whether the specified flag was specified at all on the cmd line +// IsSet indicates whether the specified flag was specified at all on the cmd line. func IsSet(name string) bool { return CommandLine.IsSet(name) } @@ -443,15 +443,15 @@ type nArgRequirement struct { // The first parameter can be Exact, Max, or Min to respectively specify the exact, // the maximum, or the minimal number of arguments required. // The actual check is done in FlagSet.CheckArgs(). -func (f *FlagSet) Require(nArgRequirementType nArgRequirementType, nArg int) { - f.nArgRequirements = append(f.nArgRequirements, nArgRequirement{nArgRequirementType, nArg}) +func (fs *FlagSet) Require(nArgRequirementType nArgRequirementType, nArg int) { + fs.nArgRequirements = append(fs.nArgRequirements, nArgRequirement{nArgRequirementType, nArg}) } // CheckArgs uses the requirements set by FlagSet.Require() to validate // the number of arguments. If the requirements are not met, // an error message string is returned. -func (f *FlagSet) CheckArgs() (message string) { - for _, req := range f.nArgRequirements { +func (fs *FlagSet) CheckArgs() (message string) { + for _, req := range fs.nArgRequirements { var arguments string if req.N == 1 { arguments = "1 argument" @@ -460,20 +460,20 @@ func (f *FlagSet) CheckArgs() (message string) { } str := func(kind string) string { - return fmt.Sprintf("%q requires %s%s", f.name, kind, arguments) + return fmt.Sprintf("%q requires %s%s", fs.name, kind, arguments) } switch req.Type { case Exact: - if f.NArg() != req.N { + if fs.NArg() != req.N { return str("") } case Max: - if f.NArg() > req.N { + if fs.NArg() > req.N { return str("a maximum of ") } case Min: - if f.NArg() < req.N { + if fs.NArg() < req.N { return str("a minimum of ") } } @@ -482,18 +482,18 @@ func (f *FlagSet) CheckArgs() (message string) { } // Set sets the value of the named flag. -func (f *FlagSet) Set(name, value string) error { - flag, ok := f.formal[name] +func (fs *FlagSet) Set(name, value string) error { + flag, ok := fs.formal[name] if !ok { return fmt.Errorf("no such flag -%v", name) } if err := flag.Value.Set(value); err != nil { return err } - if f.actual == nil { - f.actual = make(map[string]*Flag) + if fs.actual == nil { + fs.actual = make(map[string]*Flag) } - f.actual[name] = flag + fs.actual[name] = flag return nil } @@ -504,8 +504,8 @@ func Set(name, value string) error { // PrintDefaults prints, to standard error unless configured // otherwise, the default values of all defined flags in the set. -func (f *FlagSet) PrintDefaults() { - writer := tabwriter.NewWriter(f.Out(), 20, 1, 3, ' ', 0) +func (fs *FlagSet) PrintDefaults() { + writer := tabwriter.NewWriter(fs.Out(), 20, 1, 3, ' ', 0) home := homedir.Get() // Don't substitute when HOME is / @@ -514,11 +514,11 @@ func (f *FlagSet) PrintDefaults() { } // Add a blank line between cmd description and list of options - if f.FlagCount() > 0 { + if fs.FlagCount() > 0 { fmt.Fprintln(writer, "") } - f.VisitAll(func(flag *Flag) { + fs.VisitAll(func(flag *Flag) { format := " -%s=%s" names := []string{} for _, name := range flag.Names { @@ -526,7 +526,7 @@ func (f *FlagSet) PrintDefaults() { names = append(names, name) } } - if len(names) > 0 { + if len(names) > 0 && len(flag.Usage) > 0 { val := flag.DefValue if home != "" && strings.HasPrefix(val, home) { @@ -551,13 +551,13 @@ func PrintDefaults() { } // defaultUsage is the default function to print a usage message. -func defaultUsage(f *FlagSet) { - if f.name == "" { - fmt.Fprintf(f.Out(), "Usage:\n") +func defaultUsage(fs *FlagSet) { + if fs.name == "" { + fmt.Fprintf(fs.Out(), "Usage:\n") } else { - fmt.Fprintf(f.Out(), "Usage of %s:\n", f.name) + fmt.Fprintf(fs.Out(), "Usage of %s:\n", fs.name) } - f.PrintDefaults() + fs.PrintDefaults() } // NOTE: Usage is not just defaultUsage(CommandLine) @@ -578,12 +578,12 @@ var ShortUsage = func() { } // FlagCount returns the number of flags that have been defined. -func (f *FlagSet) FlagCount() int { return len(sortFlags(f.formal)) } +func (fs *FlagSet) FlagCount() int { return len(sortFlags(fs.formal)) } // FlagCountUndeprecated returns the number of undeprecated flags that have been defined. -func (f *FlagSet) FlagCountUndeprecated() int { +func (fs *FlagSet) FlagCountUndeprecated() int { count := 0 - for _, flag := range sortFlags(f.formal) { + for _, flag := range sortFlags(fs.formal) { for _, name := range flag.Names { if name[0] != '#' { count++ @@ -595,18 +595,18 @@ func (f *FlagSet) FlagCountUndeprecated() int { } // NFlag returns the number of flags that have been set. -func (f *FlagSet) NFlag() int { return len(f.actual) } +func (fs *FlagSet) NFlag() int { return len(fs.actual) } // NFlag returns the number of command-line flags that have been set. func NFlag() int { return len(CommandLine.actual) } // Arg returns the i'th argument. Arg(0) is the first remaining argument // after flags have been processed. -func (f *FlagSet) Arg(i int) string { - if i < 0 || i >= len(f.args) { +func (fs *FlagSet) Arg(i int) string { + if i < 0 || i >= len(fs.args) { return "" } - return f.args[i] + return fs.args[i] } // Arg returns the i'th command-line argument. Arg(0) is the first remaining argument @@ -616,21 +616,21 @@ func Arg(i int) string { } // NArg is the number of arguments remaining after flags have been processed. -func (f *FlagSet) NArg() int { return len(f.args) } +func (fs *FlagSet) NArg() int { return len(fs.args) } // NArg is the number of arguments remaining after flags have been processed. func NArg() int { return len(CommandLine.args) } // Args returns the non-flag arguments. -func (f *FlagSet) Args() []string { return f.args } +func (fs *FlagSet) Args() []string { return fs.args } // Args returns the non-flag command-line arguments. func Args() []string { return CommandLine.args } // BoolVar defines a bool flag with specified name, default value, and usage string. // The argument p points to a bool variable in which to store the value of the flag. -func (f *FlagSet) BoolVar(p *bool, names []string, value bool, usage string) { - f.Var(newBoolValue(value, p), names, usage) +func (fs *FlagSet) BoolVar(p *bool, names []string, value bool, usage string) { + fs.Var(newBoolValue(value, p), names, usage) } // BoolVar defines a bool flag with specified name, default value, and usage string. @@ -641,9 +641,9 @@ func BoolVar(p *bool, names []string, value bool, usage string) { // Bool defines a bool flag with specified name, default value, and usage string. // The return value is the address of a bool variable that stores the value of the flag. -func (f *FlagSet) Bool(names []string, value bool, usage string) *bool { +func (fs *FlagSet) Bool(names []string, value bool, usage string) *bool { p := new(bool) - f.BoolVar(p, names, value, usage) + fs.BoolVar(p, names, value, usage) return p } @@ -655,8 +655,8 @@ func Bool(names []string, value bool, usage string) *bool { // IntVar defines an int flag with specified name, default value, and usage string. // The argument p points to an int variable in which to store the value of the flag. -func (f *FlagSet) IntVar(p *int, names []string, value int, usage string) { - f.Var(newIntValue(value, p), names, usage) +func (fs *FlagSet) IntVar(p *int, names []string, value int, usage string) { + fs.Var(newIntValue(value, p), names, usage) } // IntVar defines an int flag with specified name, default value, and usage string. @@ -667,9 +667,9 @@ func IntVar(p *int, names []string, value int, usage string) { // Int defines an int flag with specified name, default value, and usage string. // The return value is the address of an int variable that stores the value of the flag. -func (f *FlagSet) Int(names []string, value int, usage string) *int { +func (fs *FlagSet) Int(names []string, value int, usage string) *int { p := new(int) - f.IntVar(p, names, value, usage) + fs.IntVar(p, names, value, usage) return p } @@ -681,8 +681,8 @@ func Int(names []string, value int, usage string) *int { // Int64Var defines an int64 flag with specified name, default value, and usage string. // The argument p points to an int64 variable in which to store the value of the flag. -func (f *FlagSet) Int64Var(p *int64, names []string, value int64, usage string) { - f.Var(newInt64Value(value, p), names, usage) +func (fs *FlagSet) Int64Var(p *int64, names []string, value int64, usage string) { + fs.Var(newInt64Value(value, p), names, usage) } // Int64Var defines an int64 flag with specified name, default value, and usage string. @@ -693,9 +693,9 @@ func Int64Var(p *int64, names []string, value int64, usage string) { // Int64 defines an int64 flag with specified name, default value, and usage string. // The return value is the address of an int64 variable that stores the value of the flag. -func (f *FlagSet) Int64(names []string, value int64, usage string) *int64 { +func (fs *FlagSet) Int64(names []string, value int64, usage string) *int64 { p := new(int64) - f.Int64Var(p, names, value, usage) + fs.Int64Var(p, names, value, usage) return p } @@ -707,8 +707,8 @@ func Int64(names []string, value int64, usage string) *int64 { // UintVar defines a uint flag with specified name, default value, and usage string. // The argument p points to a uint variable in which to store the value of the flag. -func (f *FlagSet) UintVar(p *uint, names []string, value uint, usage string) { - f.Var(newUintValue(value, p), names, usage) +func (fs *FlagSet) UintVar(p *uint, names []string, value uint, usage string) { + fs.Var(newUintValue(value, p), names, usage) } // UintVar defines a uint flag with specified name, default value, and usage string. @@ -719,9 +719,9 @@ func UintVar(p *uint, names []string, value uint, usage string) { // Uint defines a uint flag with specified name, default value, and usage string. // The return value is the address of a uint variable that stores the value of the flag. -func (f *FlagSet) Uint(names []string, value uint, usage string) *uint { +func (fs *FlagSet) Uint(names []string, value uint, usage string) *uint { p := new(uint) - f.UintVar(p, names, value, usage) + fs.UintVar(p, names, value, usage) return p } @@ -733,8 +733,8 @@ func Uint(names []string, value uint, usage string) *uint { // Uint64Var defines a uint64 flag with specified name, default value, and usage string. // The argument p points to a uint64 variable in which to store the value of the flag. -func (f *FlagSet) Uint64Var(p *uint64, names []string, value uint64, usage string) { - f.Var(newUint64Value(value, p), names, usage) +func (fs *FlagSet) Uint64Var(p *uint64, names []string, value uint64, usage string) { + fs.Var(newUint64Value(value, p), names, usage) } // Uint64Var defines a uint64 flag with specified name, default value, and usage string. @@ -745,9 +745,9 @@ func Uint64Var(p *uint64, names []string, value uint64, usage string) { // Uint64 defines a uint64 flag with specified name, default value, and usage string. // The return value is the address of a uint64 variable that stores the value of the flag. -func (f *FlagSet) Uint64(names []string, value uint64, usage string) *uint64 { +func (fs *FlagSet) Uint64(names []string, value uint64, usage string) *uint64 { p := new(uint64) - f.Uint64Var(p, names, value, usage) + fs.Uint64Var(p, names, value, usage) return p } @@ -759,8 +759,8 @@ func Uint64(names []string, value uint64, usage string) *uint64 { // StringVar defines a string flag with specified name, default value, and usage string. // The argument p points to a string variable in which to store the value of the flag. -func (f *FlagSet) StringVar(p *string, names []string, value string, usage string) { - f.Var(newStringValue(value, p), names, usage) +func (fs *FlagSet) StringVar(p *string, names []string, value string, usage string) { + fs.Var(newStringValue(value, p), names, usage) } // StringVar defines a string flag with specified name, default value, and usage string. @@ -771,9 +771,9 @@ func StringVar(p *string, names []string, value string, usage string) { // String defines a string flag with specified name, default value, and usage string. // The return value is the address of a string variable that stores the value of the flag. -func (f *FlagSet) String(names []string, value string, usage string) *string { +func (fs *FlagSet) String(names []string, value string, usage string) *string { p := new(string) - f.StringVar(p, names, value, usage) + fs.StringVar(p, names, value, usage) return p } @@ -785,8 +785,8 @@ func String(names []string, value string, usage string) *string { // Float64Var defines a float64 flag with specified name, default value, and usage string. // The argument p points to a float64 variable in which to store the value of the flag. -func (f *FlagSet) Float64Var(p *float64, names []string, value float64, usage string) { - f.Var(newFloat64Value(value, p), names, usage) +func (fs *FlagSet) Float64Var(p *float64, names []string, value float64, usage string) { + fs.Var(newFloat64Value(value, p), names, usage) } // Float64Var defines a float64 flag with specified name, default value, and usage string. @@ -797,9 +797,9 @@ func Float64Var(p *float64, names []string, value float64, usage string) { // Float64 defines a float64 flag with specified name, default value, and usage string. // The return value is the address of a float64 variable that stores the value of the flag. -func (f *FlagSet) Float64(names []string, value float64, usage string) *float64 { +func (fs *FlagSet) Float64(names []string, value float64, usage string) *float64 { p := new(float64) - f.Float64Var(p, names, value, usage) + fs.Float64Var(p, names, value, usage) return p } @@ -811,8 +811,8 @@ func Float64(names []string, value float64, usage string) *float64 { // DurationVar defines a time.Duration flag with specified name, default value, and usage string. // The argument p points to a time.Duration variable in which to store the value of the flag. -func (f *FlagSet) DurationVar(p *time.Duration, names []string, value time.Duration, usage string) { - f.Var(newDurationValue(value, p), names, usage) +func (fs *FlagSet) DurationVar(p *time.Duration, names []string, value time.Duration, usage string) { + fs.Var(newDurationValue(value, p), names, usage) } // DurationVar defines a time.Duration flag with specified name, default value, and usage string. @@ -823,9 +823,9 @@ func DurationVar(p *time.Duration, names []string, value time.Duration, usage st // Duration defines a time.Duration flag with specified name, default value, and usage string. // The return value is the address of a time.Duration variable that stores the value of the flag. -func (f *FlagSet) Duration(names []string, value time.Duration, usage string) *time.Duration { +func (fs *FlagSet) Duration(names []string, value time.Duration, usage string) *time.Duration { p := new(time.Duration) - f.DurationVar(p, names, value, usage) + fs.DurationVar(p, names, value, usage) return p } @@ -841,26 +841,26 @@ func Duration(names []string, value time.Duration, usage string) *time.Duration // caller could create a flag that turns a comma-separated string into a slice // of strings by giving the slice the methods of Value; in particular, Set would // decompose the comma-separated string into the slice. -func (f *FlagSet) Var(value Value, names []string, usage string) { +func (fs *FlagSet) Var(value Value, names []string, usage string) { // Remember the default value as a string; it won't change. flag := &Flag{names, usage, value, value.String()} for _, name := range names { name = strings.TrimPrefix(name, "#") - _, alreadythere := f.formal[name] + _, alreadythere := fs.formal[name] if alreadythere { var msg string - if f.name == "" { + if fs.name == "" { msg = fmt.Sprintf("flag redefined: %s", name) } else { - msg = fmt.Sprintf("%s flag redefined: %s", f.name, name) + msg = fmt.Sprintf("%s flag redefined: %s", fs.name, name) } - fmt.Fprintln(f.Out(), msg) + fmt.Fprintln(fs.Out(), msg) panic(msg) // Happens only if flags are declared with identical names } - if f.formal == nil { - f.formal = make(map[string]*Flag) + if fs.formal == nil { + fs.formal = make(map[string]*Flag) } - f.formal[name] = flag + fs.formal[name] = flag } } @@ -876,26 +876,26 @@ func Var(value Value, names []string, usage string) { // failf prints to standard error a formatted error and usage message and // returns the error. -func (f *FlagSet) failf(format string, a ...interface{}) error { +func (fs *FlagSet) failf(format string, a ...interface{}) error { err := fmt.Errorf(format, a...) - fmt.Fprintln(f.Out(), err) - if os.Args[0] == f.name { - fmt.Fprintf(f.Out(), "See '%s --help'.\n", os.Args[0]) + fmt.Fprintln(fs.Out(), err) + if os.Args[0] == fs.name { + fmt.Fprintf(fs.Out(), "See '%s --help'.\n", os.Args[0]) } else { - fmt.Fprintf(f.Out(), "See '%s %s --help'.\n", os.Args[0], f.name) + fmt.Fprintf(fs.Out(), "See '%s %s --help'.\n", os.Args[0], fs.name) } return err } // usage calls the Usage method for the flag set, or the usage function if // the flag set is CommandLine. -func (f *FlagSet) usage() { - if f == CommandLine { +func (fs *FlagSet) usage() { + if fs == CommandLine { Usage() - } else if f.Usage == nil { - defaultUsage(f) + } else if fs.Usage == nil { + defaultUsage(fs) } else { - f.Usage() + fs.Usage() } } @@ -934,25 +934,25 @@ func trimQuotes(str string) string { } // parseOne parses one flag. It reports whether a flag was seen. -func (f *FlagSet) parseOne() (bool, string, error) { - if len(f.args) == 0 { +func (fs *FlagSet) parseOne() (bool, string, error) { + if len(fs.args) == 0 { return false, "", nil } - s := f.args[0] + s := fs.args[0] if len(s) == 0 || s[0] != '-' || len(s) == 1 { return false, "", nil } if s[1] == '-' && len(s) == 2 { // "--" terminates the flags - f.args = f.args[1:] + fs.args = fs.args[1:] return false, "", nil } name := s[1:] if len(name) == 0 || name[0] == '=' { - return false, "", f.failf("bad flag syntax: %s", s) + return false, "", fs.failf("bad flag syntax: %s", s) } // it's a flag. does it have an argument? - f.args = f.args[1:] + fs.args = fs.args[1:] hasValue := false value := "" if i := strings.Index(name, "="); i != -1 { @@ -961,44 +961,44 @@ func (f *FlagSet) parseOne() (bool, string, error) { name = name[:i] } - m := f.formal + m := fs.formal flag, alreadythere := m[name] // BUG if !alreadythere { if name == "-help" || name == "help" || name == "h" { // special case for nice help message. - f.usage() + fs.usage() return false, "", ErrHelp } if len(name) > 0 && name[0] == '-' { - return false, "", f.failf("flag provided but not defined: -%s", name) + return false, "", fs.failf("flag provided but not defined: -%s", name) } return false, name, ErrRetry } if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg if hasValue { if err := fv.Set(value); err != nil { - return false, "", f.failf("invalid boolean value %q for -%s: %v", value, name, err) + return false, "", fs.failf("invalid boolean value %q for -%s: %v", value, name, err) } } else { fv.Set("true") } } else { // It must have a value, which might be the next argument. - if !hasValue && len(f.args) > 0 { + if !hasValue && len(fs.args) > 0 { // value is the next arg hasValue = true - value, f.args = f.args[0], f.args[1:] + value, fs.args = fs.args[0], fs.args[1:] } if !hasValue { - return false, "", f.failf("flag needs an argument: -%s", name) + return false, "", fs.failf("flag needs an argument: -%s", name) } if err := flag.Value.Set(value); err != nil { - return false, "", f.failf("invalid value %q for flag -%s: %v", value, name, err) + return false, "", fs.failf("invalid value %q for flag -%s: %v", value, name, err) } } - if f.actual == nil { - f.actual = make(map[string]*Flag) + if fs.actual == nil { + fs.actual = make(map[string]*Flag) } - f.actual[name] = flag + fs.actual[name] = flag for i, n := range flag.Names { if n == fmt.Sprintf("#%s", name) { replacement := "" @@ -1009,9 +1009,9 @@ func (f *FlagSet) parseOne() (bool, string, error) { } } if replacement != "" { - fmt.Fprintf(f.Out(), "Warning: '-%s' is deprecated, it will be replaced by '-%s' soon. See usage.\n", name, replacement) + fmt.Fprintf(fs.Out(), "Warning: '-%s' is deprecated, it will be replaced by '-%s' soon. See usage.\n", name, replacement) } else { - fmt.Fprintf(f.Out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name) + fmt.Fprintf(fs.Out(), "Warning: '-%s' is deprecated, it will be removed soon. See usage.\n", name) } } } @@ -1022,11 +1022,11 @@ func (f *FlagSet) parseOne() (bool, string, error) { // include the command name. Must be called after all flags in the FlagSet // are defined and before flags are accessed by the program. // The return value will be ErrHelp if -help was set but not defined. -func (f *FlagSet) Parse(arguments []string) error { - f.parsed = true - f.args = arguments +func (fs *FlagSet) Parse(arguments []string) error { + fs.parsed = true + fs.args = arguments for { - seen, name, err := f.parseOne() + seen, name, err := fs.parseOne() if seen { continue } @@ -1037,13 +1037,13 @@ func (f *FlagSet) Parse(arguments []string) error { if len(name) > 1 { err = nil for _, letter := range strings.Split(name, "") { - f.args = append([]string{"-" + letter}, f.args...) - seen2, _, err2 := f.parseOne() + fs.args = append([]string{"-" + letter}, fs.args...) + seen2, _, err2 := fs.parseOne() if seen2 { continue } if err2 != nil { - err = f.failf("flag provided but not defined: -%s", name) + err = fs.failf("flag provided but not defined: -%s", name) break } } @@ -1051,10 +1051,10 @@ func (f *FlagSet) Parse(arguments []string) error { continue } } else { - err = f.failf("flag provided but not defined: -%s", name) + err = fs.failf("flag provided but not defined: -%s", name) } } - switch f.errorHandling { + switch fs.errorHandling { case ContinueOnError: return err case ExitOnError: @@ -1067,46 +1067,48 @@ func (f *FlagSet) Parse(arguments []string) error { } // ParseFlags is a utility function that adds a help flag if withHelp is true, -// calls cmd.Parse(args) and prints a relevant error message if there are +// calls fs.Parse(args) and prints a relevant error message if there are // incorrect number of arguments. It returns error only if error handling is // set to ContinueOnError and parsing fails. If error handling is set to // ExitOnError, it's safe to ignore the return value. -func (cmd *FlagSet) ParseFlags(args []string, withHelp bool) error { +func (fs *FlagSet) ParseFlags(args []string, withHelp bool) error { var help *bool if withHelp { - help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + help = fs.Bool([]string{"#help", "-help"}, false, "Print usage") } - if err := cmd.Parse(args); err != nil { + if err := fs.Parse(args); err != nil { return err } if help != nil && *help { - cmd.SetOutput(os.Stdout) - cmd.Usage() + fs.SetOutput(os.Stdout) + fs.Usage() os.Exit(0) } - if str := cmd.CheckArgs(); str != "" { - cmd.SetOutput(os.Stderr) - cmd.ReportError(str, withHelp) - cmd.ShortUsage() + if str := fs.CheckArgs(); str != "" { + fs.SetOutput(os.Stderr) + fs.ReportError(str, withHelp) + fs.ShortUsage() os.Exit(1) } return nil } -func (cmd *FlagSet) ReportError(str string, withHelp bool) { +// ReportError is a utility method that prints a user-friendly message +// containing the error that occurred during parsing and a suggestion to get help +func (fs *FlagSet) ReportError(str string, withHelp bool) { if withHelp { - if os.Args[0] == cmd.Name() { + if os.Args[0] == fs.Name() { str += ".\nSee '" + os.Args[0] + " --help'" } else { - str += ".\nSee '" + os.Args[0] + " " + cmd.Name() + " --help'" + str += ".\nSee '" + os.Args[0] + " " + fs.Name() + " --help'" } } - fmt.Fprintf(cmd.Out(), "docker: %s.\n", str) + fmt.Fprintf(fs.Out(), "docker: %s.\n", str) } -// Parsed reports whether f.Parse has been called. -func (f *FlagSet) Parsed() bool { - return f.parsed +// Parsed reports whether fs.Parse has been called. +func (fs *FlagSet) Parsed() bool { + return fs.parsed } // Parse parses the command-line flags from os.Args[1:]. Must be called @@ -1139,7 +1141,61 @@ func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { // Init sets the name and error handling property for a flag set. // By default, the zero FlagSet uses an empty name and the // ContinueOnError error handling policy. -func (f *FlagSet) Init(name string, errorHandling ErrorHandling) { - f.name = name - f.errorHandling = errorHandling +func (fs *FlagSet) Init(name string, errorHandling ErrorHandling) { + fs.name = name + fs.errorHandling = errorHandling +} + +type mergeVal struct { + Value + key string + fset *FlagSet +} + +func (v mergeVal) Set(s string) error { + return v.fset.Set(v.key, s) +} + +func (v mergeVal) IsBoolFlag() bool { + if b, ok := v.Value.(boolFlag); ok { + return b.IsBoolFlag() + } + return false +} + +// Merge is an helper function that merges n FlagSets into a single dest FlagSet +// In case of name collision between the flagsets it will apply +// the destination FlagSet's errorHandling behaviour. +func Merge(dest *FlagSet, flagsets ...*FlagSet) error { + for _, fset := range flagsets { + for k, f := range fset.formal { + if _, ok := dest.formal[k]; ok { + var err error + if fset.name == "" { + err = fmt.Errorf("flag redefined: %s", k) + } else { + err = fmt.Errorf("%s flag redefined: %s", fset.name, k) + } + fmt.Fprintln(fset.Out(), err.Error()) + // Happens only if flags are declared with identical names + switch dest.errorHandling { + case ContinueOnError: + return err + case ExitOnError: + os.Exit(2) + case PanicOnError: + panic(err) + } + } + newF := *f + newF.Value = mergeVal{f.Value, k, fset} + dest.formal[k] = &newF + } + } + return nil +} + +// IsEmpty reports if the FlagSet is actually empty. +func (fs *FlagSet) IsEmpty() bool { + return len(fs.actual) == 0 } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mount.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mount.go index 9a20df219f..ed7216e5c0 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mount.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mount.go @@ -5,7 +5,7 @@ import ( ) // GetMounts retrieves a list of mounts for the current running process. -func GetMounts() ([]*MountInfo, error) { +func GetMounts() ([]*Info, error) { return parseMountTable() } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo.go index 8ea08648c0..e3fc3535e9 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo.go @@ -1,10 +1,10 @@ package mount -// MountInfo reveals information about a particular mounted filesystem. This +// Info reveals information about a particular mounted filesystem. This // struct is populated from the content in the /proc//mountinfo file. -type MountInfo struct { - // Id is a unique identifier of the mount (may be reused after umount). - Id int +type Info struct { + // ID is a unique identifier of the mount (may be reused after umount). + ID int // Parent indicates the ID of the mount parent (or of self for the top of the // mount tree). diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go index add7c3b0ed..4f32edcd90 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go @@ -15,7 +15,7 @@ import ( // Parse /proc/self/mountinfo because comparing Dev and ino does not work from // bind mounts. -func parseMountTable() ([]*MountInfo, error) { +func parseMountTable() ([]*Info, error) { var rawEntries *C.struct_statfs count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT)) @@ -29,9 +29,9 @@ func parseMountTable() ([]*MountInfo, error) { header.Len = count header.Data = uintptr(unsafe.Pointer(rawEntries)) - var out []*MountInfo + var out []*Info for _, entry := range entries { - var mountinfo MountInfo + var mountinfo Info mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0]) mountinfo.Source = C.GoString(&entry.f_mntfromname[0]) mountinfo.Fstype = C.GoString(&entry.f_fstypename[0]) diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_linux.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_linux.go index 351a58ea00..be69fee1d7 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_linux.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_linux.go @@ -30,7 +30,7 @@ const ( // Parse /proc/self/mountinfo because comparing Dev and ino does not work from // bind mounts -func parseMountTable() ([]*MountInfo, error) { +func parseMountTable() ([]*Info, error) { f, err := os.Open("/proc/self/mountinfo") if err != nil { return nil, err @@ -40,10 +40,10 @@ func parseMountTable() ([]*MountInfo, error) { return parseInfoFile(f) } -func parseInfoFile(r io.Reader) ([]*MountInfo, error) { +func parseInfoFile(r io.Reader) ([]*Info, error) { var ( s = bufio.NewScanner(r) - out = []*MountInfo{} + out = []*Info{} ) for s.Scan() { @@ -52,13 +52,13 @@ func parseInfoFile(r io.Reader) ([]*MountInfo, error) { } var ( - p = &MountInfo{} + p = &Info{} text = s.Text() optionalFields string ) if _, err := fmt.Sscanf(text, mountinfoFormat, - &p.Id, &p.Parent, &p.Major, &p.Minor, + &p.ID, &p.Parent, &p.Major, &p.Minor, &p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil { return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err) } @@ -84,7 +84,7 @@ func parseInfoFile(r io.Reader) ([]*MountInfo, error) { // PidMountInfo collects the mounts for a specific process ID. If the process // ID is unknown, it is better to use `GetMounts` which will inspect // "/proc/self/mountinfo" instead. -func PidMountInfo(pid int) ([]*MountInfo, error) { +func PidMountInfo(pid int) ([]*Info, error) { f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid)) if err != nil { return nil, err diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_linux_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_linux_test.go index e92b7e2c74..812d12e826 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_linux_test.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_linux_test.go @@ -457,8 +457,8 @@ func TestParseFedoraMountinfoFields(t *testing.T) { if len(infos) != expectedLength { t.Fatalf("Expected %d entries, got %d", expectedLength, len(infos)) } - mi := MountInfo{ - Id: 15, + mi := Info{ + ID: 15, Parent: 35, Major: 0, Minor: 3, diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go index 352336b9a3..8245f01d42 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go @@ -7,6 +7,6 @@ import ( "runtime" ) -func parseMountTable() ([]*MountInfo, error) { +func parseMountTable() ([]*Info, error) { return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/sharedsubtree_linux_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/sharedsubtree_linux_test.go index da9aa15015..4a8d22f02f 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/sharedsubtree_linux_test.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/mount/sharedsubtree_linux_test.go @@ -107,7 +107,7 @@ func TestSubtreePrivate(t *testing.T) { } // Testing that when a target is a shared mount, -// then child mounts propogate to the source +// then child mounts propagate to the source func TestSubtreeShared(t *testing.T) { tmp := path.Join(os.TempDir(), "mount-tests") if err := os.MkdirAll(tmp, 0777); err != nil { diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client.go index 890fd7c268..54c386e480 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client.go @@ -5,31 +5,58 @@ import ( "encoding/json" "fmt" "io/ioutil" - "net" "net/http" "strings" "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/sockets" + "github.com/docker/docker/pkg/tlsconfig" ) const ( - versionMimetype = "application/vnd.docker.plugins.v1+json" + versionMimetype = "application/vnd.docker.plugins.v1.1+json" defaultTimeOut = 30 ) -func NewClient(addr string) *Client { +type remoteError struct { + method string + err string +} + +func (e *remoteError) Error() string { + return fmt.Sprintf("Plugin Error: %s, %s", e.err, e.method) +} + +// NewClient creates a new plugin client (http). +func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) { tr := &http.Transport{} + + c, err := tlsconfig.Client(tlsConfig) + if err != nil { + return nil, err + } + tr.TLSClientConfig = c + protoAndAddr := strings.Split(addr, "://") - configureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1]) - return &Client{&http.Client{Transport: tr}, protoAndAddr[1]} + sockets.ConfigureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1]) + + scheme := protoAndAddr[0] + if scheme != "https" { + scheme = "http" + } + return &Client{&http.Client{Transport: tr}, scheme, protoAndAddr[1]}, nil } +// Client represents a plugin client. type Client struct { - http *http.Client - addr string + http *http.Client // http client to use + scheme string // scheme protocol of the plugin + addr string // http address of the plugin } +// Call calls the specified method with the specified arguments for the plugin. +// It will retry for 30 seconds if a failure occurs when calling. func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error { return c.callWithRetry(serviceMethod, args, ret, true) } @@ -45,7 +72,7 @@ func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret inter return err } req.Header.Add("Accept", versionMimetype) - req.URL.Scheme = "http" + req.URL.Scheme = c.scheme req.URL.Host = c.addr var retries int @@ -72,9 +99,9 @@ func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret inter if resp.StatusCode != http.StatusOK { remoteErr, err := ioutil.ReadAll(resp.Body) if err != nil { - return fmt.Errorf("Plugin Error: %s", err) + return &remoteError{err.Error(), serviceMethod} } - return fmt.Errorf("Plugin Error: %s", remoteErr) + return &remoteError{string(remoteErr), serviceMethod} } return json.NewDecoder(resp.Body).Decode(&ret) @@ -94,20 +121,5 @@ func backoff(retries int) time.Duration { } func abort(start time.Time, timeOff time.Duration) bool { - return timeOff+time.Since(start) > time.Duration(defaultTimeOut)*time.Second -} - -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 - } + return timeOff+time.Since(start) >= time.Duration(defaultTimeOut)*time.Second } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client_test.go index 0f7cd34dfa..60f1263faa 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client_test.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/client_test.go @@ -7,6 +7,8 @@ import ( "reflect" "testing" "time" + + "github.com/docker/docker/pkg/tlsconfig" ) var ( @@ -27,7 +29,7 @@ func teardownRemotePluginServer() { } func TestFailedConnection(t *testing.T) { - c := NewClient("tcp://127.0.0.1:1") + c, _ := NewClient("tcp://127.0.0.1:1", tlsconfig.Options{InsecureSkipVerify: true}) err := c.callWithRetry("Service.Method", nil, nil, false) if err == nil { t.Fatal("Unexpected successful connection") @@ -51,7 +53,7 @@ func TestEchoInputOutput(t *testing.T) { io.Copy(w, r.Body) }) - c := NewClient(addr) + c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true}) var output Manifest err := c.Call("Test.Echo", m, &output) if err != nil { @@ -103,3 +105,19 @@ func TestAbortRetry(t *testing.T) { } } } + +func TestClientScheme(t *testing.T) { + cases := map[string]string{ + "tcp://127.0.0.1:8080": "http", + "unix:///usr/local/plugins/foo": "http", + "http://127.0.0.1:8080": "http", + "https://127.0.0.1:8080": "https", + } + + for addr, scheme := range cases { + c, _ := NewClient(addr, tlsconfig.Options{InsecureSkipVerify: true}) + if c.scheme != scheme { + t.Fatalf("URL scheme mismatch, expected %s, got %s", scheme, c.scheme) + } + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery.go index 3a42ba6d17..7bd470db08 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery.go @@ -1,6 +1,7 @@ package plugins import ( + "encoding/json" "errors" "fmt" "io/ioutil" @@ -10,52 +11,56 @@ import ( "strings" ) -const defaultLocalRegistry = "/usr/share/docker/plugins" - var ( + // ErrNotFound plugin not found ErrNotFound = errors.New("Plugin not found") + socketsPath = "/run/docker/plugins" + specsPaths = []string{"/etc/docker/plugins", "/usr/lib/docker/plugins"} ) +// Registry defines behavior of a registry of plugins. type Registry interface { + // Plugins lists all plugins. Plugins() ([]*Plugin, error) + // Plugin returns the plugin registered with the given name (or returns an error). Plugin(name string) (*Plugin, error) } -type LocalRegistry struct { - path string -} - -func newLocalRegistry(path string) *LocalRegistry { - if len(path) == 0 { - path = defaultLocalRegistry - } +// LocalRegistry defines a registry that is local (using unix socket). +type LocalRegistry struct{} - return &LocalRegistry{path} +func newLocalRegistry() LocalRegistry { + return LocalRegistry{} } +// Plugin returns the plugin registered with the given name (or returns an error). 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) + socketpaths := pluginPaths(socketsPath, name, ".sock") + + for _, p := range socketpaths { + if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 { + return newLocalPlugin(name, "unix://"+p), nil + } } - return nil, ErrNotFound -} -func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) { - name := strings.Split(fi.Name(), ".")[0] + var txtspecpaths []string + for _, p := range specsPaths { + txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".spec")...) + txtspecpaths = append(txtspecpaths, pluginPaths(p, name, ".json")...) + } - if fi.Mode()&os.ModeSocket != 0 { - return &Plugin{ - Name: name, - Addr: "unix://" + path, - }, nil + for _, p := range txtspecpaths { + if _, err := os.Stat(p); err == nil { + if strings.HasSuffix(p, ".json") { + return readPluginJSONInfo(name, p) + } + return readPluginInfo(name, p) + } } + return nil, ErrNotFound +} +func readPluginInfo(name, path string) (*Plugin, error) { content, err := ioutil.ReadFile(path) if err != nil { return nil, err @@ -71,8 +76,31 @@ func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) { return nil, fmt.Errorf("Unknown protocol") } - return &Plugin{ - Name: name, - Addr: addr, - }, nil + return newLocalPlugin(name, addr), nil +} + +func readPluginJSONInfo(name, path string) (*Plugin, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + var p Plugin + if err := json.NewDecoder(f).Decode(&p); err != nil { + return nil, err + } + p.Name = name + if len(p.TLSConfig.CAFile) == 0 { + p.TLSConfig.InsecureSkipVerify = true + } + + return &p, nil +} + +func pluginPaths(base, name, ext string) []string { + return []string{ + filepath.Join(base, name+ext), + filepath.Join(base, name, name+ext), + } } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery_test.go index a818dc3f3c..5610fe1e9f 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery_test.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/discovery_test.go @@ -10,62 +10,72 @@ import ( "testing" ) -func TestUnknownLocalPath(t *testing.T) { +func setup(t *testing.T) (string, func()) { tmpdir, err := ioutil.TempDir("", "docker-test") if err != nil { t.Fatal(err) } - defer os.RemoveAll(tmpdir) + backup := socketsPath + socketsPath = tmpdir + specsPaths = []string{tmpdir} - l := newLocalRegistry(filepath.Join(tmpdir, "unknown")) - _, err = l.Plugin("foo") - if err == nil || err != ErrNotFound { - t.Fatalf("Expected error for unknown directory") + return tmpdir, func() { + socketsPath = backup + os.RemoveAll(tmpdir) } } 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() + tmpdir, unregister := setup(t) + defer unregister() - r := newLocalRegistry(tmpdir) - p, err := r.Plugin("echo") - if err != nil { - t.Fatal(err) + cases := []string{ + filepath.Join(tmpdir, "echo.sock"), + filepath.Join(tmpdir, "echo", "echo.sock"), } - 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) - } + for _, c := range cases { + if err := os.MkdirAll(filepath.Dir(c), 0755); err != nil { + t.Fatal(err) + } - if p.Name != "echo" { - t.Fatalf("Expected plugin `echo`, got %s\n", p.Name) - } + l, err := net.Listen("unix", c) + if err != nil { + t.Fatal(err) + } + + r := newLocalRegistry() + 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) + addr := fmt.Sprintf("unix://%s", c) + if p.Addr != addr { + t.Fatalf("Expected plugin addr `%s`, got %s\n", addr, p.Addr) + } + if p.TLSConfig.InsecureSkipVerify != true { + t.Fatalf("Expected TLS verification to be skipped") + } + l.Close() } } func TestFileSpecPlugin(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "docker-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpdir) + tmpdir, unregister := setup(t) + defer unregister() cases := []struct { path string @@ -74,16 +84,21 @@ func TestFileSpecPlugin(t *testing.T) { fail bool }{ {filepath.Join(tmpdir, "echo.spec"), "echo", "unix://var/lib/docker/plugins/echo.sock", false}, + {filepath.Join(tmpdir, "echo", "echo.spec"), "echo", "unix://var/lib/docker/plugins/echo.sock", false}, {filepath.Join(tmpdir, "foo.spec"), "foo", "tcp://localhost:8080", false}, + {filepath.Join(tmpdir, "foo", "foo.spec"), "foo", "tcp://localhost:8080", false}, {filepath.Join(tmpdir, "bar.spec"), "bar", "localhost:8080", true}, // unknown transport } for _, c := range cases { - if err = ioutil.WriteFile(c.path, []byte(c.addr), 0644); err != nil { + if err := os.MkdirAll(filepath.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) + r := newLocalRegistry() p, err := r.Plugin(c.name) if c.fail && err == nil { continue @@ -100,5 +115,55 @@ func TestFileSpecPlugin(t *testing.T) { if p.Addr != c.addr { t.Fatalf("Expected plugin addr `%s`, got %s\n", c.addr, p.Addr) } + + if p.TLSConfig.InsecureSkipVerify != true { + t.Fatalf("Expected TLS verification to be skipped") + } + } +} + +func TestFileJSONSpecPlugin(t *testing.T) { + tmpdir, unregister := setup(t) + defer unregister() + + p := filepath.Join(tmpdir, "example.json") + spec := `{ + "Name": "plugin-example", + "Addr": "https://example.com/docker/plugin", + "TLSConfig": { + "CAFile": "/usr/shared/docker/certs/example-ca.pem", + "CertFile": "/usr/shared/docker/certs/example-cert.pem", + "KeyFile": "/usr/shared/docker/certs/example-key.pem" + } +}` + + if err := ioutil.WriteFile(p, []byte(spec), 0644); err != nil { + t.Fatal(err) + } + + r := newLocalRegistry() + plugin, err := r.Plugin("example") + if err != nil { + t.Fatal(err) + } + + if plugin.Name != "example" { + t.Fatalf("Expected plugin `plugin-example`, got %s\n", plugin.Name) + } + + if plugin.Addr != "https://example.com/docker/plugin" { + t.Fatalf("Expected plugin addr `https://example.com/docker/plugin`, got %s\n", plugin.Addr) + } + + if plugin.TLSConfig.CAFile != "/usr/shared/docker/certs/example-ca.pem" { + t.Fatalf("Expected plugin CA `/usr/shared/docker/certs/example-ca.pem`, got %s\n", plugin.TLSConfig.CAFile) + } + + if plugin.TLSConfig.CertFile != "/usr/shared/docker/certs/example-cert.pem" { + t.Fatalf("Expected plugin Certificate `/usr/shared/docker/certs/example-cert.pem`, got %s\n", plugin.TLSConfig.CertFile) + } + + if plugin.TLSConfig.KeyFile != "/usr/shared/docker/certs/example-key.pem" { + t.Fatalf("Expected plugin Key `/usr/shared/docker/certs/example-key.pem`, got %s\n", plugin.TLSConfig.KeyFile) } } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/README.md b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/README.md new file mode 100644 index 0000000000..98720b2127 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/README.md @@ -0,0 +1,68 @@ +Plugin RPC Generator +==================== + +Generates go code from a Go interface definition for proxying between the plugin +API and the subsystem being extended. + +## Usage + +Given an interface definition: + +```go +type volumeDriver interface { + Create(name string, opts opts) (err error) + Remove(name string) (err error) + Path(name string) (mountpoint string, err error) + Mount(name string) (mountpoint string, err error) + Unmount(name string) (err error) +} +``` + +**Note**: All function options and return values must be named in the definition. + +Run the generator: + +```bash +$ pluginrpc-gen --type volumeDriver --name VolumeDriver -i volumes/drivers/extpoint.go -o volumes/drivers/proxy.go +``` + +Where: +- `--type` is the name of the interface to use +- `--name` is the subsystem that the plugin "Implements" +- `-i` is the input file containing the interface definition +- `-o` is the output file where the the generated code should go + +**Note**: The generated code will use the same package name as the one defined in the input file + +Optionally, you can skip functions on the interface that should not be +implemented in the generated proxy code by passing in the function name to `--skip`. +This flag can be specified multiple times. + +You can also add build tags that should be prepended to the generated code by +supplying `--tag`. This flag can be specified multiple times. + +## Known issues + +The parser can currently only handle types which are not specifically a map or +a slice. +You can, however, create a type that uses a map or a slice internally, for instance: + +```go +type opts map[string]string +``` + +This `opts` type will work, whreas using a `map[string]string` directly will not. + +## go-generate + +You can also use this with go-generate, which is pretty awesome. +To do so, place the code at the top of the file which contains the interface +definition (i.e., the input file): + +```go +//go:generate pluginrpc-gen -i $GOFILE -o proxy.go -type volumeDriver -name VolumeDriver +``` + +Then cd to the package dir and run `go generate` + +**Note**: the `pluginrpc-gen` binary must be within your `$PATH` diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/fixtures/foo.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/fixtures/foo.go new file mode 100644 index 0000000000..fcb2b6231d --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/fixtures/foo.go @@ -0,0 +1,41 @@ +package foo + +type wobble struct { + Some string + Val string + Inception *wobble +} + +// Fooer is an empty interface used for tests. +type Fooer interface{} + +// Fooer2 is an interface used for tests. +type Fooer2 interface { + Foo() +} + +// Fooer3 is an interface used for tests. +type Fooer3 interface { + Foo() + Bar(a string) + Baz(a string) (err error) + Qux(a, b string) (val string, err error) + Wobble() (w *wobble) + Wiggle() (w wobble) +} + +// Fooer4 is an interface used for tests. +type Fooer4 interface { + Foo() error +} + +// Bar is an interface used for tests. +type Bar interface { + Boo(a string, b string) (s string, err error) +} + +// Fooer5 is an interface used for tests. +type Fooer5 interface { + Foo() + Bar +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/main.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/main.go new file mode 100644 index 0000000000..772984ce3a --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/main.go @@ -0,0 +1,91 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "go/format" + "io/ioutil" + "os" + "unicode" + "unicode/utf8" +) + +type stringSet struct { + values map[string]struct{} +} + +func (s stringSet) String() string { + return "" +} + +func (s stringSet) Set(value string) error { + s.values[value] = struct{}{} + return nil +} +func (s stringSet) GetValues() map[string]struct{} { + return s.values +} + +var ( + typeName = flag.String("type", "", "interface type to generate plugin rpc proxy for") + rpcName = flag.String("name", *typeName, "RPC name, set if different from type") + inputFile = flag.String("i", "", "input file path") + outputFile = flag.String("o", *inputFile+"_proxy.go", "output file path") + + skipFuncs map[string]struct{} + flSkipFuncs = stringSet{make(map[string]struct{})} + + flBuildTags = stringSet{make(map[string]struct{})} +) + +func errorOut(msg string, err error) { + if err == nil { + return + } + fmt.Fprintf(os.Stderr, "%s: %v\n", msg, err) + os.Exit(1) +} + +func checkFlags() error { + if *outputFile == "" { + return fmt.Errorf("missing required flag `-o`") + } + if *inputFile == "" { + return fmt.Errorf("missing required flag `-i`") + } + return nil +} + +func main() { + flag.Var(flSkipFuncs, "skip", "skip parsing for function") + flag.Var(flBuildTags, "tag", "build tags to add to generated files") + flag.Parse() + skipFuncs = flSkipFuncs.GetValues() + + errorOut("error", checkFlags()) + + pkg, err := Parse(*inputFile, *typeName) + errorOut(fmt.Sprintf("error parsing requested type %s", *typeName), err) + + var analysis = struct { + InterfaceType string + RPCName string + BuildTags map[string]struct{} + *ParsedPkg + }{toLower(*typeName), *rpcName, flBuildTags.GetValues(), pkg} + var buf bytes.Buffer + + errorOut("parser error", generatedTempl.Execute(&buf, analysis)) + src, err := format.Source(buf.Bytes()) + errorOut("error formating generated source", err) + errorOut("error writing file", ioutil.WriteFile(*outputFile, src, 0644)) +} + +func toLower(s string) string { + if s == "" { + return "" + } + r, n := utf8.DecodeRuneInString(s) + return string(unicode.ToLower(r)) + s[n:] +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/parser.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/parser.go new file mode 100644 index 0000000000..3adeb4905c --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/parser.go @@ -0,0 +1,163 @@ +package main + +import ( + "errors" + "fmt" + "go/ast" + "go/parser" + "go/token" + "reflect" +) + +var errBadReturn = errors.New("found return arg with no name: all args must be named") + +type errUnexpectedType struct { + expected string + actual interface{} +} + +func (e errUnexpectedType) Error() string { + return fmt.Sprintf("got wrong type expecting %s, got: %v", e.expected, reflect.TypeOf(e.actual)) +} + +// ParsedPkg holds information about a package that has been parsed, +// its name and the list of functions. +type ParsedPkg struct { + Name string + Functions []function +} + +type function struct { + Name string + Args []arg + Returns []arg + Doc string +} + +type arg struct { + Name string + ArgType string +} + +func (a *arg) String() string { + return a.Name + " " + a.ArgType +} + +// Parse parses the given file for an interface definition with the given name. +func Parse(filePath string, objName string) (*ParsedPkg, error) { + fs := token.NewFileSet() + pkg, err := parser.ParseFile(fs, filePath, nil, parser.AllErrors) + if err != nil { + return nil, err + } + p := &ParsedPkg{} + p.Name = pkg.Name.Name + obj, exists := pkg.Scope.Objects[objName] + if !exists { + return nil, fmt.Errorf("could not find object %s in %s", objName, filePath) + } + if obj.Kind != ast.Typ { + return nil, fmt.Errorf("exected type, got %s", obj.Kind) + } + spec, ok := obj.Decl.(*ast.TypeSpec) + if !ok { + return nil, errUnexpectedType{"*ast.TypeSpec", obj.Decl} + } + iface, ok := spec.Type.(*ast.InterfaceType) + if !ok { + return nil, errUnexpectedType{"*ast.InterfaceType", spec.Type} + } + + p.Functions, err = parseInterface(iface) + if err != nil { + return nil, err + } + + return p, nil +} + +func parseInterface(iface *ast.InterfaceType) ([]function, error) { + var functions []function + for _, field := range iface.Methods.List { + switch f := field.Type.(type) { + case *ast.FuncType: + method, err := parseFunc(field) + if err != nil { + return nil, err + } + if method == nil { + continue + } + functions = append(functions, *method) + case *ast.Ident: + spec, ok := f.Obj.Decl.(*ast.TypeSpec) + if !ok { + return nil, errUnexpectedType{"*ast.TypeSpec", f.Obj.Decl} + } + iface, ok := spec.Type.(*ast.InterfaceType) + if !ok { + return nil, errUnexpectedType{"*ast.TypeSpec", spec.Type} + } + funcs, err := parseInterface(iface) + if err != nil { + fmt.Println(err) + continue + } + functions = append(functions, funcs...) + default: + return nil, errUnexpectedType{"*astFuncType or *ast.Ident", f} + } + } + return functions, nil +} + +func parseFunc(field *ast.Field) (*function, error) { + f := field.Type.(*ast.FuncType) + method := &function{Name: field.Names[0].Name} + if _, exists := skipFuncs[method.Name]; exists { + fmt.Println("skipping:", method.Name) + return nil, nil + } + if f.Params != nil { + args, err := parseArgs(f.Params.List) + if err != nil { + return nil, err + } + method.Args = args + } + if f.Results != nil { + returns, err := parseArgs(f.Results.List) + if err != nil { + return nil, fmt.Errorf("error parsing function returns for %q: %v", method.Name, err) + } + method.Returns = returns + } + return method, nil +} + +func parseArgs(fields []*ast.Field) ([]arg, error) { + var args []arg + for _, f := range fields { + if len(f.Names) == 0 { + return nil, errBadReturn + } + for _, name := range f.Names { + var typeName string + switch argType := f.Type.(type) { + case *ast.Ident: + typeName = argType.Name + case *ast.StarExpr: + i, ok := argType.X.(*ast.Ident) + if !ok { + return nil, errUnexpectedType{"*ast.Ident", f.Type} + } + typeName = "*" + i.Name + default: + return nil, errUnexpectedType{"*ast.Ident or *ast.StarExpr", f.Type} + } + + args = append(args, arg{name.Name, typeName}) + } + } + return args, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/parser_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/parser_test.go new file mode 100644 index 0000000000..5a7579cfa8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/parser_test.go @@ -0,0 +1,168 @@ +package main + +import ( + "fmt" + "path/filepath" + "runtime" + "strings" + "testing" +) + +const testFixture = "fixtures/foo.go" + +func TestParseEmptyInterface(t *testing.T) { + pkg, err := Parse(testFixture, "Fooer") + if err != nil { + t.Fatal(err) + } + + assertName(t, "foo", pkg.Name) + assertNum(t, 0, len(pkg.Functions)) +} + +func TestParseNonInterfaceType(t *testing.T) { + _, err := Parse(testFixture, "wobble") + if _, ok := err.(errUnexpectedType); !ok { + t.Fatal("expected type error when parsing non-interface type") + } +} + +func TestParseWithOneFunction(t *testing.T) { + pkg, err := Parse(testFixture, "Fooer2") + if err != nil { + t.Fatal(err) + } + + assertName(t, "foo", pkg.Name) + assertNum(t, 1, len(pkg.Functions)) + assertName(t, "Foo", pkg.Functions[0].Name) + assertNum(t, 0, len(pkg.Functions[0].Args)) + assertNum(t, 0, len(pkg.Functions[0].Returns)) +} + +func TestParseWithMultipleFuncs(t *testing.T) { + pkg, err := Parse(testFixture, "Fooer3") + if err != nil { + t.Fatal(err) + } + + assertName(t, "foo", pkg.Name) + assertNum(t, 6, len(pkg.Functions)) + + f := pkg.Functions[0] + assertName(t, "Foo", f.Name) + assertNum(t, 0, len(f.Args)) + assertNum(t, 0, len(f.Returns)) + + f = pkg.Functions[1] + assertName(t, "Bar", f.Name) + assertNum(t, 1, len(f.Args)) + assertNum(t, 0, len(f.Returns)) + arg := f.Args[0] + assertName(t, "a", arg.Name) + assertName(t, "string", arg.ArgType) + + f = pkg.Functions[2] + assertName(t, "Baz", f.Name) + assertNum(t, 1, len(f.Args)) + assertNum(t, 1, len(f.Returns)) + arg = f.Args[0] + assertName(t, "a", arg.Name) + assertName(t, "string", arg.ArgType) + arg = f.Returns[0] + assertName(t, "err", arg.Name) + assertName(t, "error", arg.ArgType) + + f = pkg.Functions[3] + assertName(t, "Qux", f.Name) + assertNum(t, 2, len(f.Args)) + assertNum(t, 2, len(f.Returns)) + arg = f.Args[0] + assertName(t, "a", f.Args[0].Name) + assertName(t, "string", f.Args[0].ArgType) + arg = f.Args[1] + assertName(t, "b", arg.Name) + assertName(t, "string", arg.ArgType) + arg = f.Returns[0] + assertName(t, "val", arg.Name) + assertName(t, "string", arg.ArgType) + arg = f.Returns[1] + assertName(t, "err", arg.Name) + assertName(t, "error", arg.ArgType) + + f = pkg.Functions[4] + assertName(t, "Wobble", f.Name) + assertNum(t, 0, len(f.Args)) + assertNum(t, 1, len(f.Returns)) + arg = f.Returns[0] + assertName(t, "w", arg.Name) + assertName(t, "*wobble", arg.ArgType) + + f = pkg.Functions[5] + assertName(t, "Wiggle", f.Name) + assertNum(t, 0, len(f.Args)) + assertNum(t, 1, len(f.Returns)) + arg = f.Returns[0] + assertName(t, "w", arg.Name) + assertName(t, "wobble", arg.ArgType) +} + +func TestParseWithUnamedReturn(t *testing.T) { + _, err := Parse(testFixture, "Fooer4") + if !strings.HasSuffix(err.Error(), errBadReturn.Error()) { + t.Fatalf("expected ErrBadReturn, got %v", err) + } +} + +func TestEmbeddedInterface(t *testing.T) { + pkg, err := Parse(testFixture, "Fooer5") + if err != nil { + t.Fatal(err) + } + + assertName(t, "foo", pkg.Name) + assertNum(t, 2, len(pkg.Functions)) + + f := pkg.Functions[0] + assertName(t, "Foo", f.Name) + assertNum(t, 0, len(f.Args)) + assertNum(t, 0, len(f.Returns)) + + f = pkg.Functions[1] + assertName(t, "Boo", f.Name) + assertNum(t, 2, len(f.Args)) + assertNum(t, 2, len(f.Returns)) + + arg := f.Args[0] + assertName(t, "a", arg.Name) + assertName(t, "string", arg.ArgType) + + arg = f.Args[1] + assertName(t, "b", arg.Name) + assertName(t, "string", arg.ArgType) + + arg = f.Returns[0] + assertName(t, "s", arg.Name) + assertName(t, "string", arg.ArgType) + + arg = f.Returns[1] + assertName(t, "err", arg.Name) + assertName(t, "error", arg.ArgType) +} + +func assertName(t *testing.T, expected, actual string) { + if expected != actual { + fatalOut(t, fmt.Sprintf("expected name to be `%s`, got: %s", expected, actual)) + } +} + +func assertNum(t *testing.T, expected, actual int) { + if expected != actual { + fatalOut(t, fmt.Sprintf("expected number to be %d, got: %d", expected, actual)) + } +} + +func fatalOut(t *testing.T, msg string) { + _, file, ln, _ := runtime.Caller(2) + t.Fatalf("%s:%d: %s", filepath.Base(file), ln, msg) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/template.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/template.go new file mode 100644 index 0000000000..704030cf0f --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/pluginrpc-gen/template.go @@ -0,0 +1,97 @@ +package main + +import ( + "strings" + "text/template" +) + +func printArgs(args []arg) string { + var argStr []string + for _, arg := range args { + argStr = append(argStr, arg.String()) + } + return strings.Join(argStr, ", ") +} + +func marshalType(t string) string { + switch t { + case "error": + // convert error types to plain strings to ensure the values are encoded/decoded properly + return "string" + default: + return t + } +} + +func isErr(t string) bool { + switch t { + case "error": + return true + default: + return false + } +} + +// Need to use this helper due to issues with go-vet +func buildTag(s string) string { + return "+build " + s +} + +var templFuncs = template.FuncMap{ + "printArgs": printArgs, + "marshalType": marshalType, + "isErr": isErr, + "lower": strings.ToLower, + "title": strings.Title, + "tag": buildTag, +} + +var generatedTempl = template.Must(template.New("rpc_cient").Funcs(templFuncs).Parse(` +// generated code - DO NOT EDIT +{{ range $k, $v := .BuildTags }} + // {{ tag $k }} {{ end }} + +package {{ .Name }} + +import "errors" + +type client interface{ + Call(string, interface{}, interface{}) error +} + +type {{ .InterfaceType }}Proxy struct { + client +} + +{{ range .Functions }} + type {{ $.InterfaceType }}Proxy{{ .Name }}Request struct{ + {{ range .Args }} + {{ title .Name }} {{ .ArgType }} {{ end }} + } + + type {{ $.InterfaceType }}Proxy{{ .Name }}Response struct{ + {{ range .Returns }} + {{ title .Name }} {{ marshalType .ArgType }} {{ end }} + } + + func (pp *{{ $.InterfaceType }}Proxy) {{ .Name }}({{ printArgs .Args }}) ({{ printArgs .Returns }}) { + var( + req {{ $.InterfaceType }}Proxy{{ .Name }}Request + ret {{ $.InterfaceType }}Proxy{{ .Name }}Response + ) + {{ range .Args }} + req.{{ title .Name }} = {{ lower .Name }} {{ end }} + if err = pp.Call("{{ $.RPCName }}.{{ .Name }}", req, &ret); err != nil { + return + } + {{ range $r := .Returns }} + {{ if isErr .ArgType }} + if ret.{{ title .Name }} != "" { + {{ lower .Name }} = errors.New(ret.{{ title .Name }}) + } {{ end }} + {{ if isErr .ArgType | not }} {{ lower .Name }} = ret.{{ title .Name }} {{ end }} {{ end }} + + return + } +{{ end }} +`)) diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/plugins.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/plugins.go index 47519486bd..f9e22d651f 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/plugins.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/plugins/plugins.go @@ -1,13 +1,38 @@ +// Package plugins provides structures and helper functions to manage Docker +// plugins. +// +// Docker discovers plugins by looking for them in the plugin directory whenever +// a user or container tries to use one by name. UNIX domain socket files must +// be located under /run/docker/plugins, whereas spec files can be located +// either under /etc/docker/plugins or /usr/lib/docker/plugins. This is handled +// by the Registry interface, which lets you list all plugins or get a plugin by +// its name if it exists. +// +// The plugins need to implement an HTTP server and bind this to the UNIX socket +// or the address specified in the spec files. +// A handshake is send at /Plugin.Activate, and plugins are expected to return +// a Manifest with a list of of Docker subsystems which this plugin implements. +// +// In order to use a plugins, you can use the ``Get`` with the name of the +// plugin and the subsystem it implements. +// +// plugin, err := plugins.Get("example", "VolumeDriver") +// if err != nil { +// return fmt.Errorf("Error looking up volume plugin example: %v", err) +// } package plugins import ( "errors" "sync" + "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/tlsconfig" ) var ( + // ErrNotImplements is returned if the plugin does not implement the requested driver. ErrNotImplements = errors.New("Plugin does not implement the requested driver") ) @@ -21,27 +46,59 @@ var ( extpointHandlers = make(map[string]func(string, *Client)) ) +// Manifest lists what a plugin implements. type Manifest struct { + // List of subsystem the plugin implements. Implements []string } +// Plugin is the definition of a docker plugin. type Plugin struct { - Name string - Addr string - Client *Client - Manifest *Manifest + // Name of the plugin + Name string `json:"-"` + // Address of the plugin + Addr string + // TLS configuration of the plugin + TLSConfig tlsconfig.Options + // Client attached to the plugin + Client *Client `json:"-"` + // Manifest of the plugin (see above) + Manifest *Manifest `json:"-"` + + activatErr error + activateOnce sync.Once +} + +func newLocalPlugin(name, addr string) *Plugin { + return &Plugin{ + Name: name, + Addr: addr, + TLSConfig: tlsconfig.Options{InsecureSkipVerify: true}, + } } func (p *Plugin) activate() error { - m := new(Manifest) - p.Client = NewClient(p.Addr) - err := p.Client.Call("Plugin.Activate", nil, m) + p.activateOnce.Do(func() { + p.activatErr = p.activateWithLock() + }) + return p.activatErr +} + +func (p *Plugin) activateWithLock() error { + c, err := NewClient(p.Addr, p.TLSConfig) if err != nil { return err } + p.Client = c + + m := new(Manifest) + if err = p.Client.Call("Plugin.Activate", nil, m); 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 { @@ -53,34 +110,58 @@ func (p *Plugin) activate() error { } 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 loadWithRetry(name, true) +} + +func loadWithRetry(name string, retry bool) (*Plugin, error) { + registry := newLocalRegistry() + start := time.Now() + + var retries int + for { + pl, err := registry.Plugin(name) + if err != nil { + if !retry { + return nil, err + } + + timeOff := backoff(retries) + if abort(start, timeOff) { + return nil, err + } + retries++ + logrus.Warnf("Unable to locate plugin: %s, retrying in %v", name, timeOff) + time.Sleep(timeOff) + continue + } + + storage.Lock() + storage.plugins[name] = pl + storage.Unlock() + + err = pl.activate() + + if err != nil { + storage.Lock() + delete(storage.plugins, name) + storage.Unlock() + } + + return pl, err } - return pl, nil } func get(name string) (*Plugin, error) { storage.Lock() - defer storage.Unlock() pl, ok := storage.plugins[name] + storage.Unlock() if ok { - return pl, nil + return pl, pl.activate() } - pl, err := load(name) - if err != nil { - return nil, err - } - - logrus.Debugf("Plugin: %v", pl) - storage.plugins[name] = pl - return pl, nil + return load(name) } +// Get returns the plugin given the specified name and requested implementation. func Get(name, imp string) (*Plugin, error) { pl, err := get(name) if err != nil { @@ -95,6 +176,7 @@ func Get(name, imp string) (*Plugin, error) { return nil, ErrNotImplements } +// Handle adds the specified function to the extpointHandlers. func Handle(iface string, fn func(string, *Client)) { extpointHandlers[iface] = fn } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/proxy.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/proxy.go index 7a711f657b..4e24e5f6a8 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/proxy.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/proxy.go @@ -1,3 +1,5 @@ +// Package proxy provides a network Proxy interface and implementations for TCP +// and UDP. package proxy import ( @@ -5,18 +7,24 @@ import ( "net" ) +// Proxy defines the behavior of a proxy. It forwards traffic back and forth +// between two endpoints : the frontend and the backend. +// It can be used to do software port-mapping between two addresses. +// e.g. forward all traffic between the frontend (host) 127.0.0.1:3000 +// to the backend (container) at 172.17.42.108:4000. type Proxy interface { - // Start forwarding traffic back and forth the front and back-end - // addresses. + // Run starts forwarding traffic back and forth between the front + // and back-end addresses. Run() - // Stop forwarding traffic and close both ends of the Proxy. + // Close stops forwarding traffic and close both ends of the Proxy. Close() - // Return the address on which the proxy is listening. + // FrontendAddr returns the address on which the proxy is listening. FrontendAddr() net.Addr - // Return the proxied address. + // BackendAddr returns the proxied address. BackendAddr() net.Addr } +// NewProxy creates a Proxy according to the specified frontendAddr and backendAddr. func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { switch frontendAddr.(type) { case *net.UDPAddr: diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/stub_proxy.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/stub_proxy.go index 7684427058..571749e467 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/stub_proxy.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/stub_proxy.go @@ -4,16 +4,25 @@ import ( "net" ) +// StubProxy is a proxy that is a stub (does nothing). type StubProxy struct { frontendAddr net.Addr backendAddr net.Addr } -func (p *StubProxy) Run() {} -func (p *StubProxy) Close() {} +// Run does nothing. +func (p *StubProxy) Run() {} + +// Close does nothing. +func (p *StubProxy) Close() {} + +// FrontendAddr returns the frontend address. func (p *StubProxy) FrontendAddr() net.Addr { return p.frontendAddr } -func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr } +// BackendAddr returns the backend address. +func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr } + +// NewStubProxy creates a new StubProxy func NewStubProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { return &StubProxy{ frontendAddr: frontendAddr, diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/tcp_proxy.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/tcp_proxy.go index 9942e6d901..3cd742af76 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/tcp_proxy.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/tcp_proxy.go @@ -8,12 +8,15 @@ import ( "github.com/Sirupsen/logrus" ) +// TCPProxy is a proxy for TCP connections. It implements the Proxy interface to +// handle TCP traffic forwarding between the frontend and backend addresses. type TCPProxy struct { listener *net.TCPListener frontendAddr *net.TCPAddr backendAddr *net.TCPAddr } +// NewTCPProxy creates a new TCPProxy. func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) { listener, err := net.ListenTCP("tcp", frontendAddr) if err != nil { @@ -53,7 +56,7 @@ func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { go broker(client, backend) go broker(backend, client) - var transferred int64 = 0 + var transferred int64 for i := 0; i < 2; i++ { select { case written := <-event: @@ -72,6 +75,7 @@ func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { backend.Close() } +// Run starts forwarding the traffic using TCP. func (proxy *TCPProxy) Run() { quit := make(chan bool) defer close(quit) @@ -85,6 +89,11 @@ func (proxy *TCPProxy) Run() { } } -func (proxy *TCPProxy) Close() { proxy.listener.Close() } +// Close stops forwarding the traffic. +func (proxy *TCPProxy) Close() { proxy.listener.Close() } + +// FrontendAddr returns the TCP address on which the proxy is listening. func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } -func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr } + +// BackendAddr returns the TCP proxied address. +func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/udp_proxy.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/udp_proxy.go index 2a073dfe8e..b8375c374f 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/udp_proxy.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/proxy/udp_proxy.go @@ -12,8 +12,10 @@ import ( ) const ( + // UDPConnTrackTimeout is the timeout used for UDP connection tracking UDPConnTrackTimeout = 90 * time.Second - UDPBufSize = 65507 + // UDPBufSize is the buffer size for the UDP proxy + UDPBufSize = 65507 ) // A net.Addr where the IP is split into two fields so you can use it as a key @@ -41,6 +43,9 @@ func newConnTrackKey(addr *net.UDPAddr) *connTrackKey { type connTrackMap map[connTrackKey]*net.UDPConn +// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy +// interface to handle UDP traffic forwarding between the frontend and backend +// addresses. type UDPProxy struct { listener *net.UDPConn frontendAddr *net.UDPAddr @@ -49,6 +54,7 @@ type UDPProxy struct { connTrackLock sync.Mutex } +// NewUDPProxy creates a new UDPProxy. func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) { listener, err := net.ListenUDP("udp", frontendAddr) if err != nil { @@ -96,6 +102,7 @@ func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr } } +// Run starts forwarding the traffic using UDP. func (proxy *UDPProxy) Run() { readBuf := make([]byte, UDPBufSize) for { @@ -135,6 +142,7 @@ func (proxy *UDPProxy) Run() { } } +// Close stops forwarding the traffic. func (proxy *UDPProxy) Close() { proxy.listener.Close() proxy.connTrackLock.Lock() @@ -144,8 +152,11 @@ func (proxy *UDPProxy) Close() { } } +// FrontendAddr returns the UDP address on which the proxy is listening. func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } -func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr } + +// BackendAddr returns the proxied UDP address. +func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr } func isClosedError(err error) bool { /* This comparison is ugly, but unfortunately, net.go doesn't export errClosing. diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/random/random.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/random/random.go new file mode 100644 index 0000000000..e560aff111 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/random/random.go @@ -0,0 +1,71 @@ +package random + +import ( + cryptorand "crypto/rand" + "io" + "math" + "math/big" + "math/rand" + "sync" + "time" +) + +// Rand is a global *rand.Rand instance, which initilized with NewSource() source. +var Rand = rand.New(NewSource()) + +// Reader is a global, shared instance of a pseudorandom bytes generator. +// It doesn't consume entropy. +var Reader io.Reader = &reader{rnd: Rand} + +// copypaste from standard math/rand +type lockedSource struct { + lk sync.Mutex + src rand.Source +} + +func (r *lockedSource) Int63() (n int64) { + r.lk.Lock() + n = r.src.Int63() + r.lk.Unlock() + return +} + +func (r *lockedSource) Seed(seed int64) { + r.lk.Lock() + r.src.Seed(seed) + r.lk.Unlock() +} + +// NewSource returns math/rand.Source safe for concurrent use and initialized +// with current unix-nano timestamp +func NewSource() rand.Source { + var seed int64 + if cryptoseed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)); err != nil { + // This should not happen, but worst-case fallback to time-based seed. + seed = time.Now().UnixNano() + } else { + seed = cryptoseed.Int64() + } + return &lockedSource{ + src: rand.NewSource(seed), + } +} + +type reader struct { + rnd *rand.Rand +} + +func (r *reader) Read(b []byte) (int, error) { + i := 0 + for { + val := r.rnd.Int63() + for val > 0 { + b[i] = byte(val) + i++ + if i == len(b) { + return i, nil + } + val >>= 8 + } + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/random/random_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/random/random_test.go new file mode 100644 index 0000000000..cf405f78cb --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/random/random_test.go @@ -0,0 +1,22 @@ +package random + +import ( + "math/rand" + "sync" + "testing" +) + +// for go test -v -race +func TestConcurrency(t *testing.T) { + rnd := rand.New(NewSource()) + var wg sync.WaitGroup + + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + rnd.Int63() + wg.Done() + }() + } + wg.Wait() +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_freebsd.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_freebsd.go new file mode 100644 index 0000000000..c7f797a5fa --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_freebsd.go @@ -0,0 +1,23 @@ +// +build freebsd + +package reexec + +import ( + "os/exec" +) + +// Self returns the path to the current process's binary. +// Uses os.Args[0]. +func Self() string { + return naiveSelf() +} + +// Command returns *exec.Cmd which have Path as current binary. +// For example if current binary is "docker" at "/usr/bin/", then cmd.Path will +// be set to "/usr/bin/docker". +func Command(args ...string) *exec.Cmd { + return &exec.Cmd{ + Path: Self(), + Args: args, + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_linux.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_linux.go index 8dc3f3a4a6..3c3a73a9d5 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_linux.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_linux.go @@ -7,6 +7,16 @@ import ( "syscall" ) +// Self returns the path to the current process's binary. +// Returns "/proc/self/exe". +func Self() string { + return "/proc/self/exe" +} + +// Command returns *exec.Cmd which have Path as current binary. Also it setting +// SysProcAttr.Pdeathsig to SIGTERM. +// This will use the in-memory version (/proc/self/exe) of the current binary, +// it is thus safe to delete or replace the on-disk binary (os.Args[0]). func Command(args ...string) *exec.Cmd { return &exec.Cmd{ Path: Self(), diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go index 4adcd8f13e..ad4ea38ebb 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_unsupported.go @@ -1,4 +1,4 @@ -// +build !linux,!windows +// +build !linux,!windows,!freebsd package reexec @@ -6,6 +6,7 @@ import ( "os/exec" ) +// Command is unsupported on operating systems apart from Linux and Windows. func Command(args ...string) *exec.Cmd { return nil } diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_windows.go index 124d42fc62..8d65e0ae1a 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_windows.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/command_windows.go @@ -6,6 +6,15 @@ import ( "os/exec" ) +// Self returns the path to the current process's binary. +// Uses os.Args[0]. +func Self() string { + return naiveSelf() +} + +// Command returns *exec.Cmd which have Path as current binary. +// For example if current binary is "docker.exe" at "C:\", then cmd.Path will +// be set to "C:\docker.exe". func Command(args ...string) *exec.Cmd { return &exec.Cmd{ Path: Self(), diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/reexec.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/reexec.go index a5f01a26e3..20491e05d6 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/reexec.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/reexec/reexec.go @@ -30,8 +30,7 @@ func Init() bool { return false } -// Self returns the path to the current processes binary -func Self() string { +func naiveSelf() string { name := os.Args[0] if filepath.Base(name) == name { if lp, err := exec.LookPath(name); err == nil { diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/README.md b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/README.md new file mode 100644 index 0000000000..2b237a5942 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/README.md @@ -0,0 +1 @@ +This package provides helper functions for dealing with signals across various operating systems \ No newline at end of file diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal.go new file mode 100644 index 0000000000..106fe20d74 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal.go @@ -0,0 +1,44 @@ +// Package signal provides helper functions for dealing with signals across +// various operating systems. +package signal + +import ( + "fmt" + "os" + "os/signal" + "strconv" + "strings" + "syscall" +) + +// CatchAll catches all signals and relays them to the specified channel. +func CatchAll(sigc chan os.Signal) { + handledSigs := []os.Signal{} + for _, s := range SignalMap { + handledSigs = append(handledSigs, s) + } + signal.Notify(sigc, handledSigs...) +} + +// StopCatch stops catching the signals and closes the specified channel. +func StopCatch(sigc chan os.Signal) { + signal.Stop(sigc) + close(sigc) +} + +// ParseSignal translates a string to a valid syscall signal. +// It returns an error if the signal map doesn't include the given signal. +func ParseSignal(rawSignal string) (syscall.Signal, error) { + s, err := strconv.Atoi(rawSignal) + if err == nil { + if s == 0 { + return -1, fmt.Errorf("Invalid signal: %s", rawSignal) + } + return syscall.Signal(s), nil + } + signal, ok := SignalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] + if !ok { + return -1, fmt.Errorf("Invalid signal: %s", rawSignal) + } + return signal, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_darwin.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_darwin.go new file mode 100644 index 0000000000..946de87e94 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_darwin.go @@ -0,0 +1,41 @@ +package signal + +import ( + "syscall" +) + +// SignalMap is a map of Darwin signals. +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUG": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "EMT": syscall.SIGEMT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INFO": syscall.SIGINFO, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "PROF": syscall.SIGPROF, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_freebsd.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_freebsd.go new file mode 100644 index 0000000000..6b9569bb75 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_freebsd.go @@ -0,0 +1,43 @@ +package signal + +import ( + "syscall" +) + +// SignalMap is a map of FreeBSD signals. +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUF": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "EMT": syscall.SIGEMT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INFO": syscall.SIGINFO, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "LWP": syscall.SIGLWP, + "PIPE": syscall.SIGPIPE, + "PROF": syscall.SIGPROF, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "THR": syscall.SIGTHR, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_linux.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_linux.go new file mode 100644 index 0000000000..1ecc3294ff --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_linux.go @@ -0,0 +1,44 @@ +package signal + +import ( + "syscall" +) + +// SignalMap is a map of Linux signals. +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUS": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CLD": syscall.SIGCLD, + "CONT": syscall.SIGCONT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "POLL": syscall.SIGPOLL, + "PROF": syscall.SIGPROF, + "PWR": syscall.SIGPWR, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STKFLT": syscall.SIGSTKFLT, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "UNUSED": syscall.SIGUNUSED, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_unix.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_unix.go new file mode 100644 index 0000000000..d4fea931d1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_unix.go @@ -0,0 +1,19 @@ +// +build !windows + +package signal + +import ( + "syscall" +) + +// Signals used in api/client (no windows equivalent, use +// invalid signals so they don't get handled) + +const ( + // SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted. + SIGCHLD = syscall.SIGCHLD + // SIGWINCH is a signal sent to a process when its controlling terminal changes its size + SIGWINCH = syscall.SIGWINCH + // DefaultStopSignal is the syscall signal used to stop a container in unix systems. + DefaultStopSignal = "SIGTERM" +) diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_unsupported.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_unsupported.go new file mode 100644 index 0000000000..cefb2407b6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_unsupported.go @@ -0,0 +1,10 @@ +// +build !linux,!darwin,!freebsd + +package signal + +import ( + "syscall" +) + +// SignalMap is an empty map of signals for unsupported platform. +var SignalMap = map[string]syscall.Signal{} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_windows.go new file mode 100644 index 0000000000..b0585b0ed9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/signal_windows.go @@ -0,0 +1,16 @@ +// +build windows + +package signal + +import ( + "syscall" +) + +// Signals used in api/client (no windows equivalent, use +// invalid signals so they don't get handled) +const ( + SIGCHLD = syscall.Signal(0xff) + SIGWINCH = syscall.Signal(0xff) + // DefaultStopSignal is the syscall signal used to stop a container in windows systems. + DefaultStopSignal = "15" +) diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/trap.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/trap.go new file mode 100644 index 0000000000..2cf5ccf0d2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/signal/trap.go @@ -0,0 +1,74 @@ +package signal + +import ( + "os" + gosignal "os/signal" + "runtime" + "sync/atomic" + "syscall" + + "github.com/Sirupsen/logrus" +) + +// Trap sets up a simplified signal "trap", appropriate for common +// behavior expected from a vanilla unix command-line tool in general +// (and the Docker engine in particular). +// +// * If SIGINT or SIGTERM are received, `cleanup` is called, then the process is terminated. +// * If SIGINT or SIGTERM are received 3 times before cleanup is complete, then cleanup is +// skipped and the process is terminated immediately (allows force quit of stuck daemon) +// * A SIGQUIT always causes an exit without cleanup, with a goroutine dump preceding exit. +// +func Trap(cleanup func()) { + c := make(chan os.Signal, 1) + // we will handle INT, TERM, QUIT here + signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT} + gosignal.Notify(c, signals...) + go func() { + interruptCount := uint32(0) + for sig := range c { + go func(sig os.Signal) { + logrus.Infof("Processing signal '%v'", sig) + switch sig { + case os.Interrupt, syscall.SIGTERM: + if atomic.LoadUint32(&interruptCount) < 3 { + // Initiate the cleanup only once + if atomic.AddUint32(&interruptCount, 1) == 1 { + // Call the provided cleanup handler + cleanup() + os.Exit(0) + } else { + return + } + } else { + // 3 SIGTERM/INT signals received; force exit without cleanup + logrus.Infof("Forcing docker daemon shutdown without cleanup; 3 interrupts received") + } + case syscall.SIGQUIT: + DumpStacks() + logrus.Infof("Forcing docker daemon shutdown without cleanup on SIGQUIT") + } + //for the SIGINT/TERM, and SIGQUIT non-clean shutdown case, exit with 128 + signal # + os.Exit(128 + int(sig.(syscall.Signal))) + }(sig) + } + }() +} + +// DumpStacks dumps the runtime stack. +func DumpStacks() { + var ( + buf []byte + stackSize int + ) + bufferLen := 16384 + for stackSize == len(buf) { + buf = make([]byte, bufferLen) + stackSize = runtime.Stack(buf, true) + bufferLen *= 2 + } + buf = buf[:stackSize] + // Note that if the daemon is started with a less-verbose log-level than "info" (the default), the goroutine + // traces won't show up in the log. + logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/sockets/README.md b/Godeps/_workspace/src/github.com/docker/docker/pkg/sockets/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/sockets/tcp_socket.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/sockets/tcp_socket.go new file mode 100644 index 0000000000..9f8319dc33 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/sockets/tcp_socket.go @@ -0,0 +1,48 @@ +// Package sockets provides helper functions to create and configure Unix or TCP +// sockets. +package sockets + +import ( + "crypto/tls" + "net" + "net/http" + "time" + + "github.com/docker/docker/pkg/listenbuffer" +) + +// NewTCPSocket creates a TCP socket listener with the specified address and +// and the specified tls configuration. If TLSConfig is set, will encapsulate the +// TCP listener inside a TLS one. +// The channel passed is used to activate the listenbuffer when the caller is ready +// to accept connections. +func NewTCPSocket(addr string, tlsConfig *tls.Config, activate <-chan struct{}) (net.Listener, error) { + l, err := listenbuffer.NewListenBuffer("tcp", addr, activate) + if err != nil { + return nil, err + } + if tlsConfig != nil { + tlsConfig.NextProtos = []string{"http/1.1"} + l = tls.NewListener(l, tlsConfig) + } + return l, nil +} + +// ConfigureTCPTransport configures the specified Transport according to the +// specified proto and addr. +// If the proto is unix (using a unix socket to communicate) the compression +// is disabled. +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 + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/sockets/unix_socket.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/sockets/unix_socket.go new file mode 100644 index 0000000000..a69c28b041 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/sockets/unix_socket.go @@ -0,0 +1,83 @@ +// +build linux freebsd + +package sockets + +import ( + "fmt" + "net" + "os" + "strconv" + "syscall" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/listenbuffer" + "github.com/opencontainers/runc/libcontainer/user" +) + +// NewUnixSocket creates a unix socket with the specified path and group. +// The channel passed is used to activate the listenbuffer when the caller is ready +// to accept connections. +func NewUnixSocket(path, group string, activate <-chan struct{}) (net.Listener, error) { + if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) { + return nil, err + } + mask := syscall.Umask(0777) + defer syscall.Umask(mask) + l, err := listenbuffer.NewListenBuffer("unix", path, activate) + if err != nil { + return nil, err + } + if err := setSocketGroup(path, group); err != nil { + l.Close() + return nil, err + } + if err := os.Chmod(path, 0660); err != nil { + l.Close() + return nil, err + } + return l, nil +} + +func setSocketGroup(path, group string) error { + if group == "" { + return nil + } + if err := changeGroup(path, group); err != nil { + if group != "docker" { + return err + } + logrus.Debugf("Warning: could not change group %s to docker: %v", path, err) + } + return nil +} + +func changeGroup(path string, nameOrGid string) error { + gid, err := lookupGidByName(nameOrGid) + if err != nil { + return err + } + logrus.Debugf("%s group found. gid: %d", nameOrGid, gid) + return os.Chown(path, 0, gid) +} + +func lookupGidByName(nameOrGid string) (int, error) { + groupFile, err := user.GetGroupPath() + if err != nil { + return -1, err + } + groups, err := user.ParseGroupFileFilter(groupFile, func(g user.Group) bool { + return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid + }) + if err != nil { + return -1, err + } + if groups != nil && len(groups) > 0 { + return groups[0].Gid, nil + } + gid, err := strconv.Atoi(nameOrGid) + if err == nil { + logrus.Warnf("Could not find GID %d", gid) + return gid, nil + } + return -1, fmt.Errorf("Group %s not found", nameOrGid) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go index 6a683b686a..ab1f9d4749 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go @@ -1,3 +1,4 @@ +// Package stringid provides helper functions for dealing with string identifiers package stringid import ( @@ -6,13 +7,15 @@ import ( "io" "regexp" "strconv" + + "github.com/docker/docker/pkg/random" ) const shortLen = 12 var validShortID = regexp.MustCompile("^[a-z0-9]{12}$") -// Determine if an arbitrary string *looks like* a short ID. +// IsShortID determines if an arbitrary string *looks like* a short ID. func IsShortID(id string) bool { return validShortID.MatchString(id) } @@ -29,20 +32,36 @@ func TruncateID(id string) string { return id[:trimTo] } -// GenerateRandomID returns an unique id -func GenerateRandomID() string { +func generateID(crypto bool) string { + b := make([]byte, 32) + var r io.Reader = random.Reader + if crypto { + r = rand.Reader + } for { - id := make([]byte, 32) - if _, err := io.ReadFull(rand.Reader, id); err != nil { + if _, err := io.ReadFull(r, b); err != nil { panic(err) // This shouldn't happen } - value := hex.EncodeToString(id) + id := hex.EncodeToString(b) // 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 { + if _, err := strconv.ParseInt(TruncateID(id), 10, 64); err == nil { continue } - return value + return id } } + +// GenerateRandomID returns an unique id. +func GenerateRandomID() string { + return generateID(true) + +} + +// GenerateNonCryptoID generates unique id without using cryptographically +// secure sources of random. +// It helps you to save entropy. +func GenerateNonCryptoID() string { + return generateID(false) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/README.md b/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/README.md index 0d1dbb70e6..8dba54fd08 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/README.md +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/README.md @@ -1,4 +1,5 @@ -Package symlink implements EvalSymlinksInScope which is an extension of filepath.EvalSymlinks +Package symlink implements EvalSymlinksInScope which is an extension of filepath.EvalSymlinks, +as well as a Windows long-path aware version of filepath.EvalSymlinks from the [Go standard library](https://golang.org/pkg/path/filepath). The code from filepath.EvalSymlinks has been adapted in fs.go. diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs.go index b4bdff24dd..dcf707f426 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs.go @@ -12,15 +12,18 @@ import ( "os" "path/filepath" "strings" + + "github.com/docker/docker/pkg/system" ) -// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an absolute path +// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an +// absolute path. This function handles paths in a platform-agnostic manner. func FollowSymlinkInScope(path, root string) (string, error) { - path, err := filepath.Abs(path) + path, err := filepath.Abs(filepath.FromSlash(path)) if err != nil { return "", err } - root, err = filepath.Abs(root) + root, err = filepath.Abs(filepath.FromSlash(root)) if err != nil { return "", err } @@ -119,7 +122,7 @@ func evalSymlinksInScope(path, root string) (string, error) { if err != nil { return "", err } - if filepath.IsAbs(dest) { + if system.IsAbs(dest) { b.Reset() } path = dest + string(filepath.Separator) + path @@ -129,3 +132,12 @@ func evalSymlinksInScope(path, root string) (string, error) { // what's happening here return filepath.Clean(root + filepath.Clean(string(filepath.Separator)+b.String())), nil } + +// EvalSymlinks returns the path name after the evaluation of any symbolic +// links. +// If path is relative the result will be relative to the current directory, +// unless one of the components is an absolute symbolic link. +// This version has been updated to support long paths prepended with `\\?\`. +func EvalSymlinks(path string) (string, error) { + return evalSymlinks(path) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs_unix.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs_unix.go new file mode 100644 index 0000000000..818004f26c --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs_unix.go @@ -0,0 +1,11 @@ +// +build !windows + +package symlink + +import ( + "path/filepath" +) + +func evalSymlinks(path string) (string, error) { + return filepath.EvalSymlinks(path) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs_windows.go new file mode 100644 index 0000000000..29bd456886 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/symlink/fs_windows.go @@ -0,0 +1,156 @@ +package symlink + +import ( + "bytes" + "errors" + "os" + "path/filepath" + "strings" + "syscall" + + "github.com/docker/docker/pkg/longpath" +) + +func toShort(path string) (string, error) { + p, err := syscall.UTF16FromString(path) + if err != nil { + return "", err + } + b := p // GetShortPathName says we can reuse buffer + n, err := syscall.GetShortPathName(&p[0], &b[0], uint32(len(b))) + if err != nil { + return "", err + } + if n > uint32(len(b)) { + b = make([]uint16, n) + n, err = syscall.GetShortPathName(&p[0], &b[0], uint32(len(b))) + if err != nil { + return "", err + } + } + return syscall.UTF16ToString(b), nil +} + +func toLong(path string) (string, error) { + p, err := syscall.UTF16FromString(path) + if err != nil { + return "", err + } + b := p // GetLongPathName says we can reuse buffer + n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b))) + if err != nil { + return "", err + } + if n > uint32(len(b)) { + b = make([]uint16, n) + n, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b))) + if err != nil { + return "", err + } + } + b = b[:n] + return syscall.UTF16ToString(b), nil +} + +func evalSymlinks(path string) (string, error) { + path, err := walkSymlinks(path) + if err != nil { + return "", err + } + + p, err := toShort(path) + if err != nil { + return "", err + } + p, err = toLong(p) + if err != nil { + return "", err + } + // syscall.GetLongPathName does not change the case of the drive letter, + // but the result of EvalSymlinks must be unique, so we have + // EvalSymlinks(`c:\a`) == EvalSymlinks(`C:\a`). + // Make drive letter upper case. + if len(p) >= 2 && p[1] == ':' && 'a' <= p[0] && p[0] <= 'z' { + p = string(p[0]+'A'-'a') + p[1:] + } else if len(p) >= 6 && p[5] == ':' && 'a' <= p[4] && p[4] <= 'z' { + p = p[:3] + string(p[4]+'A'-'a') + p[5:] + } + return filepath.Clean(p), nil +} + +const utf8RuneSelf = 0x80 + +func walkSymlinks(path string) (string, error) { + const maxIter = 255 + originalPath := path + // consume path by taking each frontmost path element, + // expanding it if it's a symlink, and appending it to b + var b bytes.Buffer + for n := 0; path != ""; n++ { + if n > maxIter { + return "", errors.New("EvalSymlinks: too many links in " + originalPath) + } + + // A path beginnging with `\\?\` represents the root, so automatically + // skip that part and begin processing the next segment. + if strings.HasPrefix(path, longpath.Prefix) { + b.WriteString(longpath.Prefix) + path = path[4:] + continue + } + + // find next path component, p + var i = -1 + for j, c := range path { + if c < utf8RuneSelf && os.IsPathSeparator(uint8(c)) { + i = j + break + } + } + var p string + if i == -1 { + p, path = path, "" + } else { + p, path = path[:i], path[i+1:] + } + + if p == "" { + if b.Len() == 0 { + // must be absolute path + b.WriteRune(filepath.Separator) + } + continue + } + + // If this is the first segment after the long path prefix, accept the + // current segment as a volume root or UNC share and move on to the next. + if b.String() == longpath.Prefix { + b.WriteString(p) + b.WriteRune(filepath.Separator) + continue + } + + fi, err := os.Lstat(b.String() + p) + if err != nil { + return "", err + } + if fi.Mode()&os.ModeSymlink == 0 { + b.WriteString(p) + if path != "" || (b.Len() == 2 && len(p) == 2 && p[1] == ':') { + b.WriteRune(filepath.Separator) + } + continue + } + + // it's a symlink, put it at the front of path + dest, err := os.Readlink(b.String() + p) + if err != nil { + return "", err + } + if filepath.IsAbs(dest) || os.IsPathSeparator(dest[0]) { + b.Reset() + } + path = dest + string(filepath.Separator) + path + } + return filepath.Clean(b.String()), nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/errors.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/errors.go new file mode 100644 index 0000000000..288318985e --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/errors.go @@ -0,0 +1,10 @@ +package system + +import ( + "errors" +) + +var ( + // ErrNotSupportedPlatform means the platform is not supported. + ErrNotSupportedPlatform = errors.New("platform and architecture is not supported") +) diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/events_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/events_windows.go new file mode 100644 index 0000000000..04e2de7871 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/events_windows.go @@ -0,0 +1,83 @@ +package system + +// This file implements syscalls for Win32 events which are not implemented +// in golang. + +import ( + "syscall" + "unsafe" +) + +var ( + procCreateEvent = modkernel32.NewProc("CreateEventW") + procOpenEvent = modkernel32.NewProc("OpenEventW") + procSetEvent = modkernel32.NewProc("SetEvent") + procResetEvent = modkernel32.NewProc("ResetEvent") + procPulseEvent = modkernel32.NewProc("PulseEvent") +) + +// CreateEvent implements win32 CreateEventW func in golang. It will create an event object. +func CreateEvent(eventAttributes *syscall.SecurityAttributes, manualReset bool, initialState bool, name string) (handle syscall.Handle, err error) { + namep, _ := syscall.UTF16PtrFromString(name) + var _p1 uint32 + if manualReset { + _p1 = 1 + } + var _p2 uint32 + if initialState { + _p2 = 1 + } + r0, _, e1 := procCreateEvent.Call(uintptr(unsafe.Pointer(eventAttributes)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(namep))) + use(unsafe.Pointer(namep)) + handle = syscall.Handle(r0) + if handle == syscall.InvalidHandle { + err = e1 + } + return +} + +// OpenEvent implements win32 OpenEventW func in golang. It opens an event object. +func OpenEvent(desiredAccess uint32, inheritHandle bool, name string) (handle syscall.Handle, err error) { + namep, _ := syscall.UTF16PtrFromString(name) + var _p1 uint32 + if inheritHandle { + _p1 = 1 + } + r0, _, e1 := procOpenEvent.Call(uintptr(desiredAccess), uintptr(_p1), uintptr(unsafe.Pointer(namep))) + use(unsafe.Pointer(namep)) + handle = syscall.Handle(r0) + if handle == syscall.InvalidHandle { + err = e1 + } + return +} + +// SetEvent implements win32 SetEvent func in golang. +func SetEvent(handle syscall.Handle) (err error) { + return setResetPulse(handle, procSetEvent) +} + +// ResetEvent implements win32 ResetEvent func in golang. +func ResetEvent(handle syscall.Handle) (err error) { + return setResetPulse(handle, procResetEvent) +} + +// PulseEvent implements win32 PulseEvent func in golang. +func PulseEvent(handle syscall.Handle) (err error) { + return setResetPulse(handle, procPulseEvent) +} + +func setResetPulse(handle syscall.Handle, proc *syscall.LazyProc) (err error) { + r0, _, _ := proc.Call(uintptr(handle)) + if r0 != 0 { + err = syscall.Errno(r0) + } + return +} + +var temp unsafe.Pointer + +// use ensures a variable is kept alive without the GC freeing while still needed +func use(p unsafe.Pointer) { + temp = p +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys.go new file mode 100644 index 0000000000..c14feb8496 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys.go @@ -0,0 +1,19 @@ +// +build !windows + +package system + +import ( + "os" + "path/filepath" +) + +// MkdirAll creates a directory named path along with any necessary parents, +// with permission specified by attribute perm for all dir created. +func MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +} + +// IsAbs is a platform-specific wrapper for filepath.IsAbs. +func IsAbs(path string) bool { + return filepath.IsAbs(path) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys_windows.go new file mode 100644 index 0000000000..16823d5517 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys_windows.go @@ -0,0 +1,82 @@ +// +build windows + +package system + +import ( + "os" + "path/filepath" + "regexp" + "strings" + "syscall" +) + +// MkdirAll implementation that is volume path aware for Windows. +func MkdirAll(path string, perm os.FileMode) error { + if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) { + return nil + } + + // The rest of this method is copied from os.MkdirAll and should be kept + // as-is to ensure compatibility. + + // Fast path: if we can tell whether path is a directory or file, stop with success or error. + dir, err := os.Stat(path) + if err == nil { + if dir.IsDir() { + return nil + } + return &os.PathError{ + Op: "mkdir", + Path: path, + Err: syscall.ENOTDIR, + } + } + + // Slow path: make sure parent exists and then call Mkdir for path. + i := len(path) + for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator. + i-- + } + + j := i + for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element. + j-- + } + + if j > 1 { + // Create parent + err = MkdirAll(path[0:j-1], perm) + if err != nil { + return err + } + } + + // Parent now exists; invoke Mkdir and use its result. + err = os.Mkdir(path, perm) + if err != nil { + // Handle arguments like "foo/." by + // double-checking that directory doesn't exist. + dir, err1 := os.Lstat(path) + if err1 == nil && dir.IsDir() { + return nil + } + return err + } + return nil +} + +// IsAbs is a platform-specific wrapper for filepath.IsAbs. On Windows, +// golang filepath.IsAbs does not consider a path \windows\system32 as absolute +// as it doesn't start with a drive-letter/colon combination. However, in +// docker we need to verify things such as WORKDIR /windows/system32 in +// a Dockerfile (which gets translated to \windows\system32 when being processed +// by the daemon. This SHOULD be treated as absolute from a docker processing +// perspective. +func IsAbs(path string) bool { + if !filepath.IsAbs(path) { + if !strings.HasPrefix(path, string(os.PathSeparator)) { + return false + } + } + return true +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat.go new file mode 100644 index 0000000000..bd23c4d50b --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat.go @@ -0,0 +1,19 @@ +// +build !windows + +package system + +import ( + "syscall" +) + +// Lstat takes a path to a file and returns +// a system.StatT type pertaining to that file. +// +// Throws an error if the file does not exist +func Lstat(path string) (*StatT, error) { + s := &syscall.Stat_t{} + if err := syscall.Lstat(path, s); err != nil { + return nil, err + } + return fromStatT(s) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_test.go new file mode 100644 index 0000000000..6bac492eb1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_test.go @@ -0,0 +1,28 @@ +package system + +import ( + "os" + "testing" +) + +// TestLstat tests Lstat for existing and non existing files +func TestLstat(t *testing.T) { + file, invalid, _, dir := prepareFiles(t) + defer os.RemoveAll(dir) + + statFile, err := Lstat(file) + if err != nil { + t.Fatal(err) + } + if statFile == nil { + t.Fatal("returned empty stat for existing file") + } + + statInvalid, err := Lstat(invalid) + if err == nil { + t.Fatal("did not return error for non-existing file") + } + if statInvalid != nil { + t.Fatal("returned non-nil stat for non-existing file") + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_windows.go new file mode 100644 index 0000000000..49e87eb40b --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_windows.go @@ -0,0 +1,25 @@ +// +build windows + +package system + +import ( + "os" +) + +// Lstat calls os.Lstat to get a fileinfo interface back. +// This is then copied into our own locally defined structure. +// Note the Linux version uses fromStatT to do the copy back, +// but that not strictly necessary when already in an OS specific module. +func Lstat(path string) (*StatT, error) { + fi, err := os.Lstat(path) + if err != nil { + return nil, err + } + + return &StatT{ + name: fi.Name(), + size: fi.Size(), + mode: fi.Mode(), + modTime: fi.ModTime(), + isDir: fi.IsDir()}, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo.go new file mode 100644 index 0000000000..3b6e947e67 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo.go @@ -0,0 +1,17 @@ +package system + +// MemInfo contains memory statistics of the host system. +type MemInfo struct { + // Total usable RAM (i.e. physical RAM minus a few reserved bits and the + // kernel binary code). + MemTotal int64 + + // Amount of free memory. + MemFree int64 + + // Total amount of swap space available. + SwapTotal int64 + + // Amount of swap space that is currently unused. + SwapFree int64 +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux.go new file mode 100644 index 0000000000..a07bb17cbc --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux.go @@ -0,0 +1,66 @@ +package system + +import ( + "bufio" + "io" + "os" + "strconv" + "strings" + + "github.com/docker/docker/pkg/units" +) + +// ReadMemInfo retrieves memory statistics of the host system and returns a +// MemInfo type. +func ReadMemInfo() (*MemInfo, error) { + file, err := os.Open("/proc/meminfo") + if err != nil { + return nil, err + } + defer file.Close() + return parseMemInfo(file) +} + +// parseMemInfo parses the /proc/meminfo file into +// a MemInfo object given a io.Reader to the file. +// +// Throws error if there are problems reading from the file +func parseMemInfo(reader io.Reader) (*MemInfo, error) { + meminfo := &MemInfo{} + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + // Expected format: ["MemTotal:", "1234", "kB"] + parts := strings.Fields(scanner.Text()) + + // Sanity checks: Skip malformed entries. + if len(parts) < 3 || parts[2] != "kB" { + continue + } + + // Convert to bytes. + size, err := strconv.Atoi(parts[1]) + if err != nil { + continue + } + bytes := int64(size) * units.KiB + + switch parts[0] { + case "MemTotal:": + meminfo.MemTotal = bytes + case "MemFree:": + meminfo.MemFree = bytes + case "SwapTotal:": + meminfo.SwapTotal = bytes + case "SwapFree:": + meminfo.SwapFree = bytes + } + + } + + // Handle errors that may have occurred during the reading of the file. + if err := scanner.Err(); err != nil { + return nil, err + } + + return meminfo, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux_test.go new file mode 100644 index 0000000000..10ddf796c6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux_test.go @@ -0,0 +1,38 @@ +package system + +import ( + "strings" + "testing" + + "github.com/docker/docker/pkg/units" +) + +// TestMemInfo tests parseMemInfo with a static meminfo string +func TestMemInfo(t *testing.T) { + const input = ` + MemTotal: 1 kB + MemFree: 2 kB + SwapTotal: 3 kB + SwapFree: 4 kB + Malformed1: + Malformed2: 1 + Malformed3: 2 MB + Malformed4: X kB + ` + meminfo, err := parseMemInfo(strings.NewReader(input)) + if err != nil { + t.Fatal(err) + } + if meminfo.MemTotal != 1*units.KiB { + t.Fatalf("Unexpected MemTotal: %d", meminfo.MemTotal) + } + if meminfo.MemFree != 2*units.KiB { + t.Fatalf("Unexpected MemFree: %d", meminfo.MemFree) + } + if meminfo.SwapTotal != 3*units.KiB { + t.Fatalf("Unexpected SwapTotal: %d", meminfo.SwapTotal) + } + if meminfo.SwapFree != 4*units.KiB { + t.Fatalf("Unexpected SwapFree: %d", meminfo.SwapFree) + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_unsupported.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_unsupported.go new file mode 100644 index 0000000000..82ddd30c1b --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_unsupported.go @@ -0,0 +1,8 @@ +// +build !linux,!windows + +package system + +// ReadMemInfo is not supported on platforms other than linux and windows. +func ReadMemInfo() (*MemInfo, error) { + return nil, ErrNotSupportedPlatform +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_windows.go new file mode 100644 index 0000000000..d46642598c --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_windows.go @@ -0,0 +1,44 @@ +package system + +import ( + "syscall" + "unsafe" +) + +var ( + modkernel32 = syscall.NewLazyDLL("kernel32.dll") + + procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx") +) + +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366589(v=vs.85).aspx +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366770(v=vs.85).aspx +type memorystatusex struct { + dwLength uint32 + dwMemoryLoad uint32 + ullTotalPhys uint64 + ullAvailPhys uint64 + ullTotalPageFile uint64 + ullAvailPageFile uint64 + ullTotalVirtual uint64 + ullAvailVirtual uint64 + ullAvailExtendedVirtual uint64 +} + +// ReadMemInfo retrieves memory statistics of the host system and returns a +// MemInfo type. +func ReadMemInfo() (*MemInfo, error) { + msi := &memorystatusex{ + dwLength: 64, + } + r1, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msi))) + if r1 == 0 { + return &MemInfo{}, nil + } + return &MemInfo{ + MemTotal: int64(msi.ullTotalPhys), + MemFree: int64(msi.ullAvailPhys), + SwapTotal: int64(msi.ullTotalPageFile), + SwapFree: int64(msi.ullAvailPageFile), + }, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod.go new file mode 100644 index 0000000000..73958182b4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod.go @@ -0,0 +1,22 @@ +// +build !windows + +package system + +import ( + "syscall" +) + +// Mknod creates a filesystem node (file, device special file or named pipe) named path +// with attributes specified by mode and dev. +func Mknod(path string, mode uint32, dev int) error { + return syscall.Mknod(path, mode, dev) +} + +// Mkdev is used to build the value of linux devices (in /dev/) which specifies major +// and minor number of the newly created device special file. +// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes. +// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major, +// then the top 12 bits of the minor. +func Mkdev(major int64, minor int64) uint32 { + return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff)) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod_windows.go new file mode 100644 index 0000000000..2e863c0215 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod_windows.go @@ -0,0 +1,13 @@ +// +build windows + +package system + +// Mknod is not implemented on Windows. +func Mknod(path string, mode uint32, dev int) error { + return ErrNotSupportedPlatform +} + +// Mkdev is not implemented on Windows. +func Mkdev(major int64, minor int64) uint32 { + panic("Mkdev not implemented on Windows.") +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat.go new file mode 100644 index 0000000000..7392c82029 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat.go @@ -0,0 +1,53 @@ +// +build !windows + +package system + +import ( + "syscall" +) + +// StatT type contains status of a file. It contains metadata +// like permission, owner, group, size, etc about a file. +type StatT struct { + mode uint32 + uid uint32 + gid uint32 + rdev uint64 + size int64 + mtim syscall.Timespec +} + +// Mode returns file's permission mode. +func (s StatT) Mode() uint32 { + return s.mode +} + +// UID returns file's user id of owner. +func (s StatT) UID() uint32 { + return s.uid +} + +// Gid returns file's group id of owner. +func (s StatT) Gid() uint32 { + return s.gid +} + +// Rdev returns file's device ID (if it's special file). +func (s StatT) Rdev() uint64 { + return s.rdev +} + +// Size returns file's size. +func (s StatT) Size() int64 { + return s.size +} + +// Mtim returns file's last modification time. +func (s StatT) Mtim() syscall.Timespec { + return s.mtim +} + +// GetLastModification returns file's last modification time. +func (s StatT) GetLastModification() syscall.Timespec { + return s.Mtim() +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_freebsd.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_freebsd.go new file mode 100644 index 0000000000..d0fb6f1519 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_freebsd.go @@ -0,0 +1,27 @@ +package system + +import ( + "syscall" +) + +// fromStatT converts a syscall.Stat_t type to a system.Stat_t type +func fromStatT(s *syscall.Stat_t) (*StatT, error) { + return &StatT{size: s.Size, + mode: uint32(s.Mode), + uid: s.Uid, + gid: s.Gid, + rdev: uint64(s.Rdev), + mtim: s.Mtimespec}, nil +} + +// Stat takes a path to a file and returns +// a system.Stat_t type pertaining to that file. +// +// Throws an error if the file does not exist +func Stat(path string) (*StatT, error) { + s := &syscall.Stat_t{} + if err := syscall.Stat(path, s); err != nil { + return nil, err + } + return fromStatT(s) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_linux.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_linux.go new file mode 100644 index 0000000000..8b1eded138 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_linux.go @@ -0,0 +1,33 @@ +package system + +import ( + "syscall" +) + +// fromStatT converts a syscall.Stat_t type to a system.Stat_t type +func fromStatT(s *syscall.Stat_t) (*StatT, error) { + return &StatT{size: s.Size, + mode: s.Mode, + uid: s.Uid, + gid: s.Gid, + rdev: s.Rdev, + mtim: s.Mtim}, nil +} + +// FromStatT exists only on linux, and loads a system.StatT from a +// syscal.Stat_t. +func FromStatT(s *syscall.Stat_t) (*StatT, error) { + return fromStatT(s) +} + +// Stat takes a path to a file and returns +// a system.StatT type pertaining to that file. +// +// Throws an error if the file does not exist +func Stat(path string) (*StatT, error) { + s := &syscall.Stat_t{} + if err := syscall.Stat(path, s); err != nil { + return nil, err + } + return fromStatT(s) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_test.go new file mode 100644 index 0000000000..57121f1579 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_test.go @@ -0,0 +1,37 @@ +package system + +import ( + "os" + "syscall" + "testing" +) + +// TestFromStatT tests fromStatT for a tempfile +func TestFromStatT(t *testing.T) { + file, _, _, dir := prepareFiles(t) + defer os.RemoveAll(dir) + + stat := &syscall.Stat_t{} + err := syscall.Lstat(file, stat) + + s, err := fromStatT(stat) + if err != nil { + t.Fatal(err) + } + + if stat.Mode != s.Mode() { + t.Fatal("got invalid mode") + } + if stat.Uid != s.UID() { + t.Fatal("got invalid uid") + } + if stat.Gid != s.Gid() { + t.Fatal("got invalid gid") + } + if stat.Rdev != s.Rdev() { + t.Fatal("got invalid rdev") + } + if stat.Mtim != s.Mtim() { + t.Fatal("got invalid mtim") + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_unsupported.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_unsupported.go new file mode 100644 index 0000000000..381ea82116 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_unsupported.go @@ -0,0 +1,17 @@ +// +build !linux,!windows,!freebsd + +package system + +import ( + "syscall" +) + +// fromStatT creates a system.StatT type from a syscall.Stat_t type +func fromStatT(s *syscall.Stat_t) (*StatT, error) { + return &StatT{size: s.Size, + mode: uint32(s.Mode), + uid: s.Uid, + gid: s.Gid, + rdev: uint64(s.Rdev), + mtim: s.Mtimespec}, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_windows.go new file mode 100644 index 0000000000..39490c625c --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat_windows.go @@ -0,0 +1,43 @@ +// +build windows + +package system + +import ( + "os" + "time" +) + +// StatT type contains status of a file. It contains metadata +// like name, permission, size, etc about a file. +type StatT struct { + name string + size int64 + mode os.FileMode + modTime time.Time + isDir bool +} + +// Name returns file's name. +func (s StatT) Name() string { + return s.name +} + +// Size returns file's size. +func (s StatT) Size() int64 { + return s.size +} + +// Mode returns file's permission mode. +func (s StatT) Mode() os.FileMode { + return s.mode +} + +// ModTime returns file's last modification time. +func (s StatT) ModTime() time.Time { + return s.modTime +} + +// IsDir returns whether file is actually a directory. +func (s StatT) IsDir() bool { + return s.isDir +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask.go new file mode 100644 index 0000000000..c670fcd758 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask.go @@ -0,0 +1,13 @@ +// +build !windows + +package system + +import ( + "syscall" +) + +// Umask sets current process's file mode creation mask to newmask +// and return oldmask. +func Umask(newmask int) (oldmask int, err error) { + return syscall.Umask(newmask), nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask_windows.go new file mode 100644 index 0000000000..13f1de1769 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/umask_windows.go @@ -0,0 +1,9 @@ +// +build windows + +package system + +// Umask is not supported on the windows platform. +func Umask(newmask int) (oldmask int, err error) { + // should not be called on cli code path + return 0, ErrNotSupportedPlatform +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_darwin.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_darwin.go new file mode 100644 index 0000000000..9e3dcddbc1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_darwin.go @@ -0,0 +1,14 @@ +package system + +import "syscall" + +// LUtimesNano is not supported by darwin platform. +func LUtimesNano(path string, ts []syscall.Timespec) error { + return ErrNotSupportedPlatform +} + +// UtimesNano is used to change access and modification time of path. +// it can't be used for symbol link file. +func UtimesNano(path string, ts []syscall.Timespec) error { + return syscall.UtimesNano(path, ts) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_freebsd.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_freebsd.go new file mode 100644 index 0000000000..15ce26f063 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_freebsd.go @@ -0,0 +1,28 @@ +package system + +import ( + "syscall" + "unsafe" +) + +// LUtimesNano is used to change access and modification time of the specified path. +// It's used for symbol link file because syscall.UtimesNano doesn't support a NOFOLLOW flag atm. +func LUtimesNano(path string, ts []syscall.Timespec) error { + var _path *byte + _path, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + if _, _, err := syscall.Syscall(syscall.SYS_LUTIMES, uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), 0); err != 0 && err != syscall.ENOSYS { + return err + } + + return nil +} + +// UtimesNano is used to change access and modification time of the specified path. +// It can't be used for symbol link file. +func UtimesNano(path string, ts []syscall.Timespec) error { + return syscall.UtimesNano(path, ts) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_linux.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_linux.go new file mode 100644 index 0000000000..7909801f9e --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_linux.go @@ -0,0 +1,32 @@ +package system + +import ( + "syscall" + "unsafe" +) + +// LUtimesNano is used to change access and modification time of the speficied path. +// It's used for symbol link file because syscall.UtimesNano doesn't support a NOFOLLOW flag atm. +func LUtimesNano(path string, ts []syscall.Timespec) error { + // These are not currently available in syscall + atFdCwd := -100 + atSymLinkNoFollow := 0x100 + + var _path *byte + _path, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + + if _, _, err := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(atFdCwd), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), uintptr(atSymLinkNoFollow), 0, 0); err != 0 && err != syscall.ENOSYS { + return err + } + + return nil +} + +// UtimesNano is used to change access and modification time of the specified path. +// It can't be used for symbol link file. +func UtimesNano(path string, ts []syscall.Timespec) error { + return syscall.UtimesNano(path, ts) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_test.go new file mode 100644 index 0000000000..350cce1ead --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_test.go @@ -0,0 +1,66 @@ +package system + +import ( + "io/ioutil" + "os" + "path/filepath" + "syscall" + "testing" +) + +// prepareFiles creates files for testing in the temp directory +func prepareFiles(t *testing.T) (string, string, string, string) { + dir, err := ioutil.TempDir("", "docker-system-test") + if err != nil { + t.Fatal(err) + } + + file := filepath.Join(dir, "exist") + if err := ioutil.WriteFile(file, []byte("hello"), 0644); err != nil { + t.Fatal(err) + } + + invalid := filepath.Join(dir, "doesnt-exist") + + symlink := filepath.Join(dir, "symlink") + if err := os.Symlink(file, symlink); err != nil { + t.Fatal(err) + } + + return file, invalid, symlink, dir +} + +func TestLUtimesNano(t *testing.T) { + file, invalid, symlink, dir := prepareFiles(t) + defer os.RemoveAll(dir) + + before, err := os.Stat(file) + if err != nil { + t.Fatal(err) + } + + ts := []syscall.Timespec{{0, 0}, {0, 0}} + if err := LUtimesNano(symlink, ts); err != nil { + t.Fatal(err) + } + + symlinkInfo, err := os.Lstat(symlink) + if err != nil { + t.Fatal(err) + } + if before.ModTime().Unix() == symlinkInfo.ModTime().Unix() { + t.Fatal("The modification time of the symlink should be different") + } + + fileInfo, err := os.Stat(file) + if err != nil { + t.Fatal(err) + } + if before.ModTime().Unix() != fileInfo.ModTime().Unix() { + t.Fatal("The modification time of the file should be same") + } + + if err := LUtimesNano(invalid, ts); err == nil { + t.Fatal("Doesn't return an error on a non-existing file") + } +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_unsupported.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_unsupported.go new file mode 100644 index 0000000000..cb614a12a3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/utimes_unsupported.go @@ -0,0 +1,15 @@ +// +build !linux,!freebsd,!darwin + +package system + +import "syscall" + +// LUtimesNano is not supported on platforms other than linux, freebsd and darwin. +func LUtimesNano(path string, ts []syscall.Timespec) error { + return ErrNotSupportedPlatform +} + +// UtimesNano is not supported on platforms other than linux, freebsd and darwin. +func UtimesNano(path string, ts []syscall.Timespec) error { + return ErrNotSupportedPlatform +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_linux.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_linux.go new file mode 100644 index 0000000000..d2e2c05799 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_linux.go @@ -0,0 +1,63 @@ +package system + +import ( + "syscall" + "unsafe" +) + +// Lgetxattr retrieves the value of the extended attribute identified by attr +// and associated with the given path in the file system. +// It will returns a nil slice and nil error if the xattr is not set. +func Lgetxattr(path string, attr string) ([]byte, error) { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return nil, err + } + attrBytes, err := syscall.BytePtrFromString(attr) + if err != nil { + return nil, err + } + + dest := make([]byte, 128) + destBytes := unsafe.Pointer(&dest[0]) + sz, _, errno := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0) + if errno == syscall.ENODATA { + return nil, nil + } + if errno == syscall.ERANGE { + dest = make([]byte, sz) + destBytes := unsafe.Pointer(&dest[0]) + sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0) + } + if errno != 0 { + return nil, errno + } + + return dest[:sz], nil +} + +var _zero uintptr + +// Lsetxattr sets the value of the extended attribute identified by attr +// and associated with the given path in the file system. +func Lsetxattr(path string, attr string, data []byte, flags int) error { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + attrBytes, err := syscall.BytePtrFromString(attr) + if err != nil { + return err + } + var dataBytes unsafe.Pointer + if len(data) > 0 { + dataBytes = unsafe.Pointer(&data[0]) + } else { + dataBytes = unsafe.Pointer(&_zero) + } + _, _, errno := syscall.Syscall6(syscall.SYS_LSETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(dataBytes), uintptr(len(data)), uintptr(flags), 0) + if errno != 0 { + return errno + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_unsupported.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_unsupported.go new file mode 100644 index 0000000000..0114f2227c --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/system/xattrs_unsupported.go @@ -0,0 +1,13 @@ +// +build !linux + +package system + +// Lgetxattr is not supported on platforms other than linux. +func Lgetxattr(path string, attr string) ([]byte, error) { + return nil, ErrNotSupportedPlatform +} + +// Lsetxattr is not supported on platforms other than linux. +func Lsetxattr(path string, attr string, data []byte, flags int) error { + return ErrNotSupportedPlatform +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_linux_cgo.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_linux_cgo.go index d47cf59b8d..1005084fd2 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_linux_cgo.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/tc_linux_cgo.go @@ -10,6 +10,9 @@ import ( // #include import "C" +// Termios is the Unix API for terminal I/O. +// It is passthgrouh for syscall.Termios in order to make it portable with +// other platforms where it is not available or handled differently. type Termios syscall.Termios // MakeRaw put the terminal connected to the given file descriptor into raw diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term.go index b945a3dcea..7912ae43e3 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term.go @@ -1,5 +1,7 @@ // +build !windows +// Package term provides provides structures and helper functions to work with +// terminal (state, sizes). package term import ( @@ -12,13 +14,16 @@ import ( ) var ( + // ErrInvalidState is returned if the state of the terminal is invalid. ErrInvalidState = errors.New("Invalid terminal state") ) +// State represents the state of the terminal. type State struct { termios Termios } +// Winsize represents the size of the terminal window. type Winsize struct { Height uint16 Width uint16 @@ -26,10 +31,12 @@ type Winsize struct { y uint16 } +// StdStreams returns the standard streams (stdin, stdout, stedrr). func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { return os.Stdin, os.Stdout, os.Stderr } +// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. func GetFdInfo(in interface{}) (uintptr, bool) { var inFd uintptr var isTerminalIn bool @@ -40,6 +47,7 @@ func GetFdInfo(in interface{}) (uintptr, bool) { return inFd, isTerminalIn } +// GetWinsize returns the window size based on the specified file descriptor. func GetWinsize(fd uintptr) (*Winsize, error) { ws := &Winsize{} _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws))) @@ -50,6 +58,7 @@ func GetWinsize(fd uintptr) (*Winsize, error) { return ws, err } +// SetWinsize tries to set the specified window size for the specified file descriptor. func SetWinsize(fd uintptr, ws *Winsize) error { _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws))) // Skipp errno = 0 @@ -65,8 +74,8 @@ func IsTerminal(fd uintptr) bool { return tcget(fd, &termios) == 0 } -// Restore restores the terminal connected to the given file descriptor to a -// previous state. +// RestoreTerminal 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 @@ -77,6 +86,7 @@ func RestoreTerminal(fd uintptr, state *State) error { return nil } +// SaveState saves the state of the terminal connected to the given file descriptor. func SaveState(fd uintptr) (*State, error) { var oldState State if err := tcget(fd, &oldState.termios); err != 0 { @@ -86,6 +96,8 @@ func SaveState(fd uintptr) (*State, error) { return &oldState, nil } +// DisableEcho applies the specified state to the terminal connected to the file +// descriptor, with echo disabled. func DisableEcho(fd uintptr, state *State) error { newState := state.termios newState.Lflag &^= syscall.ECHO @@ -97,6 +109,8 @@ func DisableEcho(fd uintptr, state *State) error { return nil } +// SetRawTerminal puts the terminal connected to the given file descriptor into +// raw mode and returns the previous state. func SetRawTerminal(fd uintptr) (*State, error) { oldState, err := MakeRaw(fd) if err != nil { diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term_windows.go index f46c9c8acf..da9295eec6 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term_windows.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/term_windows.go @@ -1,12 +1,16 @@ // +build windows + package term import ( + "fmt" "io" "os" + "os/signal" + "github.com/Azure/go-ansiterm/winterm" "github.com/Sirupsen/logrus" - "github.com/docker/docker/pkg/term/winconsole" + "github.com/docker/docker/pkg/term/windows" ) // State holds the console mode for the terminal. @@ -22,6 +26,7 @@ type Winsize struct { y uint16 } +// StdStreams returns the standard streams (stdin, stdout, stedrr). func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { switch { case os.Getenv("ConEmuANSI") == "ON": @@ -29,53 +34,97 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { return os.Stdin, os.Stdout, os.Stderr case os.Getenv("MSYSTEM") != "": // MSYS (mingw) does not emulate ANSI well. - return winconsole.WinConsoleStreams() + return windows.ConsoleStreams() default: - return winconsole.WinConsoleStreams() + return windows.ConsoleStreams() } } -// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal. +// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. func GetFdInfo(in interface{}) (uintptr, bool) { - return winconsole.GetHandleInfo(in) + return windows.GetHandleInfo(in) } -// GetWinsize retrieves the window size of the terminal connected to the passed file descriptor. +// GetWinsize returns the window size based on the specified file descriptor. func GetWinsize(fd uintptr) (*Winsize, error) { - info, err := winconsole.GetConsoleScreenBufferInfo(fd) + + info, err := winterm.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{ + winsize := &Winsize{ Width: uint16(info.Window.Right - info.Window.Left + 1), Height: uint16(info.Window.Bottom - info.Window.Top + 1), x: 0, - y: 0}, nil + y: 0} + + // Note: GetWinsize is called frequently -- uncomment only for excessive details + // logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String()) + // logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y) + return winsize, nil } -// SetWinsize sets the size of the given terminal connected to the passed file descriptor. +// SetWinsize tries to set the specified window size for the specified file descriptor. func SetWinsize(fd uintptr, ws *Winsize) error { - // TODO(azlinux): Implement SetWinsize - logrus.Debugf("[windows] SetWinsize: WARNING -- Unsupported method invoked") - return nil + + // Ensure the requested dimensions are no larger than the maximum window size + info, err := winterm.GetConsoleScreenBufferInfo(fd) + if err != nil { + return err + } + + if ws.Width == 0 || ws.Height == 0 || ws.Width > uint16(info.MaximumWindowSize.X) || ws.Height > uint16(info.MaximumWindowSize.Y) { + return fmt.Errorf("Illegal window size: (%v,%v) -- Maximum allow: (%v,%v)", + ws.Width, ws.Height, info.MaximumWindowSize.X, info.MaximumWindowSize.Y) + } + + // Narrow the sizes to that used by Windows + width := winterm.SHORT(ws.Width) + height := winterm.SHORT(ws.Height) + + // Set the dimensions while ensuring they remain within the bounds of the backing console buffer + // -- Shrinking will always succeed. Growing may push the edges past the buffer boundary. When that occurs, + // shift the upper left just enough to keep the new window within the buffer. + rect := info.Window + if width < rect.Right-rect.Left+1 { + rect.Right = rect.Left + width - 1 + } else if width > rect.Right-rect.Left+1 { + rect.Right = rect.Left + width - 1 + if rect.Right >= info.Size.X { + rect.Left = info.Size.X - width + rect.Right = info.Size.X - 1 + } + } + + if height < rect.Bottom-rect.Top+1 { + rect.Bottom = rect.Top + height - 1 + } else if height > rect.Bottom-rect.Top+1 { + rect.Bottom = rect.Top + height - 1 + if rect.Bottom >= info.Size.Y { + rect.Top = info.Size.Y - height + rect.Bottom = info.Size.Y - 1 + } + } + logrus.Debugf("[windows] SetWinsize: Requested((%v,%v)) Actual(%v)", ws.Width, ws.Height, rect) + + return winterm.SetConsoleWindowInfo(fd, true, rect) } // IsTerminal returns true if the given file descriptor is a terminal. func IsTerminal(fd uintptr) bool { - return winconsole.IsConsole(fd) + return windows.IsConsole(fd) } -// RestoreTerminal restores the terminal connected to the given file descriptor to a -// previous state. +// 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) + return winterm.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) + mode, e := winterm.GetConsoleMode(fd) if e != nil { return nil, e } @@ -83,30 +132,37 @@ func SaveState(fd uintptr) (*State, error) { } // 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 +// -- See https://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) + mode &^= winterm.ENABLE_ECHO_INPUT + mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT + + err := winterm.SetConsoleMode(fd, mode) + if err != nil { + return err + } + + // Register an interrupt handler to catch and restore prior state + restoreAtInterrupt(fd, state) + return nil } // 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. +// mode and returns the previous state. 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. + + // Register an interrupt handler to catch and restore prior state + restoreAtInterrupt(fd, 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. +// MakeRaw puts the terminal (Windows Console) 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 { @@ -119,20 +175,31 @@ func MakeRaw(fd uintptr) (*State, error) { 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 + mode &^= winterm.ENABLE_ECHO_INPUT + mode &^= winterm.ENABLE_LINE_INPUT + mode &^= winterm.ENABLE_MOUSE_INPUT + mode &^= winterm.ENABLE_WINDOW_INPUT + mode &^= winterm.ENABLE_PROCESSED_INPUT // Enable these modes - mode |= winconsole.ENABLE_EXTENDED_FLAGS - mode |= winconsole.ENABLE_INSERT_MODE - mode |= winconsole.ENABLE_QUICK_EDIT_MODE + mode |= winterm.ENABLE_EXTENDED_FLAGS + mode |= winterm.ENABLE_INSERT_MODE + mode |= winterm.ENABLE_QUICK_EDIT_MODE - err = winconsole.SetConsoleMode(fd, mode) + err = winterm.SetConsoleMode(fd, mode) if err != nil { return nil, err } return state, nil } + +func restoreAtInterrupt(fd uintptr, state *State) { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, os.Interrupt) + + go func() { + _ = <-sigchan + RestoreTerminal(fd, state) + os.Exit(0) + }() +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_darwin.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_darwin.go index 11cd70d10b..480db900ac 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_darwin.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_darwin.go @@ -8,7 +8,10 @@ import ( const ( getTermios = syscall.TIOCGETA setTermios = syscall.TIOCSETA +) +// Termios magic numbers, passthrough to the ones defined in syscall. +const ( IGNBRK = syscall.IGNBRK PARMRK = syscall.PARMRK INLCR = syscall.INLCR @@ -29,6 +32,7 @@ const ( IEXTEN = syscall.IEXTEN ) +// Termios is the Unix API for terminal I/O. type Termios struct { Iflag uint64 Oflag uint64 diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_freebsd.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_freebsd.go index ed3659572c..ed843ad69c 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_freebsd.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_freebsd.go @@ -8,7 +8,10 @@ import ( const ( getTermios = syscall.TIOCGETA setTermios = syscall.TIOCSETA +) +// Termios magic numbers, passthrough to the ones defined in syscall. +const ( IGNBRK = syscall.IGNBRK PARMRK = syscall.PARMRK INLCR = syscall.INLCR @@ -29,6 +32,7 @@ const ( IEXTEN = syscall.IEXTEN ) +// Termios is the Unix API for terminal I/O. type Termios struct { Iflag uint32 Oflag uint32 diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_linux.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_linux.go index 024187ff06..22921b6aef 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_linux.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/termios_linux.go @@ -12,6 +12,7 @@ const ( setTermios = syscall.TCSETS ) +// Termios is the Unix API for terminal I/O. type Termios struct { Iflag uint32 Oflag uint32 diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/console_windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/console_windows.go deleted file mode 100644 index ce40a93167..0000000000 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/console_windows.go +++ /dev/null @@ -1,1053 +0,0 @@ -// +build windows - -package winconsole - -import ( - "bytes" - "fmt" - "io" - "os" - "strconv" - "strings" - "sync" - "syscall" - "unsafe" - - "github.com/Sirupsen/logrus" -) - -const ( - // Consts for Get/SetConsoleMode function - // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx - ENABLE_PROCESSED_INPUT = 0x0001 - ENABLE_LINE_INPUT = 0x0002 - ENABLE_ECHO_INPUT = 0x0004 - ENABLE_WINDOW_INPUT = 0x0008 - ENABLE_MOUSE_INPUT = 0x0010 - ENABLE_INSERT_MODE = 0x0020 - ENABLE_QUICK_EDIT_MODE = 0x0040 - ENABLE_EXTENDED_FLAGS = 0x0080 - - // If parameter is a screen buffer handle, additional values - ENABLE_PROCESSED_OUTPUT = 0x0001 - ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 - - //http://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes - FOREGROUND_BLUE = 1 - FOREGROUND_GREEN = 2 - FOREGROUND_RED = 4 - FOREGROUND_INTENSITY = 8 - FOREGROUND_MASK_SET = 0x000F - FOREGROUND_MASK_UNSET = 0xFFF0 - - BACKGROUND_BLUE = 16 - BACKGROUND_GREEN = 32 - BACKGROUND_RED = 64 - BACKGROUND_INTENSITY = 128 - BACKGROUND_MASK_SET = 0x00F0 - BACKGROUND_MASK_UNSET = 0xFF0F - - COMMON_LVB_REVERSE_VIDEO = 0x4000 - COMMON_LVB_UNDERSCORE = 0x8000 - - // http://man7.org/linux/man-pages/man4/console_codes.4.html - // ECMA-48 Set Graphics Rendition - ANSI_ATTR_RESET = 0 - ANSI_ATTR_BOLD = 1 - ANSI_ATTR_DIM = 2 - ANSI_ATTR_UNDERLINE = 4 - ANSI_ATTR_BLINK = 5 - ANSI_ATTR_REVERSE = 7 - ANSI_ATTR_INVISIBLE = 8 - - ANSI_ATTR_UNDERLINE_OFF = 24 - ANSI_ATTR_BLINK_OFF = 25 - ANSI_ATTR_REVERSE_OFF = 27 - ANSI_ATTR_INVISIBLE_OFF = 8 - - ANSI_FOREGROUND_BLACK = 30 - ANSI_FOREGROUND_RED = 31 - ANSI_FOREGROUND_GREEN = 32 - ANSI_FOREGROUND_YELLOW = 33 - ANSI_FOREGROUND_BLUE = 34 - ANSI_FOREGROUND_MAGENTA = 35 - ANSI_FOREGROUND_CYAN = 36 - ANSI_FOREGROUND_WHITE = 37 - ANSI_FOREGROUND_DEFAULT = 39 - - ANSI_BACKGROUND_BLACK = 40 - ANSI_BACKGROUND_RED = 41 - ANSI_BACKGROUND_GREEN = 42 - ANSI_BACKGROUND_YELLOW = 43 - ANSI_BACKGROUND_BLUE = 44 - ANSI_BACKGROUND_MAGENTA = 45 - ANSI_BACKGROUND_CYAN = 46 - ANSI_BACKGROUND_WHITE = 47 - ANSI_BACKGROUND_DEFAULT = 49 - - ANSI_MAX_CMD_LENGTH = 256 - - MAX_INPUT_EVENTS = 128 - MAX_INPUT_BUFFER = 1024 - DEFAULT_WIDTH = 80 - DEFAULT_HEIGHT = 24 -) - -// http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx -const ( - VK_PRIOR = 0x21 // PAGE UP key - VK_NEXT = 0x22 // PAGE DOWN key - VK_END = 0x23 // END key - VK_HOME = 0x24 // HOME key - VK_LEFT = 0x25 // LEFT ARROW key - VK_UP = 0x26 // UP ARROW key - VK_RIGHT = 0x27 // RIGHT ARROW key - VK_DOWN = 0x28 // DOWN ARROW key - VK_SELECT = 0x29 // SELECT key - VK_PRINT = 0x2A // PRINT key - VK_EXECUTE = 0x2B // EXECUTE key - VK_SNAPSHOT = 0x2C // PRINT SCREEN key - VK_INSERT = 0x2D // INS key - VK_DELETE = 0x2E // DEL key - VK_HELP = 0x2F // HELP key - VK_F1 = 0x70 // F1 key - VK_F2 = 0x71 // F2 key - VK_F3 = 0x72 // F3 key - VK_F4 = 0x73 // F4 key - VK_F5 = 0x74 // F5 key - VK_F6 = 0x75 // F6 key - VK_F7 = 0x76 // F7 key - VK_F8 = 0x77 // F8 key - VK_F9 = 0x78 // F9 key - VK_F10 = 0x79 // F10 key - VK_F11 = 0x7A // F11 key - VK_F12 = 0x7B // F12 key -) - -var kernel32DLL = syscall.NewLazyDLL("kernel32.dll") - -var ( - setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode") - getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo") - setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition") - setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute") - fillConsoleOutputCharacterProc = kernel32DLL.NewProc("FillConsoleOutputCharacterW") - writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW") - readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW") - getNumberOfConsoleInputEventsProc = kernel32DLL.NewProc("GetNumberOfConsoleInputEvents") - getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo") - setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo") - setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo") - setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize") -) - -// types for calling various windows API -// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx -type ( - SHORT int16 - BOOL int32 - WORD uint16 - WCHAR uint16 - DWORD uint32 - - SMALL_RECT struct { - Left SHORT - Top SHORT - Right SHORT - Bottom SHORT - } - - COORD struct { - X SHORT - Y SHORT - } - - CONSOLE_SCREEN_BUFFER_INFO struct { - Size COORD - CursorPosition COORD - Attributes WORD - Window SMALL_RECT - MaximumWindowSize COORD - } - - CONSOLE_CURSOR_INFO struct { - Size DWORD - Visible BOOL - } - - // http://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx - KEY_EVENT_RECORD struct { - KeyDown BOOL - RepeatCount WORD - VirtualKeyCode WORD - VirtualScanCode WORD - UnicodeChar WCHAR - ControlKeyState DWORD - } - - INPUT_RECORD struct { - EventType WORD - KeyEvent KEY_EVENT_RECORD - } - - CHAR_INFO struct { - UnicodeChar WCHAR - Attributes WORD - } -) - -// TODO(azlinux): Basic type clean-up -// -- Convert all uses of uintptr to syscall.Handle to be consistent with Windows syscall -// -- Convert, as appropriate, types to use defined Windows types (e.g., DWORD instead of uint32) - -// Implements the TerminalEmulator interface -type WindowsTerminal struct { - outMutex sync.Mutex - inMutex sync.Mutex - inputBuffer []byte - inputSize int - inputEvents []INPUT_RECORD - screenBufferInfo *CONSOLE_SCREEN_BUFFER_INFO - inputEscapeSequence []byte -} - -func getStdHandle(stdhandle int) uintptr { - handle, err := syscall.GetStdHandle(stdhandle) - if err != nil { - panic(fmt.Errorf("could not get standard io handle %d", stdhandle)) - } - return uintptr(handle) -} - -func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { - handler := &WindowsTerminal{ - inputBuffer: make([]byte, MAX_INPUT_BUFFER), - inputEscapeSequence: []byte(KEY_ESC_CSI), - inputEvents: make([]INPUT_RECORD, MAX_INPUT_EVENTS), - } - - if IsConsole(os.Stdin.Fd()) { - stdIn = &terminalReader{ - wrappedReader: os.Stdin, - emulator: handler, - command: make([]byte, 0, ANSI_MAX_CMD_LENGTH), - fd: getStdHandle(syscall.STD_INPUT_HANDLE), - } - } else { - stdIn = os.Stdin - } - - if IsConsole(os.Stdout.Fd()) { - stdoutHandle := getStdHandle(syscall.STD_OUTPUT_HANDLE) - - // Save current screen buffer info - screenBufferInfo, err := GetConsoleScreenBufferInfo(stdoutHandle) - if err != nil { - // If GetConsoleScreenBufferInfo returns a nil error, it usually means that stdout is not a TTY. - // However, this is in the branch where stdout is a TTY, hence the panic. - panic("could not get console screen buffer info") - } - handler.screenBufferInfo = screenBufferInfo - - buffer = make([]CHAR_INFO, screenBufferInfo.MaximumWindowSize.X*screenBufferInfo.MaximumWindowSize.Y) - - stdOut = &terminalWriter{ - wrappedWriter: os.Stdout, - emulator: handler, - command: make([]byte, 0, ANSI_MAX_CMD_LENGTH), - fd: stdoutHandle, - } - } else { - stdOut = os.Stdout - } - - if IsConsole(os.Stderr.Fd()) { - stdErr = &terminalWriter{ - wrappedWriter: os.Stderr, - emulator: handler, - command: make([]byte, 0, ANSI_MAX_CMD_LENGTH), - fd: getStdHandle(syscall.STD_ERROR_HANDLE), - } - } else { - stdErr = os.Stderr - } - - return stdIn, stdOut, stdErr -} - -// GetHandleInfo returns file descriptor and bool indicating whether the file is a console. -func GetHandleInfo(in interface{}) (uintptr, bool) { - var inFd uintptr - var isTerminalIn bool - - switch t := in.(type) { - case *terminalReader: - in = t.wrappedReader - case *terminalWriter: - in = t.wrappedWriter - } - - if file, ok := in.(*os.File); ok { - inFd = file.Fd() - isTerminalIn = IsConsole(inFd) - } - return inFd, isTerminalIn -} - -func getError(r1, r2 uintptr, lastErr error) error { - // If the function fails, the return value is zero. - if r1 == 0 { - if lastErr != nil { - return lastErr - } - return syscall.EINVAL - } - return nil -} - -// GetConsoleMode gets the console mode for given file descriptor -// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx -func GetConsoleMode(handle uintptr) (uint32, error) { - var mode uint32 - err := syscall.GetConsoleMode(syscall.Handle(handle), &mode) - return mode, err -} - -// SetConsoleMode sets the console mode for given file descriptor -// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx -func SetConsoleMode(handle uintptr, mode uint32) error { - return getError(setConsoleModeProc.Call(handle, uintptr(mode), 0)) -} - -// SetCursorVisible sets the cursor visbility -// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx -func SetCursorVisible(handle uintptr, isVisible BOOL) (bool, error) { - var cursorInfo *CONSOLE_CURSOR_INFO = &CONSOLE_CURSOR_INFO{} - if err := getError(getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil { - return false, err - } - cursorInfo.Visible = isVisible - if err := getError(setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil { - return false, err - } - return true, nil -} - -// SetWindowSize sets the size of the console window. -func SetWindowSize(handle uintptr, width, height, max SHORT) (bool, error) { - window := SMALL_RECT{Left: 0, Top: 0, Right: width - 1, Bottom: height - 1} - coord := COORD{X: width - 1, Y: max} - if err := getError(setConsoleWindowInfoProc.Call(handle, uintptr(1), uintptr(unsafe.Pointer(&window)))); err != nil { - return false, err - } - if err := getError(setConsoleScreenBufferSizeProc.Call(handle, marshal(coord))); err != nil { - return false, err - } - return true, nil -} - -// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer. -// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx -func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) { - var info CONSOLE_SCREEN_BUFFER_INFO - if err := getError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0)); err != nil { - return nil, err - } - return &info, nil -} - -// setConsoleTextAttribute sets the attributes of characters written to the -// console screen buffer by the WriteFile or WriteConsole function, -// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx -func setConsoleTextAttribute(handle uintptr, attribute WORD) error { - return getError(setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)) -} - -func writeConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) (bool, error) { - if err := getError(writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), marshal(bufferSize), marshal(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))); err != nil { - return false, err - } - return true, nil -} - -// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682663(v=vs.85).aspx -func fillConsoleOutputCharacter(handle uintptr, fillChar byte, length uint32, writeCord COORD) (bool, error) { - out := int64(0) - if err := getError(fillConsoleOutputCharacterProc.Call(handle, uintptr(fillChar), uintptr(length), marshal(writeCord), uintptr(unsafe.Pointer(&out)))); err != nil { - return false, err - } - return true, nil -} - -// Gets the number of space characters to write for "clearing" the section of terminal -func getNumberOfChars(fromCoord COORD, toCoord COORD, screenSize COORD) uint32 { - // must be valid cursor position - if fromCoord.X < 0 || fromCoord.Y < 0 || toCoord.X < 0 || toCoord.Y < 0 { - return 0 - } - if fromCoord.X >= screenSize.X || fromCoord.Y >= screenSize.Y || toCoord.X >= screenSize.X || toCoord.Y >= screenSize.Y { - return 0 - } - // can't be backwards - if fromCoord.Y > toCoord.Y { - return 0 - } - // same line - if fromCoord.Y == toCoord.Y { - return uint32(toCoord.X-fromCoord.X) + 1 - } - // spans more than one line - if fromCoord.Y < toCoord.Y { - // from start till end of line for first line + from start of line till end - retValue := uint32(screenSize.X-fromCoord.X) + uint32(toCoord.X) + 1 - // don't count first and last line - linesBetween := toCoord.Y - fromCoord.Y - 1 - if linesBetween > 0 { - retValue = retValue + uint32(linesBetween*screenSize.X) - } - return retValue - } - return 0 -} - -var buffer []CHAR_INFO - -func clearDisplayRect(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) { - var writeRegion SMALL_RECT - writeRegion.Left = fromCoord.X - writeRegion.Top = fromCoord.Y - writeRegion.Right = toCoord.X - writeRegion.Bottom = toCoord.Y - - // allocate and initialize buffer - width := toCoord.X - fromCoord.X + 1 - height := toCoord.Y - fromCoord.Y + 1 - size := uint32(width) * uint32(height) - if size > 0 { - buffer := make([]CHAR_INFO, size) - for i := range buffer { - buffer[i] = CHAR_INFO{WCHAR(' '), attributes} - } - - // Write to buffer - r, err := writeConsoleOutput(handle, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &writeRegion) - if !r { - if err != nil { - return 0, err - } - return 0, syscall.EINVAL - } - } - return uint32(size), nil -} - -func clearDisplayRange(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) { - nw := uint32(0) - // start and end on same line - if fromCoord.Y == toCoord.Y { - return clearDisplayRect(handle, attributes, fromCoord, toCoord) - } - // TODO(azlinux): if full screen, optimize - - // spans more than one line - if fromCoord.Y < toCoord.Y { - // from start position till end of line for first line - n, err := clearDisplayRect(handle, attributes, fromCoord, COORD{X: toCoord.X, Y: fromCoord.Y}) - if err != nil { - return nw, err - } - nw += n - // lines between - linesBetween := toCoord.Y - fromCoord.Y - 1 - if linesBetween > 0 { - n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: toCoord.X, Y: toCoord.Y - 1}) - if err != nil { - return nw, err - } - nw += n - } - // lines at end - n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord) - if err != nil { - return nw, err - } - nw += n - } - return nw, nil -} - -// setConsoleCursorPosition sets the console cursor position -// Note The X and Y are zero based -// If relative is true then the new position is relative to current one -func setConsoleCursorPosition(handle uintptr, isRelative bool, column int16, line int16) error { - screenBufferInfo, err := GetConsoleScreenBufferInfo(handle) - if err != nil { - return err - } - var position COORD - if isRelative { - position.X = screenBufferInfo.CursorPosition.X + SHORT(column) - position.Y = screenBufferInfo.CursorPosition.Y + SHORT(line) - } else { - position.X = SHORT(column) - position.Y = SHORT(line) - } - return getError(setConsoleCursorPositionProc.Call(handle, marshal(position), 0)) -} - -// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683207(v=vs.85).aspx -func getNumberOfConsoleInputEvents(handle uintptr) (uint16, error) { - var n DWORD - if err := getError(getNumberOfConsoleInputEventsProc.Call(handle, uintptr(unsafe.Pointer(&n)))); err != nil { - return 0, err - } - return uint16(n), nil -} - -//http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx -func readConsoleInputKey(handle uintptr, inputBuffer []INPUT_RECORD) (int, error) { - var nr DWORD - if err := getError(readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&inputBuffer[0])), uintptr(len(inputBuffer)), uintptr(unsafe.Pointer(&nr)))); err != nil { - return 0, err - } - return int(nr), nil -} - -func getWindowsTextAttributeForAnsiValue(originalFlag WORD, defaultValue WORD, ansiValue int16) (WORD, error) { - flag := WORD(originalFlag) - if flag == 0 { - flag = defaultValue - } - switch ansiValue { - case ANSI_ATTR_RESET: - flag &^= COMMON_LVB_UNDERSCORE - flag &^= BACKGROUND_INTENSITY - flag = flag | FOREGROUND_INTENSITY - case ANSI_ATTR_INVISIBLE: - // TODO: how do you reset reverse? - case ANSI_ATTR_UNDERLINE: - flag = flag | COMMON_LVB_UNDERSCORE - case ANSI_ATTR_BLINK: - // seems like background intenisty is blink - flag = flag | BACKGROUND_INTENSITY - case ANSI_ATTR_UNDERLINE_OFF: - flag &^= COMMON_LVB_UNDERSCORE - case ANSI_ATTR_BLINK_OFF: - // seems like background intenisty is blink - flag &^= BACKGROUND_INTENSITY - case ANSI_ATTR_BOLD: - flag = flag | FOREGROUND_INTENSITY - case ANSI_ATTR_DIM: - flag &^= FOREGROUND_INTENSITY - case ANSI_ATTR_REVERSE, ANSI_ATTR_REVERSE_OFF: - // swap forground and background bits - foreground := flag & FOREGROUND_MASK_SET - background := flag & BACKGROUND_MASK_SET - flag = (flag & BACKGROUND_MASK_UNSET & FOREGROUND_MASK_UNSET) | (foreground << 4) | (background >> 4) - - // FOREGROUND - case ANSI_FOREGROUND_DEFAULT: - flag = (flag & FOREGROUND_MASK_UNSET) | (defaultValue & FOREGROUND_MASK_SET) - case ANSI_FOREGROUND_BLACK: - flag = flag ^ (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE) - case ANSI_FOREGROUND_RED: - flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED - case ANSI_FOREGROUND_GREEN: - flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_GREEN - case ANSI_FOREGROUND_YELLOW: - flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED | FOREGROUND_GREEN - case ANSI_FOREGROUND_BLUE: - flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_BLUE - case ANSI_FOREGROUND_MAGENTA: - flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED | FOREGROUND_BLUE - case ANSI_FOREGROUND_CYAN: - flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_GREEN | FOREGROUND_BLUE - case ANSI_FOREGROUND_WHITE: - flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE - - // Background - case ANSI_BACKGROUND_DEFAULT: - // Black with no intensity - flag = (flag & BACKGROUND_MASK_UNSET) | (defaultValue & BACKGROUND_MASK_SET) - case ANSI_BACKGROUND_BLACK: - flag = (flag & BACKGROUND_MASK_UNSET) - case ANSI_BACKGROUND_RED: - flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED - case ANSI_BACKGROUND_GREEN: - flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_GREEN - case ANSI_BACKGROUND_YELLOW: - flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED | BACKGROUND_GREEN - case ANSI_BACKGROUND_BLUE: - flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_BLUE - case ANSI_BACKGROUND_MAGENTA: - flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED | BACKGROUND_BLUE - case ANSI_BACKGROUND_CYAN: - flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_GREEN | BACKGROUND_BLUE - case ANSI_BACKGROUND_WHITE: - flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE - } - return flag, nil -} - -// HandleOutputCommand interpretes the Ansi commands and then makes appropriate Win32 calls -func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) (n int, err error) { - // always consider all the bytes in command, processed - n = len(command) - - parsedCommand := parseAnsiCommand(command) - logrus.Debugf("[windows] HandleOutputCommand: %v", parsedCommand) - - // console settings changes need to happen in atomic way - term.outMutex.Lock() - defer term.outMutex.Unlock() - - switch parsedCommand.Command { - case "m": - // [Value;...;Valuem - // Set Graphics Mode: - // Calls the graphics functions specified by the following values. - // These specified functions remain active until the next occurrence of this escape sequence. - // Graphics mode changes the colors and attributes of text (such as bold and underline) displayed on the screen. - screenBufferInfo, err := GetConsoleScreenBufferInfo(handle) - if err != nil { - return n, err - } - flag := screenBufferInfo.Attributes - for _, e := range parsedCommand.Parameters { - value, _ := strconv.ParseInt(e, 10, 16) // base 10, 16 bit - if value == ANSI_ATTR_RESET { - flag = term.screenBufferInfo.Attributes // reset - } else { - flag, err = getWindowsTextAttributeForAnsiValue(flag, term.screenBufferInfo.Attributes, int16(value)) - if err != nil { - return n, err - } - } - } - if err := setConsoleTextAttribute(handle, flag); err != nil { - return n, err - } - case "H", "f": - // [line;columnH - // [line;columnf - // Moves the cursor to the specified position (coordinates). - // If you do not specify a position, the cursor moves to the home position at the upper-left corner of the screen (line 0, column 0). - screenBufferInfo, err := GetConsoleScreenBufferInfo(handle) - if err != nil { - return n, err - } - line, err := parseInt16OrDefault(parsedCommand.getParam(0), 1) - if err != nil { - return n, err - } - if line > int16(screenBufferInfo.Window.Bottom) { - line = int16(screenBufferInfo.Window.Bottom) + 1 - } - column, err := parseInt16OrDefault(parsedCommand.getParam(1), 1) - if err != nil { - return n, err - } - if column > int16(screenBufferInfo.Window.Right) { - column = int16(screenBufferInfo.Window.Right) + 1 - } - // The numbers are not 0 based, but 1 based - logrus.Debugf("[windows] HandleOutputCommmand: Moving cursor to (%v,%v)", column-1, line-1) - if err := setConsoleCursorPosition(handle, false, column-1, line-1); err != nil { - return n, err - } - - case "A": - // [valueA - // Moves the cursor up by the specified number of lines without changing columns. - // If the cursor is already on the top line, ignores this sequence. - value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1) - if err != nil { - return len(command), err - } - if err := setConsoleCursorPosition(handle, true, 0, -value); err != nil { - return n, err - } - case "B": - // [valueB - // Moves the cursor down by the specified number of lines without changing columns. - // If the cursor is already on the bottom line, ignores this sequence. - value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1) - if err != nil { - return n, err - } - if err := setConsoleCursorPosition(handle, true, 0, value); err != nil { - return n, err - } - case "C": - // [valueC - // Moves the cursor forward by the specified number of columns without changing lines. - // If the cursor is already in the rightmost column, ignores this sequence. - value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1) - if err != nil { - return n, err - } - if err := setConsoleCursorPosition(handle, true, value, 0); err != nil { - return n, err - } - case "D": - // [valueD - // Moves the cursor back by the specified number of columns without changing lines. - // If the cursor is already in the leftmost column, ignores this sequence. - value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1) - if err != nil { - return n, err - } - if err := setConsoleCursorPosition(handle, true, -value, 0); err != nil { - return n, err - } - case "J": - // [J Erases from the cursor to the end of the screen, including the cursor position. - // [1J Erases from the beginning of the screen to the cursor, including the cursor position. - // [2J Erases the complete display. The cursor does not move. - // Clears the screen and moves the cursor to the home position (line 0, column 0). - value, err := parseInt16OrDefault(parsedCommand.getParam(0), 0) - if err != nil { - return n, err - } - var start COORD - var cursor COORD - var end COORD - screenBufferInfo, err := GetConsoleScreenBufferInfo(handle) - if err != nil { - return n, err - } - switch value { - case 0: - start = screenBufferInfo.CursorPosition - // end of the buffer - end.X = screenBufferInfo.Size.X - 1 - end.Y = screenBufferInfo.Size.Y - 1 - // cursor - cursor = screenBufferInfo.CursorPosition - case 1: - - // start of the screen - start.X = 0 - start.Y = 0 - // end of the screen - end = screenBufferInfo.CursorPosition - // cursor - cursor = screenBufferInfo.CursorPosition - case 2: - // start of the screen - start.X = 0 - start.Y = 0 - // end of the buffer - end.X = screenBufferInfo.Size.X - 1 - end.Y = screenBufferInfo.Size.Y - 1 - // cursor - cursor.X = 0 - cursor.Y = 0 - } - if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil { - return n, err - } - // remember the the cursor position is 1 based - if err := setConsoleCursorPosition(handle, false, int16(cursor.X), int16(cursor.Y)); err != nil { - return n, err - } - - case "K": - // [K - // Clears all characters from the cursor position to the end of the line (including the character at the cursor position). - // [K Erases from the cursor to the end of the line, including the cursor position. - // [1K Erases from the beginning of the line to the cursor, including the cursor position. - // [2K Erases the complete line. - value, err := parseInt16OrDefault(parsedCommand.getParam(0), 0) - var start COORD - var cursor COORD - var end COORD - screenBufferInfo, err := GetConsoleScreenBufferInfo(uintptr(handle)) - if err != nil { - return n, err - } - switch value { - case 0: - // start is where cursor is - start = screenBufferInfo.CursorPosition - // end of line - end.X = screenBufferInfo.Size.X - 1 - end.Y = screenBufferInfo.CursorPosition.Y - // cursor remains the same - cursor = screenBufferInfo.CursorPosition - - case 1: - // beginning of line - start.X = 0 - start.Y = screenBufferInfo.CursorPosition.Y - // until cursor - end = screenBufferInfo.CursorPosition - // cursor remains the same - cursor = screenBufferInfo.CursorPosition - case 2: - // start of the line - start.X = 0 - start.Y = screenBufferInfo.CursorPosition.Y - 1 - // end of the line - end.X = screenBufferInfo.Size.X - 1 - end.Y = screenBufferInfo.CursorPosition.Y - 1 - // cursor - cursor.X = 0 - cursor.Y = screenBufferInfo.CursorPosition.Y - 1 - } - if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil { - return n, err - } - // remember the the cursor position is 1 based - if err := setConsoleCursorPosition(uintptr(handle), false, int16(cursor.X), int16(cursor.Y)); err != nil { - return n, err - } - - case "l": - for _, value := range parsedCommand.Parameters { - switch value { - case "?25", "25": - SetCursorVisible(uintptr(handle), BOOL(0)) - case "?1049", "1049": - // TODO (azlinux): Restore terminal - case "?1", "1": - // If the DECCKM function is reset, then the arrow keys send ANSI cursor sequences to the host. - term.inputEscapeSequence = []byte(KEY_ESC_CSI) - } - } - case "h": - for _, value := range parsedCommand.Parameters { - switch value { - case "?25", "25": - SetCursorVisible(uintptr(handle), BOOL(1)) - case "?1049", "1049": - // TODO (azlinux): Save terminal - case "?1", "1": - // If the DECCKM function is set, then the arrow keys send application sequences to the host. - // DECCKM (default off): When set, the cursor keys send an ESC O prefix, rather than ESC [. - term.inputEscapeSequence = []byte(KEY_ESC_O) - } - } - - case "]": - /* - TODO (azlinux): - Linux Console Private CSI Sequences - - The following sequences are neither ECMA-48 nor native VT102. They are - native to the Linux console driver. Colors are in SGR parameters: 0 = - black, 1 = red, 2 = green, 3 = brown, 4 = blue, 5 = magenta, 6 = cyan, - 7 = white. - - ESC [ 1 ; n ] Set color n as the underline color - ESC [ 2 ; n ] Set color n as the dim color - ESC [ 8 ] Make the current color pair the default attributes. - ESC [ 9 ; n ] Set screen blank timeout to n minutes. - ESC [ 10 ; n ] Set bell frequency in Hz. - ESC [ 11 ; n ] Set bell duration in msec. - ESC [ 12 ; n ] Bring specified console to the front. - ESC [ 13 ] Unblank the screen. - ESC [ 14 ; n ] Set the VESA powerdown interval in minutes. - - */ - } - return n, nil -} - -// WriteChars writes the bytes to given writer. -func (term *WindowsTerminal) WriteChars(fd uintptr, w io.Writer, p []byte) (n int, err error) { - if len(p) == 0 { - return 0, nil - } - return w.Write(p) -} - -const ( - CAPSLOCK_ON = 0x0080 //The CAPS LOCK light is on. - ENHANCED_KEY = 0x0100 //The key is enhanced. - LEFT_ALT_PRESSED = 0x0002 //The left ALT key is pressed. - LEFT_CTRL_PRESSED = 0x0008 //The left CTRL key is pressed. - NUMLOCK_ON = 0x0020 //The NUM LOCK light is on. - RIGHT_ALT_PRESSED = 0x0001 //The right ALT key is pressed. - RIGHT_CTRL_PRESSED = 0x0004 //The right CTRL key is pressed. - SCROLLLOCK_ON = 0x0040 //The SCROLL LOCK light is on. - SHIFT_PRESSED = 0x0010 // The SHIFT key is pressed. -) - -const ( - KEY_CONTROL_PARAM_2 = ";2" - KEY_CONTROL_PARAM_3 = ";3" - KEY_CONTROL_PARAM_4 = ";4" - KEY_CONTROL_PARAM_5 = ";5" - KEY_CONTROL_PARAM_6 = ";6" - KEY_CONTROL_PARAM_7 = ";7" - KEY_CONTROL_PARAM_8 = ";8" - KEY_ESC_CSI = "\x1B[" - KEY_ESC_N = "\x1BN" - KEY_ESC_O = "\x1BO" -) - -var keyMapPrefix = map[WORD]string{ - VK_UP: "\x1B[%sA", - VK_DOWN: "\x1B[%sB", - VK_RIGHT: "\x1B[%sC", - VK_LEFT: "\x1B[%sD", - VK_HOME: "\x1B[1%s~", // showkey shows ^[[1 - VK_END: "\x1B[4%s~", // showkey shows ^[[4 - VK_INSERT: "\x1B[2%s~", - VK_DELETE: "\x1B[3%s~", - VK_PRIOR: "\x1B[5%s~", - VK_NEXT: "\x1B[6%s~", - VK_F1: "", - VK_F2: "", - VK_F3: "\x1B[13%s~", - VK_F4: "\x1B[14%s~", - VK_F5: "\x1B[15%s~", - VK_F6: "\x1B[17%s~", - VK_F7: "\x1B[18%s~", - VK_F8: "\x1B[19%s~", - VK_F9: "\x1B[20%s~", - VK_F10: "\x1B[21%s~", - VK_F11: "\x1B[23%s~", - VK_F12: "\x1B[24%s~", -} - -var arrowKeyMapPrefix = map[WORD]string{ - VK_UP: "%s%sA", - VK_DOWN: "%s%sB", - VK_RIGHT: "%s%sC", - VK_LEFT: "%s%sD", -} - -func getControlStateParameter(shift, alt, control, meta bool) string { - if shift && alt && control { - return KEY_CONTROL_PARAM_8 - } - if alt && control { - return KEY_CONTROL_PARAM_7 - } - if shift && control { - return KEY_CONTROL_PARAM_6 - } - if control { - return KEY_CONTROL_PARAM_5 - } - if shift && alt { - return KEY_CONTROL_PARAM_4 - } - if alt { - return KEY_CONTROL_PARAM_3 - } - if shift { - return KEY_CONTROL_PARAM_2 - } - return "" -} - -func getControlKeys(controlState DWORD) (shift, alt, control bool) { - shift = 0 != (controlState & SHIFT_PRESSED) - alt = 0 != (controlState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)) - control = 0 != (controlState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) - return shift, alt, control -} - -func charSequenceForKeys(key WORD, controlState DWORD, escapeSequence []byte) string { - i, ok := arrowKeyMapPrefix[key] - if ok { - shift, alt, control := getControlKeys(controlState) - modifier := getControlStateParameter(shift, alt, control, false) - return fmt.Sprintf(i, escapeSequence, modifier) - } - - i, ok = keyMapPrefix[key] - if ok { - shift, alt, control := getControlKeys(controlState) - modifier := getControlStateParameter(shift, alt, control, false) - return fmt.Sprintf(i, modifier) - } - - return "" -} - -// mapKeystokeToTerminalString maps the given input event record to string -func mapKeystokeToTerminalString(keyEvent *KEY_EVENT_RECORD, escapeSequence []byte) string { - _, alt, control := getControlKeys(keyEvent.ControlKeyState) - if keyEvent.UnicodeChar == 0 { - return charSequenceForKeys(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence) - } - if control { - // TODO(azlinux): Implement following control sequences - // -D Signals the end of input from the keyboard; also exits current shell. - // -H Deletes the first character to the left of the cursor. Also called the ERASE key. - // -Q Restarts printing after it has been stopped with -s. - // -S Suspends printing on the screen (does not stop the program). - // -U Deletes all characters on the current line. Also called the KILL key. - // -E Quits current command and creates a core - - } - // +Key generates ESC N Key - if !control && alt { - return KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar)) - } - return string(keyEvent.UnicodeChar) -} - -// getAvailableInputEvents polls the console for availble events -// The function does not return until at least one input record has been read. -func getAvailableInputEvents(handle uintptr, inputEvents []INPUT_RECORD) (n int, err error) { - // TODO(azlinux): Why is there a for loop? Seems to me, that `n` cannot be negative. - tibor - for { - // Read number of console events available - n, err = readConsoleInputKey(handle, inputEvents) - if err != nil || n >= 0 { - return n, err - } - } -} - -// getTranslatedKeyCodes converts the input events into the string of characters -// The ansi escape sequence are used to map key strokes to the strings -func getTranslatedKeyCodes(inputEvents []INPUT_RECORD, escapeSequence []byte) string { - var buf bytes.Buffer - for i := 0; i < len(inputEvents); i++ { - input := inputEvents[i] - if input.EventType == KEY_EVENT && input.KeyEvent.KeyDown != 0 { - keyString := mapKeystokeToTerminalString(&input.KeyEvent, escapeSequence) - buf.WriteString(keyString) - } - } - return buf.String() -} - -// ReadChars reads the characters from the given reader -func (term *WindowsTerminal) ReadChars(fd uintptr, r io.Reader, p []byte) (n int, err error) { - for term.inputSize == 0 { - nr, err := getAvailableInputEvents(fd, term.inputEvents) - if nr == 0 && nil != err { - return n, err - } - if nr > 0 { - keyCodes := getTranslatedKeyCodes(term.inputEvents[:nr], term.inputEscapeSequence) - term.inputSize = copy(term.inputBuffer, keyCodes) - } - } - n = copy(p, term.inputBuffer[:term.inputSize]) - term.inputSize -= n - return n, nil -} - -// HandleInputSequence interprets the input sequence command -func (term *WindowsTerminal) HandleInputSequence(fd uintptr, command []byte) (n int, err error) { - return 0, nil -} - -func marshal(c COORD) uintptr { - return uintptr(*((*DWORD)(unsafe.Pointer(&c)))) -} - -// IsConsole returns true if the given file descriptor is a terminal. -// -- The code assumes that GetConsoleMode will return an error for file descriptors that are not a console. -func IsConsole(fd uintptr) bool { - _, e := GetConsoleMode(fd) - return e == nil -} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/console_windows_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/console_windows_test.go deleted file mode 100644 index edb5d6f661..0000000000 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/console_windows_test.go +++ /dev/null @@ -1,232 +0,0 @@ -// +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) - } - } -} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/term_emulator.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/term_emulator.go deleted file mode 100644 index 2d5edc0390..0000000000 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/term_emulator.go +++ /dev/null @@ -1,234 +0,0 @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/term_emulator_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/term_emulator_test.go deleted file mode 100644 index 94104ff51f..0000000000 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/winconsole/term_emulator_test.go +++ /dev/null @@ -1,388 +0,0 @@ -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) - } -} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/ansi_reader.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/ansi_reader.go new file mode 100644 index 0000000000..bb47e120ab --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/ansi_reader.go @@ -0,0 +1,256 @@ +// +build windows + +package windows + +import ( + "bytes" + "errors" + "fmt" + "os" + "strings" + "unsafe" + + ansiterm "github.com/Azure/go-ansiterm" + "github.com/Azure/go-ansiterm/winterm" +) + +// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation. +type ansiReader struct { + file *os.File + fd uintptr + buffer []byte + cbBuffer int + command []byte + // TODO(azlinux): Remove this and hard-code the string -- it is not going to change + escapeSequence []byte +} + +func newAnsiReader(nFile int) *ansiReader { + file, fd := winterm.GetStdFile(nFile) + return &ansiReader{ + file: file, + fd: fd, + command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH), + escapeSequence: []byte(ansiterm.KEY_ESC_CSI), + buffer: make([]byte, 0), + } +} + +// Close closes the wrapped file. +func (ar *ansiReader) Close() (err error) { + return ar.file.Close() +} + +// Fd returns the file descriptor of the wrapped file. +func (ar *ansiReader) Fd() uintptr { + return ar.fd +} + +// Read reads up to len(p) bytes of translated input events into p. +func (ar *ansiReader) Read(p []byte) (int, error) { + if len(p) == 0 { + return 0, nil + } + + // Previously read bytes exist, read as much as we can and return + if len(ar.buffer) > 0 { + logger.Debugf("Reading previously cached bytes") + + originalLength := len(ar.buffer) + copiedLength := copy(p, ar.buffer) + + if copiedLength == originalLength { + ar.buffer = make([]byte, 0, len(p)) + } else { + ar.buffer = ar.buffer[copiedLength:] + } + + logger.Debugf("Read from cache p[%d]: % x", copiedLength, p) + return copiedLength, nil + } + + // Read and translate key events + events, err := readInputEvents(ar.fd, len(p)) + if err != nil { + return 0, err + } else if len(events) == 0 { + logger.Debug("No input events detected") + return 0, nil + } + + keyBytes := translateKeyEvents(events, ar.escapeSequence) + + // Save excess bytes and right-size keyBytes + if len(keyBytes) > len(p) { + logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p)) + ar.buffer = keyBytes[len(p):] + keyBytes = keyBytes[:len(p)] + } else if len(keyBytes) == 0 { + logger.Debug("No key bytes returned from the translator") + return 0, nil + } + + copiedLength := copy(p, keyBytes) + if copiedLength != len(keyBytes) { + return 0, errors.New("Unexpected copy length encountered.") + } + + logger.Debugf("Read p[%d]: % x", copiedLength, p) + logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes) + return copiedLength, nil +} + +// readInputEvents polls until at least one event is available. +func readInputEvents(fd uintptr, maxBytes int) ([]winterm.INPUT_RECORD, error) { + // Determine the maximum number of records to retrieve + // -- Cast around the type system to obtain the size of a single INPUT_RECORD. + // unsafe.Sizeof requires an expression vs. a type-reference; the casting + // tricks the type system into believing it has such an expression. + recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes))))) + countRecords := maxBytes / recordSize + if countRecords > ansiterm.MAX_INPUT_EVENTS { + countRecords = ansiterm.MAX_INPUT_EVENTS + } + logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize) + + // Wait for and read input events + events := make([]winterm.INPUT_RECORD, countRecords) + nEvents := uint32(0) + eventsExist, err := winterm.WaitForSingleObject(fd, winterm.WAIT_INFINITE) + if err != nil { + return nil, err + } + + if eventsExist { + err = winterm.ReadConsoleInput(fd, events, &nEvents) + if err != nil { + return nil, err + } + } + + // Return a slice restricted to the number of returned records + logger.Debugf("[windows] readInputEvents: Read %v events", nEvents) + return events[:nEvents], nil +} + +// KeyEvent Translation Helpers + +var arrowKeyMapPrefix = map[winterm.WORD]string{ + winterm.VK_UP: "%s%sA", + winterm.VK_DOWN: "%s%sB", + winterm.VK_RIGHT: "%s%sC", + winterm.VK_LEFT: "%s%sD", +} + +var keyMapPrefix = map[winterm.WORD]string{ + winterm.VK_UP: "\x1B[%sA", + winterm.VK_DOWN: "\x1B[%sB", + winterm.VK_RIGHT: "\x1B[%sC", + winterm.VK_LEFT: "\x1B[%sD", + winterm.VK_HOME: "\x1B[1%s~", // showkey shows ^[[1 + winterm.VK_END: "\x1B[4%s~", // showkey shows ^[[4 + winterm.VK_INSERT: "\x1B[2%s~", + winterm.VK_DELETE: "\x1B[3%s~", + winterm.VK_PRIOR: "\x1B[5%s~", + winterm.VK_NEXT: "\x1B[6%s~", + winterm.VK_F1: "", + winterm.VK_F2: "", + winterm.VK_F3: "\x1B[13%s~", + winterm.VK_F4: "\x1B[14%s~", + winterm.VK_F5: "\x1B[15%s~", + winterm.VK_F6: "\x1B[17%s~", + winterm.VK_F7: "\x1B[18%s~", + winterm.VK_F8: "\x1B[19%s~", + winterm.VK_F9: "\x1B[20%s~", + winterm.VK_F10: "\x1B[21%s~", + winterm.VK_F11: "\x1B[23%s~", + winterm.VK_F12: "\x1B[24%s~", +} + +// translateKeyEvents converts the input events into the appropriate ANSI string. +func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte { + var buffer bytes.Buffer + for _, event := range events { + if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 { + buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence)) + } + } + + return buffer.Bytes() +} + +// keyToString maps the given input event record to the corresponding string. +func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) string { + if keyEvent.UnicodeChar == 0 { + return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence) + } + + _, alt, control := getControlKeys(keyEvent.ControlKeyState) + if control { + // TODO(azlinux): Implement following control sequences + // -D Signals the end of input from the keyboard; also exits current shell. + // -H Deletes the first character to the left of the cursor. Also called the ERASE key. + // -Q Restarts printing after it has been stopped with -s. + // -S Suspends printing on the screen (does not stop the program). + // -U Deletes all characters on the current line. Also called the KILL key. + // -E Quits current command and creates a core + + } + + // +Key generates ESC N Key + if !control && alt { + return ansiterm.KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar)) + } + + return string(keyEvent.UnicodeChar) +} + +// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string. +func formatVirtualKey(key winterm.WORD, controlState winterm.DWORD, escapeSequence []byte) string { + shift, alt, control := getControlKeys(controlState) + modifier := getControlKeysModifier(shift, alt, control) + + if format, ok := arrowKeyMapPrefix[key]; ok { + return fmt.Sprintf(format, escapeSequence, modifier) + } + + if format, ok := keyMapPrefix[key]; ok { + return fmt.Sprintf(format, modifier) + } + + return "" +} + +// getControlKeys extracts the shift, alt, and ctrl key states. +func getControlKeys(controlState winterm.DWORD) (shift, alt, control bool) { + shift = 0 != (controlState & winterm.SHIFT_PRESSED) + alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED)) + control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED)) + return shift, alt, control +} + +// getControlKeysModifier returns the ANSI modifier for the given combination of control keys. +func getControlKeysModifier(shift, alt, control bool) string { + if shift && alt && control { + return ansiterm.KEY_CONTROL_PARAM_8 + } + if alt && control { + return ansiterm.KEY_CONTROL_PARAM_7 + } + if shift && control { + return ansiterm.KEY_CONTROL_PARAM_6 + } + if control { + return ansiterm.KEY_CONTROL_PARAM_5 + } + if shift && alt { + return ansiterm.KEY_CONTROL_PARAM_4 + } + if alt { + return ansiterm.KEY_CONTROL_PARAM_3 + } + if shift { + return ansiterm.KEY_CONTROL_PARAM_2 + } + return "" +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/ansi_writer.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/ansi_writer.go new file mode 100644 index 0000000000..9f3232c093 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/ansi_writer.go @@ -0,0 +1,76 @@ +// +build windows + +package windows + +import ( + "io/ioutil" + "os" + + ansiterm "github.com/Azure/go-ansiterm" + "github.com/Azure/go-ansiterm/winterm" + "github.com/Sirupsen/logrus" +) + +var logger *logrus.Logger + +// ansiWriter wraps a standard output file (e.g., os.Stdout) providing ANSI sequence translation. +type ansiWriter struct { + file *os.File + fd uintptr + infoReset *winterm.CONSOLE_SCREEN_BUFFER_INFO + command []byte + escapeSequence []byte + inAnsiSequence bool + parser *ansiterm.AnsiParser +} + +func newAnsiWriter(nFile int) *ansiWriter { + logFile := ioutil.Discard + + if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" { + logFile, _ = os.Create("ansiReaderWriter.log") + } + + logger = &logrus.Logger{ + Out: logFile, + Formatter: new(logrus.TextFormatter), + Level: logrus.DebugLevel, + } + + file, fd := winterm.GetStdFile(nFile) + info, err := winterm.GetConsoleScreenBufferInfo(fd) + if err != nil { + return nil + } + + parser := ansiterm.CreateParser("Ground", winterm.CreateWinEventHandler(fd, file)) + logger.Infof("newAnsiWriter: parser %p", parser) + + aw := &ansiWriter{ + file: file, + fd: fd, + infoReset: info, + command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH), + escapeSequence: []byte(ansiterm.KEY_ESC_CSI), + parser: parser, + } + + logger.Infof("newAnsiWriter: aw.parser %p", aw.parser) + logger.Infof("newAnsiWriter: %v", aw) + return aw +} + +func (aw *ansiWriter) Fd() uintptr { + return aw.fd +} + +// Write writes len(p) bytes from p to the underlying data stream. +func (aw *ansiWriter) Write(p []byte) (total int, err error) { + if len(p) == 0 { + return 0, nil + } + + logger.Infof("Write: % x", p) + logger.Infof("Write: %s", string(p)) + return aw.parser.Parse(p) +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/console.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/console.go new file mode 100644 index 0000000000..3711d9883c --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/console.go @@ -0,0 +1,61 @@ +// +build windows + +package windows + +import ( + "io" + "os" + "syscall" + + "github.com/Azure/go-ansiterm/winterm" +) + +// ConsoleStreams returns a wrapped version for each standard stream referencing a console, +// that handles ANSI character sequences. +func ConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { + if IsConsole(os.Stdin.Fd()) { + stdIn = newAnsiReader(syscall.STD_INPUT_HANDLE) + } else { + stdIn = os.Stdin + } + + if IsConsole(os.Stdout.Fd()) { + stdOut = newAnsiWriter(syscall.STD_OUTPUT_HANDLE) + } else { + stdOut = os.Stdout + } + + if IsConsole(os.Stderr.Fd()) { + stdErr = newAnsiWriter(syscall.STD_ERROR_HANDLE) + } else { + stdErr = os.Stderr + } + + return stdIn, stdOut, stdErr +} + +// GetHandleInfo returns file descriptor and bool indicating whether the file is a console. +func GetHandleInfo(in interface{}) (uintptr, bool) { + switch t := in.(type) { + case *ansiReader: + return t.Fd(), true + case *ansiWriter: + return t.Fd(), true + } + + var inFd uintptr + var isTerminal bool + + if file, ok := in.(*os.File); ok { + inFd = file.Fd() + isTerminal = IsConsole(inFd) + } + return inFd, isTerminal +} + +// IsConsole returns true if the given file descriptor is a Windows Console. +// The code assumes that GetConsoleMode will return an error for file descriptors that are not a console. +func IsConsole(fd uintptr) bool { + _, e := winterm.GetConsoleMode(fd) + return e == nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/windows.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/windows.go new file mode 100644 index 0000000000..bf4c7b5025 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/windows.go @@ -0,0 +1,5 @@ +// These files implement ANSI-aware input and output streams for use by the Docker Windows client. +// When asked for the set of standard streams (e.g., stdin, stdout, stderr), the code will create +// and return pseudo-streams that convert ANSI sequences to / from Windows Console API calls. + +package windows diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/windows_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/windows_test.go new file mode 100644 index 0000000000..52aeab54ec --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/term/windows/windows_test.go @@ -0,0 +1,3 @@ +// This file is necessary to pass the Docker tests. + +package windows diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/tlsconfig/config.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/tlsconfig/config.go new file mode 100644 index 0000000000..9f7f336947 --- /dev/null +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/tlsconfig/config.go @@ -0,0 +1,132 @@ +// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers. +// +// As a reminder from https://golang.org/pkg/crypto/tls/#Config: +// A Config structure is used to configure a TLS client or server. After one has been passed to a TLS function it must not be modified. +// A Config may be reused; the tls package will also not modify it. +package tlsconfig + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "os" + + "github.com/Sirupsen/logrus" +) + +// Options represents the information needed to create client and server TLS configurations. +type Options struct { + CAFile string + + // If either CertFile or KeyFile is empty, Client() will not load them + // preventing the client from authenticating to the server. + // However, Server() requires them and will error out if they are empty. + CertFile string + KeyFile string + + // client-only option + InsecureSkipVerify bool + // server-only option + ClientAuth tls.ClientAuthType +} + +// Extra (server-side) accepted CBC cipher suites - will phase out in the future +var acceptedCBCCiphers = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, +} + +// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set) +var clientCipherSuites = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, +} + +// For use by code which already has a crypto/tls options struct but wants to +// use a commonly accepted set of TLS cipher suites, with known weak algorithms removed +var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...) + +// ServerDefault is a secure-enough TLS configuration for the server TLS configuration. +var ServerDefault = tls.Config{ + // Avoid fallback to SSL protocols < TLS1.0 + MinVersion: tls.VersionTLS10, + PreferServerCipherSuites: true, + CipherSuites: DefaultServerAcceptedCiphers, +} + +// ClientDefault is a secure-enough TLS configuration for the client TLS configuration. +var ClientDefault = tls.Config{ + // Prefer TLS1.2 as the client minimum + MinVersion: tls.VersionTLS12, + CipherSuites: clientCipherSuites, +} + +// certPool returns an X.509 certificate pool from `caFile`, the certificate file. +func certPool(caFile string) (*x509.CertPool, error) { + // If we should verify the server, we need to load a trusted ca + certPool := x509.NewCertPool() + pem, err := ioutil.ReadFile(caFile) + if err != nil { + return nil, fmt.Errorf("Could not read CA certificate %q: %v", caFile, err) + } + if !certPool.AppendCertsFromPEM(pem) { + return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile) + } + s := certPool.Subjects() + subjects := make([]string, len(s)) + for i, subject := range s { + subjects[i] = string(subject) + } + logrus.Debugf("Trusting certs with subjects: %v", subjects) + return certPool, nil +} + +// Client returns a TLS configuration meant to be used by a client. +func Client(options Options) (*tls.Config, error) { + tlsConfig := ClientDefault + tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify + if !options.InsecureSkipVerify { + CAs, err := certPool(options.CAFile) + if err != nil { + return nil, err + } + tlsConfig.RootCAs = CAs + } + + if options.CertFile != "" && options.KeyFile != "" { + tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) + if err != nil { + return nil, fmt.Errorf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err) + } + tlsConfig.Certificates = []tls.Certificate{tlsCert} + } + + return &tlsConfig, nil +} + +// Server returns a TLS configuration meant to be used by a server. +func Server(options Options) (*tls.Config, error) { + tlsConfig := ServerDefault + tlsConfig.ClientAuth = options.ClientAuth + tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) + if err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("Could not load X509 key pair (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err) + } + return nil, fmt.Errorf("Error reading X509 key pair (cert: %q, key: %q): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err) + } + tlsConfig.Certificates = []tls.Certificate{tlsCert} + if options.ClientAuth >= tls.VerifyClientCertIfGiven { + CAs, err := certPool(options.CAFile) + if err != nil { + return nil, err + } + tlsConfig.ClientCAs = CAs + } + return &tlsConfig, nil +} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/units/duration.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/units/duration.go index 44012aafb5..c219a8a968 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/units/duration.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/units/duration.go @@ -1,3 +1,5 @@ +// Package units provides helper function to parse and print size and time units +// in human-readable format. package units import ( @@ -6,7 +8,7 @@ import ( ) // HumanDuration returns a human-readable approximation of a duration -// (eg. "About a minute", "4 hours ago", etc.) +// (eg. "About a minute", "4 hours ago", etc.). func HumanDuration(d time.Duration) string { if seconds := int(d.Seconds()); seconds < 1 { return "Less than a second" diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/units/size.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/units/size.go index d7850ad0b0..3b59daff31 100644 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/units/size.go +++ b/Godeps/_workspace/src/github.com/docker/docker/pkg/units/size.go @@ -38,7 +38,7 @@ var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} // CustomSize returns a human-readable approximation of a size -// using custom format +// using custom format. func CustomSize(format string, size float64, base float64, _map []string) string { i := 0 for size >= base { @@ -49,17 +49,19 @@ func CustomSize(format string, size float64, base float64, _map []string) string } // HumanSize returns a human-readable approximation of a size -// using SI standard (eg. "44kB", "17MB") +// capped at 4 valid numbers (eg. "2.746 MB", "796 KB"). func HumanSize(size float64) string { - return CustomSize("%.4g %s", float64(size), 1000.0, decimapAbbrs) + return CustomSize("%.4g %s", size, 1000.0, decimapAbbrs) } +// BytesSize returns a human-readable size in bytes, kibibytes, +// mebibytes, gibibytes, or tebibytes (eg. "44kiB", "17MiB"). func BytesSize(size float64) string { return CustomSize("%.4g %s", size, 1024.0, binaryAbbrs) } // FromHumanSize returns an integer from a human-readable specification of a -// size using SI standard (eg. "44kB", "17MB") +// size using SI standard (eg. "44kB", "17MB"). func FromHumanSize(size string) (int64, error) { return parseSize(size, decimalMap) } @@ -72,7 +74,7 @@ func RAMInBytes(size string) (int64, error) { return parseSize(size, binaryMap) } -// Parses the human-readable size string into the amount it represents +// Parses the human-readable size string into the amount it represents. func parseSize(sizeStr string, uMap unitMap) (int64, error) { matches := sizeRegex.FindStringSubmatch(sizeStr) if len(matches) != 3 { diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS deleted file mode 100644 index edbe200669..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS +++ /dev/null @@ -1,2 +0,0 @@ -Tianon Gravi (@tianon) -Aleksa Sarai (@cyphar) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go deleted file mode 100644 index 6f8a982ff7..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go +++ /dev/null @@ -1,108 +0,0 @@ -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 - }) -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go deleted file mode 100644 index 758b734c22..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go +++ /dev/null @@ -1,30 +0,0 @@ -// +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) -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go deleted file mode 100644 index 7217948870..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go +++ /dev/null @@ -1,21 +0,0 @@ -// +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 -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go deleted file mode 100644 index d7439f12e3..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go +++ /dev/null @@ -1,350 +0,0 @@ -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 -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go deleted file mode 100644 index 4fe008fb39..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go +++ /dev/null @@ -1,352 +0,0 @@ -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 - } - } -} diff --git a/Godeps/_workspace/src/github.com/docker/libkv/script/coverage b/Godeps/_workspace/src/github.com/docker/libkv/script/coverage old mode 100644 new mode 100755 diff --git a/Godeps/_workspace/src/github.com/docker/libkv/script/travis_consul.sh b/Godeps/_workspace/src/github.com/docker/libkv/script/travis_consul.sh old mode 100644 new mode 100755 diff --git a/Godeps/_workspace/src/github.com/docker/libkv/script/travis_etcd.sh b/Godeps/_workspace/src/github.com/docker/libkv/script/travis_etcd.sh old mode 100644 new mode 100755 diff --git a/Godeps/_workspace/src/github.com/docker/libkv/script/travis_zk.sh b/Godeps/_workspace/src/github.com/docker/libkv/script/travis_zk.sh old mode 100644 new mode 100755 diff --git a/Godeps/_workspace/src/github.com/docker/libkv/script/validate-gofmt b/Godeps/_workspace/src/github.com/docker/libkv/script/validate-gofmt old mode 100644 new mode 100755 diff --git a/cmd/dnet/dnet.go b/cmd/dnet/dnet.go index bcfca1dfa2..dad5c82c15 100644 --- a/cmd/dnet/dnet.go +++ b/cmd/dnet/dnet.go @@ -231,11 +231,11 @@ func startTestDriver() error { fmt.Fprintf(w, "null") }) - if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil { + if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil { return err } - if err := ioutil.WriteFile("/usr/share/docker/plugins/test.spec", []byte(server.URL), 0644); err != nil { + if err := ioutil.WriteFile("/etc/docker/plugins/test.spec", []byte(server.URL), 0644); err != nil { return err } diff --git a/drivers/remote/driver_test.go b/drivers/remote/driver_test.go index 627106e9e8..a0fd602d42 100644 --- a/drivers/remote/driver_test.go +++ b/drivers/remote/driver_test.go @@ -3,8 +3,10 @@ package remote import ( "encoding/json" "fmt" + "io/ioutil" "net" "net/http" + "net/http/httptest" "os" "testing" @@ -35,26 +37,29 @@ func handle(t *testing.T, mux *http.ServeMux, method string, h func(map[string]i } func setupPlugin(t *testing.T, name string, mux *http.ServeMux) func() { - if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil { + if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil { t.Fatal(err) } - listener, err := net.Listen("unix", fmt.Sprintf("/usr/share/docker/plugins/%s.sock", name)) - if err != nil { - t.Fatal("Could not listen to the plugin socket") + server := httptest.NewServer(mux) + if server == nil { + t.Fatal("Failed to start a HTTP Server") + } + + if err := ioutil.WriteFile(fmt.Sprintf("/etc/docker/plugins/%s.spec", name), []byte(server.URL), 0644); err != nil { + t.Fatal(err) } mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.docker.plugins.v1+json") fmt.Fprintf(w, `{"Implements": ["%s"]}`, driverapi.NetworkPluginEndpointType) }) - go http.Serve(listener, mux) - return func() { - listener.Close() - if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil { + if err := os.RemoveAll("/etc/docker/plugins"); err != nil { t.Fatal(err) } + server.Close() } } diff --git a/libnetwork_test.go b/libnetwork_test.go index f4e33394ce..a5fa7c14d0 100644 --- a/libnetwork_test.go +++ b/libnetwork_test.go @@ -2040,16 +2040,16 @@ func TestInvalidRemoteDriver(t *testing.T) { fmt.Fprintln(w, `{"Implements": ["InvalidDriver"]}`) }) - if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil { + if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil { t.Fatal(err) } defer func() { - if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil { + if err := os.RemoveAll("/etc/docker/plugins"); err != nil { t.Fatal(err) } }() - if err := ioutil.WriteFile("/usr/share/docker/plugins/invalid-network-driver.spec", []byte(server.URL), 0644); err != nil { + if err := ioutil.WriteFile("/etc/docker/plugins/invalid-network-driver.spec", []byte(server.URL), 0644); err != nil { t.Fatal(err) } @@ -2095,16 +2095,16 @@ func TestValidRemoteDriver(t *testing.T) { fmt.Fprintf(w, "null") }) - if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil { + if err := os.MkdirAll("/etc/docker/plugins", 0755); err != nil { t.Fatal(err) } defer func() { - if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil { + if err := os.RemoveAll("/etc/docker/plugins"); err != nil { t.Fatal(err) } }() - if err := ioutil.WriteFile("/usr/share/docker/plugins/valid-network-driver.spec", []byte(server.URL), 0644); err != nil { + if err := ioutil.WriteFile("/etc/docker/plugins/valid-network-driver.spec", []byte(server.URL), 0644); err != nil { t.Fatal(err) }