mirror of
https://gitee.com/rainbond/Rainbond.git
synced 2024-12-02 11:47:36 +08:00
[FIX] debugging build image from Dockerfile
This commit is contained in:
parent
ca87acc1c6
commit
7b75763445
@ -20,7 +20,6 @@ package sources
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -36,8 +35,6 @@ import (
|
||||
"github.com/docker/engine-api/types"
|
||||
//"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/goodrain/rainbond/pkg/builder/model"
|
||||
"github.com/goodrain/rainbond/pkg/event"
|
||||
@ -209,23 +206,23 @@ func ImageBuild(dockerCli *client.Client, contextDir string, options types.Image
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
progBuff := bytes.NewBuffer(nil)
|
||||
go func() {
|
||||
r := bufio.NewReader(progBuff)
|
||||
for {
|
||||
if line, _, err := r.ReadLine(); err == nil {
|
||||
if logger != nil {
|
||||
logger.Debug(string(line), map[string]string{"step": "dockerbuild"})
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
// progBuff := bytes.NewBuffer(nil)
|
||||
// go func() {
|
||||
// r := bufio.NewReader(progBuff)
|
||||
// for {
|
||||
// if line, _, err := r.ReadLine(); err == nil {
|
||||
// if logger != nil {
|
||||
// logger.Debug(string(line), map[string]string{"step": "dockerbuild"})
|
||||
// }
|
||||
// } else {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }()
|
||||
// Setup an upload progress bar
|
||||
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
|
||||
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||
rc, err := dockerCli.ImageBuild(ctx, body, options)
|
||||
// progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true)
|
||||
// var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
|
||||
rc, err := dockerCli.ImageBuild(ctx, buildCtx, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -234,6 +231,8 @@ func ImageBuild(dockerCli *client.Client, contextDir string, options types.Image
|
||||
if line, _, err := r.ReadLine(); err == nil {
|
||||
if logger != nil {
|
||||
logger.Debug(string(line), map[string]string{"step": "dockerbuild"})
|
||||
} else {
|
||||
fmt.Println(string(line))
|
||||
}
|
||||
} else {
|
||||
break
|
||||
|
@ -21,6 +21,9 @@ package sources
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types"
|
||||
)
|
||||
|
||||
func TestImageName(t *testing.T) {
|
||||
@ -34,4 +37,15 @@ func TestImageName(t *testing.T) {
|
||||
in := ImageNameHandle(i)
|
||||
fmt.Printf("host: %s, name: %s, tag: %s\n", in.Host, in.Name, in.Tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImage(t *testing.T) {
|
||||
dc, _ := client.NewEnvClient()
|
||||
buildOptions := types.ImageBuildOptions{
|
||||
Tags: []string{"build:test"},
|
||||
Remove: true,
|
||||
}
|
||||
if err := ImageBuild(dc, "/tmp/gost", buildOptions, nil, 5); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
298
vendor/github.com/docker/docker/pkg/fileutils/fileutils.go
generated
vendored
Normal file
298
vendor/github.com/docker/docker/pkg/fileutils/fileutils.go
generated
vendored
Normal file
@ -0,0 +1,298 @@
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// PatternMatcher allows checking paths agaist a list of patterns
|
||||
type PatternMatcher struct {
|
||||
patterns []*Pattern
|
||||
exclusions bool
|
||||
}
|
||||
|
||||
// NewPatternMatcher creates a new matcher object for specific patterns that can
|
||||
// be used later to match against patterns against paths
|
||||
func NewPatternMatcher(patterns []string) (*PatternMatcher, error) {
|
||||
pm := &PatternMatcher{
|
||||
patterns: make([]*Pattern, 0, len(patterns)),
|
||||
}
|
||||
for _, p := range patterns {
|
||||
// Eliminate leading and trailing whitespace.
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
p = filepath.Clean(p)
|
||||
newp := &Pattern{}
|
||||
if p[0] == '!' {
|
||||
if len(p) == 1 {
|
||||
return nil, errors.New("illegal exclusion pattern: \"!\"")
|
||||
}
|
||||
newp.exclusion = true
|
||||
p = p[1:]
|
||||
pm.exclusions = true
|
||||
}
|
||||
// Do some syntax checking on the pattern.
|
||||
// filepath's Match() has some really weird rules that are inconsistent
|
||||
// so instead of trying to dup their logic, just call Match() for its
|
||||
// error state and if there is an error in the pattern return it.
|
||||
// If this becomes an issue we can remove this since its really only
|
||||
// needed in the error (syntax) case - which isn't really critical.
|
||||
if _, err := filepath.Match(p, "."); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newp.cleanedPattern = p
|
||||
newp.dirs = strings.Split(p, string(os.PathSeparator))
|
||||
pm.patterns = append(pm.patterns, newp)
|
||||
}
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
// Matches matches path against all the patterns. Matches is not safe to be
|
||||
// called concurrently
|
||||
func (pm *PatternMatcher) Matches(file string) (bool, error) {
|
||||
matched := false
|
||||
file = filepath.FromSlash(file)
|
||||
parentPath := filepath.Dir(file)
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
|
||||
for _, pattern := range pm.patterns {
|
||||
negative := false
|
||||
|
||||
if pattern.exclusion {
|
||||
negative = true
|
||||
}
|
||||
|
||||
match, err := pattern.match(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !match && parentPath != "." {
|
||||
// Check to see if the pattern matches one of our parent dirs.
|
||||
if len(pattern.dirs) <= len(parentPathDirs) {
|
||||
match, _ = pattern.match(strings.Join(parentPathDirs[:len(pattern.dirs)], string(os.PathSeparator)))
|
||||
}
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !negative
|
||||
}
|
||||
}
|
||||
|
||||
if matched {
|
||||
logrus.Debugf("Skipping excluded path: %s", file)
|
||||
}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// Exclusions returns true if any of the patterns define exclusions
|
||||
func (pm *PatternMatcher) Exclusions() bool {
|
||||
return pm.exclusions
|
||||
}
|
||||
|
||||
// Patterns returns array of active patterns
|
||||
func (pm *PatternMatcher) Patterns() []*Pattern {
|
||||
return pm.patterns
|
||||
}
|
||||
|
||||
// Pattern defines a single regexp used used to filter file paths.
|
||||
type Pattern struct {
|
||||
cleanedPattern string
|
||||
dirs []string
|
||||
regexp *regexp.Regexp
|
||||
exclusion bool
|
||||
}
|
||||
|
||||
func (p *Pattern) String() string {
|
||||
return p.cleanedPattern
|
||||
}
|
||||
|
||||
// Exclusion returns true if this pattern defines exclusion
|
||||
func (p *Pattern) Exclusion() bool {
|
||||
return p.exclusion
|
||||
}
|
||||
|
||||
func (p *Pattern) match(path string) (bool, error) {
|
||||
|
||||
if p.regexp == nil {
|
||||
if err := p.compile(); err != nil {
|
||||
return false, filepath.ErrBadPattern
|
||||
}
|
||||
}
|
||||
|
||||
b := p.regexp.MatchString(path)
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *Pattern) compile() error {
|
||||
regStr := "^"
|
||||
pattern := p.cleanedPattern
|
||||
// Go through the pattern and convert it to a regexp.
|
||||
// We use a scanner so we can support utf-8 chars.
|
||||
var scan scanner.Scanner
|
||||
scan.Init(strings.NewReader(pattern))
|
||||
|
||||
sl := string(os.PathSeparator)
|
||||
escSL := sl
|
||||
if sl == `\` {
|
||||
escSL += `\`
|
||||
}
|
||||
|
||||
for scan.Peek() != scanner.EOF {
|
||||
ch := scan.Next()
|
||||
|
||||
if ch == '*' {
|
||||
if scan.Peek() == '*' {
|
||||
// is some flavor of "**"
|
||||
scan.Next()
|
||||
|
||||
// Treat **/ as ** so eat the "/"
|
||||
if string(scan.Peek()) == sl {
|
||||
scan.Next()
|
||||
}
|
||||
|
||||
if scan.Peek() == scanner.EOF {
|
||||
// is "**EOF" - to align with .gitignore just accept all
|
||||
regStr += ".*"
|
||||
} else {
|
||||
// is "**"
|
||||
// Note that this allows for any # of /'s (even 0) because
|
||||
// the .* will eat everything, even /'s
|
||||
regStr += "(.*" + escSL + ")?"
|
||||
}
|
||||
} else {
|
||||
// is "*" so map it to anything but "/"
|
||||
regStr += "[^" + escSL + "]*"
|
||||
}
|
||||
} else if ch == '?' {
|
||||
// "?" is any char except "/"
|
||||
regStr += "[^" + escSL + "]"
|
||||
} else if ch == '.' || ch == '$' {
|
||||
// Escape some regexp special chars that have no meaning
|
||||
// in golang's filepath.Match
|
||||
regStr += `\` + string(ch)
|
||||
} else if ch == '\\' {
|
||||
// escape next char. Note that a trailing \ in the pattern
|
||||
// will be left alone (but need to escape it)
|
||||
if sl == `\` {
|
||||
// On windows map "\" to "\\", meaning an escaped backslash,
|
||||
// and then just continue because filepath.Match on
|
||||
// Windows doesn't allow escaping at all
|
||||
regStr += escSL
|
||||
continue
|
||||
}
|
||||
if scan.Peek() != scanner.EOF {
|
||||
regStr += `\` + string(scan.Next())
|
||||
} else {
|
||||
regStr += `\`
|
||||
}
|
||||
} else {
|
||||
regStr += string(ch)
|
||||
}
|
||||
}
|
||||
|
||||
regStr += "$"
|
||||
|
||||
re, err := regexp.Compile(regStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.regexp = re
|
||||
return nil
|
||||
}
|
||||
|
||||
// Matches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
func Matches(file string, patterns []string) (bool, error) {
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
file = filepath.Clean(file)
|
||||
|
||||
if file == "." {
|
||||
// Don't let them exclude everything, kind of silly.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return pm.Matches(file)
|
||||
}
|
||||
|
||||
// CopyFile copies from src to dst until either EOF is reached
|
||||
// on src or an error occurs. It verifies src exists and removes
|
||||
// the dst if it exists.
|
||||
func CopyFile(src, dst string) (int64, error) {
|
||||
cleanSrc := filepath.Clean(src)
|
||||
cleanDst := filepath.Clean(dst)
|
||||
if cleanSrc == cleanDst {
|
||||
return 0, nil
|
||||
}
|
||||
sf, err := os.Open(cleanSrc)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer sf.Close()
|
||||
if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) {
|
||||
return 0, err
|
||||
}
|
||||
df, err := os.Create(cleanDst)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer df.Close()
|
||||
return io.Copy(df, sf)
|
||||
}
|
||||
|
||||
// ReadSymlinkedDirectory returns the target directory of a symlink.
|
||||
// The target of the symbolic link may not be a file.
|
||||
func ReadSymlinkedDirectory(path string) (string, error) {
|
||||
var realPath string
|
||||
var err error
|
||||
if realPath, err = filepath.Abs(path); err != nil {
|
||||
return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err)
|
||||
}
|
||||
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
|
||||
return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err)
|
||||
}
|
||||
realPathInfo, err := os.Stat(realPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err)
|
||||
}
|
||||
if !realPathInfo.Mode().IsDir() {
|
||||
return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
|
||||
}
|
||||
return realPath, nil
|
||||
}
|
||||
|
||||
// CreateIfNotExists creates a file or a directory only if it does not already exist.
|
||||
func CreateIfNotExists(path string, isDir bool) error {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if isDir {
|
||||
return os.MkdirAll(path, 0755)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(path, os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
27
vendor/github.com/docker/docker/pkg/fileutils/fileutils_darwin.go
generated
vendored
Normal file
27
vendor/github.com/docker/docker/pkg/fileutils/fileutils_darwin.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetTotalUsedFds returns the number of used File Descriptors by
|
||||
// executing `lsof -p PID`
|
||||
func GetTotalUsedFds() int {
|
||||
pid := os.Getpid()
|
||||
|
||||
cmd := exec.Command("lsof", "-p", strconv.Itoa(pid))
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
outputStr := strings.TrimSpace(string(output))
|
||||
|
||||
fds := strings.Split(outputStr, "\n")
|
||||
|
||||
return len(fds) - 1
|
||||
}
|
7
vendor/github.com/docker/docker/pkg/fileutils/fileutils_solaris.go
generated
vendored
Normal file
7
vendor/github.com/docker/docker/pkg/fileutils/fileutils_solaris.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
package fileutils
|
||||
|
||||
// GetTotalUsedFds Returns the number of used File Descriptors.
|
||||
// On Solaris these limits are per process and not systemwide
|
||||
func GetTotalUsedFds() int {
|
||||
return -1
|
||||
}
|
591
vendor/github.com/docker/docker/pkg/fileutils/fileutils_test.go
generated
vendored
Normal file
591
vendor/github.com/docker/docker/pkg/fileutils/fileutils_test.go
generated
vendored
Normal file
@ -0,0 +1,591 @@
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// CopyFile with invalid src
|
||||
func TestCopyFileWithInvalidSrc(t *testing.T) {
|
||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
||||
defer os.RemoveAll(tempFolder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := CopyFile("/invalid/file/path", path.Join(tempFolder, "dest"))
|
||||
if err == nil {
|
||||
t.Fatal("Should have fail to copy an invalid src file")
|
||||
}
|
||||
if bytes != 0 {
|
||||
t.Fatal("Should have written 0 bytes")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// CopyFile with invalid dest
|
||||
func TestCopyFileWithInvalidDest(t *testing.T) {
|
||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
||||
defer os.RemoveAll(tempFolder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := path.Join(tempFolder, "file")
|
||||
err = ioutil.WriteFile(src, []byte("content"), 0740)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := CopyFile(src, path.Join(tempFolder, "/invalid/dest/path"))
|
||||
if err == nil {
|
||||
t.Fatal("Should have fail to copy an invalid src file")
|
||||
}
|
||||
if bytes != 0 {
|
||||
t.Fatal("Should have written 0 bytes")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// CopyFile with same src and dest
|
||||
func TestCopyFileWithSameSrcAndDest(t *testing.T) {
|
||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
||||
defer os.RemoveAll(tempFolder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file := path.Join(tempFolder, "file")
|
||||
err = ioutil.WriteFile(file, []byte("content"), 0740)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := CopyFile(file, file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bytes != 0 {
|
||||
t.Fatal("Should have written 0 bytes as it is the same file.")
|
||||
}
|
||||
}
|
||||
|
||||
// CopyFile with same src and dest but path is different and not clean
|
||||
func TestCopyFileWithSameSrcAndDestWithPathNameDifferent(t *testing.T) {
|
||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
||||
defer os.RemoveAll(tempFolder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testFolder := path.Join(tempFolder, "test")
|
||||
err = os.MkdirAll(testFolder, 0740)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
file := path.Join(testFolder, "file")
|
||||
sameFile := testFolder + "/../test/file"
|
||||
err = ioutil.WriteFile(file, []byte("content"), 0740)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
bytes, err := CopyFile(file, sameFile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bytes != 0 {
|
||||
t.Fatal("Should have written 0 bytes as it is the same file.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
||||
defer os.RemoveAll(tempFolder)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
src := path.Join(tempFolder, "src")
|
||||
dest := path.Join(tempFolder, "dest")
|
||||
ioutil.WriteFile(src, []byte("content"), 0777)
|
||||
ioutil.WriteFile(dest, []byte("destContent"), 0777)
|
||||
bytes, err := CopyFile(src, dest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bytes != 7 {
|
||||
t.Fatalf("Should have written %d bytes but wrote %d", 7, bytes)
|
||||
}
|
||||
actual, err := ioutil.ReadFile(dest)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(actual) != "content" {
|
||||
t.Fatalf("Dest content was '%s', expected '%s'", string(actual), "content")
|
||||
}
|
||||
}
|
||||
|
||||
// Reading a symlink to a directory must return the directory
|
||||
func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) {
|
||||
// TODO Windows: Port this test
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Needs porting to Windows")
|
||||
}
|
||||
var err error
|
||||
if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil {
|
||||
t.Errorf("failed to create directory: %s", err)
|
||||
}
|
||||
|
||||
if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil {
|
||||
t.Errorf("failed to create symlink: %s", err)
|
||||
}
|
||||
|
||||
var path string
|
||||
if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil {
|
||||
t.Fatalf("failed to read symlink to directory: %s", err)
|
||||
}
|
||||
|
||||
if path != "/tmp/testReadSymlinkToExistingDirectory" {
|
||||
t.Fatalf("symlink returned unexpected directory: %s", path)
|
||||
}
|
||||
|
||||
if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil {
|
||||
t.Errorf("failed to remove temporary directory: %s", err)
|
||||
}
|
||||
|
||||
if err = os.Remove("/tmp/dirLinkTest"); err != nil {
|
||||
t.Errorf("failed to remove symlink: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Reading a non-existing symlink must fail
|
||||
func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) {
|
||||
var path string
|
||||
var err error
|
||||
if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil {
|
||||
t.Fatalf("error expected for non-existing symlink")
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
t.Fatalf("expected empty path, but '%s' was returned", path)
|
||||
}
|
||||
}
|
||||
|
||||
// Reading a symlink to a file must fail
|
||||
func TestReadSymlinkedDirectoryToFile(t *testing.T) {
|
||||
// TODO Windows: Port this test
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Needs porting to Windows")
|
||||
}
|
||||
var err error
|
||||
var file *os.File
|
||||
|
||||
if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil {
|
||||
t.Fatalf("failed to create file: %s", err)
|
||||
}
|
||||
|
||||
file.Close()
|
||||
|
||||
if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil {
|
||||
t.Errorf("failed to create symlink: %s", err)
|
||||
}
|
||||
|
||||
var path string
|
||||
if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil {
|
||||
t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed")
|
||||
}
|
||||
|
||||
if path != "" {
|
||||
t.Fatalf("path should've been empty: %s", path)
|
||||
}
|
||||
|
||||
if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil {
|
||||
t.Errorf("failed to remove file: %s", err)
|
||||
}
|
||||
|
||||
if err = os.Remove("/tmp/fileLinkTest"); err != nil {
|
||||
t.Errorf("failed to remove symlink: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardMatches(t *testing.T) {
|
||||
match, _ := Matches("fileutils.go", []string{"*"})
|
||||
if !match {
|
||||
t.Errorf("failed to get a wildcard match, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A simple pattern match should return true.
|
||||
func TestPatternMatches(t *testing.T) {
|
||||
match, _ := Matches("fileutils.go", []string{"*.go"})
|
||||
if !match {
|
||||
t.Errorf("failed to get a match, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// An exclusion followed by an inclusion should return true.
|
||||
func TestExclusionPatternMatchesPatternBefore(t *testing.T) {
|
||||
match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"})
|
||||
if !match {
|
||||
t.Errorf("failed to get true match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A folder pattern followed by an exception should return false.
|
||||
func TestPatternMatchesFolderExclusions(t *testing.T) {
|
||||
match, _ := Matches("docs/README.md", []string{"docs", "!docs/README.md"})
|
||||
if match {
|
||||
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A folder pattern followed by an exception should return false.
|
||||
func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) {
|
||||
match, _ := Matches("docs/README.md", []string{"docs/", "!docs/README.md"})
|
||||
if match {
|
||||
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A folder pattern followed by an exception should return false.
|
||||
func TestPatternMatchesFolderWildcardExclusions(t *testing.T) {
|
||||
match, _ := Matches("docs/README.md", []string{"docs/*", "!docs/README.md"})
|
||||
if match {
|
||||
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A pattern followed by an exclusion should return false.
|
||||
func TestExclusionPatternMatchesPatternAfter(t *testing.T) {
|
||||
match, _ := Matches("fileutils.go", []string{"*.go", "!fileutils.go"})
|
||||
if match {
|
||||
t.Errorf("failed to get false match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A filename evaluating to . should return false.
|
||||
func TestExclusionPatternMatchesWholeDirectory(t *testing.T) {
|
||||
match, _ := Matches(".", []string{"*.go"})
|
||||
if match {
|
||||
t.Errorf("failed to get false match on ., got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A single ! pattern should return an error.
|
||||
func TestSingleExclamationError(t *testing.T) {
|
||||
_, err := Matches("fileutils.go", []string{"!"})
|
||||
if err == nil {
|
||||
t.Errorf("failed to get an error for a single exclamation point, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Matches with no patterns
|
||||
func TestMatchesWithNoPatterns(t *testing.T) {
|
||||
matches, err := Matches("/any/path/there", []string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if matches {
|
||||
t.Fatalf("Should not have match anything")
|
||||
}
|
||||
}
|
||||
|
||||
// Matches with malformed patterns
|
||||
func TestMatchesWithMalformedPatterns(t *testing.T) {
|
||||
matches, err := Matches("/any/path/there", []string{"["})
|
||||
if err == nil {
|
||||
t.Fatal("Should have failed because of a malformed syntax in the pattern")
|
||||
}
|
||||
if matches {
|
||||
t.Fatalf("Should not have match anything")
|
||||
}
|
||||
}
|
||||
|
||||
type matchesTestCase struct {
|
||||
pattern string
|
||||
text string
|
||||
pass bool
|
||||
}
|
||||
|
||||
func TestMatches(t *testing.T) {
|
||||
tests := []matchesTestCase{
|
||||
{"**", "file", true},
|
||||
{"**", "file/", true},
|
||||
{"**/", "file", true}, // weird one
|
||||
{"**/", "file/", true},
|
||||
{"**", "/", true},
|
||||
{"**/", "/", true},
|
||||
{"**", "dir/file", true},
|
||||
{"**/", "dir/file", true},
|
||||
{"**", "dir/file/", true},
|
||||
{"**/", "dir/file/", true},
|
||||
{"**/**", "dir/file", true},
|
||||
{"**/**", "dir/file/", true},
|
||||
{"dir/**", "dir/file", true},
|
||||
{"dir/**", "dir/file/", true},
|
||||
{"dir/**", "dir/dir2/file", true},
|
||||
{"dir/**", "dir/dir2/file/", true},
|
||||
{"**/dir2/*", "dir/dir2/file", true},
|
||||
{"**/dir2/*", "dir/dir2/file/", true},
|
||||
{"**/dir2/**", "dir/dir2/dir3/file", true},
|
||||
{"**/dir2/**", "dir/dir2/dir3/file/", true},
|
||||
{"**file", "file", true},
|
||||
{"**file", "dir/file", true},
|
||||
{"**/file", "dir/file", true},
|
||||
{"**file", "dir/dir/file", true},
|
||||
{"**/file", "dir/dir/file", true},
|
||||
{"**/file*", "dir/dir/file", true},
|
||||
{"**/file*", "dir/dir/file.txt", true},
|
||||
{"**/file*txt", "dir/dir/file.txt", true},
|
||||
{"**/file*.txt", "dir/dir/file.txt", true},
|
||||
{"**/file*.txt*", "dir/dir/file.txt", true},
|
||||
{"**/**/*.txt", "dir/dir/file.txt", true},
|
||||
{"**/**/*.txt2", "dir/dir/file.txt", false},
|
||||
{"**/*.txt", "file.txt", true},
|
||||
{"**/**/*.txt", "file.txt", true},
|
||||
{"a**/*.txt", "a/file.txt", true},
|
||||
{"a**/*.txt", "a/dir/file.txt", true},
|
||||
{"a**/*.txt", "a/dir/dir/file.txt", true},
|
||||
{"a/*.txt", "a/dir/file.txt", false},
|
||||
{"a/*.txt", "a/file.txt", true},
|
||||
{"a/*.txt**", "a/file.txt", true},
|
||||
{"a[b-d]e", "ae", false},
|
||||
{"a[b-d]e", "ace", true},
|
||||
{"a[b-d]e", "aae", false},
|
||||
{"a[^b-d]e", "aze", true},
|
||||
{".*", ".foo", true},
|
||||
{".*", "foo", false},
|
||||
{"abc.def", "abcdef", false},
|
||||
{"abc.def", "abc.def", true},
|
||||
{"abc.def", "abcZdef", false},
|
||||
{"abc?def", "abcZdef", true},
|
||||
{"abc?def", "abcdef", false},
|
||||
{"a\\\\", "a\\", true},
|
||||
{"**/foo/bar", "foo/bar", true},
|
||||
{"**/foo/bar", "dir/foo/bar", true},
|
||||
{"**/foo/bar", "dir/dir2/foo/bar", true},
|
||||
{"abc/**", "abc", false},
|
||||
{"abc/**", "abc/def", true},
|
||||
{"abc/**", "abc/def/ghi", true},
|
||||
{"**/.foo", ".foo", true},
|
||||
{"**/.foo", "bar.foo", false},
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
tests = append(tests, []matchesTestCase{
|
||||
{"a\\*b", "a*b", true},
|
||||
{"a\\", "a", false},
|
||||
{"a\\", "a\\", false},
|
||||
}...)
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
desc := fmt.Sprintf("pattern=%q text=%q", test.pattern, test.text)
|
||||
pm, err := NewPatternMatcher([]string{test.pattern})
|
||||
require.NoError(t, err, desc)
|
||||
res, _ := pm.Matches(test.text)
|
||||
assert.Equal(t, test.pass, res, desc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatterns(t *testing.T) {
|
||||
patterns := []string{"docs", "config"}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
cleaned := pm.Patterns()
|
||||
if len(cleaned) != 2 {
|
||||
t.Errorf("expected 2 element slice, got %v", len(cleaned))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsStripEmptyPatterns(t *testing.T) {
|
||||
patterns := []string{"docs", "config", ""}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
cleaned := pm.Patterns()
|
||||
if len(cleaned) != 2 {
|
||||
t.Errorf("expected 2 element slice, got %v", len(cleaned))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsExceptionFlag(t *testing.T) {
|
||||
patterns := []string{"docs", "!docs/README.md"}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
if !pm.Exclusions() {
|
||||
t.Errorf("expected exceptions to be true, got %v", pm.Exclusions())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsLeadingSpaceTrimmed(t *testing.T) {
|
||||
patterns := []string{"docs", " !docs/README.md"}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
if !pm.Exclusions() {
|
||||
t.Errorf("expected exceptions to be true, got %v", pm.Exclusions())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsTrailingSpaceTrimmed(t *testing.T) {
|
||||
patterns := []string{"docs", "!docs/README.md "}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
if !pm.Exclusions() {
|
||||
t.Errorf("expected exceptions to be true, got %v", pm.Exclusions())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsErrorSingleException(t *testing.T) {
|
||||
patterns := []string{"!"}
|
||||
_, err := NewPatternMatcher(patterns)
|
||||
if err == nil {
|
||||
t.Errorf("expected error on single exclamation point, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIfNotExistsDir(t *testing.T) {
|
||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempFolder)
|
||||
|
||||
folderToCreate := filepath.Join(tempFolder, "tocreate")
|
||||
|
||||
if err := CreateIfNotExists(folderToCreate, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fileinfo, err := os.Stat(folderToCreate)
|
||||
if err != nil {
|
||||
t.Fatalf("Should have create a folder, got %v", err)
|
||||
}
|
||||
|
||||
if !fileinfo.IsDir() {
|
||||
t.Fatalf("Should have been a dir, seems it's not")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIfNotExistsFile(t *testing.T) {
|
||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tempFolder)
|
||||
|
||||
fileToCreate := filepath.Join(tempFolder, "file/to/create")
|
||||
|
||||
if err := CreateIfNotExists(fileToCreate, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fileinfo, err := os.Stat(fileToCreate)
|
||||
if err != nil {
|
||||
t.Fatalf("Should have create a file, got %v", err)
|
||||
}
|
||||
|
||||
if fileinfo.IsDir() {
|
||||
t.Fatalf("Should have been a file, seems it's not")
|
||||
}
|
||||
}
|
||||
|
||||
// These matchTests are stolen from go's filepath Match tests.
|
||||
type matchTest struct {
|
||||
pattern, s string
|
||||
match bool
|
||||
err error
|
||||
}
|
||||
|
||||
var matchTests = []matchTest{
|
||||
{"abc", "abc", true, nil},
|
||||
{"*", "abc", true, nil},
|
||||
{"*c", "abc", true, nil},
|
||||
{"a*", "a", true, nil},
|
||||
{"a*", "abc", true, nil},
|
||||
{"a*", "ab/c", true, nil},
|
||||
{"a*/b", "abc/b", true, nil},
|
||||
{"a*/b", "a/c/b", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
|
||||
{"ab[c]", "abc", true, nil},
|
||||
{"ab[b-d]", "abc", true, nil},
|
||||
{"ab[e-g]", "abc", false, nil},
|
||||
{"ab[^c]", "abc", false, nil},
|
||||
{"ab[^b-d]", "abc", false, nil},
|
||||
{"ab[^e-g]", "abc", true, nil},
|
||||
{"a\\*b", "a*b", true, nil},
|
||||
{"a\\*b", "ab", false, nil},
|
||||
{"a?b", "a☺b", true, nil},
|
||||
{"a[^a]b", "a☺b", true, nil},
|
||||
{"a???b", "a☺b", false, nil},
|
||||
{"a[^a][^a][^a]b", "a☺b", false, nil},
|
||||
{"[a-ζ]*", "α", true, nil},
|
||||
{"*[a-ζ]", "A", false, nil},
|
||||
{"a?b", "a/b", false, nil},
|
||||
{"a*b", "a/b", false, nil},
|
||||
{"[\\]a]", "]", true, nil},
|
||||
{"[\\-]", "-", true, nil},
|
||||
{"[x\\-]", "x", true, nil},
|
||||
{"[x\\-]", "-", true, nil},
|
||||
{"[x\\-]", "z", false, nil},
|
||||
{"[\\-x]", "x", true, nil},
|
||||
{"[\\-x]", "-", true, nil},
|
||||
{"[\\-x]", "a", false, nil},
|
||||
{"[]a]", "]", false, filepath.ErrBadPattern},
|
||||
{"[-]", "-", false, filepath.ErrBadPattern},
|
||||
{"[x-]", "x", false, filepath.ErrBadPattern},
|
||||
{"[x-]", "-", false, filepath.ErrBadPattern},
|
||||
{"[x-]", "z", false, filepath.ErrBadPattern},
|
||||
{"[-x]", "x", false, filepath.ErrBadPattern},
|
||||
{"[-x]", "-", false, filepath.ErrBadPattern},
|
||||
{"[-x]", "a", false, filepath.ErrBadPattern},
|
||||
{"\\", "a", false, filepath.ErrBadPattern},
|
||||
{"[a-b-c]", "a", false, filepath.ErrBadPattern},
|
||||
{"[", "a", false, filepath.ErrBadPattern},
|
||||
{"[^", "a", false, filepath.ErrBadPattern},
|
||||
{"[^bc", "a", false, filepath.ErrBadPattern},
|
||||
{"a[", "a", false, filepath.ErrBadPattern}, // was nil but IMO its wrong
|
||||
{"a[", "ab", false, filepath.ErrBadPattern},
|
||||
{"*x", "xxx", true, nil},
|
||||
}
|
||||
|
||||
func errp(e error) string {
|
||||
if e == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
// TestMatch test's our version of filepath.Match, called regexpMatch.
|
||||
func TestMatch(t *testing.T) {
|
||||
for _, tt := range matchTests {
|
||||
pattern := tt.pattern
|
||||
s := tt.s
|
||||
if runtime.GOOS == "windows" {
|
||||
if strings.Contains(pattern, "\\") {
|
||||
// no escape allowed on windows.
|
||||
continue
|
||||
}
|
||||
pattern = filepath.Clean(pattern)
|
||||
s = filepath.Clean(s)
|
||||
}
|
||||
ok, err := Matches(s, []string{pattern})
|
||||
if ok != tt.match || err != tt.err {
|
||||
t.Fatalf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
|
||||
}
|
||||
}
|
||||
}
|
22
vendor/github.com/docker/docker/pkg/fileutils/fileutils_unix.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/fileutils/fileutils_unix.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package fileutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetTotalUsedFds Returns the number of used File Descriptors by
|
||||
// reading it via /proc filesystem.
|
||||
func GetTotalUsedFds() int {
|
||||
if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
|
||||
logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
|
||||
} else {
|
||||
return len(fds)
|
||||
}
|
||||
return -1
|
||||
}
|
7
vendor/github.com/docker/docker/pkg/fileutils/fileutils_windows.go
generated
vendored
Normal file
7
vendor/github.com/docker/docker/pkg/fileutils/fileutils_windows.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
package fileutils
|
||||
|
||||
// GetTotalUsedFds Returns the number of used File Descriptors. Not supported
|
||||
// on Windows.
|
||||
func GetTotalUsedFds() int {
|
||||
return -1
|
||||
}
|
197
vendor/github.com/docker/docker/pkg/idtools/idtools.go
generated
vendored
Normal file
197
vendor/github.com/docker/docker/pkg/idtools/idtools.go
generated
vendored
Normal file
@ -0,0 +1,197 @@
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IDMap contains a single entry for user namespace range remapping. An array
|
||||
// of IDMap entries represents the structure that will be provided to the Linux
|
||||
// kernel for creating a user namespace.
|
||||
type IDMap struct {
|
||||
ContainerID int `json:"container_id"`
|
||||
HostID int `json:"host_id"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
type subIDRange struct {
|
||||
Start int
|
||||
Length int
|
||||
}
|
||||
|
||||
type ranges []subIDRange
|
||||
|
||||
func (e ranges) Len() int { return len(e) }
|
||||
func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
|
||||
|
||||
const (
|
||||
subuidFileName string = "/etc/subuid"
|
||||
subgidFileName string = "/etc/subgid"
|
||||
)
|
||||
|
||||
// MkdirAllAs creates a directory (include any along the path) and then modifies
|
||||
// ownership to the requested uid/gid. If the directory already exists, this
|
||||
// function will still change ownership to the requested uid/gid pair.
|
||||
func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
||||
return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
|
||||
}
|
||||
|
||||
// MkdirAllNewAs creates a directory (include any along the path) and then modifies
|
||||
// ownership ONLY of newly created directories to the requested uid/gid. If the
|
||||
// directories along the path exist, no change of ownership will be performed
|
||||
func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
||||
return mkdirAs(path, mode, ownerUID, ownerGID, true, false)
|
||||
}
|
||||
|
||||
// MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
|
||||
// If the directory already exists, this function still changes ownership
|
||||
func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
||||
return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
|
||||
}
|
||||
|
||||
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
|
||||
// If the maps are empty, then the root uid/gid will default to "real" 0/0
|
||||
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
|
||||
var uid, gid int
|
||||
|
||||
if uidMap != nil {
|
||||
xUID, err := ToHost(0, uidMap)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
uid = xUID
|
||||
}
|
||||
if gidMap != nil {
|
||||
xGID, err := ToHost(0, gidMap)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
gid = xGID
|
||||
}
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
// ToContainer takes an id mapping, and uses it to translate a
|
||||
// host ID to the remapped ID. If no map is provided, then the translation
|
||||
// assumes a 1-to-1 mapping and returns the passed in id
|
||||
func ToContainer(hostID int, idMap []IDMap) (int, error) {
|
||||
if idMap == nil {
|
||||
return hostID, nil
|
||||
}
|
||||
for _, m := range idMap {
|
||||
if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
|
||||
contID := m.ContainerID + (hostID - m.HostID)
|
||||
return contID, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
|
||||
}
|
||||
|
||||
// ToHost takes an id mapping and a remapped ID, and translates the
|
||||
// ID to the mapped host ID. If no map is provided, then the translation
|
||||
// assumes a 1-to-1 mapping and returns the passed in id #
|
||||
func ToHost(contID int, idMap []IDMap) (int, error) {
|
||||
if idMap == nil {
|
||||
return contID, nil
|
||||
}
|
||||
for _, m := range idMap {
|
||||
if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
|
||||
hostID := m.HostID + (contID - m.ContainerID)
|
||||
return hostID, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
|
||||
}
|
||||
|
||||
// CreateIDMappings takes a requested user and group name and
|
||||
// using the data from /etc/sub{uid,gid} ranges, creates the
|
||||
// proper uid and gid remapping ranges for that user/group pair
|
||||
func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) {
|
||||
subuidRanges, err := parseSubuid(username)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
subgidRanges, err := parseSubgid(groupname)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(subuidRanges) == 0 {
|
||||
return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username)
|
||||
}
|
||||
if len(subgidRanges) == 0 {
|
||||
return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
|
||||
}
|
||||
|
||||
return createIDMap(subuidRanges), createIDMap(subgidRanges), nil
|
||||
}
|
||||
|
||||
func createIDMap(subidRanges ranges) []IDMap {
|
||||
idMap := []IDMap{}
|
||||
|
||||
// sort the ranges by lowest ID first
|
||||
sort.Sort(subidRanges)
|
||||
containerID := 0
|
||||
for _, idrange := range subidRanges {
|
||||
idMap = append(idMap, IDMap{
|
||||
ContainerID: containerID,
|
||||
HostID: idrange.Start,
|
||||
Size: idrange.Length,
|
||||
})
|
||||
containerID = containerID + idrange.Length
|
||||
}
|
||||
return idMap
|
||||
}
|
||||
|
||||
func parseSubuid(username string) (ranges, error) {
|
||||
return parseSubidFile(subuidFileName, username)
|
||||
}
|
||||
|
||||
func parseSubgid(username string) (ranges, error) {
|
||||
return parseSubidFile(subgidFileName, username)
|
||||
}
|
||||
|
||||
// parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
|
||||
// and return all found ranges for a specified username. If the special value
|
||||
// "ALL" is supplied for username, then all ranges in the file will be returned
|
||||
func parseSubidFile(path, username string) (ranges, error) {
|
||||
var rangeList ranges
|
||||
|
||||
subidFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return rangeList, err
|
||||
}
|
||||
defer subidFile.Close()
|
||||
|
||||
s := bufio.NewScanner(subidFile)
|
||||
for s.Scan() {
|
||||
if err := s.Err(); err != nil {
|
||||
return rangeList, err
|
||||
}
|
||||
|
||||
text := strings.TrimSpace(s.Text())
|
||||
if text == "" || strings.HasPrefix(text, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(text, ":")
|
||||
if len(parts) != 3 {
|
||||
return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
|
||||
}
|
||||
if parts[0] == username || username == "ALL" {
|
||||
startid, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
|
||||
}
|
||||
length, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
|
||||
}
|
||||
rangeList = append(rangeList, subIDRange{startid, length})
|
||||
}
|
||||
}
|
||||
return rangeList, nil
|
||||
}
|
204
vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go
generated
vendored
Normal file
204
vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go
generated
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
// +build !windows
|
||||
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
)
|
||||
|
||||
var (
|
||||
entOnce sync.Once
|
||||
getentCmd string
|
||||
)
|
||||
|
||||
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
||||
// make an array containing the original path asked for, plus (for mkAll == true)
|
||||
// all path components leading up to the complete path that don't exist before we MkdirAll
|
||||
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
|
||||
// chown the full directory path if it exists
|
||||
var paths []string
|
||||
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
||||
paths = []string{path}
|
||||
} else if err == nil && chownExisting {
|
||||
// short-circuit--we were called with an existing directory and chown was requested
|
||||
return os.Chown(path, ownerUID, ownerGID)
|
||||
} else if err == nil {
|
||||
// nothing to do; directory path fully exists already and chown was NOT requested
|
||||
return nil
|
||||
}
|
||||
|
||||
if mkAll {
|
||||
// walk back to "/" looking for directories which do not exist
|
||||
// and add them to the paths array for chown after creation
|
||||
dirPath := path
|
||||
for {
|
||||
dirPath = filepath.Dir(dirPath)
|
||||
if dirPath == "/" {
|
||||
break
|
||||
}
|
||||
if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
|
||||
paths = append(paths, dirPath)
|
||||
}
|
||||
}
|
||||
if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// even if it existed, we will chown the requested path + any subpaths that
|
||||
// didn't exist when we called MkdirAll
|
||||
for _, pathComponent := range paths {
|
||||
if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||
// if that uid, gid pair has access (execute bit) to the directory
|
||||
func CanAccess(path string, uid, gid int) bool {
|
||||
statInfo, err := system.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fileMode := os.FileMode(statInfo.Mode())
|
||||
permBits := fileMode.Perm()
|
||||
return accessible(statInfo.UID() == uint32(uid),
|
||||
statInfo.GID() == uint32(gid), permBits)
|
||||
}
|
||||
|
||||
func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
|
||||
if isOwner && (perms&0100 == 0100) {
|
||||
return true
|
||||
}
|
||||
if isGroup && (perms&0010 == 0010) {
|
||||
return true
|
||||
}
|
||||
if perms&0001 == 0001 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupUser(username string) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUser(username)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username))
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
return usr, nil
|
||||
}
|
||||
|
||||
// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupUID(uid int) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUid(uid)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
return getentUser(fmt.Sprintf("%s %d", "passwd", uid))
|
||||
}
|
||||
|
||||
func getentUser(args string) (user.User, error) {
|
||||
reader, err := callGetent(args)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
users, err := user.ParsePasswd(reader)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1])
|
||||
}
|
||||
return users[0], nil
|
||||
}
|
||||
|
||||
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupGroup(groupname string) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGroup(groupname)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(fmt.Sprintf("%s %s", "group", groupname))
|
||||
}
|
||||
|
||||
// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupGID(gid int) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGid(gid)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(fmt.Sprintf("%s %d", "group", gid))
|
||||
}
|
||||
|
||||
func getentGroup(args string) (user.Group, error) {
|
||||
reader, err := callGetent(args)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
groups, err := user.ParseGroup(reader)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1])
|
||||
}
|
||||
return groups[0], nil
|
||||
}
|
||||
|
||||
func callGetent(args string) (io.Reader, error) {
|
||||
entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
|
||||
// if no `getent` command on host, can't do anything else
|
||||
if getentCmd == "" {
|
||||
return nil, fmt.Errorf("")
|
||||
}
|
||||
out, err := execCmd(getentCmd, args)
|
||||
if err != nil {
|
||||
exitCode, errC := system.GetExitCode(err)
|
||||
if errC != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch exitCode {
|
||||
case 1:
|
||||
return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
|
||||
case 2:
|
||||
terms := strings.Split(args, " ")
|
||||
return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0])
|
||||
case 3:
|
||||
return nil, fmt.Errorf("getent database doesn't support enumeration")
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return bytes.NewReader(out), nil
|
||||
}
|
271
vendor/github.com/docker/docker/pkg/idtools/idtools_unix_test.go
generated
vendored
Normal file
271
vendor/github.com/docker/docker/pkg/idtools/idtools_unix_test.go
generated
vendored
Normal file
@ -0,0 +1,271 @@
|
||||
// +build !windows
|
||||
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type node struct {
|
||||
uid int
|
||||
gid int
|
||||
}
|
||||
|
||||
func TestMkdirAllAs(t *testing.T) {
|
||||
dirName, err := ioutil.TempDir("", "mkdirall")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
testTree := map[string]node{
|
||||
"usr": {0, 0},
|
||||
"usr/bin": {0, 0},
|
||||
"lib": {33, 33},
|
||||
"lib/x86_64": {45, 45},
|
||||
"lib/x86_64/share": {1, 1},
|
||||
}
|
||||
|
||||
if err := buildTree(dirName, testTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
|
||||
if err := MkdirAllAs(filepath.Join(dirName, "usr", "share"), 0755, 99, 99); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testTree["usr/share"] = node{99, 99}
|
||||
verifyTree, err := readTree(dirName, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test 2-deep new directories--both should be owned by the uid/gid pair
|
||||
if err := MkdirAllAs(filepath.Join(dirName, "lib", "some", "other"), 0755, 101, 101); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testTree["lib/some"] = node{101, 101}
|
||||
testTree["lib/some/other"] = node{101, 101}
|
||||
verifyTree, err = readTree(dirName, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test a directory that already exists; should be chowned, but nothing else
|
||||
if err := MkdirAllAs(filepath.Join(dirName, "usr"), 0755, 102, 102); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testTree["usr"] = node{102, 102}
|
||||
verifyTree, err = readTree(dirName, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMkdirAllNewAs(t *testing.T) {
|
||||
|
||||
dirName, err := ioutil.TempDir("", "mkdirnew")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
testTree := map[string]node{
|
||||
"usr": {0, 0},
|
||||
"usr/bin": {0, 0},
|
||||
"lib": {33, 33},
|
||||
"lib/x86_64": {45, 45},
|
||||
"lib/x86_64/share": {1, 1},
|
||||
}
|
||||
|
||||
if err := buildTree(dirName, testTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
|
||||
if err := MkdirAllNewAs(filepath.Join(dirName, "usr", "share"), 0755, 99, 99); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testTree["usr/share"] = node{99, 99}
|
||||
verifyTree, err := readTree(dirName, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test 2-deep new directories--both should be owned by the uid/gid pair
|
||||
if err := MkdirAllNewAs(filepath.Join(dirName, "lib", "some", "other"), 0755, 101, 101); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testTree["lib/some"] = node{101, 101}
|
||||
testTree["lib/some/other"] = node{101, 101}
|
||||
verifyTree, err = readTree(dirName, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test a directory that already exists; should NOT be chowned
|
||||
if err := MkdirAllNewAs(filepath.Join(dirName, "usr"), 0755, 102, 102); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifyTree, err = readTree(dirName, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMkdirAs(t *testing.T) {
|
||||
|
||||
dirName, err := ioutil.TempDir("", "mkdir")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
testTree := map[string]node{
|
||||
"usr": {0, 0},
|
||||
}
|
||||
if err := buildTree(dirName, testTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// test a directory that already exists; should just chown to the requested uid/gid
|
||||
if err := MkdirAs(filepath.Join(dirName, "usr"), 0755, 99, 99); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testTree["usr"] = node{99, 99}
|
||||
verifyTree, err := readTree(dirName, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// create a subdir under a dir which doesn't exist--should fail
|
||||
if err := MkdirAs(filepath.Join(dirName, "usr", "bin", "subdir"), 0755, 102, 102); err == nil {
|
||||
t.Fatalf("Trying to create a directory with Mkdir where the parent doesn't exist should have failed")
|
||||
}
|
||||
|
||||
// create a subdir under an existing dir; should only change the ownership of the new subdir
|
||||
if err := MkdirAs(filepath.Join(dirName, "usr", "bin"), 0755, 102, 102); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testTree["usr/bin"] = node{102, 102}
|
||||
verifyTree, err = readTree(dirName, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func buildTree(base string, tree map[string]node) error {
|
||||
for path, node := range tree {
|
||||
fullPath := filepath.Join(base, path)
|
||||
if err := os.MkdirAll(fullPath, 0755); err != nil {
|
||||
return fmt.Errorf("Couldn't create path: %s; error: %v", fullPath, err)
|
||||
}
|
||||
if err := os.Chown(fullPath, node.uid, node.gid); err != nil {
|
||||
return fmt.Errorf("Couldn't chown path: %s; error: %v", fullPath, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readTree(base, root string) (map[string]node, error) {
|
||||
tree := make(map[string]node)
|
||||
|
||||
dirInfos, err := ioutil.ReadDir(base)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't read directory entries for %q: %v", base, err)
|
||||
}
|
||||
|
||||
for _, info := range dirInfos {
|
||||
s := &syscall.Stat_t{}
|
||||
if err := syscall.Stat(filepath.Join(base, info.Name()), s); err != nil {
|
||||
return nil, fmt.Errorf("Can't stat file %q: %v", filepath.Join(base, info.Name()), err)
|
||||
}
|
||||
tree[filepath.Join(root, info.Name())] = node{int(s.Uid), int(s.Gid)}
|
||||
if info.IsDir() {
|
||||
// read the subdirectory
|
||||
subtree, err := readTree(filepath.Join(base, info.Name()), filepath.Join(root, info.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for path, nodeinfo := range subtree {
|
||||
tree[path] = nodeinfo
|
||||
}
|
||||
}
|
||||
}
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
func compareTrees(left, right map[string]node) error {
|
||||
if len(left) != len(right) {
|
||||
return fmt.Errorf("Trees aren't the same size")
|
||||
}
|
||||
for path, nodeLeft := range left {
|
||||
if nodeRight, ok := right[path]; ok {
|
||||
if nodeRight.uid != nodeLeft.uid || nodeRight.gid != nodeLeft.gid {
|
||||
// mismatch
|
||||
return fmt.Errorf("mismatched ownership for %q: expected: %d:%d, got: %d:%d", path,
|
||||
nodeLeft.uid, nodeLeft.gid, nodeRight.uid, nodeRight.gid)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return fmt.Errorf("right tree didn't contain path %q", path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestParseSubidFileWithNewlinesAndComments(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "parsesubid")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fnamePath := filepath.Join(tmpDir, "testsubuid")
|
||||
fcontent := `tss:100000:65536
|
||||
# empty default subuid/subgid file
|
||||
|
||||
dockremap:231072:65536`
|
||||
if err := ioutil.WriteFile(fnamePath, []byte(fcontent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ranges, err := parseSubidFile(fnamePath, "dockremap")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(ranges) != 1 {
|
||||
t.Fatalf("wanted 1 element in ranges, got %d instead", len(ranges))
|
||||
}
|
||||
if ranges[0].Start != 231072 {
|
||||
t.Fatalf("wanted 231072, got %d instead", ranges[0].Start)
|
||||
}
|
||||
if ranges[0].Length != 65536 {
|
||||
t.Fatalf("wanted 65536, got %d instead", ranges[0].Length)
|
||||
}
|
||||
}
|
25
vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
25
vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// +build windows
|
||||
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// Platforms such as Windows do not support the UID/GID concept. So make this
|
||||
// just a wrapper around system.MkdirAll.
|
||||
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
||||
if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||
// if that uid, gid pair has access (execute bit) to the directory
|
||||
// Windows does not require/support this function, so always return true
|
||||
func CanAccess(path string, uid, gid int) bool {
|
||||
return true
|
||||
}
|
164
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
164
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
|
||||
// Linux distribution commands:
|
||||
// adduser --system --shell /bin/false --disabled-login --disabled-password --no-create-home --group <username>
|
||||
// useradd -r -s /bin/false <username>
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
userCommand string
|
||||
|
||||
cmdTemplates = map[string]string{
|
||||
"adduser": "--system --shell /bin/false --no-create-home --disabled-login --disabled-password --group %s",
|
||||
"useradd": "-r -s /bin/false %s",
|
||||
"usermod": "-%s %d-%d %s",
|
||||
}
|
||||
|
||||
idOutRegexp = regexp.MustCompile(`uid=([0-9]+).*gid=([0-9]+)`)
|
||||
// default length for a UID/GID subordinate range
|
||||
defaultRangeLen = 65536
|
||||
defaultRangeStart = 100000
|
||||
userMod = "usermod"
|
||||
)
|
||||
|
||||
// AddNamespaceRangesUser takes a username and uses the standard system
|
||||
// utility to create a system user/group pair used to hold the
|
||||
// /etc/sub{uid,gid} ranges which will be used for user namespace
|
||||
// mapping ranges in containers.
|
||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||
if err := addUser(name); err != nil {
|
||||
return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err)
|
||||
}
|
||||
|
||||
// Query the system for the created uid and gid pair
|
||||
out, err := execCmd("id", name)
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("Error trying to find uid/gid for new user %q: %v", name, err)
|
||||
}
|
||||
matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out)))
|
||||
if len(matches) != 3 {
|
||||
return -1, -1, fmt.Errorf("Can't find uid, gid from `id` output: %q", string(out))
|
||||
}
|
||||
uid, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("Can't convert found uid (%s) to int: %v", matches[1], err)
|
||||
}
|
||||
gid, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("Can't convert found gid (%s) to int: %v", matches[2], err)
|
||||
}
|
||||
|
||||
// Now we need to create the subuid/subgid ranges for our new user/group (system users
|
||||
// do not get auto-created ranges in subuid/subgid)
|
||||
|
||||
if err := createSubordinateRanges(name); err != nil {
|
||||
return -1, -1, fmt.Errorf("Couldn't create subordinate ID ranges: %v", err)
|
||||
}
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
func addUser(userName string) error {
|
||||
once.Do(func() {
|
||||
// set up which commands are used for adding users/groups dependent on distro
|
||||
if _, err := resolveBinary("adduser"); err == nil {
|
||||
userCommand = "adduser"
|
||||
} else if _, err := resolveBinary("useradd"); err == nil {
|
||||
userCommand = "useradd"
|
||||
}
|
||||
})
|
||||
if userCommand == "" {
|
||||
return fmt.Errorf("Cannot add user; no useradd/adduser binary found")
|
||||
}
|
||||
args := fmt.Sprintf(cmdTemplates[userCommand], userName)
|
||||
out, err := execCmd(userCommand, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to add user with error: %v; output: %q", err, string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSubordinateRanges(name string) error {
|
||||
|
||||
// first, we should verify that ranges weren't automatically created
|
||||
// by the distro tooling
|
||||
ranges, err := parseSubuid(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while looking for subuid ranges for user %q: %v", name, err)
|
||||
}
|
||||
if len(ranges) == 0 {
|
||||
// no UID ranges; let's create one
|
||||
startID, err := findNextUIDRange()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't find available subuid range: %v", err)
|
||||
}
|
||||
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "v", startID, startID+defaultRangeLen-1, name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to add subuid range to user: %q; output: %s, err: %v", name, out, err)
|
||||
}
|
||||
}
|
||||
|
||||
ranges, err = parseSubgid(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while looking for subgid ranges for user %q: %v", name, err)
|
||||
}
|
||||
if len(ranges) == 0 {
|
||||
// no GID ranges; let's create one
|
||||
startID, err := findNextGIDRange()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't find available subgid range: %v", err)
|
||||
}
|
||||
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "w", startID, startID+defaultRangeLen-1, name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to add subgid range to user: %q; output: %s, err: %v", name, out, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findNextUIDRange() (int, error) {
|
||||
ranges, err := parseSubuid("ALL")
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subuid file: %v", err)
|
||||
}
|
||||
sort.Sort(ranges)
|
||||
return findNextRangeStart(ranges)
|
||||
}
|
||||
|
||||
func findNextGIDRange() (int, error) {
|
||||
ranges, err := parseSubgid("ALL")
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subgid file: %v", err)
|
||||
}
|
||||
sort.Sort(ranges)
|
||||
return findNextRangeStart(ranges)
|
||||
}
|
||||
|
||||
func findNextRangeStart(rangeList ranges) (int, error) {
|
||||
startID := defaultRangeStart
|
||||
for _, arange := range rangeList {
|
||||
if wouldOverlap(arange, startID) {
|
||||
startID = arange.Start + arange.Length
|
||||
}
|
||||
}
|
||||
return startID, nil
|
||||
}
|
||||
|
||||
func wouldOverlap(arange subIDRange, ID int) bool {
|
||||
low := ID
|
||||
high := ID + defaultRangeLen
|
||||
if (low >= arange.Start && low <= arange.Start+arange.Length) ||
|
||||
(high <= arange.Start+arange.Length && high >= arange.Start) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
12
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build !linux
|
||||
|
||||
package idtools
|
||||
|
||||
import "fmt"
|
||||
|
||||
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
|
||||
// and calls the appropriate helper function to add the group and then
|
||||
// the user to the group in /etc/group and /etc/passwd respectively.
|
||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||
return -1, -1, fmt.Errorf("No support for adding users or groups on this OS")
|
||||
}
|
32
vendor/github.com/docker/docker/pkg/idtools/utils_unix.go
generated
vendored
Normal file
32
vendor/github.com/docker/docker/pkg/idtools/utils_unix.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// +build !windows
|
||||
|
||||
package idtools
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func resolveBinary(binname string) (string, error) {
|
||||
binaryPath, err := exec.LookPath(binname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolvedPath, err := filepath.EvalSymlinks(binaryPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//only return no error if the final resolved binary basename
|
||||
//matches what was searched for
|
||||
if filepath.Base(resolvedPath) == binname {
|
||||
return resolvedPath, nil
|
||||
}
|
||||
return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
||||
}
|
||||
|
||||
func execCmd(cmd, args string) ([]byte, error) {
|
||||
execCmd := exec.Command(cmd, strings.Split(args, " ")...)
|
||||
return execCmd.CombinedOutput()
|
||||
}
|
51
vendor/github.com/docker/docker/pkg/ioutils/buffer.go
generated
vendored
Normal file
51
vendor/github.com/docker/docker/pkg/ioutils/buffer.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var errBufferFull = errors.New("buffer is full")
|
||||
|
||||
type fixedBuffer struct {
|
||||
buf []byte
|
||||
pos int
|
||||
lastRead int
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Write(p []byte) (int, error) {
|
||||
n := copy(b.buf[b.pos:cap(b.buf)], p)
|
||||
b.pos += n
|
||||
|
||||
if n < len(p) {
|
||||
if b.pos == cap(b.buf) {
|
||||
return n, errBufferFull
|
||||
}
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Read(p []byte) (int, error) {
|
||||
n := copy(p, b.buf[b.lastRead:b.pos])
|
||||
b.lastRead += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Len() int {
|
||||
return b.pos - b.lastRead
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Cap() int {
|
||||
return cap(b.buf)
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Reset() {
|
||||
b.pos = 0
|
||||
b.lastRead = 0
|
||||
b.buf = b.buf[:0]
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) String() string {
|
||||
return string(b.buf[b.lastRead:b.pos])
|
||||
}
|
153
vendor/github.com/docker/docker/pkg/ioutils/buffer_test.go
generated
vendored
Normal file
153
vendor/github.com/docker/docker/pkg/ioutils/buffer_test.go
generated
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFixedBufferCap(t *testing.T) {
|
||||
buf := &fixedBuffer{buf: make([]byte, 0, 5)}
|
||||
|
||||
n := buf.Cap()
|
||||
if n != 5 {
|
||||
t.Fatalf("expected buffer capacity to be 5 bytes, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixedBufferLen(t *testing.T) {
|
||||
buf := &fixedBuffer{buf: make([]byte, 0, 10)}
|
||||
|
||||
buf.Write([]byte("hello"))
|
||||
l := buf.Len()
|
||||
if l != 5 {
|
||||
t.Fatalf("expected buffer length to be 5 bytes, got %d", l)
|
||||
}
|
||||
|
||||
buf.Write([]byte("world"))
|
||||
l = buf.Len()
|
||||
if l != 10 {
|
||||
t.Fatalf("expected buffer length to be 10 bytes, got %d", l)
|
||||
}
|
||||
|
||||
// read 5 bytes
|
||||
b := make([]byte, 5)
|
||||
buf.Read(b)
|
||||
|
||||
l = buf.Len()
|
||||
if l != 5 {
|
||||
t.Fatalf("expected buffer length to be 5 bytes, got %d", l)
|
||||
}
|
||||
|
||||
n, err := buf.Write([]byte("i-wont-fit"))
|
||||
if n != 0 {
|
||||
t.Fatalf("expected no bytes to be written to buffer, got %d", n)
|
||||
}
|
||||
if err != errBufferFull {
|
||||
t.Fatalf("expected errBufferFull, got %v", err)
|
||||
}
|
||||
|
||||
l = buf.Len()
|
||||
if l != 5 {
|
||||
t.Fatalf("expected buffer length to still be 5 bytes, got %d", l)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
l = buf.Len()
|
||||
if l != 0 {
|
||||
t.Fatalf("expected buffer length to still be 0 bytes, got %d", l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixedBufferString(t *testing.T) {
|
||||
buf := &fixedBuffer{buf: make([]byte, 0, 10)}
|
||||
|
||||
buf.Write([]byte("hello"))
|
||||
buf.Write([]byte("world"))
|
||||
|
||||
out := buf.String()
|
||||
if out != "helloworld" {
|
||||
t.Fatalf("expected output to be \"helloworld\", got %q", out)
|
||||
}
|
||||
|
||||
// read 5 bytes
|
||||
b := make([]byte, 5)
|
||||
buf.Read(b)
|
||||
|
||||
// test that fixedBuffer.String() only returns the part that hasn't been read
|
||||
out = buf.String()
|
||||
if out != "world" {
|
||||
t.Fatalf("expected output to be \"world\", got %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixedBufferWrite(t *testing.T) {
|
||||
buf := &fixedBuffer{buf: make([]byte, 0, 64)}
|
||||
n, err := buf.Write([]byte("hello"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n != 5 {
|
||||
t.Fatalf("expected 5 bytes written, got %d", n)
|
||||
}
|
||||
|
||||
if string(buf.buf[:5]) != "hello" {
|
||||
t.Fatalf("expected \"hello\", got %q", string(buf.buf[:5]))
|
||||
}
|
||||
|
||||
n, err = buf.Write(bytes.Repeat([]byte{1}, 64))
|
||||
if n != 59 {
|
||||
t.Fatalf("expected 59 bytes written before buffer is full, got %d", n)
|
||||
}
|
||||
if err != errBufferFull {
|
||||
t.Fatalf("expected errBufferFull, got %v - %v", err, buf.buf[:64])
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixedBufferRead(t *testing.T) {
|
||||
buf := &fixedBuffer{buf: make([]byte, 0, 64)}
|
||||
if _, err := buf.Write([]byte("hello world")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b := make([]byte, 5)
|
||||
n, err := buf.Read(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n != 5 {
|
||||
t.Fatalf("expected 5 bytes read, got %d - %s", n, buf.String())
|
||||
}
|
||||
|
||||
if string(b) != "hello" {
|
||||
t.Fatalf("expected \"hello\", got %q", string(b))
|
||||
}
|
||||
|
||||
n, err = buf.Read(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n != 5 {
|
||||
t.Fatalf("expected 5 bytes read, got %d", n)
|
||||
}
|
||||
|
||||
if string(b) != " worl" {
|
||||
t.Fatalf("expected \" worl\", got %s", string(b))
|
||||
}
|
||||
|
||||
b = b[:1]
|
||||
n, err = buf.Read(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n != 1 {
|
||||
t.Fatalf("expected 1 byte read, got %d - %s", n, buf.String())
|
||||
}
|
||||
|
||||
if string(b) != "d" {
|
||||
t.Fatalf("expected \"d\", got %s", string(b))
|
||||
}
|
||||
}
|
186
vendor/github.com/docker/docker/pkg/ioutils/bytespipe.go
generated
vendored
Normal file
186
vendor/github.com/docker/docker/pkg/ioutils/bytespipe.go
generated
vendored
Normal file
@ -0,0 +1,186 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// maxCap is the highest capacity to use in byte slices that buffer data.
|
||||
const maxCap = 1e6
|
||||
|
||||
// minCap is the lowest capacity to use in byte slices that buffer data
|
||||
const minCap = 64
|
||||
|
||||
// blockThreshold is the minimum number of bytes in the buffer which will cause
|
||||
// a write to BytesPipe to block when allocating a new slice.
|
||||
const blockThreshold = 1e6
|
||||
|
||||
var (
|
||||
// ErrClosed is returned when Write is called on a closed BytesPipe.
|
||||
ErrClosed = errors.New("write to closed BytesPipe")
|
||||
|
||||
bufPools = make(map[int]*sync.Pool)
|
||||
bufPoolsLock sync.Mutex
|
||||
)
|
||||
|
||||
// BytesPipe is io.ReadWriteCloser which works similarly to pipe(queue).
|
||||
// All written data may be read at most once. Also, BytesPipe allocates
|
||||
// and releases new byte slices to adjust to current needs, so the buffer
|
||||
// won't be overgrown after peak loads.
|
||||
type BytesPipe struct {
|
||||
mu sync.Mutex
|
||||
wait *sync.Cond
|
||||
buf []*fixedBuffer
|
||||
bufLen int
|
||||
closeErr error // error to return from next Read. set to nil if not closed.
|
||||
}
|
||||
|
||||
// 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() *BytesPipe {
|
||||
bp := &BytesPipe{}
|
||||
bp.buf = append(bp.buf, getBuffer(minCap))
|
||||
bp.wait = sync.NewCond(&bp.mu)
|
||||
return bp
|
||||
}
|
||||
|
||||
// Write writes p to BytesPipe.
|
||||
// It can allocate new []byte slices in a process of writing.
|
||||
func (bp *BytesPipe) Write(p []byte) (int, error) {
|
||||
bp.mu.Lock()
|
||||
|
||||
written := 0
|
||||
loop0:
|
||||
for {
|
||||
if bp.closeErr != nil {
|
||||
bp.mu.Unlock()
|
||||
return written, ErrClosed
|
||||
}
|
||||
|
||||
if len(bp.buf) == 0 {
|
||||
bp.buf = append(bp.buf, getBuffer(64))
|
||||
}
|
||||
// get the last buffer
|
||||
b := bp.buf[len(bp.buf)-1]
|
||||
|
||||
n, err := b.Write(p)
|
||||
written += n
|
||||
bp.bufLen += n
|
||||
|
||||
// errBufferFull is an error we expect to get if the buffer is full
|
||||
if err != nil && err != errBufferFull {
|
||||
bp.wait.Broadcast()
|
||||
bp.mu.Unlock()
|
||||
return written, err
|
||||
}
|
||||
|
||||
// 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:]
|
||||
|
||||
// make sure the buffer doesn't grow too big from this write
|
||||
for bp.bufLen >= blockThreshold {
|
||||
bp.wait.Wait()
|
||||
if bp.closeErr != nil {
|
||||
continue loop0
|
||||
}
|
||||
}
|
||||
|
||||
// add new byte slice to the buffers slice and continue writing
|
||||
nextCap := b.Cap() * 2
|
||||
if nextCap > maxCap {
|
||||
nextCap = maxCap
|
||||
}
|
||||
bp.buf = append(bp.buf, getBuffer(nextCap))
|
||||
}
|
||||
bp.wait.Broadcast()
|
||||
bp.mu.Unlock()
|
||||
return written, nil
|
||||
}
|
||||
|
||||
// CloseWithError causes further reads from a BytesPipe to return immediately.
|
||||
func (bp *BytesPipe) CloseWithError(err error) error {
|
||||
bp.mu.Lock()
|
||||
if err != nil {
|
||||
bp.closeErr = err
|
||||
} else {
|
||||
bp.closeErr = io.EOF
|
||||
}
|
||||
bp.wait.Broadcast()
|
||||
bp.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close causes further reads from a BytesPipe to return immediately.
|
||||
func (bp *BytesPipe) Close() error {
|
||||
return bp.CloseWithError(nil)
|
||||
}
|
||||
|
||||
// Read reads bytes from BytesPipe.
|
||||
// Data could be read only once.
|
||||
func (bp *BytesPipe) Read(p []byte) (n int, err error) {
|
||||
bp.mu.Lock()
|
||||
if bp.bufLen == 0 {
|
||||
if bp.closeErr != nil {
|
||||
bp.mu.Unlock()
|
||||
return 0, bp.closeErr
|
||||
}
|
||||
bp.wait.Wait()
|
||||
if bp.bufLen == 0 && bp.closeErr != nil {
|
||||
err := bp.closeErr
|
||||
bp.mu.Unlock()
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
for bp.bufLen > 0 {
|
||||
b := bp.buf[0]
|
||||
read, _ := b.Read(p) // ignore error since fixedBuffer doesn't really return an error
|
||||
n += read
|
||||
bp.bufLen -= read
|
||||
|
||||
if b.Len() == 0 {
|
||||
// it's empty so return it to the pool and move to the next one
|
||||
returnBuffer(b)
|
||||
bp.buf[0] = nil
|
||||
bp.buf = bp.buf[1:]
|
||||
}
|
||||
|
||||
if len(p) == read {
|
||||
break
|
||||
}
|
||||
|
||||
p = p[read:]
|
||||
}
|
||||
|
||||
bp.wait.Broadcast()
|
||||
bp.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func returnBuffer(b *fixedBuffer) {
|
||||
b.Reset()
|
||||
bufPoolsLock.Lock()
|
||||
pool := bufPools[b.Cap()]
|
||||
bufPoolsLock.Unlock()
|
||||
if pool != nil {
|
||||
pool.Put(b)
|
||||
}
|
||||
}
|
||||
|
||||
func getBuffer(size int) *fixedBuffer {
|
||||
bufPoolsLock.Lock()
|
||||
pool, ok := bufPools[size]
|
||||
if !ok {
|
||||
pool = &sync.Pool{New: func() interface{} { return &fixedBuffer{buf: make([]byte, 0, size)} }}
|
||||
bufPools[size] = pool
|
||||
}
|
||||
bufPoolsLock.Unlock()
|
||||
return pool.Get().(*fixedBuffer)
|
||||
}
|
159
vendor/github.com/docker/docker/pkg/ioutils/bytespipe_test.go
generated
vendored
Normal file
159
vendor/github.com/docker/docker/pkg/ioutils/bytespipe_test.go
generated
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBytesPipeRead(t *testing.T) {
|
||||
buf := NewBytesPipe()
|
||||
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()
|
||||
buf.Write([]byte("12"))
|
||||
buf.Write([]byte("34"))
|
||||
buf.Write([]byte("56"))
|
||||
buf.Write([]byte("78"))
|
||||
buf.Write([]byte("90"))
|
||||
if buf.buf[0].String() != "1234567890" {
|
||||
t.Fatalf("Buffer %q, must be %q", buf.buf[0].String(), "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()
|
||||
hash.Reset()
|
||||
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
// random delay before read starts
|
||||
<-time.After(time.Duration(rand.Intn(10)) * time.Millisecond)
|
||||
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])
|
||||
}
|
||||
|
||||
close(done)
|
||||
}()
|
||||
|
||||
for i := 0; i < c.iterations; i++ {
|
||||
for w := 0; w < c.writesPerLoop; w++ {
|
||||
buf.Write(testMessage[:writeChunks[(i*c.writesPerLoop+w)%len(writeChunks)]])
|
||||
}
|
||||
}
|
||||
buf.Close()
|
||||
<-done
|
||||
|
||||
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) {
|
||||
testData := []byte("pretty short line, because why not?")
|
||||
for i := 0; i < b.N; i++ {
|
||||
readBuf := make([]byte, 1024)
|
||||
buf := NewBytesPipe()
|
||||
go func() {
|
||||
var err error
|
||||
for err == nil {
|
||||
_, err = buf.Read(readBuf)
|
||||
}
|
||||
}()
|
||||
for j := 0; j < 1000; j++ {
|
||||
buf.Write(testData)
|
||||
}
|
||||
buf.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBytesPipeRead(b *testing.B) {
|
||||
rd := make([]byte, 512)
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
buf := NewBytesPipe()
|
||||
for j := 0; j < 500; j++ {
|
||||
buf.Write(make([]byte, 1024))
|
||||
}
|
||||
b.StartTimer()
|
||||
for j := 0; j < 1000; j++ {
|
||||
if n, _ := buf.Read(rd); n != 512 {
|
||||
b.Fatalf("Wrong number of bytes: %d", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
vendor/github.com/docker/docker/pkg/ioutils/fmt.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/ioutils/fmt.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// FprintfIfNotEmpty prints the string value if it's not empty
|
||||
func FprintfIfNotEmpty(w io.Writer, format, value string) (int, error) {
|
||||
if value != "" {
|
||||
return fmt.Fprintf(w, format, value)
|
||||
}
|
||||
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
|
||||
}
|
17
vendor/github.com/docker/docker/pkg/ioutils/fmt_test.go
generated
vendored
Normal file
17
vendor/github.com/docker/docker/pkg/ioutils/fmt_test.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
package ioutils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFprintfIfNotEmpty(t *testing.T) {
|
||||
wc := NewWriteCounter(&NopWriter{})
|
||||
n, _ := FprintfIfNotEmpty(wc, "foo%s", "")
|
||||
|
||||
if wc.Count != 0 || n != 0 {
|
||||
t.Errorf("Wrong count: %v vs. %v vs. 0", wc.Count, n)
|
||||
}
|
||||
|
||||
n, _ = FprintfIfNotEmpty(wc, "foo%s", "bar")
|
||||
if wc.Count != 6 || n != 6 {
|
||||
t.Errorf("Wrong count: %v vs. %v vs. 6", wc.Count, n)
|
||||
}
|
||||
}
|
162
vendor/github.com/docker/docker/pkg/ioutils/fswriters.go
generated
vendored
Normal file
162
vendor/github.com/docker/docker/pkg/ioutils/fswriters.go
generated
vendored
Normal file
@ -0,0 +1,162 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// NewAtomicFileWriter returns WriteCloser so that writing to it writes to a
|
||||
// temporary file and closing it atomically changes the temporary file to
|
||||
// destination path. Writing and closing concurrently is not allowed.
|
||||
func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) {
|
||||
f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
abspath, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &atomicFileWriter{
|
||||
f: f,
|
||||
fn: abspath,
|
||||
perm: perm,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AtomicWriteFile atomically writes data to a file named by filename.
|
||||
func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
f, err := NewAtomicFileWriter(filename, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := f.Write(data)
|
||||
if err == nil && n < len(data) {
|
||||
err = io.ErrShortWrite
|
||||
f.(*atomicFileWriter).writeErr = err
|
||||
}
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type atomicFileWriter struct {
|
||||
f *os.File
|
||||
fn string
|
||||
writeErr error
|
||||
perm os.FileMode
|
||||
}
|
||||
|
||||
func (w *atomicFileWriter) Write(dt []byte) (int, error) {
|
||||
n, err := w.f.Write(dt)
|
||||
if err != nil {
|
||||
w.writeErr = err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (w *atomicFileWriter) Close() (retErr error) {
|
||||
defer func() {
|
||||
if retErr != nil || w.writeErr != nil {
|
||||
os.Remove(w.f.Name())
|
||||
}
|
||||
}()
|
||||
if err := w.f.Sync(); err != nil {
|
||||
w.f.Close()
|
||||
return err
|
||||
}
|
||||
if err := w.f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(w.f.Name(), w.perm); err != nil {
|
||||
return err
|
||||
}
|
||||
if w.writeErr == nil {
|
||||
return os.Rename(w.f.Name(), w.fn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AtomicWriteSet is used to atomically write a set
|
||||
// of files and ensure they are visible at the same time.
|
||||
// Must be committed to a new directory.
|
||||
type AtomicWriteSet struct {
|
||||
root string
|
||||
}
|
||||
|
||||
// NewAtomicWriteSet creates a new atomic write set to
|
||||
// atomically create a set of files. The given directory
|
||||
// is used as the base directory for storing files before
|
||||
// commit. If no temporary directory is given the system
|
||||
// default is used.
|
||||
func NewAtomicWriteSet(tmpDir string) (*AtomicWriteSet, error) {
|
||||
td, err := ioutil.TempDir(tmpDir, "write-set-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AtomicWriteSet{
|
||||
root: td,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WriteFile writes a file to the set, guaranteeing the file
|
||||
// has been synced.
|
||||
func (ws *AtomicWriteSet) WriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
f, err := ws.FileWriter(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := f.Write(data)
|
||||
if err == nil && n < len(data) {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type syncFileCloser struct {
|
||||
*os.File
|
||||
}
|
||||
|
||||
func (w syncFileCloser) Close() error {
|
||||
err := w.File.Sync()
|
||||
if err1 := w.File.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// FileWriter opens a file writer inside the set. The file
|
||||
// should be synced and closed before calling commit.
|
||||
func (ws *AtomicWriteSet) FileWriter(name string, flag int, perm os.FileMode) (io.WriteCloser, error) {
|
||||
f, err := os.OpenFile(filepath.Join(ws.root, name), flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return syncFileCloser{f}, nil
|
||||
}
|
||||
|
||||
// Cancel cancels the set and removes all temporary data
|
||||
// created in the set.
|
||||
func (ws *AtomicWriteSet) Cancel() error {
|
||||
return os.RemoveAll(ws.root)
|
||||
}
|
||||
|
||||
// Commit moves all created files to the target directory. The
|
||||
// target directory must not exist and the parent of the target
|
||||
// directory must exist.
|
||||
func (ws *AtomicWriteSet) Commit(target string) error {
|
||||
return os.Rename(ws.root, target)
|
||||
}
|
||||
|
||||
// String returns the location the set is writing to.
|
||||
func (ws *AtomicWriteSet) String() string {
|
||||
return ws.root
|
||||
}
|
132
vendor/github.com/docker/docker/pkg/ioutils/fswriters_test.go
generated
vendored
Normal file
132
vendor/github.com/docker/docker/pkg/ioutils/fswriters_test.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
testMode os.FileMode = 0640
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Windows does not support full Linux file mode
|
||||
if runtime.GOOS == "windows" {
|
||||
testMode = 0666
|
||||
}
|
||||
}
|
||||
|
||||
func TestAtomicWriteToFile(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "atomic-writers-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Error when creating temporary directory: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
expected := []byte("barbaz")
|
||||
if err := AtomicWriteFile(filepath.Join(tmpDir, "foo"), expected, testMode); err != nil {
|
||||
t.Fatalf("Error writing to file: %v", err)
|
||||
}
|
||||
|
||||
actual, err := ioutil.ReadFile(filepath.Join(tmpDir, "foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading from file: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(actual, expected) {
|
||||
t.Fatalf("Data mismatch, expected %q, got %q", expected, actual)
|
||||
}
|
||||
|
||||
st, err := os.Stat(filepath.Join(tmpDir, "foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error statting file: %v", err)
|
||||
}
|
||||
if expected := os.FileMode(testMode); st.Mode() != expected {
|
||||
t.Fatalf("Mode mismatched, expected %o, got %o", expected, st.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAtomicWriteSetCommit(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "atomic-writerset-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Error when creating temporary directory: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if err := os.Mkdir(filepath.Join(tmpDir, "tmp"), 0700); err != nil {
|
||||
t.Fatalf("Error creating tmp directory: %s", err)
|
||||
}
|
||||
|
||||
targetDir := filepath.Join(tmpDir, "target")
|
||||
ws, err := NewAtomicWriteSet(filepath.Join(tmpDir, "tmp"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating atomic write set: %s", err)
|
||||
}
|
||||
|
||||
expected := []byte("barbaz")
|
||||
if err := ws.WriteFile("foo", expected, testMode); err != nil {
|
||||
t.Fatalf("Error writing to file: %v", err)
|
||||
}
|
||||
|
||||
if _, err := ioutil.ReadFile(filepath.Join(targetDir, "foo")); err == nil {
|
||||
t.Fatalf("Expected error reading file where should not exist")
|
||||
}
|
||||
|
||||
if err := ws.Commit(targetDir); err != nil {
|
||||
t.Fatalf("Error committing file: %s", err)
|
||||
}
|
||||
|
||||
actual, err := ioutil.ReadFile(filepath.Join(targetDir, "foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error reading from file: %v", err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(actual, expected) {
|
||||
t.Fatalf("Data mismatch, expected %q, got %q", expected, actual)
|
||||
}
|
||||
|
||||
st, err := os.Stat(filepath.Join(targetDir, "foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error statting file: %v", err)
|
||||
}
|
||||
if expected := os.FileMode(testMode); st.Mode() != expected {
|
||||
t.Fatalf("Mode mismatched, expected %o, got %o", expected, st.Mode())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAtomicWriteSetCancel(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "atomic-writerset-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Error when creating temporary directory: %s", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
if err := os.Mkdir(filepath.Join(tmpDir, "tmp"), 0700); err != nil {
|
||||
t.Fatalf("Error creating tmp directory: %s", err)
|
||||
}
|
||||
|
||||
ws, err := NewAtomicWriteSet(filepath.Join(tmpDir, "tmp"))
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating atomic write set: %s", err)
|
||||
}
|
||||
|
||||
expected := []byte("barbaz")
|
||||
if err := ws.WriteFile("foo", expected, testMode); err != nil {
|
||||
t.Fatalf("Error writing to file: %v", err)
|
||||
}
|
||||
|
||||
if err := ws.Cancel(); err != nil {
|
||||
t.Fatalf("Error committing file: %s", err)
|
||||
}
|
||||
|
||||
if _, err := ioutil.ReadFile(filepath.Join(tmpDir, "target", "foo")); err == nil {
|
||||
t.Fatalf("Expected error reading file where should not exist")
|
||||
} else if !os.IsNotExist(err) {
|
||||
t.Fatalf("Unexpected error reading file: %s", err)
|
||||
}
|
||||
}
|
224
vendor/github.com/docker/docker/pkg/ioutils/multireader.go
generated
vendored
Normal file
224
vendor/github.com/docker/docker/pkg/ioutils/multireader.go
generated
vendored
Normal file
@ -0,0 +1,224 @@
|
||||
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 offsetTo int64
|
||||
|
||||
for _, rdr := range r.readers {
|
||||
size, err := getReadSeekerSize(rdr)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if offsetTo+size > offset {
|
||||
return rdr, offset - offsetTo, nil
|
||||
}
|
||||
if rdr == r.readers[len(r.readers)-1] {
|
||||
return rdr, offsetTo + offset, nil
|
||||
}
|
||||
offsetTo += size
|
||||
}
|
||||
|
||||
return nil, 0, 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 {
|
||||
// make sure all readers are at 0
|
||||
r.Seek(0, os.SEEK_SET)
|
||||
}
|
||||
|
||||
bLen := int64(len(b))
|
||||
buf := bytes.NewBuffer(nil)
|
||||
var rdr io.ReadSeeker
|
||||
|
||||
for _, rdr = range r.readers[r.pos.idx:] {
|
||||
readBytes, err := io.CopyN(buf, rdr, bLen)
|
||||
if err != nil && err != io.EOF {
|
||||
return -1, err
|
||||
}
|
||||
bLen -= readBytes
|
||||
|
||||
if bLen == 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,
|
||||
}
|
||||
}
|
225
vendor/github.com/docker/docker/pkg/ioutils/multireader_test.go
generated
vendored
Normal file
225
vendor/github.com/docker/docker/pkg/ioutils/multireader_test.go
generated
vendored
Normal file
@ -0,0 +1,225 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"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)
|
||||
}
|
||||
|
||||
// The positions of some readers are not 0
|
||||
s1.Seek(0, os.SEEK_SET)
|
||||
s2.Seek(0, os.SEEK_END)
|
||||
s3.Seek(0, os.SEEK_SET)
|
||||
mr = MultiReadSeeker(s1, s2, s3)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiReadSeekerCurAfterSet(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)
|
||||
|
||||
mid := int64(s1.Len() + s2.Len()/2)
|
||||
|
||||
size, err := mr.Seek(mid, os.SEEK_SET)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if size != mid {
|
||||
t.Fatalf("reader size does not match, got %d, expected %d", size, mid)
|
||||
}
|
||||
|
||||
size, err = mr.Seek(3, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if size != mid+3 {
|
||||
t.Fatalf("reader size does not match, got %d, expected %d", size, mid+3)
|
||||
}
|
||||
size, err = mr.Seek(5, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if size != mid+8 {
|
||||
t.Fatalf("reader size does not match, got %d, expected %d", size, mid+8)
|
||||
}
|
||||
|
||||
size, err = mr.Seek(10, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if size != mid+18 {
|
||||
t.Fatalf("reader size does not match, got %d, expected %d", size, mid+18)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiReadSeekerSmallReads(t *testing.T) {
|
||||
readers := []io.ReadSeeker{}
|
||||
for i := 0; i < 10; i++ {
|
||||
integer := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(integer, uint32(i))
|
||||
readers = append(readers, bytes.NewReader(integer))
|
||||
}
|
||||
|
||||
reader := MultiReadSeeker(readers...)
|
||||
for i := 0; i < 10; i++ {
|
||||
var integer uint32
|
||||
if err := binary.Read(reader, binary.BigEndian, &integer); err != nil {
|
||||
t.Fatalf("Read from NewMultiReadSeeker failed: %v", err)
|
||||
}
|
||||
if uint32(i) != integer {
|
||||
t.Fatalf("Read wrong value from NewMultiReadSeeker: %d != %d", i, integer)
|
||||
}
|
||||
}
|
||||
}
|
154
vendor/github.com/docker/docker/pkg/ioutils/readers.go
generated
vendored
Normal file
154
vendor/github.com/docker/docker/pkg/ioutils/readers.go
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type readCloserWrapper struct {
|
||||
io.Reader
|
||||
closer func() error
|
||||
}
|
||||
|
||||
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,
|
||||
closer: closer,
|
||||
}
|
||||
}
|
||||
|
||||
type readerErrWrapper struct {
|
||||
reader io.Reader
|
||||
closer func()
|
||||
}
|
||||
|
||||
func (r *readerErrWrapper) Read(p []byte) (int, error) {
|
||||
n, err := r.reader.Read(p)
|
||||
if err != nil {
|
||||
r.closer()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReaderErrWrapper returns a new io.Reader.
|
||||
func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
|
||||
return &readerErrWrapper{
|
||||
reader: r,
|
||||
closer: closer,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return "", err
|
||||
}
|
||||
return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// OnEOFReader wraps an 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
|
||||
}
|
||||
}
|
||||
|
||||
// cancelReadCloser wraps an io.ReadCloser with a context for cancelling read
|
||||
// operations.
|
||||
type cancelReadCloser struct {
|
||||
cancel func()
|
||||
pR *io.PipeReader // Stream to read from
|
||||
pW *io.PipeWriter
|
||||
}
|
||||
|
||||
// NewCancelReadCloser creates a wrapper that closes the ReadCloser when the
|
||||
// context is cancelled. The returned io.ReadCloser must be closed when it is
|
||||
// no longer needed.
|
||||
func NewCancelReadCloser(ctx context.Context, in io.ReadCloser) io.ReadCloser {
|
||||
pR, pW := io.Pipe()
|
||||
|
||||
// Create a context used to signal when the pipe is closed
|
||||
doneCtx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
p := &cancelReadCloser{
|
||||
cancel: cancel,
|
||||
pR: pR,
|
||||
pW: pW,
|
||||
}
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(pW, in)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// If the context was closed, p.closeWithError
|
||||
// was already called. Calling it again would
|
||||
// change the error that Read returns.
|
||||
default:
|
||||
p.closeWithError(err)
|
||||
}
|
||||
in.Close()
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
p.closeWithError(ctx.Err())
|
||||
case <-doneCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Read wraps the Read method of the pipe that provides data from the wrapped
|
||||
// ReadCloser.
|
||||
func (p *cancelReadCloser) Read(buf []byte) (n int, err error) {
|
||||
return p.pR.Read(buf)
|
||||
}
|
||||
|
||||
// closeWithError closes the wrapper and its underlying reader. It will
|
||||
// cause future calls to Read to return err.
|
||||
func (p *cancelReadCloser) closeWithError(err error) {
|
||||
p.pW.CloseWithError(err)
|
||||
p.cancel()
|
||||
}
|
||||
|
||||
// Close closes the wrapper its underlying reader. It will cause
|
||||
// future calls to Read to return io.EOF.
|
||||
func (p *cancelReadCloser) Close() error {
|
||||
p.closeWithError(io.EOF)
|
||||
return nil
|
||||
}
|
94
vendor/github.com/docker/docker/pkg/ioutils/readers_test.go
generated
vendored
Normal file
94
vendor/github.com/docker/docker/pkg/ioutils/readers_test.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Implement io.Reader
|
||||
type errorReader struct{}
|
||||
|
||||
func (r *errorReader) Read(p []byte) (int, error) {
|
||||
return 0, fmt.Errorf("Error reader always fail.")
|
||||
}
|
||||
|
||||
func TestReadCloserWrapperClose(t *testing.T) {
|
||||
reader := strings.NewReader("A string reader")
|
||||
wrapper := NewReadCloserWrapper(reader, func() error {
|
||||
return fmt.Errorf("This will be called when closing")
|
||||
})
|
||||
err := wrapper.Close()
|
||||
if err == nil || !strings.Contains(err.Error(), "This will be called when closing") {
|
||||
t.Fatalf("readCloserWrapper should have call the anonymous func and thus, fail.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaderErrWrapperReadOnError(t *testing.T) {
|
||||
called := false
|
||||
reader := &errorReader{}
|
||||
wrapper := NewReaderErrWrapper(reader, func() {
|
||||
called = true
|
||||
})
|
||||
_, err := wrapper.Read([]byte{})
|
||||
if err == nil || !strings.Contains(err.Error(), "Error reader always fail.") {
|
||||
t.Fatalf("readErrWrapper should returned an error")
|
||||
}
|
||||
if !called {
|
||||
t.Fatalf("readErrWrapper should have call the anonymous function on failure")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaderErrWrapperRead(t *testing.T) {
|
||||
reader := strings.NewReader("a string reader.")
|
||||
wrapper := NewReaderErrWrapper(reader, func() {
|
||||
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))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if num != 16 {
|
||||
t.Fatalf("readerErrWrapper should have read 16 byte, but read %d", num)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashData(t *testing.T) {
|
||||
reader := strings.NewReader("hash-me")
|
||||
actual, err := HashData(reader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "sha256:4d11186aed035cc624d553e10db358492c84a7cd6b9670d92123c144930450aa"
|
||||
if actual != expected {
|
||||
t.Fatalf("Expecting %s, got %s", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
type perpetualReader struct{}
|
||||
|
||||
func (p *perpetualReader) Read(buf []byte) (n int, err error) {
|
||||
for i := 0; i != len(buf); i++ {
|
||||
buf[i] = 'a'
|
||||
}
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func TestCancelReadCloser(t *testing.T) {
|
||||
ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
cancelReadCloser := NewCancelReadCloser(ctx, ioutil.NopCloser(&perpetualReader{}))
|
||||
for {
|
||||
var buf [128]byte
|
||||
_, err := cancelReadCloser.Read(buf[:])
|
||||
if err == context.DeadlineExceeded {
|
||||
break
|
||||
} else if err != nil {
|
||||
t.Fatalf("got unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
10
vendor/github.com/docker/docker/pkg/ioutils/temp_unix.go
generated
vendored
Normal file
10
vendor/github.com/docker/docker/pkg/ioutils/temp_unix.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// +build !windows
|
||||
|
||||
package ioutils
|
||||
|
||||
import "io/ioutil"
|
||||
|
||||
// TempDir on Unix systems is equivalent to ioutil.TempDir.
|
||||
func TempDir(dir, prefix string) (string, error) {
|
||||
return ioutil.TempDir(dir, prefix)
|
||||
}
|
18
vendor/github.com/docker/docker/pkg/ioutils/temp_windows.go
generated
vendored
Normal file
18
vendor/github.com/docker/docker/pkg/ioutils/temp_windows.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
// +build windows
|
||||
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/docker/docker/pkg/longpath"
|
||||
)
|
||||
|
||||
// TempDir is the equivalent of ioutil.TempDir, except that the result is in Windows longpath format.
|
||||
func TempDir(dir, prefix string) (string, error) {
|
||||
tempDir, err := ioutil.TempDir(dir, prefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return longpath.AddPrefix(tempDir), nil
|
||||
}
|
92
vendor/github.com/docker/docker/pkg/ioutils/writeflusher.go
generated
vendored
Normal file
92
vendor/github.com/docker/docker/pkg/ioutils/writeflusher.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// WriteFlusher wraps the Write and Flush operation ensuring that every write
|
||||
// is a flush. In addition, the Close method can be called to intercept
|
||||
// Read/Write calls if the targets lifecycle has already ended.
|
||||
type WriteFlusher struct {
|
||||
w io.Writer
|
||||
flusher flusher
|
||||
flushed chan struct{}
|
||||
flushedOnce sync.Once
|
||||
closed chan struct{}
|
||||
closeLock sync.Mutex
|
||||
}
|
||||
|
||||
type flusher interface {
|
||||
Flush()
|
||||
}
|
||||
|
||||
var errWriteFlusherClosed = io.EOF
|
||||
|
||||
func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-wf.closed:
|
||||
return 0, errWriteFlusherClosed
|
||||
default:
|
||||
}
|
||||
|
||||
n, err = wf.w.Write(b)
|
||||
wf.Flush() // every write is a flush.
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Flush the stream immediately.
|
||||
func (wf *WriteFlusher) Flush() {
|
||||
select {
|
||||
case <-wf.closed:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
wf.flushedOnce.Do(func() {
|
||||
close(wf.flushed)
|
||||
})
|
||||
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 {
|
||||
// BUG(stevvooe): Remove this method. Its use is inherently racy. Seems to
|
||||
// be used to detect whether or a response code has been issued or not.
|
||||
// Another hook should be used instead.
|
||||
var flushed bool
|
||||
select {
|
||||
case <-wf.flushed:
|
||||
flushed = true
|
||||
default:
|
||||
}
|
||||
return flushed
|
||||
}
|
||||
|
||||
// Close closes the write flusher, disallowing any further writes to the
|
||||
// target. After the flusher is closed, all calls to write or flush will
|
||||
// result in an error.
|
||||
func (wf *WriteFlusher) Close() error {
|
||||
wf.closeLock.Lock()
|
||||
defer wf.closeLock.Unlock()
|
||||
|
||||
select {
|
||||
case <-wf.closed:
|
||||
return errWriteFlusherClosed
|
||||
default:
|
||||
close(wf.closed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewWriteFlusher returns a new WriteFlusher.
|
||||
func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
||||
var fl flusher
|
||||
if f, ok := w.(flusher); ok {
|
||||
fl = f
|
||||
} else {
|
||||
fl = &NopFlusher{}
|
||||
}
|
||||
return &WriteFlusher{w: w, flusher: fl, closed: make(chan struct{}), flushed: make(chan struct{})}
|
||||
}
|
66
vendor/github.com/docker/docker/pkg/ioutils/writers.go
generated
vendored
Normal file
66
vendor/github.com/docker/docker/pkg/ioutils/writers.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package ioutils
|
||||
|
||||
import "io"
|
||||
|
||||
// NopWriter represents a type which write operation is nop.
|
||||
type NopWriter struct{}
|
||||
|
||||
func (*NopWriter) Write(buf []byte) (int, error) {
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
type nopWriteCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (w *nopWriteCloser) Close() error { return nil }
|
||||
|
||||
// NopWriteCloser returns a nopWriteCloser.
|
||||
func NopWriteCloser(w io.Writer) io.WriteCloser {
|
||||
return &nopWriteCloser{w}
|
||||
}
|
||||
|
||||
// NopFlusher represents a type which flush operation is nop.
|
||||
type NopFlusher struct{}
|
||||
|
||||
// Flush is a nop operation.
|
||||
func (f *NopFlusher) Flush() {}
|
||||
|
||||
type writeCloserWrapper struct {
|
||||
io.Writer
|
||||
closer func() error
|
||||
}
|
||||
|
||||
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,
|
||||
closer: closer,
|
||||
}
|
||||
}
|
||||
|
||||
// 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())
|
||||
type WriteCounter struct {
|
||||
Count int64
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
// NewWriteCounter returns a new WriteCounter.
|
||||
func NewWriteCounter(w io.Writer) *WriteCounter {
|
||||
return &WriteCounter{
|
||||
Writer: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *WriteCounter) Write(p []byte) (count int, err error) {
|
||||
count, err = wc.Writer.Write(p)
|
||||
wc.Count += int64(count)
|
||||
return
|
||||
}
|
65
vendor/github.com/docker/docker/pkg/ioutils/writers_test.go
generated
vendored
Normal file
65
vendor/github.com/docker/docker/pkg/ioutils/writers_test.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
package ioutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWriteCloserWrapperClose(t *testing.T) {
|
||||
called := false
|
||||
writer := bytes.NewBuffer([]byte{})
|
||||
wrapper := NewWriteCloserWrapper(writer, func() error {
|
||||
called = true
|
||||
return nil
|
||||
})
|
||||
if err := wrapper.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !called {
|
||||
t.Fatalf("writeCloserWrapper should have call the anonymous function.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNopWriteCloser(t *testing.T) {
|
||||
writer := bytes.NewBuffer([]byte{})
|
||||
wrapper := NopWriteCloser(writer)
|
||||
if err := wrapper.Close(); err != nil {
|
||||
t.Fatal("NopWriteCloser always return nil on Close.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNopWriter(t *testing.T) {
|
||||
nw := &NopWriter{}
|
||||
l, err := nw.Write([]byte{'c'})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if l != 1 {
|
||||
t.Fatalf("Expected 1 got %d", l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteCounter(t *testing.T) {
|
||||
dummy1 := "This is a dummy string."
|
||||
dummy2 := "This is another dummy string."
|
||||
totalLength := int64(len(dummy1) + len(dummy2))
|
||||
|
||||
reader1 := strings.NewReader(dummy1)
|
||||
reader2 := strings.NewReader(dummy2)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
wc := NewWriteCounter(&buffer)
|
||||
|
||||
reader1.WriteTo(wc)
|
||||
reader2.WriteTo(wc)
|
||||
|
||||
if wc.Count != totalLength {
|
||||
t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength)
|
||||
}
|
||||
|
||||
if buffer.String() != dummy1+dummy2 {
|
||||
t.Error("Wrong message written")
|
||||
}
|
||||
}
|
297
vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go
generated
vendored
Normal file
297
vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go
generated
vendored
Normal file
@ -0,0 +1,297 @@
|
||||
package jsonmessage
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Nvveen/Gotty"
|
||||
|
||||
"github.com/docker/docker/pkg/jsonlog"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// JSONError wraps a concrete Code and Message, `Code` is
|
||||
// is an integer error code, `Message` is the error message.
|
||||
type JSONError struct {
|
||||
Code int `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (e *JSONError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// JSONProgress describes a Progress. terminalFd is the fd of the current terminal,
|
||||
// Start is the initial value for the operation. Current is the current status and
|
||||
// value of the progress made towards Total. Total is the end value describing when
|
||||
// we made 100% progress for an operation.
|
||||
type JSONProgress struct {
|
||||
terminalFd uintptr
|
||||
Current int64 `json:"current,omitempty"`
|
||||
Total int64 `json:"total,omitempty"`
|
||||
Start int64 `json:"start,omitempty"`
|
||||
// If true, don't show xB/yB
|
||||
HideCounts bool `json:"hidecounts,omitempty"`
|
||||
}
|
||||
|
||||
func (p *JSONProgress) String() string {
|
||||
var (
|
||||
width = 200
|
||||
pbBox string
|
||||
numbersBox string
|
||||
timeLeftBox string
|
||||
)
|
||||
|
||||
ws, err := term.GetWinsize(p.terminalFd)
|
||||
if err == nil {
|
||||
width = int(ws.Width)
|
||||
}
|
||||
|
||||
if p.Current <= 0 && p.Total <= 0 {
|
||||
return ""
|
||||
}
|
||||
current := units.HumanSize(float64(p.Current))
|
||||
if p.Total <= 0 {
|
||||
return fmt.Sprintf("%8v", current)
|
||||
}
|
||||
total := units.HumanSize(float64(p.Total))
|
||||
percentage := int(float64(p.Current)/float64(p.Total)*100) / 2
|
||||
if percentage > 50 {
|
||||
percentage = 50
|
||||
}
|
||||
if width > 110 {
|
||||
// this number can't be negative gh#7136
|
||||
numSpaces := 0
|
||||
if 50-percentage > 0 {
|
||||
numSpaces = 50 - percentage
|
||||
}
|
||||
pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces))
|
||||
}
|
||||
|
||||
if !p.HideCounts {
|
||||
numbersBox = fmt.Sprintf("%8v/%v", current, total)
|
||||
|
||||
if p.Current > p.Total {
|
||||
// remove total display if the reported current is wonky.
|
||||
numbersBox = fmt.Sprintf("%8v", current)
|
||||
}
|
||||
}
|
||||
|
||||
if p.Current > 0 && p.Start > 0 && percentage < 50 {
|
||||
fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0))
|
||||
perEntry := fromStart / time.Duration(p.Current)
|
||||
left := time.Duration(p.Total-p.Current) * perEntry
|
||||
left = (left / time.Second) * time.Second
|
||||
|
||||
if width > 50 {
|
||||
timeLeftBox = " " + left.String()
|
||||
}
|
||||
}
|
||||
return pbBox + numbersBox + timeLeftBox
|
||||
}
|
||||
|
||||
// JSONMessage defines a message struct. It describes
|
||||
// the created time, where it from, status, ID of the
|
||||
// message. It's used for docker events.
|
||||
type JSONMessage struct {
|
||||
Stream string `json:"stream,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Progress *JSONProgress `json:"progressDetail,omitempty"`
|
||||
ProgressMessage string `json:"progress,omitempty"` //deprecated
|
||||
ID string `json:"id,omitempty"`
|
||||
From string `json:"from,omitempty"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
TimeNano int64 `json:"timeNano,omitempty"`
|
||||
Error *JSONError `json:"errorDetail,omitempty"`
|
||||
ErrorMessage string `json:"error,omitempty"` //deprecated
|
||||
// Aux contains out-of-band data, such as digests for push signing.
|
||||
Aux *json.RawMessage `json:"aux,omitempty"`
|
||||
}
|
||||
|
||||
/* Satisfied by gotty.TermInfo as well as noTermInfo from below */
|
||||
type termInfo interface {
|
||||
Parse(attr string, params ...interface{}) (string, error)
|
||||
}
|
||||
|
||||
type noTermInfo struct{} // canary used when no terminfo.
|
||||
|
||||
func (ti *noTermInfo) Parse(attr string, params ...interface{}) (string, error) {
|
||||
return "", fmt.Errorf("noTermInfo")
|
||||
}
|
||||
|
||||
func clearLine(out io.Writer, ti termInfo) {
|
||||
// el2 (clear whole line) is not exposed by terminfo.
|
||||
|
||||
// First clear line from beginning to cursor
|
||||
if attr, err := ti.Parse("el1"); err == nil {
|
||||
fmt.Fprintf(out, "%s", attr)
|
||||
} else {
|
||||
fmt.Fprintf(out, "\x1b[1K")
|
||||
}
|
||||
// Then clear line from cursor to end
|
||||
if attr, err := ti.Parse("el"); err == nil {
|
||||
fmt.Fprintf(out, "%s", attr)
|
||||
} else {
|
||||
fmt.Fprintf(out, "\x1b[K")
|
||||
}
|
||||
}
|
||||
|
||||
func cursorUp(out io.Writer, ti termInfo, l int) {
|
||||
if l == 0 { // Should never be the case, but be tolerant
|
||||
return
|
||||
}
|
||||
if attr, err := ti.Parse("cuu", l); err == nil {
|
||||
fmt.Fprintf(out, "%s", attr)
|
||||
} else {
|
||||
fmt.Fprintf(out, "\x1b[%dA", l)
|
||||
}
|
||||
}
|
||||
|
||||
func cursorDown(out io.Writer, ti termInfo, l int) {
|
||||
if l == 0 { // Should never be the case, but be tolerant
|
||||
return
|
||||
}
|
||||
if attr, err := ti.Parse("cud", l); err == nil {
|
||||
fmt.Fprintf(out, "%s", attr)
|
||||
} else {
|
||||
fmt.Fprintf(out, "\x1b[%dB", l)
|
||||
}
|
||||
}
|
||||
|
||||
// Display displays the JSONMessage to `out`. `termInfo` is non-nil if `out`
|
||||
// is a terminal. If this is the case, it will erase the entire current line
|
||||
// when displaying the progressbar.
|
||||
func (jm *JSONMessage) Display(out io.Writer, termInfo termInfo) error {
|
||||
if jm.Error != nil {
|
||||
if jm.Error.Code == 401 {
|
||||
return fmt.Errorf("Authentication is required.")
|
||||
}
|
||||
return jm.Error
|
||||
}
|
||||
var endl string
|
||||
if termInfo != nil && jm.Stream == "" && jm.Progress != nil {
|
||||
clearLine(out, termInfo)
|
||||
endl = "\r"
|
||||
fmt.Fprintf(out, endl)
|
||||
} else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal
|
||||
return nil
|
||||
}
|
||||
if jm.TimeNano != 0 {
|
||||
fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(jsonlog.RFC3339NanoFixed))
|
||||
} else if jm.Time != 0 {
|
||||
fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(jsonlog.RFC3339NanoFixed))
|
||||
}
|
||||
if jm.ID != "" {
|
||||
fmt.Fprintf(out, "%s: ", jm.ID)
|
||||
}
|
||||
if jm.From != "" {
|
||||
fmt.Fprintf(out, "(from %s) ", jm.From)
|
||||
}
|
||||
if jm.Progress != nil && termInfo != nil {
|
||||
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl)
|
||||
} else if jm.ProgressMessage != "" { //deprecated
|
||||
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl)
|
||||
} else if jm.Stream != "" {
|
||||
fmt.Fprintf(out, "%s%s", jm.Stream, endl)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal`
|
||||
// describes if `out` is a terminal. If this is the case, it will print `\n` at the end of
|
||||
// each line and move the cursor while displaying.
|
||||
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(*json.RawMessage)) error {
|
||||
var (
|
||||
dec = json.NewDecoder(in)
|
||||
ids = make(map[string]int)
|
||||
)
|
||||
|
||||
var termInfo termInfo
|
||||
|
||||
if isTerminal {
|
||||
term := os.Getenv("TERM")
|
||||
if term == "" {
|
||||
term = "vt102"
|
||||
}
|
||||
|
||||
var err error
|
||||
if termInfo, err = gotty.OpenTermInfo(term); err != nil {
|
||||
termInfo = &noTermInfo{}
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
diff := 0
|
||||
var jm JSONMessage
|
||||
if err := dec.Decode(&jm); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if jm.Aux != nil {
|
||||
if auxCallback != nil {
|
||||
auxCallback(jm.Aux)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if jm.Progress != nil {
|
||||
jm.Progress.terminalFd = terminalFd
|
||||
}
|
||||
if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") {
|
||||
line, ok := ids[jm.ID]
|
||||
if !ok {
|
||||
// NOTE: This approach of using len(id) to
|
||||
// figure out the number of lines of history
|
||||
// only works as long as we clear the history
|
||||
// when we output something that's not
|
||||
// accounted for in the map, such as a line
|
||||
// with no ID.
|
||||
line = len(ids)
|
||||
ids[jm.ID] = line
|
||||
if termInfo != nil {
|
||||
fmt.Fprintf(out, "\n")
|
||||
}
|
||||
}
|
||||
diff = len(ids) - line
|
||||
if termInfo != nil {
|
||||
cursorUp(out, termInfo, diff)
|
||||
}
|
||||
} else {
|
||||
// When outputting something that isn't progress
|
||||
// output, clear the history of previous lines. We
|
||||
// don't want progress entries from some previous
|
||||
// operation to be updated (for example, pull -a
|
||||
// with multiple tags).
|
||||
ids = make(map[string]int)
|
||||
}
|
||||
err := jm.Display(out, termInfo)
|
||||
if jm.ID != "" && termInfo != nil {
|
||||
cursorDown(out, termInfo, diff)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type stream interface {
|
||||
io.Writer
|
||||
FD() uintptr
|
||||
IsTerminal() bool
|
||||
}
|
||||
|
||||
// DisplayJSONMessagesToStream prints json messages to the output stream
|
||||
func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error {
|
||||
return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
|
||||
}
|
253
vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage_test.go
generated
vendored
Normal file
253
vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage_test.go
generated
vendored
Normal file
@ -0,0 +1,253 @@
|
||||
package jsonmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/jsonlog"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
)
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
je := JSONError{404, "Not found"}
|
||||
if je.Error() != "Not found" {
|
||||
t.Fatalf("Expected 'Not found' got '%s'", je.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgress(t *testing.T) {
|
||||
termsz, err := term.GetWinsize(0)
|
||||
if err != nil {
|
||||
// we can safely ignore the err here
|
||||
termsz = nil
|
||||
}
|
||||
jp := JSONProgress{}
|
||||
if jp.String() != "" {
|
||||
t.Fatalf("Expected empty string, got '%s'", jp.String())
|
||||
}
|
||||
|
||||
expected := " 1B"
|
||||
jp2 := JSONProgress{Current: 1}
|
||||
if jp2.String() != expected {
|
||||
t.Fatalf("Expected %q, got %q", expected, jp2.String())
|
||||
}
|
||||
|
||||
expectedStart := "[==========> ] 20B/100B"
|
||||
if termsz != nil && termsz.Width <= 110 {
|
||||
expectedStart = " 20B/100B"
|
||||
}
|
||||
jp3 := JSONProgress{Current: 20, Total: 100, Start: time.Now().Unix()}
|
||||
// Just look at the start of the string
|
||||
// (the remaining time is really hard to test -_-)
|
||||
if jp3.String()[:len(expectedStart)] != expectedStart {
|
||||
t.Fatalf("Expected to start with %q, got %q", expectedStart, jp3.String())
|
||||
}
|
||||
|
||||
expected = "[=========================> ] 50B/100B"
|
||||
if termsz != nil && termsz.Width <= 110 {
|
||||
expected = " 50B/100B"
|
||||
}
|
||||
jp4 := JSONProgress{Current: 50, Total: 100}
|
||||
if jp4.String() != expected {
|
||||
t.Fatalf("Expected %q, got %q", expected, jp4.String())
|
||||
}
|
||||
|
||||
// this number can't be negative gh#7136
|
||||
expected = "[==================================================>] 50B"
|
||||
if termsz != nil && termsz.Width <= 110 {
|
||||
expected = " 50B"
|
||||
}
|
||||
jp5 := JSONProgress{Current: 50, Total: 40}
|
||||
if jp5.String() != expected {
|
||||
t.Fatalf("Expected %q, got %q", expected, jp5.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONMessageDisplay(t *testing.T) {
|
||||
now := time.Now()
|
||||
messages := map[JSONMessage][]string{
|
||||
// Empty
|
||||
JSONMessage{}: {"\n", "\n"},
|
||||
// Status
|
||||
JSONMessage{
|
||||
Status: "status",
|
||||
}: {
|
||||
"status\n",
|
||||
"status\n",
|
||||
},
|
||||
// General
|
||||
JSONMessage{
|
||||
Time: now.Unix(),
|
||||
ID: "ID",
|
||||
From: "From",
|
||||
Status: "status",
|
||||
}: {
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(jsonlog.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(now.Unix(), 0).Format(jsonlog.RFC3339NanoFixed)),
|
||||
},
|
||||
// General, with nano precision time
|
||||
JSONMessage{
|
||||
TimeNano: now.UnixNano(),
|
||||
ID: "ID",
|
||||
From: "From",
|
||||
Status: "status",
|
||||
}: {
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
|
||||
},
|
||||
// General, with both times Nano is preferred
|
||||
JSONMessage{
|
||||
Time: now.Unix(),
|
||||
TimeNano: now.UnixNano(),
|
||||
ID: "ID",
|
||||
From: "From",
|
||||
Status: "status",
|
||||
}: {
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
|
||||
fmt.Sprintf("%v ID: (from From) status\n", time.Unix(0, now.UnixNano()).Format(jsonlog.RFC3339NanoFixed)),
|
||||
},
|
||||
// Stream over status
|
||||
JSONMessage{
|
||||
Status: "status",
|
||||
Stream: "stream",
|
||||
}: {
|
||||
"stream",
|
||||
"stream",
|
||||
},
|
||||
// With progress message
|
||||
JSONMessage{
|
||||
Status: "status",
|
||||
ProgressMessage: "progressMessage",
|
||||
}: {
|
||||
"status progressMessage",
|
||||
"status progressMessage",
|
||||
},
|
||||
// With progress, stream empty
|
||||
JSONMessage{
|
||||
Status: "status",
|
||||
Stream: "",
|
||||
Progress: &JSONProgress{Current: 1},
|
||||
}: {
|
||||
"",
|
||||
fmt.Sprintf("%c[1K%c[K\rstatus 1B\r", 27, 27),
|
||||
},
|
||||
}
|
||||
|
||||
// The tests :)
|
||||
for jsonMessage, expectedMessages := range messages {
|
||||
// Without terminal
|
||||
data := bytes.NewBuffer([]byte{})
|
||||
if err := jsonMessage.Display(data, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if data.String() != expectedMessages[0] {
|
||||
t.Fatalf("Expected %q,got %q", expectedMessages[0], data.String())
|
||||
}
|
||||
// With terminal
|
||||
data = bytes.NewBuffer([]byte{})
|
||||
if err := jsonMessage.Display(data, &noTermInfo{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if data.String() != expectedMessages[1] {
|
||||
t.Fatalf("\nExpected %q\n got %q", expectedMessages[1], data.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test JSONMessage with an Error. It will return an error with the text as error, not the meaning of the HTTP code.
|
||||
func TestJSONMessageDisplayWithJSONError(t *testing.T) {
|
||||
data := bytes.NewBuffer([]byte{})
|
||||
jsonMessage := JSONMessage{Error: &JSONError{404, "Can't find it"}}
|
||||
|
||||
err := jsonMessage.Display(data, &noTermInfo{})
|
||||
if err == nil || err.Error() != "Can't find it" {
|
||||
t.Fatalf("Expected a JSONError 404, got %q", err)
|
||||
}
|
||||
|
||||
jsonMessage = JSONMessage{Error: &JSONError{401, "Anything"}}
|
||||
err = jsonMessage.Display(data, &noTermInfo{})
|
||||
if err == nil || err.Error() != "Authentication is required." {
|
||||
t.Fatalf("Expected an error \"Authentication is required.\", got %q", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayJSONMessagesStreamInvalidJSON(t *testing.T) {
|
||||
var (
|
||||
inFd uintptr
|
||||
)
|
||||
data := bytes.NewBuffer([]byte{})
|
||||
reader := strings.NewReader("This is not a 'valid' JSON []")
|
||||
inFd, _ = term.GetFdInfo(reader)
|
||||
|
||||
if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err == nil && err.Error()[:17] != "invalid character" {
|
||||
t.Fatalf("Should have thrown an error (invalid character in ..), got %q", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayJSONMessagesStream(t *testing.T) {
|
||||
var (
|
||||
inFd uintptr
|
||||
)
|
||||
|
||||
messages := map[string][]string{
|
||||
// empty string
|
||||
"": {
|
||||
"",
|
||||
""},
|
||||
// Without progress & ID
|
||||
"{ \"status\": \"status\" }": {
|
||||
"status\n",
|
||||
"status\n",
|
||||
},
|
||||
// Without progress, with ID
|
||||
"{ \"id\": \"ID\",\"status\": \"status\" }": {
|
||||
"ID: status\n",
|
||||
fmt.Sprintf("ID: status\n"),
|
||||
},
|
||||
// With progress
|
||||
"{ \"id\": \"ID\", \"status\": \"status\", \"progress\": \"ProgressMessage\" }": {
|
||||
"ID: status ProgressMessage",
|
||||
fmt.Sprintf("\n%c[%dAID: status ProgressMessage%c[%dB", 27, 1, 27, 1),
|
||||
},
|
||||
// With progressDetail
|
||||
"{ \"id\": \"ID\", \"status\": \"status\", \"progressDetail\": { \"Current\": 1} }": {
|
||||
"", // progressbar is disabled in non-terminal
|
||||
fmt.Sprintf("\n%c[%dA%c[1K%c[K\rID: status 1B\r%c[%dB", 27, 1, 27, 27, 27, 1),
|
||||
},
|
||||
}
|
||||
|
||||
// Use $TERM which is unlikely to exist, forcing DisplayJSONMessageStream to
|
||||
// (hopefully) use &noTermInfo.
|
||||
origTerm := os.Getenv("TERM")
|
||||
os.Setenv("TERM", "xyzzy-non-existent-terminfo")
|
||||
|
||||
for jsonMessage, expectedMessages := range messages {
|
||||
data := bytes.NewBuffer([]byte{})
|
||||
reader := strings.NewReader(jsonMessage)
|
||||
inFd, _ = term.GetFdInfo(reader)
|
||||
|
||||
// Without terminal
|
||||
if err := DisplayJSONMessagesStream(reader, data, inFd, false, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if data.String() != expectedMessages[0] {
|
||||
t.Fatalf("Expected an %q, got %q", expectedMessages[0], data.String())
|
||||
}
|
||||
|
||||
// With terminal
|
||||
data = bytes.NewBuffer([]byte{})
|
||||
reader = strings.NewReader(jsonMessage)
|
||||
if err := DisplayJSONMessagesStream(reader, data, inFd, true, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if data.String() != expectedMessages[1] {
|
||||
t.Fatalf("\nExpected %q\n got %q", expectedMessages[1], data.String())
|
||||
}
|
||||
}
|
||||
os.Setenv("TERM", origTerm)
|
||||
|
||||
}
|
116
vendor/github.com/docker/docker/pkg/pools/pools.go
generated
vendored
Normal file
116
vendor/github.com/docker/docker/pkg/pools/pools.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
// Package pools provides a collection of pools which provide various
|
||||
// data types with buffers. These can be used to lower the number of
|
||||
// memory allocations and reuse buffers.
|
||||
//
|
||||
// New pools should be added to this package to allow them to be
|
||||
// shared across packages.
|
||||
//
|
||||
// Utility functions which operate on pools should be added to this
|
||||
// package to allow them to be reused.
|
||||
package pools
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
)
|
||||
|
||||
var (
|
||||
// BufioReader32KPool is a pool which returns bufio.Reader with a 32K buffer.
|
||||
BufioReader32KPool = newBufioReaderPoolWithSize(buffer32K)
|
||||
// BufioWriter32KPool is a pool which returns bufio.Writer with a 32K buffer.
|
||||
BufioWriter32KPool = newBufioWriterPoolWithSize(buffer32K)
|
||||
)
|
||||
|
||||
const buffer32K = 32 * 1024
|
||||
|
||||
// BufioReaderPool is a bufio reader that uses sync.Pool.
|
||||
type BufioReaderPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// newBufioReaderPoolWithSize is unexported because new pools should be
|
||||
// added here to be shared where required.
|
||||
func newBufioReaderPoolWithSize(size int) *BufioReaderPool {
|
||||
return &BufioReaderPool{
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} { return bufio.NewReaderSize(nil, size) },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a bufio.Reader which reads from r. The buffer size is that of the pool.
|
||||
func (bufPool *BufioReaderPool) Get(r io.Reader) *bufio.Reader {
|
||||
buf := bufPool.pool.Get().(*bufio.Reader)
|
||||
buf.Reset(r)
|
||||
return buf
|
||||
}
|
||||
|
||||
// Put puts the bufio.Reader back into the pool.
|
||||
func (bufPool *BufioReaderPool) Put(b *bufio.Reader) {
|
||||
b.Reset(nil)
|
||||
bufPool.pool.Put(b)
|
||||
}
|
||||
|
||||
// Copy is a convenience wrapper which uses a buffer to avoid allocation in io.Copy.
|
||||
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
||||
buf := BufioReader32KPool.Get(src)
|
||||
written, err = io.Copy(dst, buf)
|
||||
BufioReader32KPool.Put(buf)
|
||||
return
|
||||
}
|
||||
|
||||
// NewReadCloserWrapper returns a wrapper which puts the bufio.Reader back
|
||||
// into the pool and closes the reader if it's an io.ReadCloser.
|
||||
func (bufPool *BufioReaderPool) NewReadCloserWrapper(buf *bufio.Reader, r io.Reader) io.ReadCloser {
|
||||
return ioutils.NewReadCloserWrapper(r, func() error {
|
||||
if readCloser, ok := r.(io.ReadCloser); ok {
|
||||
readCloser.Close()
|
||||
}
|
||||
bufPool.Put(buf)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BufioWriterPool is a bufio writer that uses sync.Pool.
|
||||
type BufioWriterPool struct {
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// newBufioWriterPoolWithSize is unexported because new pools should be
|
||||
// added here to be shared where required.
|
||||
func newBufioWriterPoolWithSize(size int) *BufioWriterPool {
|
||||
return &BufioWriterPool{
|
||||
pool: sync.Pool{
|
||||
New: func() interface{} { return bufio.NewWriterSize(nil, size) },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a bufio.Writer which writes to w. The buffer size is that of the pool.
|
||||
func (bufPool *BufioWriterPool) Get(w io.Writer) *bufio.Writer {
|
||||
buf := bufPool.pool.Get().(*bufio.Writer)
|
||||
buf.Reset(w)
|
||||
return buf
|
||||
}
|
||||
|
||||
// Put puts the bufio.Writer back into the pool.
|
||||
func (bufPool *BufioWriterPool) Put(b *bufio.Writer) {
|
||||
b.Reset(nil)
|
||||
bufPool.pool.Put(b)
|
||||
}
|
||||
|
||||
// NewWriteCloserWrapper returns a wrapper which puts the bufio.Writer back
|
||||
// into the pool and closes the writer if it's an io.Writecloser.
|
||||
func (bufPool *BufioWriterPool) NewWriteCloserWrapper(buf *bufio.Writer, w io.Writer) io.WriteCloser {
|
||||
return ioutils.NewWriteCloserWrapper(w, func() error {
|
||||
buf.Flush()
|
||||
if writeCloser, ok := w.(io.WriteCloser); ok {
|
||||
writeCloser.Close()
|
||||
}
|
||||
bufPool.Put(buf)
|
||||
return nil
|
||||
})
|
||||
}
|
161
vendor/github.com/docker/docker/pkg/pools/pools_test.go
generated
vendored
Normal file
161
vendor/github.com/docker/docker/pkg/pools/pools_test.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
||||
package pools
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBufioReaderPoolGetWithNoReaderShouldCreateOne(t *testing.T) {
|
||||
reader := BufioReader32KPool.Get(nil)
|
||||
if reader == nil {
|
||||
t.Fatalf("BufioReaderPool should have create a bufio.Reader but did not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufioReaderPoolPutAndGet(t *testing.T) {
|
||||
sr := bufio.NewReader(strings.NewReader("foobar"))
|
||||
reader := BufioReader32KPool.Get(sr)
|
||||
if reader == nil {
|
||||
t.Fatalf("BufioReaderPool should not return a nil reader.")
|
||||
}
|
||||
// verify the first 3 byte
|
||||
buf1 := make([]byte, 3)
|
||||
_, err := reader.Read(buf1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if actual := string(buf1); actual != "foo" {
|
||||
t.Fatalf("The first letter should have been 'foo' but was %v", actual)
|
||||
}
|
||||
BufioReader32KPool.Put(reader)
|
||||
// Try to read the next 3 bytes
|
||||
_, err = sr.Read(make([]byte, 3))
|
||||
if err == nil || err != io.EOF {
|
||||
t.Fatalf("The buffer should have been empty, issue an EOF error.")
|
||||
}
|
||||
}
|
||||
|
||||
type simpleReaderCloser struct {
|
||||
io.Reader
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (r *simpleReaderCloser) Close() error {
|
||||
r.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNewReadCloserWrapperWithAReadCloser(t *testing.T) {
|
||||
br := bufio.NewReader(strings.NewReader(""))
|
||||
sr := &simpleReaderCloser{
|
||||
Reader: strings.NewReader("foobar"),
|
||||
closed: false,
|
||||
}
|
||||
reader := BufioReader32KPool.NewReadCloserWrapper(br, sr)
|
||||
if reader == nil {
|
||||
t.Fatalf("NewReadCloserWrapper should not return a nil reader.")
|
||||
}
|
||||
// Verify the content of reader
|
||||
buf := make([]byte, 3)
|
||||
_, err := reader.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if actual := string(buf); actual != "foo" {
|
||||
t.Fatalf("The first 3 letter should have been 'foo' but were %v", actual)
|
||||
}
|
||||
reader.Close()
|
||||
// Read 3 more bytes "bar"
|
||||
_, err = reader.Read(buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if actual := string(buf); actual != "bar" {
|
||||
t.Fatalf("The first 3 letter should have been 'bar' but were %v", actual)
|
||||
}
|
||||
if !sr.closed {
|
||||
t.Fatalf("The ReaderCloser should have been closed, it is not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufioWriterPoolGetWithNoReaderShouldCreateOne(t *testing.T) {
|
||||
writer := BufioWriter32KPool.Get(nil)
|
||||
if writer == nil {
|
||||
t.Fatalf("BufioWriterPool should have create a bufio.Writer but did not.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBufioWriterPoolPutAndGet(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
bw := bufio.NewWriter(buf)
|
||||
writer := BufioWriter32KPool.Get(bw)
|
||||
if writer == nil {
|
||||
t.Fatalf("BufioReaderPool should not return a nil writer.")
|
||||
}
|
||||
written, err := writer.Write([]byte("foobar"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if written != 6 {
|
||||
t.Fatalf("Should have written 6 bytes, but wrote %v bytes", written)
|
||||
}
|
||||
// Make sure we Flush all the way ?
|
||||
writer.Flush()
|
||||
bw.Flush()
|
||||
if len(buf.Bytes()) != 6 {
|
||||
t.Fatalf("The buffer should contain 6 bytes ('foobar') but contains %v ('%v')", buf.Bytes(), string(buf.Bytes()))
|
||||
}
|
||||
// Reset the buffer
|
||||
buf.Reset()
|
||||
BufioWriter32KPool.Put(writer)
|
||||
// Try to write something
|
||||
if _, err = writer.Write([]byte("barfoo")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// If we now try to flush it, it should panic (the writer is nil)
|
||||
// recover it
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Fatal("Trying to flush the writter should have 'paniced', did not.")
|
||||
}
|
||||
}()
|
||||
writer.Flush()
|
||||
}
|
||||
|
||||
type simpleWriterCloser struct {
|
||||
io.Writer
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (r *simpleWriterCloser) Close() error {
|
||||
r.closed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestNewWriteCloserWrapperWithAWriteCloser(t *testing.T) {
|
||||
buf := new(bytes.Buffer)
|
||||
bw := bufio.NewWriter(buf)
|
||||
sw := &simpleWriterCloser{
|
||||
Writer: new(bytes.Buffer),
|
||||
closed: false,
|
||||
}
|
||||
bw.Flush()
|
||||
writer := BufioWriter32KPool.NewWriteCloserWrapper(bw, sw)
|
||||
if writer == nil {
|
||||
t.Fatalf("BufioReaderPool should not return a nil writer.")
|
||||
}
|
||||
written, err := writer.Write([]byte("foobar"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if written != 6 {
|
||||
t.Fatalf("Should have written 6 bytes, but wrote %v bytes", written)
|
||||
}
|
||||
writer.Close()
|
||||
if !sw.closed {
|
||||
t.Fatalf("The ReaderCloser should have been closed, it is not.")
|
||||
}
|
||||
}
|
11
vendor/github.com/docker/docker/pkg/promise/promise.go
generated
vendored
Normal file
11
vendor/github.com/docker/docker/pkg/promise/promise.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package promise
|
||||
|
||||
// Go is a basic promise implementation: it wraps calls a function in a goroutine,
|
||||
// and returns a channel which will later return the function's return value.
|
||||
func Go(f func() error) chan error {
|
||||
ch := make(chan error, 1)
|
||||
go func() {
|
||||
ch <- f()
|
||||
}()
|
||||
return ch
|
||||
}
|
52
vendor/github.com/docker/docker/pkg/system/chtimes.go
generated
vendored
Normal file
52
vendor/github.com/docker/docker/pkg/system/chtimes.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
maxTime time.Time
|
||||
)
|
||||
|
||||
func init() {
|
||||
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
|
||||
// This is a 64 bit timespec
|
||||
// os.Chtimes limits time to the following
|
||||
maxTime = time.Unix(0, 1<<63-1)
|
||||
} else {
|
||||
// This is a 32 bit timespec
|
||||
maxTime = time.Unix(1<<31-1, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Chtimes changes the access time and modified time of a file at the given path
|
||||
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
unixMinTime := time.Unix(0, 0)
|
||||
unixMaxTime := maxTime
|
||||
|
||||
// If the modified time is prior to the Unix Epoch, or after the
|
||||
// end of Unix Time, os.Chtimes has undefined behavior
|
||||
// default to Unix Epoch in this case, just in case
|
||||
|
||||
if atime.Before(unixMinTime) || atime.After(unixMaxTime) {
|
||||
atime = unixMinTime
|
||||
}
|
||||
|
||||
if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) {
|
||||
mtime = unixMinTime
|
||||
}
|
||||
|
||||
if err := os.Chtimes(name, atime, mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Take platform specific action for setting create time.
|
||||
if err := setCTime(name, mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
94
vendor/github.com/docker/docker/pkg/system/chtimes_test.go
generated
vendored
Normal file
94
vendor/github.com/docker/docker/pkg/system/chtimes_test.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// prepareTempFile creates a temporary file in a temporary directory.
|
||||
func prepareTempFile(t *testing.T) (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)
|
||||
}
|
||||
return file, dir
|
||||
}
|
||||
|
||||
// TestChtimes tests Chtimes on a tempfile. Test only mTime, because aTime is OS dependent
|
||||
func TestChtimes(t *testing.T) {
|
||||
file, dir := prepareTempFile(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
|
||||
unixEpochTime := time.Unix(0, 0)
|
||||
afterUnixEpochTime := time.Unix(100, 0)
|
||||
unixMaxTime := maxTime
|
||||
|
||||
// Test both aTime and mTime set to Unix Epoch
|
||||
Chtimes(file, unixEpochTime, unixEpochTime)
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime() != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
|
||||
}
|
||||
|
||||
// Test aTime before Unix Epoch and mTime set to Unix Epoch
|
||||
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime() != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
|
||||
}
|
||||
|
||||
// Test aTime set to Unix Epoch and mTime before Unix Epoch
|
||||
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime() != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
|
||||
}
|
||||
|
||||
// Test both aTime and mTime set to after Unix Epoch (valid time)
|
||||
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime() != afterUnixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, f.ModTime())
|
||||
}
|
||||
|
||||
// Test both aTime and mTime set to Unix max time
|
||||
Chtimes(file, unixMaxTime, unixMaxTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if f.ModTime().Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
||||
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), f.ModTime().Truncate(time.Second))
|
||||
}
|
||||
}
|
14
vendor/github.com/docker/docker/pkg/system/chtimes_unix.go
generated
vendored
Normal file
14
vendor/github.com/docker/docker/pkg/system/chtimes_unix.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// +build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//setCTime will set the create time on a file. On Unix, the create
|
||||
//time is updated as a side effect of setting the modified time, so
|
||||
//no action is required.
|
||||
func setCTime(path string, ctime time.Time) error {
|
||||
return nil
|
||||
}
|
91
vendor/github.com/docker/docker/pkg/system/chtimes_unix_test.go
generated
vendored
Normal file
91
vendor/github.com/docker/docker/pkg/system/chtimes_unix_test.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
// +build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestChtimesLinux tests Chtimes access time on a tempfile on Linux
|
||||
func TestChtimesLinux(t *testing.T) {
|
||||
file, dir := prepareTempFile(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
|
||||
unixEpochTime := time.Unix(0, 0)
|
||||
afterUnixEpochTime := time.Unix(100, 0)
|
||||
unixMaxTime := maxTime
|
||||
|
||||
// Test both aTime and mTime set to Unix Epoch
|
||||
Chtimes(file, unixEpochTime, unixEpochTime)
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat := f.Sys().(*syscall.Stat_t)
|
||||
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
|
||||
// Test aTime before Unix Epoch and mTime set to Unix Epoch
|
||||
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat = f.Sys().(*syscall.Stat_t)
|
||||
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
|
||||
// Test aTime set to Unix Epoch and mTime before Unix Epoch
|
||||
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat = f.Sys().(*syscall.Stat_t)
|
||||
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
|
||||
// Test both aTime and mTime set to after Unix Epoch (valid time)
|
||||
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat = f.Sys().(*syscall.Stat_t)
|
||||
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
||||
if aTime != afterUnixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
|
||||
}
|
||||
|
||||
// Test both aTime and mTime set to Unix max time
|
||||
Chtimes(file, unixMaxTime, unixMaxTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat = f.Sys().(*syscall.Stat_t)
|
||||
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
||||
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
||||
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
|
||||
}
|
||||
}
|
27
vendor/github.com/docker/docker/pkg/system/chtimes_windows.go
generated
vendored
Normal file
27
vendor/github.com/docker/docker/pkg/system/chtimes_windows.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
// +build windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
//setCTime will set the create time on a file. On Windows, this requires
|
||||
//calling SetFileTime and explicitly including the create time.
|
||||
func setCTime(path string, ctime time.Time) error {
|
||||
ctimespec := syscall.NsecToTimespec(ctime.UnixNano())
|
||||
pathp, e := syscall.UTF16PtrFromString(path)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
h, e := syscall.CreateFile(pathp,
|
||||
syscall.FILE_WRITE_ATTRIBUTES, syscall.FILE_SHARE_WRITE, nil,
|
||||
syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer syscall.Close(h)
|
||||
c := syscall.NsecToFiletime(syscall.TimespecToNsec(ctimespec))
|
||||
return syscall.SetFileTime(h, &c, nil, nil)
|
||||
}
|
86
vendor/github.com/docker/docker/pkg/system/chtimes_windows_test.go
generated
vendored
Normal file
86
vendor/github.com/docker/docker/pkg/system/chtimes_windows_test.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
// +build windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestChtimesWindows tests Chtimes access time on a tempfile on Windows
|
||||
func TestChtimesWindows(t *testing.T) {
|
||||
file, dir := prepareTempFile(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
|
||||
unixEpochTime := time.Unix(0, 0)
|
||||
afterUnixEpochTime := time.Unix(100, 0)
|
||||
unixMaxTime := maxTime
|
||||
|
||||
// Test both aTime and mTime set to Unix Epoch
|
||||
Chtimes(file, unixEpochTime, unixEpochTime)
|
||||
|
||||
f, err := os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
|
||||
// Test aTime before Unix Epoch and mTime set to Unix Epoch
|
||||
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
|
||||
// Test aTime set to Unix Epoch and mTime before Unix Epoch
|
||||
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime != unixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
||||
}
|
||||
|
||||
// Test both aTime and mTime set to after Unix Epoch (valid time)
|
||||
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime != afterUnixEpochTime {
|
||||
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
|
||||
}
|
||||
|
||||
// Test both aTime and mTime set to Unix max time
|
||||
Chtimes(file, unixMaxTime, unixMaxTime)
|
||||
|
||||
f, err = os.Stat(file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
||||
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
||||
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
|
||||
}
|
||||
}
|
10
vendor/github.com/docker/docker/pkg/system/errors.go
generated
vendored
Normal file
10
vendor/github.com/docker/docker/pkg/system/errors.go
generated
vendored
Normal file
@ -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")
|
||||
)
|
85
vendor/github.com/docker/docker/pkg/system/events_windows.go
generated
vendored
Normal file
85
vendor/github.com/docker/docker/pkg/system/events_windows.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package system
|
||||
|
||||
// This file implements syscalls for Win32 events which are not implemented
|
||||
// in golang.
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
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 *windows.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
|
||||
}
|
33
vendor/github.com/docker/docker/pkg/system/exitcode.go
generated
vendored
Normal file
33
vendor/github.com/docker/docker/pkg/system/exitcode.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// GetExitCode returns the ExitStatus of the specified error if its type is
|
||||
// exec.ExitError, returns 0 and an error otherwise.
|
||||
func GetExitCode(err error) (int, error) {
|
||||
exitCode := 0
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
return procExit.ExitStatus(), nil
|
||||
}
|
||||
}
|
||||
return exitCode, fmt.Errorf("failed to get exit code")
|
||||
}
|
||||
|
||||
// ProcessExitCode process the specified error and returns the exit status code
|
||||
// if the error was of type exec.ExitError, returns nothing otherwise.
|
||||
func ProcessExitCode(err error) (exitCode int) {
|
||||
if err != nil {
|
||||
var exiterr error
|
||||
if exitCode, exiterr = GetExitCode(err); exiterr != nil {
|
||||
// TODO: Fix this so we check the error's text.
|
||||
// we've failed to retrieve exit code, so we set it to 127
|
||||
exitCode = 127
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
68
vendor/github.com/docker/docker/pkg/system/filesys.go
generated
vendored
Normal file
68
vendor/github.com/docker/docker/pkg/system/filesys.go
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
// +build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
|
||||
// ACL'd for Builtin Administrators and Local System.
|
||||
func MkdirAllWithACL(path string, perm os.FileMode) error {
|
||||
return MkdirAll(path, perm)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// The functions below here are wrappers for the equivalents in the os and ioutils packages.
|
||||
// They are passthrough on Unix platforms, and only relevant on Windows.
|
||||
|
||||
// CreateSequential creates the named file with mode 0666 (before umask), truncating
|
||||
// it if it already exists. If successful, methods on the returned
|
||||
// File can be used for I/O; the associated file descriptor has mode
|
||||
// O_RDWR.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func CreateSequential(name string) (*os.File, error) {
|
||||
return os.Create(name)
|
||||
}
|
||||
|
||||
// OpenSequential opens the named file for reading. If successful, methods on
|
||||
// the returned file can be used for reading; the associated file
|
||||
// descriptor has mode O_RDONLY.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenSequential(name string) (*os.File, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
// OpenFileSequential is the generalized open call; most users will use Open
|
||||
// or Create instead. It opens the named file with specified flag
|
||||
// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful,
|
||||
// methods on the returned File can be used for I/O.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenFileSequential(name string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
return os.OpenFile(name, flag, perm)
|
||||
}
|
||||
|
||||
// TempFileSequential creates a new temporary file in the directory dir
|
||||
// with a name beginning with prefix, opens the file for reading
|
||||
// and writing, and returns the resulting *os.File.
|
||||
// If dir is the empty string, TempFile uses the default directory
|
||||
// for temporary files (see os.TempDir).
|
||||
// Multiple programs calling TempFile simultaneously
|
||||
// will not choose the same file. The caller can use f.Name()
|
||||
// to find the pathname of the file. It is the caller's responsibility
|
||||
// to remove the file when no longer needed.
|
||||
func TempFileSequential(dir, prefix string) (f *os.File, err error) {
|
||||
return ioutil.TempFile(dir, prefix)
|
||||
}
|
291
vendor/github.com/docker/docker/pkg/system/filesys_windows.go
generated
vendored
Normal file
291
vendor/github.com/docker/docker/pkg/system/filesys_windows.go
generated
vendored
Normal file
@ -0,0 +1,291 @@
|
||||
// +build windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
winio "github.com/Microsoft/go-winio"
|
||||
)
|
||||
|
||||
// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
|
||||
// ACL'd for Builtin Administrators and Local System.
|
||||
func MkdirAllWithACL(path string, perm os.FileMode) error {
|
||||
return mkdirall(path, true)
|
||||
}
|
||||
|
||||
// MkdirAll implementation that is volume path aware for Windows.
|
||||
func MkdirAll(path string, _ os.FileMode) error {
|
||||
return mkdirall(path, false)
|
||||
}
|
||||
|
||||
// mkdirall is a custom version of os.MkdirAll modified for use on Windows
|
||||
// so that it is both volume path aware, and can create a directory with
|
||||
// a DACL.
|
||||
func mkdirall(path string, adminAndLocalSystem bool) error {
|
||||
if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The rest of this method is largely 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], false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Parent now exists; invoke os.Mkdir or mkdirWithACL and use its result.
|
||||
if adminAndLocalSystem {
|
||||
err = mkdirWithACL(path)
|
||||
} else {
|
||||
err = os.Mkdir(path, 0)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// mkdirWithACL creates a new directory. If there is an error, it will be of
|
||||
// type *PathError. .
|
||||
//
|
||||
// This is a modified and combined version of os.Mkdir and syscall.Mkdir
|
||||
// in golang to cater for creating a directory am ACL permitting full
|
||||
// access, with inheritance, to any subfolder/file for Built-in Administrators
|
||||
// and Local System.
|
||||
func mkdirWithACL(name string) error {
|
||||
sa := syscall.SecurityAttributes{Length: 0}
|
||||
sddl := "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
|
||||
sd, err := winio.SddlToSecurityDescriptor(sddl)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||
}
|
||||
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||
sa.InheritHandle = 1
|
||||
sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0]))
|
||||
|
||||
namep, err := syscall.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||
}
|
||||
|
||||
e := syscall.CreateDirectory(namep, &sa)
|
||||
if e != nil {
|
||||
return &os.PathError{Op: "mkdir", Path: name, Err: e}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// The origin of the functions below here are the golang OS and syscall packages,
|
||||
// slightly modified to only cope with files, not directories due to the
|
||||
// specific use case.
|
||||
//
|
||||
// The alteration is to allow a file on Windows to be opened with
|
||||
// FILE_FLAG_SEQUENTIAL_SCAN (particular for docker load), to avoid eating
|
||||
// the standby list, particularly when accessing large files such as layer.tar.
|
||||
|
||||
// CreateSequential creates the named file with mode 0666 (before umask), truncating
|
||||
// it if it already exists. If successful, methods on the returned
|
||||
// File can be used for I/O; the associated file descriptor has mode
|
||||
// O_RDWR.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func CreateSequential(name string) (*os.File, error) {
|
||||
return OpenFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0)
|
||||
}
|
||||
|
||||
// OpenSequential opens the named file for reading. If successful, methods on
|
||||
// the returned file can be used for reading; the associated file
|
||||
// descriptor has mode O_RDONLY.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenSequential(name string) (*os.File, error) {
|
||||
return OpenFileSequential(name, os.O_RDONLY, 0)
|
||||
}
|
||||
|
||||
// OpenFileSequential is the generalized open call; most users will use Open
|
||||
// or Create instead.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenFileSequential(name string, flag int, _ os.FileMode) (*os.File, error) {
|
||||
if name == "" {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT}
|
||||
}
|
||||
r, errf := syscallOpenFileSequential(name, flag, 0)
|
||||
if errf == nil {
|
||||
return r, nil
|
||||
}
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: errf}
|
||||
}
|
||||
|
||||
func syscallOpenFileSequential(name string, flag int, _ os.FileMode) (file *os.File, err error) {
|
||||
r, e := syscallOpenSequential(name, flag|syscall.O_CLOEXEC, 0)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return os.NewFile(uintptr(r), name), nil
|
||||
}
|
||||
|
||||
func makeInheritSa() *syscall.SecurityAttributes {
|
||||
var sa syscall.SecurityAttributes
|
||||
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||
sa.InheritHandle = 1
|
||||
return &sa
|
||||
}
|
||||
|
||||
func syscallOpenSequential(path string, mode int, _ uint32) (fd syscall.Handle, err error) {
|
||||
if len(path) == 0 {
|
||||
return syscall.InvalidHandle, syscall.ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
pathp, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return syscall.InvalidHandle, err
|
||||
}
|
||||
var access uint32
|
||||
switch mode & (syscall.O_RDONLY | syscall.O_WRONLY | syscall.O_RDWR) {
|
||||
case syscall.O_RDONLY:
|
||||
access = syscall.GENERIC_READ
|
||||
case syscall.O_WRONLY:
|
||||
access = syscall.GENERIC_WRITE
|
||||
case syscall.O_RDWR:
|
||||
access = syscall.GENERIC_READ | syscall.GENERIC_WRITE
|
||||
}
|
||||
if mode&syscall.O_CREAT != 0 {
|
||||
access |= syscall.GENERIC_WRITE
|
||||
}
|
||||
if mode&syscall.O_APPEND != 0 {
|
||||
access &^= syscall.GENERIC_WRITE
|
||||
access |= syscall.FILE_APPEND_DATA
|
||||
}
|
||||
sharemode := uint32(syscall.FILE_SHARE_READ | syscall.FILE_SHARE_WRITE)
|
||||
var sa *syscall.SecurityAttributes
|
||||
if mode&syscall.O_CLOEXEC == 0 {
|
||||
sa = makeInheritSa()
|
||||
}
|
||||
var createmode uint32
|
||||
switch {
|
||||
case mode&(syscall.O_CREAT|syscall.O_EXCL) == (syscall.O_CREAT | syscall.O_EXCL):
|
||||
createmode = syscall.CREATE_NEW
|
||||
case mode&(syscall.O_CREAT|syscall.O_TRUNC) == (syscall.O_CREAT | syscall.O_TRUNC):
|
||||
createmode = syscall.CREATE_ALWAYS
|
||||
case mode&syscall.O_CREAT == syscall.O_CREAT:
|
||||
createmode = syscall.OPEN_ALWAYS
|
||||
case mode&syscall.O_TRUNC == syscall.O_TRUNC:
|
||||
createmode = syscall.TRUNCATE_EXISTING
|
||||
default:
|
||||
createmode = syscall.OPEN_EXISTING
|
||||
}
|
||||
// Use FILE_FLAG_SEQUENTIAL_SCAN rather than FILE_ATTRIBUTE_NORMAL as implemented in golang.
|
||||
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
|
||||
const fileFlagSequentialScan = 0x08000000 // FILE_FLAG_SEQUENTIAL_SCAN
|
||||
h, e := syscall.CreateFile(pathp, access, sharemode, sa, createmode, fileFlagSequentialScan, 0)
|
||||
return h, e
|
||||
}
|
||||
|
||||
// Helpers for TempFileSequential
|
||||
var rand uint32
|
||||
var randmu sync.Mutex
|
||||
|
||||
func reseed() uint32 {
|
||||
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
|
||||
}
|
||||
func nextSuffix() string {
|
||||
randmu.Lock()
|
||||
r := rand
|
||||
if r == 0 {
|
||||
r = reseed()
|
||||
}
|
||||
r = r*1664525 + 1013904223 // constants from Numerical Recipes
|
||||
rand = r
|
||||
randmu.Unlock()
|
||||
return strconv.Itoa(int(1e9 + r%1e9))[1:]
|
||||
}
|
||||
|
||||
// TempFileSequential is a copy of ioutil.TempFile, modified to use sequential
|
||||
// file access. Below is the original comment from golang:
|
||||
// TempFile creates a new temporary file in the directory dir
|
||||
// with a name beginning with prefix, opens the file for reading
|
||||
// and writing, and returns the resulting *os.File.
|
||||
// If dir is the empty string, TempFile uses the default directory
|
||||
// for temporary files (see os.TempDir).
|
||||
// Multiple programs calling TempFile simultaneously
|
||||
// will not choose the same file. The caller can use f.Name()
|
||||
// to find the pathname of the file. It is the caller's responsibility
|
||||
// to remove the file when no longer needed.
|
||||
func TempFileSequential(dir, prefix string) (f *os.File, err error) {
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
|
||||
nconflict := 0
|
||||
for i := 0; i < 10000; i++ {
|
||||
name := filepath.Join(dir, prefix+nextSuffix())
|
||||
f, err = OpenFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if os.IsExist(err) {
|
||||
if nconflict++; nconflict > 10 {
|
||||
randmu.Lock()
|
||||
rand = reseed()
|
||||
randmu.Unlock()
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
17
vendor/github.com/docker/docker/pkg/system/lstat_unix.go
generated
vendored
Normal file
17
vendor/github.com/docker/docker/pkg/system/lstat_unix.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// +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)
|
||||
}
|
30
vendor/github.com/docker/docker/pkg/system/lstat_unix_test.go
generated
vendored
Normal file
30
vendor/github.com/docker/docker/pkg/system/lstat_unix_test.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// +build linux freebsd
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
14
vendor/github.com/docker/docker/pkg/system/lstat_windows.go
generated
vendored
Normal file
14
vendor/github.com/docker/docker/pkg/system/lstat_windows.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
package system
|
||||
|
||||
import "os"
|
||||
|
||||
// Lstat calls os.Lstat to get a fileinfo interface back.
|
||||
// This is then copied into our own locally defined structure.
|
||||
func Lstat(path string) (*StatT, error) {
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromStatT(&fi)
|
||||
}
|
17
vendor/github.com/docker/docker/pkg/system/meminfo.go
generated
vendored
Normal file
17
vendor/github.com/docker/docker/pkg/system/meminfo.go
generated
vendored
Normal file
@ -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
|
||||
}
|
65
vendor/github.com/docker/docker/pkg/system/meminfo_linux.go
generated
vendored
Normal file
65
vendor/github.com/docker/docker/pkg/system/meminfo_linux.go
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-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 an 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
|
||||
}
|
129
vendor/github.com/docker/docker/pkg/system/meminfo_solaris.go
generated
vendored
Normal file
129
vendor/github.com/docker/docker/pkg/system/meminfo_solaris.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
// +build solaris,cgo
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #cgo CFLAGS: -std=c99
|
||||
// #cgo LDFLAGS: -lkstat
|
||||
// #include <unistd.h>
|
||||
// #include <stdlib.h>
|
||||
// #include <stdio.h>
|
||||
// #include <kstat.h>
|
||||
// #include <sys/swap.h>
|
||||
// #include <sys/param.h>
|
||||
// struct swaptable *allocSwaptable(int num) {
|
||||
// struct swaptable *st;
|
||||
// struct swapent *swapent;
|
||||
// st = (struct swaptable *)malloc(num * sizeof(swapent_t) + sizeof (int));
|
||||
// swapent = st->swt_ent;
|
||||
// for (int i = 0; i < num; i++,swapent++) {
|
||||
// swapent->ste_path = (char *)malloc(MAXPATHLEN * sizeof (char));
|
||||
// }
|
||||
// st->swt_n = num;
|
||||
// return st;
|
||||
//}
|
||||
// void freeSwaptable (struct swaptable *st) {
|
||||
// struct swapent *swapent = st->swt_ent;
|
||||
// for (int i = 0; i < st->swt_n; i++,swapent++) {
|
||||
// free(swapent->ste_path);
|
||||
// }
|
||||
// free(st);
|
||||
// }
|
||||
// swapent_t getSwapEnt(swapent_t *ent, int i) {
|
||||
// return ent[i];
|
||||
// }
|
||||
// int64_t getPpKernel() {
|
||||
// int64_t pp_kernel = 0;
|
||||
// kstat_ctl_t *ksc;
|
||||
// kstat_t *ks;
|
||||
// kstat_named_t *knp;
|
||||
// kid_t kid;
|
||||
//
|
||||
// if ((ksc = kstat_open()) == NULL) {
|
||||
// return -1;
|
||||
// }
|
||||
// if ((ks = kstat_lookup(ksc, "unix", 0, "system_pages")) == NULL) {
|
||||
// return -1;
|
||||
// }
|
||||
// if (((kid = kstat_read(ksc, ks, NULL)) == -1) ||
|
||||
// ((knp = kstat_data_lookup(ks, "pp_kernel")) == NULL)) {
|
||||
// return -1;
|
||||
// }
|
||||
// switch (knp->data_type) {
|
||||
// case KSTAT_DATA_UINT64:
|
||||
// pp_kernel = knp->value.ui64;
|
||||
// break;
|
||||
// case KSTAT_DATA_UINT32:
|
||||
// pp_kernel = knp->value.ui32;
|
||||
// break;
|
||||
// }
|
||||
// pp_kernel *= sysconf(_SC_PAGESIZE);
|
||||
// return (pp_kernel > 0 ? pp_kernel : -1);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
// Get the system memory info using sysconf same as prtconf
|
||||
func getTotalMem() int64 {
|
||||
pagesize := C.sysconf(C._SC_PAGESIZE)
|
||||
npages := C.sysconf(C._SC_PHYS_PAGES)
|
||||
return int64(pagesize * npages)
|
||||
}
|
||||
|
||||
func getFreeMem() int64 {
|
||||
pagesize := C.sysconf(C._SC_PAGESIZE)
|
||||
npages := C.sysconf(C._SC_AVPHYS_PAGES)
|
||||
return int64(pagesize * npages)
|
||||
}
|
||||
|
||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||
// MemInfo type.
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
|
||||
ppKernel := C.getPpKernel()
|
||||
MemTotal := getTotalMem()
|
||||
MemFree := getFreeMem()
|
||||
SwapTotal, SwapFree, err := getSysSwap()
|
||||
|
||||
if ppKernel < 0 || MemTotal < 0 || MemFree < 0 || SwapTotal < 0 ||
|
||||
SwapFree < 0 {
|
||||
return nil, fmt.Errorf("error getting system memory info %v\n", err)
|
||||
}
|
||||
|
||||
meminfo := &MemInfo{}
|
||||
// Total memory is total physical memory less than memory locked by kernel
|
||||
meminfo.MemTotal = MemTotal - int64(ppKernel)
|
||||
meminfo.MemFree = MemFree
|
||||
meminfo.SwapTotal = SwapTotal
|
||||
meminfo.SwapFree = SwapFree
|
||||
|
||||
return meminfo, nil
|
||||
}
|
||||
|
||||
func getSysSwap() (int64, int64, error) {
|
||||
var tSwap int64
|
||||
var fSwap int64
|
||||
var diskblksPerPage int64
|
||||
num, err := C.swapctl(C.SC_GETNSWP, nil)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
st := C.allocSwaptable(num)
|
||||
_, err = C.swapctl(C.SC_LIST, unsafe.Pointer(st))
|
||||
if err != nil {
|
||||
C.freeSwaptable(st)
|
||||
return -1, -1, err
|
||||
}
|
||||
|
||||
diskblksPerPage = int64(C.sysconf(C._SC_PAGESIZE) >> C.DEV_BSHIFT)
|
||||
for i := 0; i < int(num); i++ {
|
||||
swapent := C.getSwapEnt(&st.swt_ent[0], C.int(i))
|
||||
tSwap += int64(swapent.ste_pages) * diskblksPerPage
|
||||
fSwap += int64(swapent.ste_free) * diskblksPerPage
|
||||
}
|
||||
C.freeSwaptable(st)
|
||||
return tSwap, fSwap, nil
|
||||
}
|
40
vendor/github.com/docker/docker/pkg/system/meminfo_unix_test.go
generated
vendored
Normal file
40
vendor/github.com/docker/docker/pkg/system/meminfo_unix_test.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/go-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)
|
||||
}
|
||||
}
|
8
vendor/github.com/docker/docker/pkg/system/meminfo_unsupported.go
generated
vendored
Normal file
8
vendor/github.com/docker/docker/pkg/system/meminfo_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
// +build !linux,!windows,!solaris
|
||||
|
||||
package system
|
||||
|
||||
// ReadMemInfo is not supported on platforms other than linux and windows.
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
return nil, ErrNotSupportedPlatform
|
||||
}
|
45
vendor/github.com/docker/docker/pkg/system/meminfo_windows.go
generated
vendored
Normal file
45
vendor/github.com/docker/docker/pkg/system/meminfo_windows.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
modkernel32 = windows.NewLazySystemDLL("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
|
||||
}
|
22
vendor/github.com/docker/docker/pkg/system/mknod.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/system/mknod.go
generated
vendored
Normal file
@ -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))
|
||||
}
|
13
vendor/github.com/docker/docker/pkg/system/mknod_windows.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/mknod_windows.go
generated
vendored
Normal file
@ -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.")
|
||||
}
|
14
vendor/github.com/docker/docker/pkg/system/path_unix.go
generated
vendored
Normal file
14
vendor/github.com/docker/docker/pkg/system/path_unix.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// +build !windows
|
||||
|
||||
package system
|
||||
|
||||
// DefaultPathEnv is unix style list of directories to search for
|
||||
// executables. Each directory is separated from the next by a colon
|
||||
// ':' character .
|
||||
const DefaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter,
|
||||
// is the system drive. This is a no-op on Linux.
|
||||
func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) {
|
||||
return path, nil
|
||||
}
|
37
vendor/github.com/docker/docker/pkg/system/path_windows.go
generated
vendored
Normal file
37
vendor/github.com/docker/docker/pkg/system/path_windows.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// +build windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultPathEnv is deliberately empty on Windows as the default path will be set by
|
||||
// the container. Docker has no context of what the default path should be.
|
||||
const DefaultPathEnv = ""
|
||||
|
||||
// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path.
|
||||
// This is used, for example, when validating a user provided path in docker cp.
|
||||
// If a drive letter is supplied, it must be the system drive. The drive letter
|
||||
// is always removed. Also, it translates it to OS semantics (IOW / to \). We
|
||||
// need the path in this syntax so that it can ultimately be contatenated with
|
||||
// a Windows long-path which doesn't support drive-letters. Examples:
|
||||
// C: --> Fail
|
||||
// C:\ --> \
|
||||
// a --> a
|
||||
// /a --> \a
|
||||
// d:\ --> Fail
|
||||
func CheckSystemDriveAndRemoveDriveLetter(path string) (string, error) {
|
||||
if len(path) == 2 && string(path[1]) == ":" {
|
||||
return "", fmt.Errorf("No relative path specified in %q", path)
|
||||
}
|
||||
if !filepath.IsAbs(path) || len(path) < 2 {
|
||||
return filepath.FromSlash(path), nil
|
||||
}
|
||||
if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") {
|
||||
return "", fmt.Errorf("The specified path is not on the system drive (C:)")
|
||||
}
|
||||
return filepath.FromSlash(path[2:]), nil
|
||||
}
|
78
vendor/github.com/docker/docker/pkg/system/path_windows_test.go
generated
vendored
Normal file
78
vendor/github.com/docker/docker/pkg/system/path_windows_test.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
// +build windows
|
||||
|
||||
package system
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestCheckSystemDriveAndRemoveDriveLetter tests CheckSystemDriveAndRemoveDriveLetter
|
||||
func TestCheckSystemDriveAndRemoveDriveLetter(t *testing.T) {
|
||||
// Fails if not C drive.
|
||||
path, err := CheckSystemDriveAndRemoveDriveLetter(`d:\`)
|
||||
if err == nil || (err != nil && err.Error() != "The specified path is not on the system drive (C:)") {
|
||||
t.Fatalf("Expected error for d:")
|
||||
}
|
||||
|
||||
// Single character is unchanged
|
||||
if path, err = CheckSystemDriveAndRemoveDriveLetter("z"); err != nil {
|
||||
t.Fatalf("Single character should pass")
|
||||
}
|
||||
if path != "z" {
|
||||
t.Fatalf("Single character should be unchanged")
|
||||
}
|
||||
|
||||
// Two characters without colon is unchanged
|
||||
if path, err = CheckSystemDriveAndRemoveDriveLetter("AB"); err != nil {
|
||||
t.Fatalf("2 characters without colon should pass")
|
||||
}
|
||||
if path != "AB" {
|
||||
t.Fatalf("2 characters without colon should be unchanged")
|
||||
}
|
||||
|
||||
// Abs path without drive letter
|
||||
if path, err = CheckSystemDriveAndRemoveDriveLetter(`\l`); err != nil {
|
||||
t.Fatalf("abs path no drive letter should pass")
|
||||
}
|
||||
if path != `\l` {
|
||||
t.Fatalf("abs path without drive letter should be unchanged")
|
||||
}
|
||||
|
||||
// Abs path without drive letter, linux style
|
||||
if path, err = CheckSystemDriveAndRemoveDriveLetter(`/l`); err != nil {
|
||||
t.Fatalf("abs path no drive letter linux style should pass")
|
||||
}
|
||||
if path != `\l` {
|
||||
t.Fatalf("abs path without drive letter linux failed %s", path)
|
||||
}
|
||||
|
||||
// Drive-colon should be stripped
|
||||
if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:\`); err != nil {
|
||||
t.Fatalf("An absolute path should pass")
|
||||
}
|
||||
if path != `\` {
|
||||
t.Fatalf(`An absolute path should have been shortened to \ %s`, path)
|
||||
}
|
||||
|
||||
// Verify with a linux-style path
|
||||
if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:/`); err != nil {
|
||||
t.Fatalf("An absolute path should pass")
|
||||
}
|
||||
if path != `\` {
|
||||
t.Fatalf(`A linux style absolute path should have been shortened to \ %s`, path)
|
||||
}
|
||||
|
||||
// Failure on c:
|
||||
if path, err = CheckSystemDriveAndRemoveDriveLetter(`c:`); err == nil {
|
||||
t.Fatalf("c: should fail")
|
||||
}
|
||||
if err.Error() != `No relative path specified in "c:"` {
|
||||
t.Fatalf(path, err)
|
||||
}
|
||||
|
||||
// Failure on d:
|
||||
if path, err = CheckSystemDriveAndRemoveDriveLetter(`d:`); err == nil {
|
||||
t.Fatalf("c: should fail")
|
||||
}
|
||||
if err.Error() != `No relative path specified in "d:"` {
|
||||
t.Fatalf(path, err)
|
||||
}
|
||||
}
|
22
vendor/github.com/docker/docker/pkg/system/process_unix.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/system/process_unix.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// +build linux freebsd solaris darwin
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// IsProcessAlive returns true if process with a given pid is running.
|
||||
func IsProcessAlive(pid int) bool {
|
||||
err := syscall.Kill(pid, syscall.Signal(0))
|
||||
if err == nil || err == syscall.EPERM {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// KillProcess force-stops a process.
|
||||
func KillProcess(pid int) {
|
||||
syscall.Kill(pid, syscall.SIGKILL)
|
||||
}
|
13
vendor/github.com/docker/docker/pkg/system/stat_darwin.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/stat_darwin.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
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
|
||||
}
|
13
vendor/github.com/docker/docker/pkg/system/stat_freebsd.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/stat_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
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
|
||||
}
|
19
vendor/github.com/docker/docker/pkg/system/stat_linux.go
generated
vendored
Normal file
19
vendor/github.com/docker/docker/pkg/system/stat_linux.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
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.Mtim}, nil
|
||||
}
|
||||
|
||||
// FromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
||||
// This is exposed on Linux as pkg/archive/changes uses it.
|
||||
func FromStatT(s *syscall.Stat_t) (*StatT, error) {
|
||||
return fromStatT(s)
|
||||
}
|
13
vendor/github.com/docker/docker/pkg/system/stat_openbsd.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/stat_openbsd.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
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.Mtim}, nil
|
||||
}
|
13
vendor/github.com/docker/docker/pkg/system/stat_solaris.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/stat_solaris.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
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.Mtim}, nil
|
||||
}
|
58
vendor/github.com/docker/docker/pkg/system/stat_unix.go
generated
vendored
Normal file
58
vendor/github.com/docker/docker/pkg/system/stat_unix.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
// +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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
39
vendor/github.com/docker/docker/pkg/system/stat_unix_test.go
generated
vendored
Normal file
39
vendor/github.com/docker/docker/pkg/system/stat_unix_test.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// +build linux freebsd
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
49
vendor/github.com/docker/docker/pkg/system/stat_windows.go
generated
vendored
Normal file
49
vendor/github.com/docker/docker/pkg/system/stat_windows.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StatT type contains status of a file. It contains metadata
|
||||
// like permission, size, etc about a file.
|
||||
type StatT struct {
|
||||
mode os.FileMode
|
||||
size int64
|
||||
mtim time.Time
|
||||
}
|
||||
|
||||
// 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 os.FileMode(s.mode)
|
||||
}
|
||||
|
||||
// Mtim returns file's last modification time.
|
||||
func (s StatT) Mtim() time.Time {
|
||||
return time.Time(s.mtim)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromStatT(&fi)
|
||||
}
|
||||
|
||||
// fromStatT converts a os.FileInfo type to a system.StatT type
|
||||
func fromStatT(fi *os.FileInfo) (*StatT, error) {
|
||||
return &StatT{
|
||||
size: (*fi).Size(),
|
||||
mode: (*fi).Mode(),
|
||||
mtim: (*fi).ModTime()}, nil
|
||||
}
|
17
vendor/github.com/docker/docker/pkg/system/syscall_unix.go
generated
vendored
Normal file
17
vendor/github.com/docker/docker/pkg/system/syscall_unix.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package system
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Unmount is a platform-specific helper function to call
|
||||
// the unmount syscall.
|
||||
func Unmount(dest string) error {
|
||||
return syscall.Unmount(dest, 0)
|
||||
}
|
||||
|
||||
// CommandLineToArgv should not be used on Unix.
|
||||
// It simply returns commandLine in the only element in the returned array.
|
||||
func CommandLineToArgv(commandLine string) ([]string, error) {
|
||||
return []string{commandLine}, nil
|
||||
}
|
105
vendor/github.com/docker/docker/pkg/system/syscall_windows.go
generated
vendored
Normal file
105
vendor/github.com/docker/docker/pkg/system/syscall_windows.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
ntuserApiset = syscall.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0")
|
||||
procGetVersionExW = modkernel32.NewProc("GetVersionExW")
|
||||
)
|
||||
|
||||
// OSVersion is a wrapper for Windows version information
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
|
||||
type OSVersion struct {
|
||||
Version uint32
|
||||
MajorVersion uint8
|
||||
MinorVersion uint8
|
||||
Build uint16
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx
|
||||
type osVersionInfoEx struct {
|
||||
OSVersionInfoSize uint32
|
||||
MajorVersion uint32
|
||||
MinorVersion uint32
|
||||
BuildNumber uint32
|
||||
PlatformID uint32
|
||||
CSDVersion [128]uint16
|
||||
ServicePackMajor uint16
|
||||
ServicePackMinor uint16
|
||||
SuiteMask uint16
|
||||
ProductType byte
|
||||
Reserve byte
|
||||
}
|
||||
|
||||
// GetOSVersion gets the operating system version on Windows. Note that
|
||||
// docker.exe must be manifested to get the correct version information.
|
||||
func GetOSVersion() OSVersion {
|
||||
var err error
|
||||
osv := OSVersion{}
|
||||
osv.Version, err = syscall.GetVersion()
|
||||
if err != nil {
|
||||
// GetVersion never fails.
|
||||
panic(err)
|
||||
}
|
||||
osv.MajorVersion = uint8(osv.Version & 0xFF)
|
||||
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF)
|
||||
osv.Build = uint16(osv.Version >> 16)
|
||||
return osv
|
||||
}
|
||||
|
||||
// IsWindowsClient returns true if the SKU is client
|
||||
// @engine maintainers - this function should not be removed or modified as it
|
||||
// is used to enforce licensing restrictions on Windows.
|
||||
func IsWindowsClient() bool {
|
||||
osviex := &osVersionInfoEx{OSVersionInfoSize: 284}
|
||||
r1, _, err := procGetVersionExW.Call(uintptr(unsafe.Pointer(osviex)))
|
||||
if r1 == 0 {
|
||||
logrus.Warnf("GetVersionExW failed - assuming server SKU: %v", err)
|
||||
return false
|
||||
}
|
||||
const verNTWorkstation = 0x00000001
|
||||
return osviex.ProductType == verNTWorkstation
|
||||
}
|
||||
|
||||
// Unmount is a platform-specific helper function to call
|
||||
// the unmount syscall. Not supported on Windows
|
||||
func Unmount(dest string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandLineToArgv wraps the Windows syscall to turn a commandline into an argument array.
|
||||
func CommandLineToArgv(commandLine string) ([]string, error) {
|
||||
var argc int32
|
||||
|
||||
argsPtr, err := syscall.UTF16PtrFromString(commandLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
argv, err := syscall.CommandLineToArgv(argsPtr, &argc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer syscall.LocalFree(syscall.Handle(uintptr(unsafe.Pointer(argv))))
|
||||
|
||||
newArgs := make([]string, argc)
|
||||
for i, v := range (*argv)[:argc] {
|
||||
newArgs[i] = string(syscall.UTF16ToString((*v)[:]))
|
||||
}
|
||||
|
||||
return newArgs, nil
|
||||
}
|
||||
|
||||
// HasWin32KSupport determines whether containers that depend on win32k can
|
||||
// run on this machine. Win32k is the driver used to implement windowing.
|
||||
func HasWin32KSupport() bool {
|
||||
// For now, check for ntuser API support on the host. In the future, a host
|
||||
// may support win32k in containers even if the host does not support ntuser
|
||||
// APIs.
|
||||
return ntuserApiset.Load() == nil
|
||||
}
|
9
vendor/github.com/docker/docker/pkg/system/syscall_windows_test.go
generated
vendored
Normal file
9
vendor/github.com/docker/docker/pkg/system/syscall_windows_test.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package system
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestHasWin32KSupport(t *testing.T) {
|
||||
s := HasWin32KSupport() // make sure this doesn't panic
|
||||
|
||||
t.Logf("win32k: %v", s) // will be different on different platforms -- informative only
|
||||
}
|
13
vendor/github.com/docker/docker/pkg/system/umask.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/umask.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// +build !windows
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Umask sets current process's file mode creation mask to newmask
|
||||
// and returns oldmask.
|
||||
func Umask(newmask int) (oldmask int, err error) {
|
||||
return syscall.Umask(newmask), nil
|
||||
}
|
9
vendor/github.com/docker/docker/pkg/system/umask_windows.go
generated
vendored
Normal file
9
vendor/github.com/docker/docker/pkg/system/umask_windows.go
generated
vendored
Normal file
@ -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
|
||||
}
|
22
vendor/github.com/docker/docker/pkg/system/utimes_freebsd.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/system/utimes_freebsd.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
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
|
||||
}
|
26
vendor/github.com/docker/docker/pkg/system/utimes_linux.go
generated
vendored
Normal file
26
vendor/github.com/docker/docker/pkg/system/utimes_linux.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
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 {
|
||||
// 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
|
||||
}
|
68
vendor/github.com/docker/docker/pkg/system/utimes_unix_test.go
generated
vendored
Normal file
68
vendor/github.com/docker/docker/pkg/system/utimes_unix_test.go
generated
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
// +build linux freebsd
|
||||
|
||||
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{{Sec: 0, Nsec: 0}, {Sec: 0, Nsec: 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")
|
||||
}
|
||||
}
|
10
vendor/github.com/docker/docker/pkg/system/utimes_unsupported.go
generated
vendored
Normal file
10
vendor/github.com/docker/docker/pkg/system/utimes_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// +build !linux,!freebsd
|
||||
|
||||
package system
|
||||
|
||||
import "syscall"
|
||||
|
||||
// LUtimesNano is only supported on linux and freebsd.
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
63
vendor/github.com/docker/docker/pkg/system/xattrs_linux.go
generated
vendored
Normal file
63
vendor/github.com/docker/docker/pkg/system/xattrs_linux.go
generated
vendored
Normal file
@ -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
|
||||
}
|
13
vendor/github.com/docker/docker/pkg/system/xattrs_unsupported.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/xattrs_unsupported.go
generated
vendored
Normal file
@ -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
|
||||
}
|
143
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/linux.go
generated
vendored
Normal file
143
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/linux.go
generated
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
// +build linux
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// If arg2 is nonzero, set the "child subreaper" attribute of the
|
||||
// calling process; if arg2 is zero, unset the attribute. When a
|
||||
// process is marked as a child subreaper, all of the children
|
||||
// that it creates, and their descendants, will be marked as
|
||||
// having a subreaper. In effect, a subreaper fulfills the role
|
||||
// of init(1) for its descendant processes. Upon termination of
|
||||
// a process that is orphaned (i.e., its immediate parent has
|
||||
// already terminated) and marked as having a subreaper, the
|
||||
// nearest still living ancestor subreaper will receive a SIGCHLD
|
||||
// signal and be able to wait(2) on the process to discover its
|
||||
// termination status.
|
||||
const PR_SET_CHILD_SUBREAPER = 36
|
||||
|
||||
type ParentDeathSignal int
|
||||
|
||||
func (p ParentDeathSignal) Restore() error {
|
||||
if p == 0 {
|
||||
return nil
|
||||
}
|
||||
current, err := GetParentDeathSignal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p == current {
|
||||
return nil
|
||||
}
|
||||
return p.Set()
|
||||
}
|
||||
|
||||
func (p ParentDeathSignal) Set() error {
|
||||
return SetParentDeathSignal(uintptr(p))
|
||||
}
|
||||
|
||||
func Execv(cmd string, args []string, env []string) error {
|
||||
name, err := exec.LookPath(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return syscall.Exec(name, args, env)
|
||||
}
|
||||
|
||||
func Prlimit(pid, resource int, limit syscall.Rlimit) error {
|
||||
_, _, err := syscall.RawSyscall6(syscall.SYS_PRLIMIT64, uintptr(pid), uintptr(resource), uintptr(unsafe.Pointer(&limit)), uintptr(unsafe.Pointer(&limit)), 0, 0)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetParentDeathSignal(sig uintptr) error {
|
||||
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_PDEATHSIG, sig, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetParentDeathSignal() (ParentDeathSignal, error) {
|
||||
var sig int
|
||||
_, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_GET_PDEATHSIG, uintptr(unsafe.Pointer(&sig)), 0)
|
||||
if err != 0 {
|
||||
return -1, err
|
||||
}
|
||||
return ParentDeathSignal(sig), nil
|
||||
}
|
||||
|
||||
func SetKeepCaps() error {
|
||||
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 1, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ClearKeepCaps() error {
|
||||
if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_KEEPCAPS, 0, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Setctty() error {
|
||||
if _, _, err := syscall.RawSyscall(syscall.SYS_IOCTL, 0, uintptr(syscall.TIOCSCTTY), 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunningInUserNS detects whether we are currently running in a user namespace.
|
||||
// Copied from github.com/lxc/lxd/shared/util.go
|
||||
func RunningInUserNS() bool {
|
||||
file, err := os.Open("/proc/self/uid_map")
|
||||
if err != nil {
|
||||
// This kernel-provided file only exists if user namespaces are supported
|
||||
return false
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buf := bufio.NewReader(file)
|
||||
l, _, err := buf.ReadLine()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
line := string(l)
|
||||
var a, b, c int64
|
||||
fmt.Sscanf(line, "%d %d %d", &a, &b, &c)
|
||||
/*
|
||||
* We assume we are in the initial user namespace if we have a full
|
||||
* range - 4294967295 uids starting at uid 0.
|
||||
*/
|
||||
if a == 0 && b == 0 && c == 4294967295 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SetSubreaper sets the value i as the subreaper setting for the calling process
|
||||
func SetSubreaper(i int) error {
|
||||
return Prctl(PR_SET_CHILD_SUBREAPER, uintptr(i), 0, 0, 0)
|
||||
}
|
||||
|
||||
func Prctl(option int, arg2, arg3, arg4, arg5 uintptr) (err error) {
|
||||
_, _, e1 := syscall.Syscall6(syscall.SYS_PRCTL, uintptr(option), arg2, arg3, arg4, arg5, 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
43
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/proc.go
generated
vendored
Normal file
43
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/proc.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// look in /proc to find the process start time so that we can verify
|
||||
// that this pid has started after ourself
|
||||
func GetProcessStartTime(pid int) (string, error) {
|
||||
data, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return parseStartTime(string(data))
|
||||
}
|
||||
|
||||
func parseStartTime(stat string) (string, error) {
|
||||
// the starttime is located at pos 22
|
||||
// from the man page
|
||||
//
|
||||
// starttime %llu (was %lu before Linux 2.6)
|
||||
// (22) The time the process started after system boot. In kernels before Linux 2.6, this
|
||||
// value was expressed in jiffies. Since Linux 2.6, the value is expressed in clock ticks
|
||||
// (divide by sysconf(_SC_CLK_TCK)).
|
||||
//
|
||||
// NOTE:
|
||||
// pos 2 could contain space and is inside `(` and `)`:
|
||||
// (2) comm %s
|
||||
// The filename of the executable, in parentheses.
|
||||
// This is visible whether or not the executable is
|
||||
// swapped out.
|
||||
//
|
||||
// the following is an example:
|
||||
// 89653 (gunicorn: maste) S 89630 89653 89653 0 -1 4194560 29689 28896 0 3 146 32 76 19 20 0 1 0 2971844 52965376 3920 18446744073709551615 1 1 0 0 0 0 0 16781312 137447943 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
|
||||
// get parts after last `)`:
|
||||
s := strings.Split(stat, ")")
|
||||
parts := strings.Split(strings.TrimSpace(s[len(s)-1]), " ")
|
||||
return parts[22-3], nil // starts at 3 (after the filename pos `2`)
|
||||
}
|
40
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/setns_linux.go
generated
vendored
Normal file
40
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/setns_linux.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Via http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=7b21fddd087678a70ad64afc0f632e0f1071b092
|
||||
//
|
||||
// We need different setns values for the different platforms and arch
|
||||
// We are declaring the macro here because the SETNS syscall does not exist in th stdlib
|
||||
var setNsMap = map[string]uintptr{
|
||||
"linux/386": 346,
|
||||
"linux/arm64": 268,
|
||||
"linux/amd64": 308,
|
||||
"linux/arm": 375,
|
||||
"linux/ppc": 350,
|
||||
"linux/ppc64": 350,
|
||||
"linux/ppc64le": 350,
|
||||
"linux/s390x": 339,
|
||||
}
|
||||
|
||||
var sysSetns = setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)]
|
||||
|
||||
func SysSetns() uint32 {
|
||||
return uint32(sysSetns)
|
||||
}
|
||||
|
||||
func Setns(fd uintptr, flags uintptr) error {
|
||||
ns, exists := setNsMap[fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)]
|
||||
if !exists {
|
||||
return fmt.Errorf("unsupported platform %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
_, _, err := syscall.RawSyscall(ns, fd, flags, 0)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
25
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/syscall_linux_386.go
generated
vendored
Normal file
25
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/syscall_linux_386.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// +build linux,386
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Setuid sets the uid of the calling thread to the specified uid.
|
||||
func Setuid(uid int) (err error) {
|
||||
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID32, uintptr(uid), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Setgid sets the gid of the calling thread to the specified gid.
|
||||
func Setgid(gid int) (err error) {
|
||||
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
25
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/syscall_linux_64.go
generated
vendored
Normal file
25
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/syscall_linux_64.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Setuid sets the uid of the calling thread to the specified uid.
|
||||
func Setuid(uid int) (err error) {
|
||||
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(uid), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Setgid sets the gid of the calling thread to the specified gid.
|
||||
func Setgid(gid int) (err error) {
|
||||
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID, uintptr(gid), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
25
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/syscall_linux_arm.go
generated
vendored
Normal file
25
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/syscall_linux_arm.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// +build linux,arm
|
||||
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Setuid sets the uid of the calling thread to the specified uid.
|
||||
func Setuid(uid int) (err error) {
|
||||
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID32, uintptr(uid), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Setgid sets the gid of the calling thread to the specified gid.
|
||||
func Setgid(gid int) (err error) {
|
||||
_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0)
|
||||
if e1 != 0 {
|
||||
err = e1
|
||||
}
|
||||
return
|
||||
}
|
12
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/sysconfig.go
generated
vendored
Normal file
12
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/sysconfig.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// +build cgo,linux cgo,freebsd
|
||||
|
||||
package system
|
||||
|
||||
/*
|
||||
#include <unistd.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
func GetClockTicks() int {
|
||||
return int(C.sysconf(C._SC_CLK_TCK))
|
||||
}
|
15
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/sysconfig_notcgo.go
generated
vendored
Normal file
15
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/sysconfig_notcgo.go
generated
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// +build !cgo windows
|
||||
|
||||
package system
|
||||
|
||||
func GetClockTicks() int {
|
||||
// TODO figure out a better alternative for platforms where we're missing cgo
|
||||
//
|
||||
// TODO Windows. This could be implemented using Win32 QueryPerformanceFrequency().
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms644905(v=vs.85).aspx
|
||||
//
|
||||
// An example of its usage can be found here.
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx
|
||||
|
||||
return 100
|
||||
}
|
9
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/unsupported.go
generated
vendored
Normal file
9
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/unsupported.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
// +build !linux
|
||||
|
||||
package system
|
||||
|
||||
// RunningInUserNS is a stub for non-Linux systems
|
||||
// Always returns false
|
||||
func RunningInUserNS() bool {
|
||||
return false
|
||||
}
|
99
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/xattrs_linux.go
generated
vendored
Normal file
99
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/system/xattrs_linux.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var _zero uintptr
|
||||
|
||||
// Returns the size of xattrs and nil error
|
||||
// Requires path, takes allocated []byte or nil as last argument
|
||||
func Llistxattr(path string, dest []byte) (size int, err error) {
|
||||
pathBytes, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
var newpathBytes unsafe.Pointer
|
||||
if len(dest) > 0 {
|
||||
newpathBytes = unsafe.Pointer(&dest[0])
|
||||
} else {
|
||||
newpathBytes = unsafe.Pointer(&_zero)
|
||||
}
|
||||
|
||||
_size, _, errno := syscall.Syscall6(syscall.SYS_LLISTXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(newpathBytes), uintptr(len(dest)), 0, 0, 0)
|
||||
size = int(_size)
|
||||
if errno != 0 {
|
||||
return -1, errno
|
||||
}
|
||||
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// Returns a []byte slice if the xattr is set and nil otherwise
|
||||
// Requires path and its attribute as arguments
|
||||
func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
var sz int
|
||||
pathBytes, err := syscall.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attrBytes, err := syscall.BytePtrFromString(attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Start with a 128 length byte array
|
||||
sz = 128
|
||||
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)
|
||||
|
||||
switch {
|
||||
case errno == syscall.ENODATA:
|
||||
return nil, errno
|
||||
case errno == syscall.ENOTSUP:
|
||||
return nil, errno
|
||||
case errno == syscall.ERANGE:
|
||||
// 128 byte array might just not be good enough,
|
||||
// A dummy buffer is used ``uintptr(0)`` to get real size
|
||||
// of the xattrs on disk
|
||||
_sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(unsafe.Pointer(nil)), uintptr(0), 0, 0)
|
||||
sz = int(_sz)
|
||||
if sz < 0 {
|
||||
return nil, errno
|
||||
}
|
||||
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
|
||||
}
|
||||
case errno != 0:
|
||||
return nil, errno
|
||||
}
|
||||
sz = int(_sz)
|
||||
return dest[:sz], nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
110
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/user/lookup.go
generated
vendored
Normal file
110
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/user/lookup.go
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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")
|
||||
// No matching entries found in file.
|
||||
ErrNoPasswdEntries = errors.New("no matching entries in passwd file")
|
||||
ErrNoGroupEntries = errors.New("no matching entries in group file")
|
||||
)
|
||||
|
||||
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{}, ErrNoPasswdEntries
|
||||
}
|
||||
|
||||
// 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{}, ErrNoGroupEntries
|
||||
}
|
||||
|
||||
// Assume the first entry is the "correct" one.
|
||||
return groups[0], nil
|
||||
}
|
||||
|
||||
// CurrentGroup looks up the current user's group by their primary group id's
|
||||
// entry in /etc/passwd. If the group cannot be found (or there is no
|
||||
// /etc/group file on the filesystem), then CurrentGroup returns an error.
|
||||
func CurrentGroup() (Group, error) {
|
||||
return LookupGid(syscall.Getgid())
|
||||
}
|
||||
|
||||
// LookupGroup looks up a group by its name in /etc/group. If the group cannot
|
||||
// be found (or there is no /etc/group file on the filesystem), then LookupGroup
|
||||
// returns an error.
|
||||
func LookupGroup(groupname string) (Group, error) {
|
||||
return lookupGroup(func(g Group) bool {
|
||||
return g.Name == groupname
|
||||
})
|
||||
}
|
||||
|
||||
// LookupGid looks up a group by its group id in /etc/group. If the group cannot
|
||||
// be found (or there is no /etc/group file on the filesystem), then LookupGid
|
||||
// returns an error.
|
||||
func LookupGid(gid int) (Group, error) {
|
||||
return lookupGroup(func(g Group) bool {
|
||||
return g.Gid == gid
|
||||
})
|
||||
}
|
30
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go
generated
vendored
Normal file
30
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Unix-specific path to the passwd and group formatted files.
|
||||
const (
|
||||
unixPasswdPath = "/etc/passwd"
|
||||
unixGroupPath = "/etc/group"
|
||||
)
|
||||
|
||||
func GetPasswdPath() (string, error) {
|
||||
return unixPasswdPath, nil
|
||||
}
|
||||
|
||||
func GetPasswd() (io.ReadCloser, error) {
|
||||
return os.Open(unixPasswdPath)
|
||||
}
|
||||
|
||||
func GetGroupPath() (string, error) {
|
||||
return unixGroupPath, nil
|
||||
}
|
||||
|
||||
func GetGroup() (io.ReadCloser, error) {
|
||||
return os.Open(unixGroupPath)
|
||||
}
|
21
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go
generated
vendored
Normal file
21
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||
|
||||
package user
|
||||
|
||||
import "io"
|
||||
|
||||
func GetPasswdPath() (string, error) {
|
||||
return "", ErrUnsupported
|
||||
}
|
||||
|
||||
func GetPasswd() (io.ReadCloser, error) {
|
||||
return nil, ErrUnsupported
|
||||
}
|
||||
|
||||
func GetGroupPath() (string, error) {
|
||||
return "", ErrUnsupported
|
||||
}
|
||||
|
||||
func GetGroup() (io.ReadCloser, error) {
|
||||
return nil, ErrUnsupported
|
||||
}
|
441
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/user/user.go
generated
vendored
Normal file
441
vendor/github.com/docker/docker/vendor/github.com/opencontainers/runc/libcontainer/user/user.go
generated
vendored
Normal file
@ -0,0 +1,441 @@
|
||||
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 {
|
||||
// Ignore cases where we don't have enough fields to populate the arguments.
|
||||
// Some configuration files like to misbehave.
|
||||
if len(v) <= i {
|
||||
break
|
||||
}
|
||||
|
||||
// Use the type of the argument to figure out how to parse it, scanf() style.
|
||||
// This is legit.
|
||||
switch e := v[i].(type) {
|
||||
case *string:
|
||||
*e = p
|
||||
case *int:
|
||||
// "numbers", with conversion errors ignored because of some misbehaving configuration files.
|
||||
*e, _ = strconv.Atoi(p)
|
||||
case *[]string:
|
||||
// Comma-separated lists.
|
||||
if p != "" {
|
||||
*e = strings.Split(p, ",")
|
||||
} else {
|
||||
*e = []string{}
|
||||
}
|
||||
default:
|
||||
// Someone goof'd when writing code using this function. Scream so they can hear us.
|
||||
panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
line := strings.TrimSpace(s.Text())
|
||||
if line == "" {
|
||||
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(line, &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 int
|
||||
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"
|
||||
//
|
||||
// It should be noted that if you specify a numeric user or group id, they will
|
||||
// not be evaluated as usernames (only the metadata will be filled). So attempting
|
||||
// to parse a user with user.Name = "1337" will produce the user with a UID of
|
||||
// 1337.
|
||||
func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
|
||||
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
|
||||
var userArg, groupArg string
|
||||
parseLine(userSpec, &userArg, &groupArg)
|
||||
|
||||
// Convert userArg and groupArg to be numeric, so we don't have to execute
|
||||
// Atoi *twice* for each iteration over lines.
|
||||
uidArg, uidErr := strconv.Atoi(userArg)
|
||||
gidArg, gidErr := strconv.Atoi(groupArg)
|
||||
|
||||
// Find the matching user.
|
||||
users, err := ParsePasswdFilter(passwd, func(u User) bool {
|
||||
if userArg == "" {
|
||||
// Default to current state of the user.
|
||||
return u.Uid == user.Uid
|
||||
}
|
||||
|
||||
if uidErr == nil {
|
||||
// If the userArg is numeric, always treat it as a UID.
|
||||
return uidArg == u.Uid
|
||||
}
|
||||
|
||||
return u.Name == userArg
|
||||
})
|
||||
|
||||
// If we can't find the user, we have to bail.
|
||||
if err != nil && passwd != nil {
|
||||
if userArg == "" {
|
||||
userArg = strconv.Itoa(user.Uid)
|
||||
}
|
||||
return nil, fmt.Errorf("unable to find user %s: %v", userArg, err)
|
||||
}
|
||||
|
||||
var matchedUserName string
|
||||
if len(users) > 0 {
|
||||
// First match wins, even if there's more than one matching entry.
|
||||
matchedUserName = users[0].Name
|
||||
user.Uid = users[0].Uid
|
||||
user.Gid = users[0].Gid
|
||||
user.Home = users[0].Home
|
||||
} else if userArg != "" {
|
||||
// If we can't find a user with the given username, the only other valid
|
||||
// option is if it's a numeric username with no associated entry in passwd.
|
||||
|
||||
if uidErr != nil {
|
||||
// Not numeric.
|
||||
return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries)
|
||||
}
|
||||
user.Uid = uidArg
|
||||
|
||||
// Must be inside valid uid range.
|
||||
if user.Uid < minId || user.Uid > maxId {
|
||||
return nil, ErrRange
|
||||
}
|
||||
|
||||
// Okay, so it's numeric. We can just roll with this.
|
||||
}
|
||||
|
||||
// On to the groups. If we matched a username, we need to do this because of
|
||||
// the supplementary group IDs.
|
||||
if groupArg != "" || matchedUserName != "" {
|
||||
groups, err := ParseGroupFilter(group, func(g Group) bool {
|
||||
// If the group argument isn't explicit, we'll just search for it.
|
||||
if groupArg == "" {
|
||||
// Check if user is a member of this group.
|
||||
for _, u := range g.List {
|
||||
if u == matchedUserName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if gidErr == nil {
|
||||
// If the groupArg is numeric, always treat it as a GID.
|
||||
return gidArg == g.Gid
|
||||
}
|
||||
|
||||
return g.Name == groupArg
|
||||
})
|
||||
if err != nil && group != nil {
|
||||
return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err)
|
||||
}
|
||||
|
||||
// Only start modifying user.Gid if it is in explicit form.
|
||||
if groupArg != "" {
|
||||
if len(groups) > 0 {
|
||||
// First match wins, even if there's more than one matching entry.
|
||||
user.Gid = groups[0].Gid
|
||||
} else if groupArg != "" {
|
||||
// If we can't find a group with the given name, the only other valid
|
||||
// option is if it's a numeric group name with no associated entry in group.
|
||||
|
||||
if gidErr != nil {
|
||||
// Not numeric.
|
||||
return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries)
|
||||
}
|
||||
user.Gid = gidArg
|
||||
|
||||
// Must be inside valid gid range.
|
||||
if user.Gid < minId || user.Gid > maxId {
|
||||
return nil, ErrRange
|
||||
}
|
||||
|
||||
// Okay, so it's numeric. We can just roll with this.
|
||||
}
|
||||
} else if len(groups) > 0 {
|
||||
// Supplementary group ids only make sense if in the implicit form.
|
||||
user.Sgids = make([]int, len(groups))
|
||||
for i, group := range groups {
|
||||
user.Sgids[i] = group.Gid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetAdditionalGroups looks up a list of groups by name or group id
|
||||
// against the given /etc/group formatted data. If a group name cannot
|
||||
// be found, an error will be returned. If a group id cannot be found,
|
||||
// or the given group data is nil, the id will be returned as-is
|
||||
// provided it is in the legal range.
|
||||
func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
|
||||
var groups = []Group{}
|
||||
if group != nil {
|
||||
var err error
|
||||
groups, err = ParseGroupFilter(group, func(g Group) bool {
|
||||
for _, ag := range additionalGroups {
|
||||
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err)
|
||||
}
|
||||
}
|
||||
|
||||
gidMap := make(map[int]struct{})
|
||||
for _, ag := range additionalGroups {
|
||||
var found bool
|
||||
for _, g := range groups {
|
||||
// if we found a matched group either by name or gid, take the
|
||||
// first matched as correct
|
||||
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||
if _, ok := gidMap[g.Gid]; !ok {
|
||||
gidMap[g.Gid] = struct{}{}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// we asked for a group but didn't find it. let's check to see
|
||||
// if we wanted a numeric group
|
||||
if !found {
|
||||
gid, err := strconv.Atoi(ag)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to find group %s", ag)
|
||||
}
|
||||
// Ensure gid is inside gid range.
|
||||
if gid < minId || gid > maxId {
|
||||
return nil, ErrRange
|
||||
}
|
||||
gidMap[gid] = struct{}{}
|
||||
}
|
||||
}
|
||||
gids := []int{}
|
||||
for gid := range gidMap {
|
||||
gids = append(gids, gid)
|
||||
}
|
||||
return gids, nil
|
||||
}
|
||||
|
||||
// GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups
|
||||
// that opens the groupPath given and gives it as an argument to
|
||||
// GetAdditionalGroups.
|
||||
func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
|
||||
group, err := os.Open(groupPath)
|
||||
if err == nil {
|
||||
defer group.Close()
|
||||
}
|
||||
return GetAdditionalGroups(additionalGroups, group)
|
||||
}
|
Loading…
Reference in New Issue
Block a user