Merge branch master into bump_v1.4.0

Docker-DCO-1.1-Signed-off-by: Jessica Frazelle <jess@docker.com> (github: jfrazelle)
This commit is contained in:
Jessica Frazelle 2014-12-11 15:00:03 -08:00
commit e4b13e4efb
88 changed files with 3624 additions and 496 deletions

View file

@ -18,8 +18,8 @@ import (
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/log"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/system"
@ -34,6 +34,7 @@ type (
Excludes []string
Compression Compression
NoLchown bool
Name string
}
// Archiver allows the reuse of most utility functions of this package
@ -164,7 +165,15 @@ func (compression *Compression) Extension() string {
return ""
}
func addTarFile(path, name string, tw *tar.Writer, twBuf *bufio.Writer) error {
type tarAppender struct {
TarWriter *tar.Writer
Buffer *bufio.Writer
// for hardlink mapping
SeenFiles map[uint64]string
}
func (ta *tarAppender) addTarFile(path, name string) error {
fi, err := os.Lstat(path)
if err != nil {
return err
@ -188,15 +197,23 @@ func addTarFile(path, name string, tw *tar.Writer, twBuf *bufio.Writer) error {
hdr.Name = name
stat, ok := fi.Sys().(*syscall.Stat_t)
if ok {
// Currently go does not fill in the major/minors
if stat.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
stat.Mode&syscall.S_IFCHR == syscall.S_IFCHR {
hdr.Devmajor = int64(major(uint64(stat.Rdev)))
hdr.Devminor = int64(minor(uint64(stat.Rdev)))
}
nlink, inode, err := setHeaderForSpecialDevice(hdr, ta, name, fi.Sys())
if err != nil {
return err
}
// if it's a regular file and has more than 1 link,
// it's hardlinked, so set the type flag accordingly
if fi.Mode().IsRegular() && nlink > 1 {
// a link should have a name that it links too
// and that linked name should be first in the tar archive
if oldpath, ok := ta.SeenFiles[inode]; ok {
hdr.Typeflag = tar.TypeLink
hdr.Linkname = oldpath
hdr.Size = 0 // This Must be here for the writer math to add up!
} else {
ta.SeenFiles[inode] = name
}
}
capability, _ := system.Lgetxattr(path, "security.capability")
@ -205,7 +222,7 @@ func addTarFile(path, name string, tw *tar.Writer, twBuf *bufio.Writer) error {
hdr.Xattrs["security.capability"] = string(capability)
}
if err := tw.WriteHeader(hdr); err != nil {
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
return err
}
@ -215,17 +232,17 @@ func addTarFile(path, name string, tw *tar.Writer, twBuf *bufio.Writer) error {
return err
}
twBuf.Reset(tw)
_, err = io.Copy(twBuf, file)
ta.Buffer.Reset(ta.TarWriter)
defer ta.Buffer.Reset(nil)
_, err = io.Copy(ta.Buffer, file)
file.Close()
if err != nil {
return err
}
err = twBuf.Flush()
err = ta.Buffer.Flush()
if err != nil {
return err
}
twBuf.Reset(nil)
}
return nil
@ -270,7 +287,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
mode |= syscall.S_IFIFO
}
if err := syscall.Mknod(path, mode, int(mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
return err
}
@ -370,9 +387,15 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
return nil, err
}
tw := tar.NewWriter(compressWriter)
go func() {
ta := &tarAppender{
TarWriter: tar.NewWriter(compressWriter),
Buffer: pools.BufioWriter32KPool.Get(nil),
SeenFiles: make(map[uint64]string),
}
// this buffer is needed for the duration of this piped stream
defer pools.BufioWriter32KPool.Put(ta.Buffer)
// In general we log errors here but ignore them because
// during e.g. a diff operation the container can continue
// mutating the filesystem and we can see transient errors
@ -382,9 +405,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
options.Includes = []string{"."}
}
twBuf := pools.BufioWriter32KPool.Get(nil)
defer pools.BufioWriter32KPool.Put(twBuf)
var renamedRelFilePath string // For when tar.Options.Name is set
for _, include := range options.Includes {
filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error {
if err != nil {
@ -393,7 +414,9 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
}
relFilePath, err := filepath.Rel(srcPath, filePath)
if err != nil {
if err != nil || (relFilePath == "." && f.IsDir()) {
// Error getting relative path OR we are looking
// at the root path. Skip in both situations.
return nil
}
@ -410,7 +433,16 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
return nil
}
if err := addTarFile(filePath, relFilePath, tw, twBuf); err != nil {
// Rename the base resource
if options.Name != "" && filePath == srcPath+"/"+filepath.Base(relFilePath) {
renamedRelFilePath = relFilePath
}
// Set this to make sure the items underneath also get renamed
if options.Name != "" {
relFilePath = strings.Replace(relFilePath, renamedRelFilePath, options.Name, 1)
}
if err := ta.addTarFile(filePath, relFilePath); err != nil {
log.Debugf("Can't add file %s to tar: %s", srcPath, err)
}
return nil
@ -418,7 +450,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
}
// Make sure to check the error on Close.
if err := tw.Close(); err != nil {
if err := ta.TarWriter.Close(); err != nil {
log.Debugf("Can't close tar writer: %s", err)
}
if err := compressWriter.Close(); err != nil {
@ -737,17 +769,33 @@ func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
return nil, err
}
size := st.Size()
return &TempArchive{f, size}, nil
return &TempArchive{File: f, Size: size}, nil
}
type TempArchive struct {
*os.File
Size int64 // Pre-computed from Stat().Size() as a convenience
Size int64 // Pre-computed from Stat().Size() as a convenience
read int64
closed bool
}
// Close closes the underlying file if it's still open, or does a no-op
// to allow callers to try to close the TempArchive multiple times safely.
func (archive *TempArchive) Close() error {
if archive.closed {
return nil
}
archive.closed = true
return archive.File.Close()
}
func (archive *TempArchive) Read(data []byte) (int, error) {
n, err := archive.File.Read(data)
if err != nil {
archive.read += int64(n)
if err != nil || archive.read == archive.Size {
archive.Close()
os.Remove(archive.File.Name())
}
return n, err

View file

@ -9,6 +9,8 @@ import (
"os/exec"
"path"
"path/filepath"
"strings"
"syscall"
"testing"
"time"
@ -64,6 +66,50 @@ func TestCmdStreamGood(t *testing.T) {
}
}
func TestTarFiles(t *testing.T) {
// try without hardlinks
if err := checkNoChanges(1000, false); err != nil {
t.Fatal(err)
}
// try with hardlinks
if err := checkNoChanges(1000, true); err != nil {
t.Fatal(err)
}
}
func checkNoChanges(fileNum int, hardlinks bool) error {
srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
if err != nil {
return err
}
defer os.RemoveAll(srcDir)
destDir, err := ioutil.TempDir("", "docker-test-destDir")
if err != nil {
return err
}
defer os.RemoveAll(destDir)
_, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks)
if err != nil {
return err
}
err = TarUntar(srcDir, destDir)
if err != nil {
return err
}
changes, err := ChangesDirs(destDir, srcDir)
if err != nil {
return err
}
if len(changes) > 0 {
return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes))
}
return nil
}
func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
archive, err := TarWithOptions(origin, options)
if err != nil {
@ -210,13 +256,100 @@ func TestUntarUstarGnuConflict(t *testing.T) {
}
}
func prepareUntarSourceDirectory(numberOfFiles int, targetPath string) (int, error) {
func TestTarWithHardLink(t *testing.T) {
origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(origin)
if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
t.Fatal(err)
}
if err := os.Link(path.Join(origin, "1"), path.Join(origin, "2")); err != nil {
t.Fatal(err)
}
var i1, i2 uint64
if i1, err = getNlink(path.Join(origin, "1")); err != nil {
t.Fatal(err)
}
// sanity check that we can hardlink
if i1 != 2 {
t.Skipf("skipping since hardlinks don't work here; expected 2 links, got %d", i1)
}
dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dest)
// we'll do this in two steps to separate failure
fh, err := Tar(origin, Uncompressed)
if err != nil {
t.Fatal(err)
}
// ensure we can read the whole thing with no error, before writing back out
buf, err := ioutil.ReadAll(fh)
if err != nil {
t.Fatal(err)
}
bRdr := bytes.NewReader(buf)
err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed})
if err != nil {
t.Fatal(err)
}
if i1, err = getInode(path.Join(dest, "1")); err != nil {
t.Fatal(err)
}
if i2, err = getInode(path.Join(dest, "2")); err != nil {
t.Fatal(err)
}
if i1 != i2 {
t.Errorf("expected matching inodes, but got %d and %d", i1, i2)
}
}
func getNlink(path string) (uint64, error) {
stat, err := os.Stat(path)
if err != nil {
return 0, err
}
statT, ok := stat.Sys().(*syscall.Stat_t)
if !ok {
return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
}
return statT.Nlink, nil
}
func getInode(path string) (uint64, error) {
stat, err := os.Stat(path)
if err != nil {
return 0, err
}
statT, ok := stat.Sys().(*syscall.Stat_t)
if !ok {
return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
}
return statT.Ino, nil
}
func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
fileData := []byte("fooo")
for n := 0; n < numberOfFiles; n++ {
fileName := fmt.Sprintf("file-%d", n)
if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil {
return 0, err
}
if makeLinks {
if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil {
return 0, err
}
}
}
totalSize := numberOfFiles * len(fileData)
return totalSize, nil
@ -232,14 +365,43 @@ func BenchmarkTarUntar(b *testing.B) {
b.Fatal(err)
}
target := path.Join(tempDir, "dest")
n, err := prepareUntarSourceDirectory(100, origin)
n, err := prepareUntarSourceDirectory(100, origin, false)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
b.SetBytes(int64(n))
defer os.RemoveAll(origin)
defer os.RemoveAll(tempDir)
b.ResetTimer()
b.SetBytes(int64(n))
for n := 0; n < b.N; n++ {
err := TarUntar(origin, target)
if err != nil {
b.Fatal(err)
}
os.RemoveAll(target)
}
}
func BenchmarkTarUntarWithLinks(b *testing.B) {
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
if err != nil {
b.Fatal(err)
}
tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
if err != nil {
b.Fatal(err)
}
target := path.Join(tempDir, "dest")
n, err := prepareUntarSourceDirectory(100, origin, true)
if err != nil {
b.Fatal(err)
}
defer os.RemoveAll(origin)
defer os.RemoveAll(tempDir)
b.ResetTimer()
b.SetBytes(int64(n))
for n := 0; n < b.N; n++ {
err := TarUntar(origin, target)
if err != nil {
@ -446,3 +608,18 @@ func TestUntarInvalidSymlink(t *testing.T) {
}
}
}
func TestTempArchiveCloseMultipleTimes(t *testing.T) {
reader := ioutil.NopCloser(strings.NewReader("hello"))
tempArchive, err := NewTempArchive(reader, "")
buf := make([]byte, 10)
n, err := tempArchive.Read(buf)
if n != 5 {
t.Fatalf("Expected to read 5 bytes. Read %d instead", n)
}
for i := 0; i < 3; i++ {
if err = tempArchive.Close(); err != nil {
t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err)
}
}
}

39
archive/archive_unix.go Normal file
View file

@ -0,0 +1,39 @@
// +build !windows
package archive
import (
"errors"
"syscall"
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
s, ok := stat.(*syscall.Stat_t)
if !ok {
err = errors.New("cannot convert stat value to syscall.Stat_t")
return
}
nlink = uint32(s.Nlink)
inode = uint64(s.Ino)
// Currently go does not fil in the major/minors
if s.Mode&syscall.S_IFBLK == syscall.S_IFBLK ||
s.Mode&syscall.S_IFCHR == syscall.S_IFCHR {
hdr.Devmajor = int64(major(uint64(s.Rdev)))
hdr.Devminor = int64(minor(uint64(s.Rdev)))
}
return
}
func major(device uint64) uint64 {
return (device >> 8) & 0xfff
}
func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}

View file

@ -0,0 +1,12 @@
// +build windows
package archive
import (
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (nlink uint32, inode uint64, err error) {
// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
return
}

View file

@ -12,7 +12,7 @@ import (
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
"github.com/docker/docker/pkg/log"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/system"
)
@ -135,7 +135,7 @@ func Changes(layers []string, rw string) ([]Change, error) {
type FileInfo struct {
parent *FileInfo
name string
stat syscall.Stat_t
stat *system.Stat
children map[string]*FileInfo
capability []byte
added bool
@ -168,7 +168,7 @@ func (info *FileInfo) path() string {
}
func (info *FileInfo) isDir() bool {
return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR == syscall.S_IFDIR
}
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
@ -199,21 +199,21 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
oldChild, _ := oldChildren[name]
if oldChild != nil {
// change?
oldStat := &oldChild.stat
newStat := &newChild.stat
oldStat := oldChild.stat
newStat := newChild.stat
// Note: We can't compare inode or ctime or blocksize here, because these change
// when copying a file into a container. However, that is not generally a problem
// because any content change will change mtime, and any status change should
// be visible when actually comparing the stat fields. The only time this
// breaks down is if some code intentionally hides a change by setting
// back mtime
if oldStat.Mode != newStat.Mode ||
oldStat.Uid != newStat.Uid ||
oldStat.Gid != newStat.Gid ||
oldStat.Rdev != newStat.Rdev ||
if oldStat.Mode() != newStat.Mode() ||
oldStat.Uid() != newStat.Uid() ||
oldStat.Gid() != newStat.Gid() ||
oldStat.Rdev() != newStat.Rdev() ||
// Don't look at size for dirs, its not a good measure of change
(oldStat.Size != newStat.Size && oldStat.Mode&syscall.S_IFDIR != syscall.S_IFDIR) ||
!sameFsTimeSpec(system.GetLastModification(oldStat), system.GetLastModification(newStat)) ||
(oldStat.Size() != newStat.Size() && oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR) ||
!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) ||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
change := Change{
Path: newChild.path(),
@ -299,9 +299,11 @@ func collectFileInfo(sourceDir string) (*FileInfo, error) {
parent: parent,
}
if err := syscall.Lstat(path, &info.stat); err != nil {
s, err := system.Lstat(path)
if err != nil {
return err
}
info.stat = s
info.capability, _ = system.Lgetxattr(path, "security.capability")
@ -333,6 +335,8 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) {
newRoot, err2 = collectFileInfo(newDir)
errs <- err2
}()
// block until both routines have returned
for i := 0; i < 2; i++ {
if err := <-errs; err != nil {
return nil, err
@ -357,22 +361,18 @@ func ChangesSize(newDir string, changes []Change) int64 {
return size
}
func major(device uint64) uint64 {
return (device >> 8) & 0xfff
}
func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}
// ExportChanges produces an Archive from the provided changes, relative to dir.
func ExportChanges(dir string, changes []Change) (Archive, error) {
reader, writer := io.Pipe()
tw := tar.NewWriter(writer)
go func() {
twBuf := pools.BufioWriter32KPool.Get(nil)
defer pools.BufioWriter32KPool.Put(twBuf)
ta := &tarAppender{
TarWriter: tar.NewWriter(writer),
Buffer: pools.BufioWriter32KPool.Get(nil),
SeenFiles: make(map[uint64]string),
}
// this buffer is needed for the duration of this piped stream
defer pools.BufioWriter32KPool.Put(ta.Buffer)
// In general we log errors here but ignore them because
// during e.g. a diff operation the container can continue
// mutating the filesystem and we can see transient errors
@ -390,22 +390,24 @@ func ExportChanges(dir string, changes []Change) (Archive, error) {
AccessTime: timestamp,
ChangeTime: timestamp,
}
if err := tw.WriteHeader(hdr); err != nil {
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
log.Debugf("Can't write whiteout header: %s", err)
}
} else {
path := filepath.Join(dir, change.Path)
if err := addTarFile(path, change.Path[1:], tw, twBuf); err != nil {
if err := ta.addTarFile(path, change.Path[1:]); err != nil {
log.Debugf("Can't add file %s to tar: %s", path, err)
}
}
}
// Make sure to check the error on Close.
if err := tw.Close(); err != nil {
if err := ta.TarWriter.Close(); err != nil {
log.Debugf("Can't close layer: %s", err)
}
writer.Close()
if err := writer.Close(); err != nil {
log.Debugf("failed close Changes writer: %s", err)
}
}()
return reader, nil
}

View file

@ -12,15 +12,9 @@ import (
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/system"
)
// 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))
}
func UnpackLayer(dest string, layer ArchiveReader) error {
tr := tar.NewReader(layer)
trBuf := pools.BufioReader32KPool.Get(tr)
@ -155,11 +149,15 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
// applies it to the directory `dest`.
func ApplyLayer(dest string, layer ArchiveReader) error {
dest = filepath.Clean(dest)
// We need to be able to set any perms
oldmask := syscall.Umask(0)
defer syscall.Umask(oldmask)
layer, err := DecompressStream(layer)
// We need to be able to set any perms
oldmask, err := system.Umask(0)
if err != nil {
return err
}
defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
layer, err = DecompressStream(layer)
if err != nil {
return err
}

View file

@ -0,0 +1,97 @@
// +build ignore
// Simple tool to create an archive stream from an old and new directory
//
// By default it will stream the comparison of two temporary directories with junk files
package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/archive"
)
var (
flDebug = flag.Bool("D", false, "debugging output")
flNewDir = flag.String("newdir", "", "")
flOldDir = flag.String("olddir", "", "")
log = logrus.New()
)
func main() {
flag.Usage = func() {
fmt.Println("Produce a tar from comparing two directory paths. By default a demo tar is created of around 200 files (including hardlinks)")
fmt.Printf("%s [OPTIONS]\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
log.Out = os.Stderr
if (len(os.Getenv("DEBUG")) > 0) || *flDebug {
logrus.SetLevel(logrus.DebugLevel)
}
var newDir, oldDir string
if len(*flNewDir) == 0 {
var err error
newDir, err = ioutil.TempDir("", "docker-test-newDir")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(newDir)
if _, err := prepareUntarSourceDirectory(100, newDir, true); err != nil {
log.Fatal(err)
}
} else {
newDir = *flNewDir
}
if len(*flOldDir) == 0 {
oldDir, err := ioutil.TempDir("", "docker-test-oldDir")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(oldDir)
} else {
oldDir = *flOldDir
}
changes, err := archive.ChangesDirs(newDir, oldDir)
if err != nil {
log.Fatal(err)
}
a, err := archive.ExportChanges(newDir, changes)
if err != nil {
log.Fatal(err)
}
defer a.Close()
i, err := io.Copy(os.Stdout, a)
if err != nil && err != io.EOF {
log.Fatal(err)
}
fmt.Fprintf(os.Stderr, "wrote archive of %d bytes", i)
}
func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
fileData := []byte("fooo")
for n := 0; n < numberOfFiles; n++ {
fileName := fmt.Sprintf("file-%d", n)
if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil {
return 0, err
}
if makeLinks {
if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil {
return 0, err
}
}
}
totalSize := numberOfFiles * len(fileData)
return totalSize, nil
}

View file

@ -6,8 +6,8 @@ import (
"sync"
"time"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/jsonlog"
"github.com/docker/docker/pkg/log"
)
// BroadcastWriter accumulate multiple io.WriteCloser by stream.

1
devicemapper/MAINTAINERS Normal file
View file

@ -0,0 +1 @@
Vincent Batts <vbatts@redhat.com> (@vbatts)

View file

@ -0,0 +1,129 @@
// +build linux
package devicemapper
import (
"fmt"
"os"
"syscall"
log "github.com/Sirupsen/logrus"
)
func stringToLoopName(src string) [LoNameSize]uint8 {
var dst [LoNameSize]uint8
copy(dst[:], src[:])
return dst
}
func getNextFreeLoopbackIndex() (int, error) {
f, err := os.OpenFile("/dev/loop-control", os.O_RDONLY, 0644)
if err != nil {
return 0, err
}
defer f.Close()
index, err := ioctlLoopCtlGetFree(f.Fd())
if index < 0 {
index = 0
}
return index, err
}
func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.File, err error) {
// Start looking for a free /dev/loop
for {
target := fmt.Sprintf("/dev/loop%d", index)
index++
fi, err := os.Stat(target)
if err != nil {
if os.IsNotExist(err) {
log.Errorf("There are no more loopback devices available.")
}
return nil, ErrAttachLoopbackDevice
}
if fi.Mode()&os.ModeDevice != os.ModeDevice {
log.Errorf("Loopback device %s is not a block device.", target)
continue
}
// OpenFile adds O_CLOEXEC
loopFile, err = os.OpenFile(target, os.O_RDWR, 0644)
if err != nil {
log.Errorf("Error opening loopback device: %s", err)
return nil, ErrAttachLoopbackDevice
}
// Try to attach to the loop file
if err := ioctlLoopSetFd(loopFile.Fd(), sparseFile.Fd()); err != nil {
loopFile.Close()
// If the error is EBUSY, then try the next loopback
if err != syscall.EBUSY {
log.Errorf("Cannot set up loopback device %s: %s", target, err)
return nil, ErrAttachLoopbackDevice
}
// Otherwise, we keep going with the loop
continue
}
// In case of success, we finished. Break the loop.
break
}
// This can't happen, but let's be sure
if loopFile == nil {
log.Errorf("Unreachable code reached! Error attaching %s to a loopback device.", sparseFile.Name())
return nil, ErrAttachLoopbackDevice
}
return loopFile, nil
}
// attachLoopDevice attaches the given sparse file to the next
// available loopback device. It returns an opened *os.File.
func AttachLoopDevice(sparseName string) (loop *os.File, err error) {
// Try to retrieve the next available loopback device via syscall.
// If it fails, we discard error and start loopking for a
// loopback from index 0.
startIndex, err := getNextFreeLoopbackIndex()
if err != nil {
log.Debugf("Error retrieving the next available loopback: %s", err)
}
// OpenFile adds O_CLOEXEC
sparseFile, err := os.OpenFile(sparseName, os.O_RDWR, 0644)
if err != nil {
log.Errorf("Error opening sparse file %s: %s", sparseName, err)
return nil, ErrAttachLoopbackDevice
}
defer sparseFile.Close()
loopFile, err := openNextAvailableLoopback(startIndex, sparseFile)
if err != nil {
return nil, err
}
// Set the status of the loopback device
loopInfo := &LoopInfo64{
loFileName: stringToLoopName(loopFile.Name()),
loOffset: 0,
loFlags: LoFlagsAutoClear,
}
if err := ioctlLoopSetStatus64(loopFile.Fd(), loopInfo); err != nil {
log.Errorf("Cannot set up loopback device info: %s", err)
// If the call failed, then free the loopback device
if err := ioctlLoopClrFd(loopFile.Fd()); err != nil {
log.Errorf("Error while cleaning up the loopback device")
}
loopFile.Close()
return nil, ErrAttachLoopbackDevice
}
return loopFile, nil
}

669
devicemapper/devmapper.go Normal file
View file

@ -0,0 +1,669 @@
// +build linux
package devicemapper
import (
"errors"
"fmt"
"os"
"runtime"
"syscall"
log "github.com/Sirupsen/logrus"
)
type DevmapperLogger interface {
DMLog(level int, file string, line int, dmError int, message string)
}
const (
DeviceCreate TaskType = iota
DeviceReload
DeviceRemove
DeviceRemoveAll
DeviceSuspend
DeviceResume
DeviceInfo
DeviceDeps
DeviceRename
DeviceVersion
DeviceStatus
DeviceTable
DeviceWaitevent
DeviceList
DeviceClear
DeviceMknodes
DeviceListVersions
DeviceTargetMsg
DeviceSetGeometry
)
const (
AddNodeOnResume AddNodeType = iota
AddNodeOnCreate
)
var (
ErrTaskRun = errors.New("dm_task_run failed")
ErrTaskSetName = errors.New("dm_task_set_name failed")
ErrTaskSetMessage = errors.New("dm_task_set_message failed")
ErrTaskSetAddNode = errors.New("dm_task_set_add_node failed")
ErrTaskSetRo = errors.New("dm_task_set_ro failed")
ErrTaskAddTarget = errors.New("dm_task_add_target failed")
ErrTaskSetSector = errors.New("dm_task_set_sector failed")
ErrTaskGetDeps = errors.New("dm_task_get_deps failed")
ErrTaskGetInfo = errors.New("dm_task_get_info failed")
ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed")
ErrTaskSetCookie = errors.New("dm_task_set_cookie failed")
ErrNilCookie = errors.New("cookie ptr can't be nil")
ErrAttachLoopbackDevice = errors.New("loopback mounting failed")
ErrGetBlockSize = errors.New("Can't get block size")
ErrUdevWait = errors.New("wait on udev cookie failed")
ErrSetDevDir = errors.New("dm_set_dev_dir failed")
ErrGetLibraryVersion = errors.New("dm_get_library_version failed")
ErrCreateRemoveTask = errors.New("Can't create task of type DeviceRemove")
ErrRunRemoveDevice = errors.New("running RemoveDevice failed")
ErrInvalidAddNode = errors.New("Invalid AddNode type")
ErrGetLoopbackBackingFile = errors.New("Unable to get loopback backing file")
ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity")
ErrBusy = errors.New("Device is Busy")
ErrDeviceIdExists = errors.New("Device Id Exists")
dmSawBusy bool
dmSawExist bool
)
type (
Task struct {
unmanaged *CDmTask
}
Deps struct {
Count uint32
Filler uint32
Device []uint64
}
Info struct {
Exists int
Suspended int
LiveTable int
InactiveTable int
OpenCount int32
EventNr uint32
Major uint32
Minor uint32
ReadOnly int
TargetCount int32
}
TaskType int
AddNodeType int
)
// Returns whether error conveys the information about device Id already
// exist or not. This will be true if device creation or snap creation
// operation fails if device or snap device already exists in pool.
// Current implementation is little crude as it scans the error string
// for exact pattern match. Replacing it with more robust implementation
// is desirable.
func DeviceIdExists(err error) bool {
return fmt.Sprint(err) == fmt.Sprint(ErrDeviceIdExists)
}
func (t *Task) destroy() {
if t != nil {
DmTaskDestroy(t.unmanaged)
runtime.SetFinalizer(t, nil)
}
}
// TaskCreateNamed is a convenience function for TaskCreate when a name
// will be set on the task as well
func TaskCreateNamed(t TaskType, name string) (*Task, error) {
task := TaskCreate(t)
if task == nil {
return nil, fmt.Errorf("Can't create task of type %d", int(t))
}
if err := task.SetName(name); err != nil {
return nil, fmt.Errorf("Can't set task name %s", name)
}
return task, nil
}
// TaskCreate initializes a devicemapper task of tasktype
func TaskCreate(tasktype TaskType) *Task {
Ctask := DmTaskCreate(int(tasktype))
if Ctask == nil {
return nil
}
task := &Task{unmanaged: Ctask}
runtime.SetFinalizer(task, (*Task).destroy)
return task
}
func (t *Task) Run() error {
if res := DmTaskRun(t.unmanaged); res != 1 {
return ErrTaskRun
}
return nil
}
func (t *Task) SetName(name string) error {
if res := DmTaskSetName(t.unmanaged, name); res != 1 {
return ErrTaskSetName
}
return nil
}
func (t *Task) SetMessage(message string) error {
if res := DmTaskSetMessage(t.unmanaged, message); res != 1 {
return ErrTaskSetMessage
}
return nil
}
func (t *Task) SetSector(sector uint64) error {
if res := DmTaskSetSector(t.unmanaged, sector); res != 1 {
return ErrTaskSetSector
}
return nil
}
func (t *Task) SetCookie(cookie *uint, flags uint16) error {
if cookie == nil {
return ErrNilCookie
}
if res := DmTaskSetCookie(t.unmanaged, cookie, flags); res != 1 {
return ErrTaskSetCookie
}
return nil
}
func (t *Task) SetAddNode(addNode AddNodeType) error {
if addNode != AddNodeOnResume && addNode != AddNodeOnCreate {
return ErrInvalidAddNode
}
if res := DmTaskSetAddNode(t.unmanaged, addNode); res != 1 {
return ErrTaskSetAddNode
}
return nil
}
func (t *Task) SetRo() error {
if res := DmTaskSetRo(t.unmanaged); res != 1 {
return ErrTaskSetRo
}
return nil
}
func (t *Task) AddTarget(start, size uint64, ttype, params string) error {
if res := DmTaskAddTarget(t.unmanaged, start, size,
ttype, params); res != 1 {
return ErrTaskAddTarget
}
return nil
}
func (t *Task) GetDeps() (*Deps, error) {
var deps *Deps
if deps = DmTaskGetDeps(t.unmanaged); deps == nil {
return nil, ErrTaskGetDeps
}
return deps, nil
}
func (t *Task) GetInfo() (*Info, error) {
info := &Info{}
if res := DmTaskGetInfo(t.unmanaged, info); res != 1 {
return nil, ErrTaskGetInfo
}
return info, nil
}
func (t *Task) GetDriverVersion() (string, error) {
res := DmTaskGetDriverVersion(t.unmanaged)
if res == "" {
return "", ErrTaskGetDriverVersion
}
return res, nil
}
func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64,
length uint64, targetType string, params string) {
return DmGetNextTarget(t.unmanaged, next, &start, &length,
&targetType, &params),
start, length, targetType, params
}
func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) {
loopInfo, err := ioctlLoopGetStatus64(file.Fd())
if err != nil {
log.Errorf("Error get loopback backing file: %s", err)
return 0, 0, ErrGetLoopbackBackingFile
}
return loopInfo.loDevice, loopInfo.loInode, nil
}
func LoopbackSetCapacity(file *os.File) error {
if err := ioctlLoopSetCapacity(file.Fd(), 0); err != nil {
log.Errorf("Error loopbackSetCapacity: %s", err)
return ErrLoopbackSetCapacity
}
return nil
}
func FindLoopDeviceFor(file *os.File) *os.File {
stat, err := file.Stat()
if err != nil {
return nil
}
targetInode := stat.Sys().(*syscall.Stat_t).Ino
targetDevice := stat.Sys().(*syscall.Stat_t).Dev
for i := 0; true; i++ {
path := fmt.Sprintf("/dev/loop%d", i)
file, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
if os.IsNotExist(err) {
return nil
}
// Ignore all errors until the first not-exist
// we want to continue looking for the file
continue
}
dev, inode, err := getLoopbackBackingFile(file)
if err == nil && dev == targetDevice && inode == targetInode {
return file
}
file.Close()
}
return nil
}
func UdevWait(cookie uint) error {
if res := DmUdevWait(cookie); res != 1 {
log.Debugf("Failed to wait on udev cookie %d", cookie)
return ErrUdevWait
}
return nil
}
func LogInitVerbose(level int) {
DmLogInitVerbose(level)
}
var dmLogger DevmapperLogger = nil
// initialize the logger for the device mapper library
func LogInit(logger DevmapperLogger) {
dmLogger = logger
LogWithErrnoInit()
}
func SetDevDir(dir string) error {
if res := DmSetDevDir(dir); res != 1 {
log.Debugf("Error dm_set_dev_dir")
return ErrSetDevDir
}
return nil
}
func GetLibraryVersion() (string, error) {
var version string
if res := DmGetLibraryVersion(&version); res != 1 {
return "", ErrGetLibraryVersion
}
return version, nil
}
// Useful helper for cleanup
func RemoveDevice(name string) error {
log.Debugf("[devmapper] RemoveDevice START")
defer log.Debugf("[devmapper] RemoveDevice END")
task, err := TaskCreateNamed(DeviceRemove, name)
if task == nil {
return err
}
var cookie uint = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can not set cookie: %s", err)
}
defer UdevWait(cookie)
dmSawBusy = false // reset before the task is run
if err = task.Run(); err != nil {
if dmSawBusy {
return ErrBusy
}
return fmt.Errorf("Error running RemoveDevice %s", err)
}
return nil
}
func GetBlockDeviceSize(file *os.File) (uint64, error) {
size, err := ioctlBlkGetSize64(file.Fd())
if err != nil {
log.Errorf("Error getblockdevicesize: %s", err)
return 0, ErrGetBlockSize
}
return uint64(size), nil
}
func BlockDeviceDiscard(path string) error {
file, err := os.OpenFile(path, os.O_RDWR, 0)
if err != nil {
return err
}
defer file.Close()
size, err := GetBlockDeviceSize(file)
if err != nil {
return err
}
if err := ioctlBlkDiscard(file.Fd(), 0, size); err != nil {
return err
}
// Without this sometimes the remove of the device that happens after
// discard fails with EBUSY.
syscall.Sync()
return nil
}
// This is the programmatic example of "dmsetup create"
func CreatePool(poolName string, dataFile, metadataFile *os.File, poolBlockSize uint32) error {
task, err := TaskCreateNamed(DeviceCreate, poolName)
if task == nil {
return err
}
size, err := GetBlockDeviceSize(dataFile)
if err != nil {
return fmt.Errorf("Can't get data size %s", err)
}
params := fmt.Sprintf("%s %s %d 32768 1 skip_block_zeroing", metadataFile.Name(), dataFile.Name(), poolBlockSize)
if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
return fmt.Errorf("Can't add target %s", err)
}
var cookie uint = 0
var flags uint16 = DmUdevDisableSubsystemRulesFlag | DmUdevDisableDiskRulesFlag | DmUdevDisableOtherRulesFlag
if err := task.SetCookie(&cookie, flags); err != nil {
return fmt.Errorf("Can't set cookie %s", err)
}
defer UdevWait(cookie)
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate (CreatePool) %s", err)
}
return nil
}
func ReloadPool(poolName string, dataFile, metadataFile *os.File, poolBlockSize uint32) error {
task, err := TaskCreateNamed(DeviceReload, poolName)
if task == nil {
return err
}
size, err := GetBlockDeviceSize(dataFile)
if err != nil {
return fmt.Errorf("Can't get data size %s", err)
}
params := fmt.Sprintf("%s %s %d 32768 1 skip_block_zeroing", metadataFile.Name(), dataFile.Name(), poolBlockSize)
if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil {
return fmt.Errorf("Can't add target %s", err)
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate %s", err)
}
return nil
}
func GetDeps(name string) (*Deps, error) {
task, err := TaskCreateNamed(DeviceDeps, name)
if task == nil {
return nil, err
}
if err := task.Run(); err != nil {
return nil, err
}
return task.GetDeps()
}
func GetInfo(name string) (*Info, error) {
task, err := TaskCreateNamed(DeviceInfo, name)
if task == nil {
return nil, err
}
if err := task.Run(); err != nil {
return nil, err
}
return task.GetInfo()
}
func GetDriverVersion() (string, error) {
task := TaskCreate(DeviceVersion)
if task == nil {
return "", fmt.Errorf("Can't create DeviceVersion task")
}
if err := task.Run(); err != nil {
return "", err
}
return task.GetDriverVersion()
}
func GetStatus(name string) (uint64, uint64, string, string, error) {
task, err := TaskCreateNamed(DeviceStatus, name)
if task == nil {
log.Debugf("GetStatus: Error TaskCreateNamed: %s", err)
return 0, 0, "", "", err
}
if err := task.Run(); err != nil {
log.Debugf("GetStatus: Error Run: %s", err)
return 0, 0, "", "", err
}
devinfo, err := task.GetInfo()
if err != nil {
log.Debugf("GetStatus: Error GetInfo: %s", err)
return 0, 0, "", "", err
}
if devinfo.Exists == 0 {
log.Debugf("GetStatus: Non existing device %s", name)
return 0, 0, "", "", fmt.Errorf("Non existing device %s", name)
}
_, start, length, targetType, params := task.GetNextTarget(0)
return start, length, targetType, params, nil
}
func SetTransactionId(poolName string, oldId uint64, newId uint64) error {
task, err := TaskCreateNamed(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector %s", err)
}
if err := task.SetMessage(fmt.Sprintf("set_transaction_id %d %d", oldId, newId)); err != nil {
return fmt.Errorf("Can't set message %s", err)
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running SetTransactionId %s", err)
}
return nil
}
func SuspendDevice(name string) error {
task, err := TaskCreateNamed(DeviceSuspend, name)
if task == nil {
return err
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceSuspend %s", err)
}
return nil
}
func ResumeDevice(name string) error {
task, err := TaskCreateNamed(DeviceResume, name)
if task == nil {
return err
}
var cookie uint = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie %s", err)
}
defer UdevWait(cookie)
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceResume %s", err)
}
return nil
}
func CreateDevice(poolName string, deviceId int) error {
log.Debugf("[devmapper] CreateDevice(poolName=%v, deviceId=%v)", poolName, deviceId)
task, err := TaskCreateNamed(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector %s", err)
}
if err := task.SetMessage(fmt.Sprintf("create_thin %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message %s", err)
}
dmSawExist = false // reset before the task is run
if err := task.Run(); err != nil {
// Caller wants to know about ErrDeviceIdExists so that it can try with a different device id.
if dmSawExist {
return ErrDeviceIdExists
} else {
return fmt.Errorf("Error running CreateDevice %s", err)
}
}
return nil
}
func DeleteDevice(poolName string, deviceId int) error {
task, err := TaskCreateNamed(DeviceTargetMsg, poolName)
if task == nil {
return err
}
if err := task.SetSector(0); err != nil {
return fmt.Errorf("Can't set sector %s", err)
}
if err := task.SetMessage(fmt.Sprintf("delete %d", deviceId)); err != nil {
return fmt.Errorf("Can't set message %s", err)
}
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeleteDevice %s", err)
}
return nil
}
func ActivateDevice(poolName string, name string, deviceId int, size uint64) error {
task, err := TaskCreateNamed(DeviceCreate, name)
if task == nil {
return err
}
params := fmt.Sprintf("%s %d", poolName, deviceId)
if err := task.AddTarget(0, size/512, "thin", params); err != nil {
return fmt.Errorf("Can't add target %s", err)
}
if err := task.SetAddNode(AddNodeOnCreate); err != nil {
return fmt.Errorf("Can't add node %s", err)
}
var cookie uint = 0
if err := task.SetCookie(&cookie, 0); err != nil {
return fmt.Errorf("Can't set cookie %s", err)
}
defer UdevWait(cookie)
if err := task.Run(); err != nil {
return fmt.Errorf("Error running DeviceCreate (ActivateDevice) %s", err)
}
return nil
}
func CreateSnapDevice(poolName string, deviceId int, baseName string, baseDeviceId int) error {
devinfo, _ := GetInfo(baseName)
doSuspend := devinfo != nil && devinfo.Exists != 0
if doSuspend {
if err := SuspendDevice(baseName); err != nil {
return err
}
}
task, err := TaskCreateNamed(DeviceTargetMsg, poolName)
if task == nil {
if doSuspend {
ResumeDevice(baseName)
}
return err
}
if err := task.SetSector(0); err != nil {
if doSuspend {
ResumeDevice(baseName)
}
return fmt.Errorf("Can't set sector %s", err)
}
if err := task.SetMessage(fmt.Sprintf("create_snap %d %d", deviceId, baseDeviceId)); err != nil {
if doSuspend {
ResumeDevice(baseName)
}
return fmt.Errorf("Can't set message %s", err)
}
dmSawExist = false // reset before the task is run
if err := task.Run(); err != nil {
if doSuspend {
ResumeDevice(baseName)
}
// Caller wants to know about ErrDeviceIdExists so that it can try with a different device id.
if dmSawExist {
return ErrDeviceIdExists
} else {
return fmt.Errorf("Error running DeviceCreate (createSnapDevice) %s", err)
}
}
if doSuspend {
if err := ResumeDevice(baseName); err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,30 @@
// +build linux
package devicemapper
import "C"
import (
"strings"
)
// Due to the way cgo works this has to be in a separate file, as devmapper.go has
// definitions in the cgo block, which is incompatible with using "//export"
//export DevmapperLogCallback
func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_class C.int, message *C.char) {
msg := C.GoString(message)
if level < 7 {
if strings.Contains(msg, "busy") {
dmSawBusy = true
}
if strings.Contains(msg, "File exists") {
dmSawExist = true
}
}
if dmLogger != nil {
dmLogger.DMLog(int(level), C.GoString(file), int(line), int(dm_errno_or_class), msg)
}
}

View file

@ -0,0 +1,260 @@
// +build linux
package devicemapper
/*
#cgo LDFLAGS: -L. -ldevmapper
#include <libdevmapper.h>
#include <linux/loop.h> // FIXME: present only for defines, maybe we can remove it?
#include <linux/fs.h> // FIXME: present only for BLKGETSIZE64, maybe we can remove it?
#ifndef LOOP_CTL_GET_FREE
#define LOOP_CTL_GET_FREE 0x4C82
#endif
#ifndef LO_FLAGS_PARTSCAN
#define LO_FLAGS_PARTSCAN 8
#endif
// FIXME: Can't we find a way to do the logging in pure Go?
extern void DevmapperLogCallback(int level, char *file, int line, int dm_errno_or_class, char *str);
static void log_cb(int level, const char *file, int line, int dm_errno_or_class, const char *f, ...)
{
char buffer[256];
va_list ap;
va_start(ap, f);
vsnprintf(buffer, 256, f, ap);
va_end(ap);
DevmapperLogCallback(level, (char *)file, line, dm_errno_or_class, buffer);
}
static void log_with_errno_init()
{
dm_log_with_errno_init(log_cb);
}
*/
import "C"
import "unsafe"
type (
CDmTask C.struct_dm_task
CLoopInfo64 C.struct_loop_info64
LoopInfo64 struct {
loDevice uint64 /* ioctl r/o */
loInode uint64 /* ioctl r/o */
loRdevice uint64 /* ioctl r/o */
loOffset uint64
loSizelimit uint64 /* bytes, 0 == max available */
loNumber uint32 /* ioctl r/o */
loEncrypt_type uint32
loEncrypt_key_size uint32 /* ioctl w/o */
loFlags uint32 /* ioctl r/o */
loFileName [LoNameSize]uint8
loCryptName [LoNameSize]uint8
loEncryptKey [LoKeySize]uint8 /* ioctl w/o */
loInit [2]uint64
}
)
// IOCTL consts
const (
BlkGetSize64 = C.BLKGETSIZE64
BlkDiscard = C.BLKDISCARD
LoopSetFd = C.LOOP_SET_FD
LoopCtlGetFree = C.LOOP_CTL_GET_FREE
LoopGetStatus64 = C.LOOP_GET_STATUS64
LoopSetStatus64 = C.LOOP_SET_STATUS64
LoopClrFd = C.LOOP_CLR_FD
LoopSetCapacity = C.LOOP_SET_CAPACITY
)
const (
LoFlagsAutoClear = C.LO_FLAGS_AUTOCLEAR
LoFlagsReadOnly = C.LO_FLAGS_READ_ONLY
LoFlagsPartScan = C.LO_FLAGS_PARTSCAN
LoKeySize = C.LO_KEY_SIZE
LoNameSize = C.LO_NAME_SIZE
)
const (
DmUdevDisableSubsystemRulesFlag = C.DM_UDEV_DISABLE_SUBSYSTEM_RULES_FLAG
DmUdevDisableDiskRulesFlag = C.DM_UDEV_DISABLE_DISK_RULES_FLAG
DmUdevDisableOtherRulesFlag = C.DM_UDEV_DISABLE_OTHER_RULES_FLAG
)
var (
DmGetLibraryVersion = dmGetLibraryVersionFct
DmGetNextTarget = dmGetNextTargetFct
DmLogInitVerbose = dmLogInitVerboseFct
DmSetDevDir = dmSetDevDirFct
DmTaskAddTarget = dmTaskAddTargetFct
DmTaskCreate = dmTaskCreateFct
DmTaskDestroy = dmTaskDestroyFct
DmTaskGetDeps = dmTaskGetDepsFct
DmTaskGetInfo = dmTaskGetInfoFct
DmTaskGetDriverVersion = dmTaskGetDriverVersionFct
DmTaskRun = dmTaskRunFct
DmTaskSetAddNode = dmTaskSetAddNodeFct
DmTaskSetCookie = dmTaskSetCookieFct
DmTaskSetMessage = dmTaskSetMessageFct
DmTaskSetName = dmTaskSetNameFct
DmTaskSetRo = dmTaskSetRoFct
DmTaskSetSector = dmTaskSetSectorFct
DmUdevWait = dmUdevWaitFct
LogWithErrnoInit = logWithErrnoInitFct
)
func free(p *C.char) {
C.free(unsafe.Pointer(p))
}
func dmTaskDestroyFct(task *CDmTask) {
C.dm_task_destroy((*C.struct_dm_task)(task))
}
func dmTaskCreateFct(taskType int) *CDmTask {
return (*CDmTask)(C.dm_task_create(C.int(taskType)))
}
func dmTaskRunFct(task *CDmTask) int {
ret, _ := C.dm_task_run((*C.struct_dm_task)(task))
return int(ret)
}
func dmTaskSetNameFct(task *CDmTask, name string) int {
Cname := C.CString(name)
defer free(Cname)
return int(C.dm_task_set_name((*C.struct_dm_task)(task), Cname))
}
func dmTaskSetMessageFct(task *CDmTask, message string) int {
Cmessage := C.CString(message)
defer free(Cmessage)
return int(C.dm_task_set_message((*C.struct_dm_task)(task), Cmessage))
}
func dmTaskSetSectorFct(task *CDmTask, sector uint64) int {
return int(C.dm_task_set_sector((*C.struct_dm_task)(task), C.uint64_t(sector)))
}
func dmTaskSetCookieFct(task *CDmTask, cookie *uint, flags uint16) int {
cCookie := C.uint32_t(*cookie)
defer func() {
*cookie = uint(cCookie)
}()
return int(C.dm_task_set_cookie((*C.struct_dm_task)(task), &cCookie, C.uint16_t(flags)))
}
func dmTaskSetAddNodeFct(task *CDmTask, addNode AddNodeType) int {
return int(C.dm_task_set_add_node((*C.struct_dm_task)(task), C.dm_add_node_t(addNode)))
}
func dmTaskSetRoFct(task *CDmTask) int {
return int(C.dm_task_set_ro((*C.struct_dm_task)(task)))
}
func dmTaskAddTargetFct(task *CDmTask,
start, size uint64, ttype, params string) int {
Cttype := C.CString(ttype)
defer free(Cttype)
Cparams := C.CString(params)
defer free(Cparams)
return int(C.dm_task_add_target((*C.struct_dm_task)(task), C.uint64_t(start), C.uint64_t(size), Cttype, Cparams))
}
func dmTaskGetDepsFct(task *CDmTask) *Deps {
Cdeps := C.dm_task_get_deps((*C.struct_dm_task)(task))
if Cdeps == nil {
return nil
}
deps := &Deps{
Count: uint32(Cdeps.count),
Filler: uint32(Cdeps.filler),
}
for _, device := range Cdeps.device {
deps.Device = append(deps.Device, (uint64)(device))
}
return deps
}
func dmTaskGetInfoFct(task *CDmTask, info *Info) int {
Cinfo := C.struct_dm_info{}
defer func() {
info.Exists = int(Cinfo.exists)
info.Suspended = int(Cinfo.suspended)
info.LiveTable = int(Cinfo.live_table)
info.InactiveTable = int(Cinfo.inactive_table)
info.OpenCount = int32(Cinfo.open_count)
info.EventNr = uint32(Cinfo.event_nr)
info.Major = uint32(Cinfo.major)
info.Minor = uint32(Cinfo.minor)
info.ReadOnly = int(Cinfo.read_only)
info.TargetCount = int32(Cinfo.target_count)
}()
return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo))
}
func dmTaskGetDriverVersionFct(task *CDmTask) string {
buffer := C.malloc(128)
defer C.free(buffer)
res := C.dm_task_get_driver_version((*C.struct_dm_task)(task), (*C.char)(buffer), 128)
if res == 0 {
return ""
}
return C.GoString((*C.char)(buffer))
}
func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr {
var (
Cstart, Clength C.uint64_t
CtargetType, Cparams *C.char
)
defer func() {
*start = uint64(Cstart)
*length = uint64(Clength)
*target = C.GoString(CtargetType)
*params = C.GoString(Cparams)
}()
nextp := C.dm_get_next_target((*C.struct_dm_task)(task), unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams)
return uintptr(nextp)
}
func dmUdevWaitFct(cookie uint) int {
return int(C.dm_udev_wait(C.uint32_t(cookie)))
}
func dmLogInitVerboseFct(level int) {
C.dm_log_init_verbose(C.int(level))
}
func logWithErrnoInitFct() {
C.log_with_errno_init()
}
func dmSetDevDirFct(dir string) int {
Cdir := C.CString(dir)
defer free(Cdir)
return int(C.dm_set_dev_dir(Cdir))
}
func dmGetLibraryVersionFct(version *string) int {
buffer := C.CString(string(make([]byte, 128)))
defer free(buffer)
defer func() {
*version = C.GoString(buffer)
}()
return int(C.dm_get_library_version(buffer, 128))
}

72
devicemapper/ioctl.go Normal file
View file

@ -0,0 +1,72 @@
// +build linux
package devicemapper
import (
"syscall"
"unsafe"
)
func ioctlLoopCtlGetFree(fd uintptr) (int, error) {
index, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, LoopCtlGetFree, 0)
if err != 0 {
return 0, err
}
return int(index), nil
}
func ioctlLoopSetFd(loopFd, sparseFd uintptr) error {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetFd, sparseFd); err != 0 {
return err
}
return nil
}
func ioctlLoopSetStatus64(loopFd uintptr, loopInfo *LoopInfo64) error {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 {
return err
}
return nil
}
func ioctlLoopClrFd(loopFd uintptr) error {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopClrFd, 0); err != 0 {
return err
}
return nil
}
func ioctlLoopGetStatus64(loopFd uintptr) (*LoopInfo64, error) {
loopInfo := &LoopInfo64{}
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopGetStatus64, uintptr(unsafe.Pointer(loopInfo))); err != 0 {
return nil, err
}
return loopInfo, nil
}
func ioctlLoopSetCapacity(loopFd uintptr, value int) error {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, loopFd, LoopSetCapacity, uintptr(value)); err != 0 {
return err
}
return nil
}
func ioctlBlkGetSize64(fd uintptr) (int64, error) {
var size int64
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, BlkGetSize64, uintptr(unsafe.Pointer(&size))); err != 0 {
return 0, err
}
return size, nil
}
func ioctlBlkDiscard(fd uintptr, offset, length uint64) error {
var r [2]uint64
r[0] = offset
r[1] = length
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, BlkDiscard, uintptr(unsafe.Pointer(&r[0]))); err != 0 {
return err
}
return nil
}

View file

@ -1,7 +1,7 @@
package fileutils
import (
"github.com/docker/docker/pkg/log"
log "github.com/Sirupsen/logrus"
"path/filepath"
)

View file

@ -4,31 +4,15 @@ package graphdb
import (
"database/sql"
"os"
_ "code.google.com/p/gosqlite/sqlite3" // registers sqlite
)
func NewSqliteConn(root string) (*Database, error) {
initDatabase := false
stat, err := os.Stat(root)
if err != nil {
if os.IsNotExist(err) {
initDatabase = true
} else {
return nil, err
}
}
if stat != nil && stat.Size() == 0 {
initDatabase = true
}
conn, err := sql.Open("sqlite3", root)
if err != nil {
return nil, err
}
return NewDatabase(conn, initDatabase)
return NewDatabase(conn)
}

View file

@ -73,45 +73,55 @@ func IsNonUniqueNameError(err error) bool {
}
// Create a new graph database initialized with a root entity
func NewDatabase(conn *sql.DB, init bool) (*Database, error) {
func NewDatabase(conn *sql.DB) (*Database, error) {
if conn == nil {
return nil, fmt.Errorf("Database connection cannot be nil")
}
db := &Database{conn: conn}
if init {
if _, err := conn.Exec(createEntityTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeIndices); err != nil {
return nil, err
}
rollback := func() {
conn.Exec("ROLLBACK")
}
// Create root entities
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
}
if _, err := conn.Exec(createEntityTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeTable); err != nil {
return nil, err
}
if _, err := conn.Exec(createEdgeIndices); err != nil {
return nil, err
}
rollback := func() {
conn.Exec("ROLLBACK")
}
// Create root entities
if _, err := conn.Exec("BEGIN"); err != nil {
return nil, err
}
if _, err := conn.Exec("DELETE FROM entity where id = ?", "0"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("DELETE FROM edge where entity_id=? and name=?", "0", "/"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
rollback()
return nil, err
}
if _, err := conn.Exec("COMMIT"); err != nil {
return nil, err
}
return db, nil
}
@ -131,8 +141,8 @@ func (db *Database) Set(fullPath, id string) (*Entity, error) {
if _, err := db.conn.Exec("BEGIN EXCLUSIVE"); err != nil {
return nil, err
}
var entityId string
if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityId); err != nil {
var entityID string
if err := db.conn.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityID); err != nil {
if err == sql.ErrNoRows {
if _, err := db.conn.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
rollback()
@ -320,14 +330,14 @@ func (db *Database) RefPaths(id string) Edges {
for rows.Next() {
var name string
var parentId string
if err := rows.Scan(&name, &parentId); err != nil {
var parentID string
if err := rows.Scan(&name, &parentID); err != nil {
return refs
}
refs = append(refs, &Edge{
EntityID: id,
Name: name,
ParentID: parentId,
ParentID: parentID,
})
}
return refs
@ -443,11 +453,11 @@ func (db *Database) children(e *Entity, name string, depth int, entities []WalkM
defer rows.Close()
for rows.Next() {
var entityId, entityName string
if err := rows.Scan(&entityId, &entityName); err != nil {
var entityID, entityName string
if err := rows.Scan(&entityID, &entityName); err != nil {
return nil, err
}
child := &Entity{entityId}
child := &Entity{entityID}
edge := &Edge{
ParentID: e.id,
Name: entityName,
@ -490,11 +500,11 @@ func (db *Database) parents(e *Entity) (parents []string, err error) {
defer rows.Close()
for rows.Next() {
var parentId string
if err := rows.Scan(&parentId); err != nil {
var parentID string
if err := rows.Scan(&parentID); err != nil {
return nil, err
}
parents = append(parents, parentId)
parents = append(parents, parentID)
}
return parents, nil

View file

@ -14,7 +14,7 @@ import (
func newTestDb(t *testing.T) (*Database, string) {
p := path.Join(os.TempDir(), "sqlite.db")
conn, err := sql.Open("sqlite3", p)
db, err := NewDatabase(conn, true)
db, err := NewDatabase(conn)
if err != nil {
t.Fatal(err)
}

View file

@ -6,7 +6,7 @@ import (
"net/http"
"time"
"github.com/docker/docker/pkg/log"
log "github.com/Sirupsen/logrus"
)
type resumableRequestReader struct {

View file

@ -1 +1,2 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
Jessie Frazelle <jess@docker.com> (@jfrazelle)

View file

@ -4,11 +4,12 @@ import (
"errors"
"fmt"
"net"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
)
type Action string
@ -19,9 +20,9 @@ const (
)
var (
ErrIptablesNotFound = errors.New("Iptables not found")
nat = []string{"-t", "nat"}
supportsXlock = false
ErrIptablesNotFound = errors.New("Iptables not found")
)
type Chain struct {
@ -29,6 +30,15 @@ type Chain struct {
Bridge string
}
type ChainError struct {
Chain string
Output []byte
}
func (e *ChainError) Error() string {
return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output))
}
func init() {
supportsXlock = exec.Command("iptables", "--wait", "-L", "-n").Run() == nil
}
@ -77,7 +87,7 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr str
"--to-destination", net.JoinHostPort(dest_addr, strconv.Itoa(dest_port))); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Error iptables forward: %s", output)
return &ChainError{Chain: "FORWARD", Output: output}
}
fAction := action
@ -93,7 +103,7 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, dest_addr str
"-j", "ACCEPT"); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Error iptables forward: %s", output)
return &ChainError{Chain: "FORWARD", Output: output}
}
return nil
@ -107,7 +117,7 @@ func (c *Chain) Prerouting(action Action, args ...string) error {
if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Error iptables prerouting: %s", output)
return &ChainError{Chain: "PREROUTING", Output: output}
}
return nil
}
@ -120,7 +130,7 @@ func (c *Chain) Output(action Action, args ...string) error {
if output, err := Raw(append(a, "-j", c.Name)...); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Error iptables output: %s", output)
return &ChainError{Chain: "OUTPUT", Output: output}
}
return nil
}
@ -175,9 +185,7 @@ func Raw(args ...string) ([]byte, error) {
args = append([]string{"--wait"}, args...)
}
if os.Getenv("DEBUG") != "" {
fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s, %v\n", path, args))
}
log.Debugf("%s, %v", path, args)
output, err := exec.Command(path, args...).CombinedOutput()
if err != nil {

View file

@ -4,8 +4,9 @@ import (
"encoding/json"
"fmt"
"io"
"log"
"time"
log "github.com/Sirupsen/logrus"
)
type JSONLog struct {

View file

@ -1,83 +0,0 @@
package log
import (
"fmt"
"io"
"os"
"runtime"
"strings"
)
type priority int
const (
errorFormat = "[%s] %s:%d %s\n"
logFormat = "[%s] %s\n"
fatal priority = iota
error
info
debug
)
// A common interface to access the Fatal method of
// both testing.B and testing.T.
type Fataler interface {
Fatal(args ...interface{})
}
func (p priority) String() string {
switch p {
case fatal:
return "fatal"
case error:
return "error"
case info:
return "info"
case debug:
return "debug"
}
return ""
}
// Debug function, if the debug flag is set, then display. Do nothing otherwise
// If Docker is in damon mode, also send the debug info on the socket
func Debugf(format string, a ...interface{}) {
if os.Getenv("DEBUG") != "" {
logf(os.Stderr, debug, format, a...)
}
}
func Infof(format string, a ...interface{}) {
logf(os.Stdout, info, format, a...)
}
func Errorf(format string, a ...interface{}) {
logf(os.Stderr, error, format, a...)
}
func Fatalf(format string, a ...interface{}) {
logf(os.Stderr, fatal, format, a...)
os.Exit(1)
}
func logf(stream io.Writer, level priority, format string, a ...interface{}) {
var prefix string
if level <= error || level == debug {
// Retrieve the stack infos
_, file, line, ok := runtime.Caller(2)
if !ok {
file = "<unknown>"
line = -1
} else {
file = file[strings.LastIndex(file, "/")+1:]
}
prefix = fmt.Sprintf(errorFormat, level.String(), file, line, format)
} else {
prefix = fmt.Sprintf(logFormat, level.String(), format)
}
fmt.Fprintf(stream, prefix, a...)
}

View file

@ -1,37 +0,0 @@
package log
import (
"bytes"
"regexp"
"testing"
)
func TestLogFatalf(t *testing.T) {
var output *bytes.Buffer
tests := []struct {
Level priority
Format string
Values []interface{}
ExpectedPattern string
}{
{fatal, "%d + %d = %d", []interface{}{1, 1, 2}, "\\[fatal\\] testing.go:\\d+ 1 \\+ 1 = 2"},
{error, "%d + %d = %d", []interface{}{1, 1, 2}, "\\[error\\] testing.go:\\d+ 1 \\+ 1 = 2"},
{info, "%d + %d = %d", []interface{}{1, 1, 2}, "\\[info\\] 1 \\+ 1 = 2"},
{debug, "%d + %d = %d", []interface{}{1, 1, 2}, "\\[debug\\] testing.go:\\d+ 1 \\+ 1 = 2"},
}
for i, test := range tests {
output = &bytes.Buffer{}
logf(output, test.Level, test.Format, test.Values...)
expected := regexp.MustCompile(test.ExpectedPattern)
if !expected.MatchString(output.String()) {
t.Errorf("[%d] Log output does not match expected pattern:\n\tExpected: %s\n\tOutput: %s",
i,
expected.String(),
output.String())
}
}
}

View file

@ -23,12 +23,12 @@
flag.Var(&flagVal, []string{"name"}, "help message for flagname")
For such flags, the default value is just the initial value of the variable.
You can also add "deprecated" flags, they are still usable, bur are not shown
You can also add "deprecated" flags, they are still usable, but are not shown
in the usage and will display a warning when you try to use them:
var ip = flag.Int([]string{"f", "#flagname", "-flagname"}, 1234, "help message for flagname")
this will display: `Warning: '-flagname' is deprecated, it will be replaced by '--flagname' soon. See usage.` and
var ip = flag.Int([]string{"#f", "#flagname", "-flagname2"}, 1234, "help message for flagname")
this will display: `Warning: '--flagname' is deprecated, it will be replaced by '--flagname2' soon. See usage.` and
var ip = flag.Int([]string{"f", "#flagname"}, 1234, "help message for flagname")
will display: `Warning: '-t' is deprecated, it will be removed soon. See usage.`
will display: `Warning: '-f' is deprecated, it will be removed soon. See usage.`
You can also group one letter flags, bif you declare
var v = flag.Bool([]string{"v", "-verbose"}, false, "help message for verbose")
@ -394,12 +394,22 @@ func (f *FlagSet) Lookup(name string) *Flag {
return f.formal[name]
}
// Indicates whether the specified flag was specified at all on the cmd line
func (f *FlagSet) IsSet(name string) bool {
return f.actual[name] != nil
}
// Lookup returns the Flag structure of the named command-line flag,
// returning nil if none exists.
func Lookup(name string) *Flag {
return CommandLine.formal[name]
}
// Indicates whether the specified flag was specified at all on the cmd line
func IsSet(name string) bool {
return CommandLine.IsSet(name)
}
// Set sets the value of the named flag.
func (f *FlagSet) Set(name, value string) error {
flag, ok := f.formal[name]

View file

@ -168,11 +168,14 @@ func testParse(f *FlagSet, t *testing.T) {
}
boolFlag := f.Bool([]string{"bool"}, false, "bool value")
bool2Flag := f.Bool([]string{"bool2"}, false, "bool2 value")
f.Bool([]string{"bool3"}, false, "bool3 value")
bool4Flag := f.Bool([]string{"bool4"}, false, "bool4 value")
intFlag := f.Int([]string{"-int"}, 0, "int value")
int64Flag := f.Int64([]string{"-int64"}, 0, "int64 value")
uintFlag := f.Uint([]string{"uint"}, 0, "uint value")
uint64Flag := f.Uint64([]string{"-uint64"}, 0, "uint64 value")
stringFlag := f.String([]string{"string"}, "0", "string value")
f.String([]string{"string2"}, "0", "string2 value")
singleQuoteFlag := f.String([]string{"squote"}, "", "single quoted value")
doubleQuoteFlag := f.String([]string{"dquote"}, "", "double quoted value")
mixedQuoteFlag := f.String([]string{"mquote"}, "", "mixed quoted value")
@ -185,6 +188,7 @@ func testParse(f *FlagSet, t *testing.T) {
args := []string{
"-bool",
"-bool2=true",
"-bool4=false",
"--int", "22",
"--int64", "0x23",
"-uint", "24",
@ -212,6 +216,18 @@ func testParse(f *FlagSet, t *testing.T) {
if *bool2Flag != true {
t.Error("bool2 flag should be true, is ", *bool2Flag)
}
if !f.IsSet("bool2") {
t.Error("bool2 should be marked as set")
}
if f.IsSet("bool3") {
t.Error("bool3 should not be marked as set")
}
if !f.IsSet("bool4") {
t.Error("bool4 should be marked as set")
}
if *bool4Flag != false {
t.Error("bool4 flag should be false, is ", *bool4Flag)
}
if *intFlag != 22 {
t.Error("int flag should be 22, is ", *intFlag)
}
@ -227,6 +243,12 @@ func testParse(f *FlagSet, t *testing.T) {
if *stringFlag != "hello" {
t.Error("string flag should be `hello`, is ", *stringFlag)
}
if !f.IsSet("string") {
t.Error("string flag should be marked as set")
}
if f.IsSet("string2") {
t.Error("string2 flag should not be marked as set")
}
if *singleQuoteFlag != "single" {
t.Error("single quote string flag should be `single`, is ", *singleQuoteFlag)
}

View file

@ -37,7 +37,14 @@ func parseOptions(options string) (int, string) {
"nodiratime": {false, NODIRATIME},
"bind": {false, BIND},
"rbind": {false, RBIND},
"unbindable": {false, UNBINDABLE},
"runbindable": {false, RUNBINDABLE},
"private": {false, PRIVATE},
"rprivate": {false, RPRIVATE},
"shared": {false, SHARED},
"rshared": {false, RSHARED},
"slave": {false, SLAVE},
"rslave": {false, RSLAVE},
"relatime": {false, RELATIME},
"norelatime": {true, RELATIME},
"strictatime": {false, STRICTATIME},

View file

@ -19,7 +19,14 @@ const (
MANDLOCK = 0
NODEV = 0
NODIRATIME = 0
UNBINDABLE = 0
RUNBINDABLE = 0
PRIVATE = 0
RPRIVATE = 0
SHARED = 0
RSHARED = 0
SLAVE = 0
RSLAVE = 0
RBIND = 0
RELATIVE = 0
RELATIME = 0

View file

@ -17,7 +17,14 @@ const (
NODIRATIME = syscall.MS_NODIRATIME
BIND = syscall.MS_BIND
RBIND = syscall.MS_BIND | syscall.MS_REC
UNBINDABLE = syscall.MS_UNBINDABLE
RUNBINDABLE = syscall.MS_UNBINDABLE | syscall.MS_REC
PRIVATE = syscall.MS_PRIVATE
RPRIVATE = syscall.MS_PRIVATE | syscall.MS_REC
SLAVE = syscall.MS_SLAVE
RSLAVE = syscall.MS_SLAVE | syscall.MS_REC
SHARED = syscall.MS_SHARED
RSHARED = syscall.MS_SHARED | syscall.MS_REC
RELATIME = syscall.MS_RELATIME
STRICTATIME = syscall.MS_STRICTATIME
)

View file

@ -11,7 +11,14 @@ const (
NODIRATIME = 0
NOEXEC = 0
NOSUID = 0
UNBINDABLE = 0
RUNBINDABLE = 0
PRIVATE = 0
RPRIVATE = 0
SHARED = 0
RSHARED = 0
SLAVE = 0
RSLAVE = 0
RBIND = 0
RELATIME = 0
RELATIVE = 0

View file

@ -1,7 +1,7 @@
package mount
type MountInfo struct {
Id, Parent, Major, Minor int
Root, Mountpoint, Opts string
Fstype, Source, VfsOpts string
Id, Parent, Major, Minor int
Root, Mountpoint, Opts, Optional string
Fstype, Source, VfsOpts string
}

View file

@ -32,6 +32,8 @@ func parseMountTable() ([]*MountInfo, error) {
for _, entry := range entries {
var mountinfo MountInfo
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
out = append(out, &mountinfo)
}
return out, nil

View file

@ -1,3 +1,5 @@
// +build linux
package mount
import (
@ -23,7 +25,7 @@ const (
(9) filesystem type: name of filesystem of the form "type[.subtype]"
(10) mount source: filesystem specific information or "none"
(11) super options: per super block options*/
mountinfoFormat = "%d %d %d:%d %s %s %s "
mountinfoFormat = "%d %d %d:%d %s %s %s %s"
)
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts
@ -49,13 +51,14 @@ func parseInfoFile(r io.Reader) ([]*MountInfo, error) {
}
var (
p = &MountInfo{}
text = s.Text()
p = &MountInfo{}
text = s.Text()
optionalFields string
)
if _, err := fmt.Sscanf(text, mountinfoFormat,
&p.Id, &p.Parent, &p.Major, &p.Minor,
&p.Root, &p.Mountpoint, &p.Opts); err != nil {
&p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil {
return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err)
}
// Safe as mountinfo encodes mountpoints with spaces as \040.
@ -65,6 +68,10 @@ func parseInfoFile(r io.Reader) ([]*MountInfo, error) {
return nil, fmt.Errorf("Error found less than 3 fields post '-' in %q", text)
}
if optionalFields != "-" {
p.Optional = optionalFields
}
p.Fstype = postSeparatorFields[0]
p.Source = postSeparatorFields[1]
p.VfsOpts = strings.Join(postSeparatorFields[2:], " ")
@ -72,3 +79,14 @@ func parseInfoFile(r io.Reader) ([]*MountInfo, error) {
}
return out, nil
}
// PidMountInfo collects the mounts for a specific Pid
func PidMountInfo(pid int) ([]*MountInfo, error) {
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
if err != nil {
return nil, err
}
defer f.Close()
return parseInfoFile(f)
}

View file

@ -446,3 +446,32 @@ func TestParseGentooMountinfo(t *testing.T) {
t.Fatal(err)
}
}
func TestParseFedoraMountinfoFields(t *testing.T) {
r := bytes.NewBuffer([]byte(fedoraMountinfo))
infos, err := parseInfoFile(r)
if err != nil {
t.Fatal(err)
}
expectedLength := 58
if len(infos) != expectedLength {
t.Fatalf("Expected %d entries, got %d", expectedLength, len(infos))
}
mi := MountInfo{
Id: 15,
Parent: 35,
Major: 0,
Minor: 3,
Root: "/",
Mountpoint: "/proc",
Opts: "rw,nosuid,nodev,noexec,relatime",
Optional: "shared:5",
Fstype: "proc",
Source: "proc",
VfsOpts: "rw",
}
if *infos[0] != mi {
t.Fatalf("expected %#v, got %#v", mi, infos[0])
}
}

View file

@ -0,0 +1,54 @@
// +build linux
package mount
func MakeShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "shared")
}
func MakeRShared(mountPoint string) error {
return ensureMountedAs(mountPoint, "rshared")
}
func MakePrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "private")
}
func MakeRPrivate(mountPoint string) error {
return ensureMountedAs(mountPoint, "rprivate")
}
func MakeSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "slave")
}
func MakeRSlave(mountPoint string) error {
return ensureMountedAs(mountPoint, "rslave")
}
func MakeUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "unbindable")
}
func MakeRUnbindable(mountPoint string) error {
return ensureMountedAs(mountPoint, "runbindable")
}
func ensureMountedAs(mountPoint, options string) error {
mounted, err := Mounted(mountPoint)
if err != nil {
return err
}
if !mounted {
if err := Mount(mountPoint, mountPoint, "none", "bind,rw"); err != nil {
return err
}
}
mounted, err = Mounted(mountPoint)
if err != nil {
return err
}
return ForceMount("", mountPoint, "none", options)
}

View file

@ -0,0 +1,331 @@
// +build linux
package mount
import (
"os"
"path"
"syscall"
"testing"
)
// nothing is propogated in or out
func TestSubtreePrivate(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
outside1Dir = path.Join(tmp, "outside1")
outside2Dir = path.Join(tmp, "outside2")
outside1Path = path.Join(outside1Dir, "file.txt")
outside2Path = path.Join(outside2Dir, "file.txt")
outside1CheckPath = path.Join(targetDir, "a", "file.txt")
outside2CheckPath = path.Join(sourceDir, "b", "file.txt")
)
if err := os.MkdirAll(path.Join(sourceDir, "a"), 0777); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(path.Join(sourceDir, "b"), 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(targetDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outside1Dir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outside2Dir, 0777); err != nil {
t.Fatal(err)
}
if err := createFile(outside1Path); err != nil {
t.Fatal(err)
}
if err := createFile(outside2Path); err != nil {
t.Fatal(err)
}
// mount the shared directory to a target
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// next, make the target private
if err := MakePrivate(targetDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// mount in an outside path to a mounted path inside the _source_
if err := Mount(outside1Dir, path.Join(sourceDir, "a"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(sourceDir, "a")); err != nil {
t.Fatal(err)
}
}()
// check that this file _does_not_ show in the _target_
if _, err := os.Stat(outside1CheckPath); err != nil && !os.IsNotExist(err) {
t.Fatal(err)
} else if err == nil {
t.Fatalf("%q should not be visible, but is", outside1CheckPath)
}
// next mount outside2Dir into the _target_
if err := Mount(outside2Dir, path.Join(targetDir, "b"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(targetDir, "b")); err != nil {
t.Fatal(err)
}
}()
// check that this file _does_not_ show in the _source_
if _, err := os.Stat(outside2CheckPath); err != nil && !os.IsNotExist(err) {
t.Fatal(err)
} else if err == nil {
t.Fatalf("%q should not be visible, but is", outside2CheckPath)
}
}
// Testing that when a target is a shared mount,
// then child mounts propogate to the source
func TestSubtreeShared(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
outsideDir = path.Join(tmp, "outside")
outsidePath = path.Join(outsideDir, "file.txt")
sourceCheckPath = path.Join(sourceDir, "a", "file.txt")
)
if err := os.MkdirAll(path.Join(sourceDir, "a"), 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(targetDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outsideDir, 0777); err != nil {
t.Fatal(err)
}
if err := createFile(outsidePath); err != nil {
t.Fatal(err)
}
// mount the source as shared
if err := MakeShared(sourceDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(sourceDir); err != nil {
t.Fatal(err)
}
}()
// mount the shared directory to a target
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// mount in an outside path to a mounted path inside the target
if err := Mount(outsideDir, path.Join(targetDir, "a"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(targetDir, "a")); err != nil {
t.Fatal(err)
}
}()
// NOW, check that the file from the outside directory is avaible in the source directory
if _, err := os.Stat(sourceCheckPath); err != nil {
t.Fatal(err)
}
}
// testing that mounts to a shared source show up in the slave target,
// and that mounts into a slave target do _not_ show up in the shared source
func TestSubtreeSharedSlave(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
outside1Dir = path.Join(tmp, "outside1")
outside2Dir = path.Join(tmp, "outside2")
outside1Path = path.Join(outside1Dir, "file.txt")
outside2Path = path.Join(outside2Dir, "file.txt")
outside1CheckPath = path.Join(targetDir, "a", "file.txt")
outside2CheckPath = path.Join(sourceDir, "b", "file.txt")
)
if err := os.MkdirAll(path.Join(sourceDir, "a"), 0777); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(path.Join(sourceDir, "b"), 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(targetDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outside1Dir, 0777); err != nil {
t.Fatal(err)
}
if err := os.Mkdir(outside2Dir, 0777); err != nil {
t.Fatal(err)
}
if err := createFile(outside1Path); err != nil {
t.Fatal(err)
}
if err := createFile(outside2Path); err != nil {
t.Fatal(err)
}
// mount the source as shared
if err := MakeShared(sourceDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(sourceDir); err != nil {
t.Fatal(err)
}
}()
// mount the shared directory to a target
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// next, make the target slave
if err := MakeSlave(targetDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
// mount in an outside path to a mounted path inside the _source_
if err := Mount(outside1Dir, path.Join(sourceDir, "a"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(sourceDir, "a")); err != nil {
t.Fatal(err)
}
}()
// check that this file _does_ show in the _target_
if _, err := os.Stat(outside1CheckPath); err != nil {
t.Fatal(err)
}
// next mount outside2Dir into the _target_
if err := Mount(outside2Dir, path.Join(targetDir, "b"), "none", "bind,rw"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(path.Join(targetDir, "b")); err != nil {
t.Fatal(err)
}
}()
// check that this file _does_not_ show in the _source_
if _, err := os.Stat(outside2CheckPath); err != nil && !os.IsNotExist(err) {
t.Fatal(err)
} else if err == nil {
t.Fatalf("%q should not be visible, but is", outside2CheckPath)
}
}
func TestSubtreeUnbindable(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourceDir = path.Join(tmp, "source")
targetDir = path.Join(tmp, "target")
)
if err := os.MkdirAll(sourceDir, 0777); err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(targetDir, 0777); err != nil {
t.Fatal(err)
}
// next, make the source unbindable
if err := MakeUnbindable(sourceDir); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(sourceDir); err != nil {
t.Fatal(err)
}
}()
// then attempt to mount it to target. It should fail
if err := Mount(sourceDir, targetDir, "none", "bind,rw"); err != nil && err != syscall.EINVAL {
t.Fatal(err)
} else if err == nil {
t.Fatalf("%q should not have been bindable", sourceDir)
}
defer func() {
if err := Unmount(targetDir); err != nil {
t.Fatal(err)
}
}()
}
func createFile(path string) error {
f, err := os.Create(path)
if err != nil {
return err
}
f.WriteString("hello world!")
return f.Close()
}

View file

@ -7,7 +7,7 @@ import (
)
var (
left = [...]string{"happy", "jolly", "dreamy", "sad", "angry", "pensive", "focused", "sleepy", "grave", "distracted", "determined", "stoic", "stupefied", "sharp", "agitated", "cocky", "tender", "goofy", "furious", "desperate", "hopeful", "compassionate", "silly", "lonely", "condescending", "naughty", "kickass", "drunk", "boring", "nostalgic", "ecstatic", "insane", "cranky", "mad", "jovial", "sick", "hungry", "thirsty", "elegant", "backstabbing", "clever", "trusting", "loving", "suspicious", "berserk", "high", "romantic", "prickly", "evil"}
left = [...]string{"happy", "jolly", "dreamy", "sad", "angry", "pensive", "focused", "sleepy", "grave", "distracted", "determined", "stoic", "stupefied", "sharp", "agitated", "cocky", "tender", "goofy", "furious", "desperate", "hopeful", "compassionate", "silly", "lonely", "condescending", "naughty", "kickass", "drunk", "boring", "nostalgic", "ecstatic", "insane", "cranky", "mad", "jovial", "sick", "hungry", "thirsty", "elegant", "backstabbing", "clever", "trusting", "loving", "suspicious", "berserk", "high", "romantic", "prickly", "evil", "admiring", "adoring", "reverent", "serene", "fervent", "modest", "gloomy", "elated"}
// Docker 0.7.x generates names from notable scientists and hackers.
//
// Ada Lovelace invented the first algorithm. http://en.wikipedia.org/wiki/Ada_Lovelace (thanks James Turnbull)
@ -22,6 +22,7 @@ var (
// Charles Babbage invented the concept of a programmable computer. http://en.wikipedia.org/wiki/Charles_Babbage.
// Charles Darwin established the principles of natural evolution. http://en.wikipedia.org/wiki/Charles_Darwin.
// Dennis Ritchie and Ken Thompson created UNIX and the C programming language. http://en.wikipedia.org/wiki/Dennis_Ritchie http://en.wikipedia.org/wiki/Ken_Thompson
// Dorothy Hodgkin was a British biochemist, credited with the development of protein crystallography. She was awarded the Nobel Prize in Chemistry in 1964. http://en.wikipedia.org/wiki/Dorothy_Hodgkin
// Douglas Engelbart gave the mother of all demos: http://en.wikipedia.org/wiki/Douglas_Engelbart
// Elizabeth Blackwell - American doctor and first American woman to receive a medical degree - http://en.wikipedia.org/wiki/Elizabeth_Blackwell
// Emmett Brown invented time travel. http://en.wikipedia.org/wiki/Emmett_Brown (thanks Brian Goff)
@ -31,6 +32,7 @@ var (
// Françoise Barré-Sinoussi - French virologist and Nobel Prize Laureate in Physiology or Medicine; her work was fundamental in identifying HIV as the cause of AIDS. http://en.wikipedia.org/wiki/Fran%C3%A7oise_Barr%C3%A9-Sinoussi
// Galileo was a founding father of modern astronomy, and faced politics and obscurantism to establish scientific truth. http://en.wikipedia.org/wiki/Galileo_Galilei
// Gertrude Elion - American biochemist, pharmacologist and the 1988 recipient of the Nobel Prize in Medicine - http://en.wikipedia.org/wiki/Gertrude_Elion
// Gerty Theresa Cori - American biochemist who became the third woman—and first American woman—to win a Nobel Prize in science, and the first woman to be awarded the Nobel Prize in Physiology or Medicine. Cori was born in Prague. http://en.wikipedia.org/wiki/Gerty_Cori
// Grace Hopper developed the first compiler for a computer programming language and is credited with popularizing the term "debugging" for fixing computer glitches. http://en.wikipedia.org/wiki/Grace_Hopper
// Henry Poincare made fundamental contributions in several fields of mathematics. http://en.wikipedia.org/wiki/Henri_Poincar%C3%A9
// Hypatia - Greek Alexandrine Neoplatonist philosopher in Egypt who was one of the earliest mothers of mathematics - http://en.wikipedia.org/wiki/Hypatia
@ -56,7 +58,7 @@ var (
// Mary Leakey - British paleoanthropologist who discovered the first fossilized Proconsul skull - http://en.wikipedia.org/wiki/Mary_Leakey
// Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. http://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB
// Niels Bohr is the father of quantum theory. http://en.wikipedia.org/wiki/Niels_Bohr.
// Nikola Tesla invented the AC electric system and every gaget ever used by a James Bond villain. http://en.wikipedia.org/wiki/Nikola_Tesla
// Nikola Tesla invented the AC electric system and every gadget ever used by a James Bond villain. http://en.wikipedia.org/wiki/Nikola_Tesla
// Pierre de Fermat pioneered several aspects of modern mathematics. http://en.wikipedia.org/wiki/Pierre_de_Fermat
// Rachel Carson - American marine biologist and conservationist, her book Silent Spring and other writings are credited with advancing the global environmental movement. http://en.wikipedia.org/wiki/Rachel_Carson
// Radia Perlman is a software designer and network engineer and most famous for her invention of the spanning-tree protocol (STP). http://en.wikipedia.org/wiki/Radia_Perlman
@ -64,6 +66,7 @@ var (
// Richard Matthew Stallman - the founder of the Free Software movement, the GNU project, the Free Software Foundation, and the League for Programming Freedom. He also invented the concept of copyleft to protect the ideals of this movement, and enshrined this concept in the widely-used GPL (General Public License) for software. http://en.wikiquote.org/wiki/Richard_Stallman
// Rob Pike was a key contributor to Unix, Plan 9, the X graphic system, utf-8, and the Go programming language. http://en.wikipedia.org/wiki/Rob_Pike
// Rosalind Franklin - British biophysicist and X-ray crystallographer whose research was critical to the understanding of DNA - http://en.wikipedia.org/wiki/Rosalind_Franklin
// Rosalyn Sussman Yalow - Rosalyn Sussman Yalow was an American medical physicist, and a co-winner of the 1977 Nobel Prize in Physiology or Medicine for development of the radioimmunoassay technique. http://en.wikipedia.org/wiki/Rosalyn_Sussman_Yalow
// Sophie Kowalevski - Russian mathematician responsible for important original contributions to analysis, differential equations and mechanics - http://en.wikipedia.org/wiki/Sofia_Kovalevskaya
// Sophie Wilson designed the first Acorn Micro-Computer and the instruction set for ARM processors. http://en.wikipedia.org/wiki/Sophie_Wilson
// Stephen Hawking pioneered the field of cosmology by combining general relativity and quantum mechanics. http://en.wikipedia.org/wiki/Stephen_Hawking
@ -73,7 +76,8 @@ var (
// http://en.wikipedia.org/wiki/John_Bardeen
// http://en.wikipedia.org/wiki/Walter_Houser_Brattain
// http://en.wikipedia.org/wiki/William_Shockley
right = [...]string{"albattani", "almeida", "archimedes", "ardinghelli", "babbage", "bardeen", "bartik", "bell", "blackwell", "bohr", "brattain", "brown", "carson", "colden", "curie", "darwin", "davinci", "einstein", "elion", "engelbart", "euclid", "fermat", "fermi", "feynman", "franklin", "galileo", "goldstine", "goodall", "hawking", "heisenberg", "hoover", "hopper", "hypatia", "jones", "kirch", "kowalevski", "lalande", "leakey", "lovelace", "lumiere", "mayer", "mccarthy", "mcclintock", "mclean", "meitner", "mestorf", "morse", "newton", "nobel", "pare", "pasteur", "perlman", "pike", "poincare", "ptolemy", "ritchie", "rosalind", "sammet", "shockley", "sinoussi", "stallman", "tesla", "thompson", "torvalds", "turing", "wilson", "wozniak", "wright", "yonath"}
// Yeong-Sil Jang was a Korean scientist and astronomer during the Joseon Dynasty; he invented the first metal printing press and water gauge. http://en.wikipedia.org/wiki/Jang_Yeong-sil
right = [...]string{"albattani", "almeida", "archimedes", "ardinghelli", "babbage", "bardeen", "bartik", "bell", "blackwell", "bohr", "brattain", "brown", "carson", "colden", "cori", "curie", "darwin", "davinci", "einstein", "elion", "engelbart", "euclid", "fermat", "fermi", "feynman", "franklin", "galileo", "goldstine", "goodall", "hawking", "heisenberg", "hodgkin", "hoover", "hopper", "hypatia", "jang", "jones", "kirch", "kowalevski", "lalande", "leakey", "lovelace", "lumiere", "mayer", "mccarthy", "mcclintock", "mclean", "meitner", "mestorf", "morse", "newton", "nobel", "pare", "pasteur", "perlman", "pike", "poincare", "ptolemy", "ritchie", "rosalind", "sammet", "shockley", "sinoussi", "stallman", "tesla", "thompson", "torvalds", "turing", "wilson", "wozniak", "wright", "yalow", "yonath"}
)
func GetRandomName(retry int) string {

View file

@ -3,40 +3,54 @@ package etchosts
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"regexp"
)
var defaultContent = map[string]string{
"localhost": "127.0.0.1",
"localhost ip6-localhost ip6-loopback": "::1",
"ip6-localnet": "fe00::0",
"ip6-mcastprefix": "ff00::0",
"ip6-allnodes": "ff02::1",
"ip6-allrouters": "ff02::2",
type Record struct {
Hosts string
IP string
}
func Build(path, IP, hostname, domainname string, extraContent *map[string]string) error {
func (r Record) WriteTo(w io.Writer) (int64, error) {
n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts)
return int64(n), err
}
var defaultContent = []Record{
{Hosts: "localhost", IP: "127.0.0.1"},
{Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"},
{Hosts: "ip6-localnet", IP: "fe00::0"},
{Hosts: "ip6-mcastprefix", IP: "ff00::0"},
{Hosts: "ip6-allnodes", IP: "ff02::1"},
{Hosts: "ip6-allrouters", IP: "ff02::2"},
}
func Build(path, IP, hostname, domainname string, extraContent []Record) error {
content := bytes.NewBuffer(nil)
if IP != "" {
var mainRec Record
mainRec.IP = IP
if domainname != "" {
content.WriteString(fmt.Sprintf("%s\t%s.%s %s\n", IP, hostname, domainname, hostname))
mainRec.Hosts = fmt.Sprintf("%s.%s %s", hostname, domainname, hostname)
} else {
content.WriteString(fmt.Sprintf("%s\t%s\n", IP, hostname))
mainRec.Hosts = hostname
}
}
for hosts, ip := range defaultContent {
if _, err := content.WriteString(fmt.Sprintf("%s\t%s\n", ip, hosts)); err != nil {
if _, err := mainRec.WriteTo(content); err != nil {
return err
}
}
if extraContent != nil {
for hosts, ip := range *extraContent {
if _, err := content.WriteString(fmt.Sprintf("%s\t%s\n", ip, hosts)); err != nil {
return err
}
for _, r := range defaultContent {
if _, err := r.WriteTo(content); err != nil {
return err
}
}
for _, r := range extraContent {
if _, err := r.WriteTo(content); err != nil {
return err
}
}

View file

@ -7,6 +7,32 @@ import (
"testing"
)
func TestBuildDefault(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer os.Remove(file.Name())
// check that /etc/hosts has consistent ordering
for i := 0; i <= 5; i++ {
err = Build(file.Name(), "", "", "", nil)
if err != nil {
t.Fatal(err)
}
content, err := ioutil.ReadFile(file.Name())
if err != nil {
t.Fatal(err)
}
expected := "127.0.0.1\tlocalhost\n::1\tlocalhost ip6-localhost ip6-loopback\nfe00::0\tip6-localnet\nff00::0\tip6-mcastprefix\nff02::1\tip6-allnodes\nff02::2\tip6-allrouters\n"
if expected != string(content) {
t.Fatalf("Expected to find '%s' got '%s'", expected, content)
}
}
}
func TestBuildHostnameDomainname(t *testing.T) {
file, err := ioutil.TempFile("", "")
if err != nil {

View file

@ -3,6 +3,7 @@ package filters
import (
"encoding/json"
"errors"
"regexp"
"strings"
)
@ -28,7 +29,9 @@ func ParseFlag(arg string, prev Args) (Args, error) {
}
f := strings.SplitN(arg, "=", 2)
filters[f[0]] = append(filters[f[0]], f[1])
name := strings.ToLower(strings.TrimSpace(f[0]))
value := strings.TrimSpace(f[1])
filters[name] = append(filters[name], value)
return filters, nil
}
@ -61,3 +64,22 @@ func FromParam(p string) (Args, error) {
}
return args, nil
}
func (filters Args) Match(field, source string) bool {
fieldValues := filters[field]
//do not filter if there is no filter set or cannot determine filter
if len(fieldValues) == 0 {
return true
}
for _, name2match := range fieldValues {
match, err := regexp.MatchString(name2match, source)
if err != nil {
continue
}
if match {
return true
}
}
return false
}

View file

@ -38,12 +38,13 @@ BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"`)
)
dir := os.TempDir()
etcOsRelease = filepath.Join(dir, "etcOsRelease")
defer func() {
os.Remove(etcOsRelease)
etcOsRelease = backup
os.RemoveAll(dir)
}()
etcOsRelease = filepath.Join(dir, "etcOsRelease")
for expect, osRelease := range map[string][]byte{
"Ubuntu 14.04 LTS": ubuntuTrusty,
"Gentoo/Linux": gentoo,
@ -92,13 +93,13 @@ func TestIsContainerized(t *testing.T) {
)
dir := os.TempDir()
defer func() {
proc1Cgroup = backup
os.RemoveAll(dir)
}()
proc1Cgroup = filepath.Join(dir, "proc1Cgroup")
defer func() {
os.Remove(proc1Cgroup)
proc1Cgroup = backup
}()
if err := ioutil.WriteFile(proc1Cgroup, nonContainerizedProc1Cgroup, 0600); err != nil {
t.Fatalf("failed to write to %s: %v", proc1Cgroup, err)
}

View file

@ -7,63 +7,59 @@ import (
)
// FIXME: Change this not to receive default value as parameter
func ParseHost(defaultHost string, defaultUnix, addr string) (string, error) {
var (
proto string
host string
port int
)
func ParseHost(defaultTCPAddr, defaultUnixAddr, addr string) (string, error) {
addr = strings.TrimSpace(addr)
switch {
case addr == "tcp://":
return "", fmt.Errorf("Invalid bind address format: %s", addr)
case strings.HasPrefix(addr, "unix://"):
proto = "unix"
addr = strings.TrimPrefix(addr, "unix://")
if addr == "" {
addr = defaultUnix
}
case strings.HasPrefix(addr, "tcp://"):
proto = "tcp"
addr = strings.TrimPrefix(addr, "tcp://")
case strings.HasPrefix(addr, "fd://"):
if addr == "" {
addr = fmt.Sprintf("unix://%s", defaultUnixAddr)
}
addrParts := strings.Split(addr, "://")
if len(addrParts) == 1 {
addrParts = []string{"tcp", addrParts[0]}
}
switch addrParts[0] {
case "tcp":
return ParseTCPAddr(addrParts[1], defaultTCPAddr)
case "unix":
return ParseUnixAddr(addrParts[1], defaultUnixAddr)
case "fd":
return addr, nil
case addr == "":
proto = "unix"
addr = defaultUnix
default:
if strings.Contains(addr, "://") {
return "", fmt.Errorf("Invalid bind address protocol: %s", addr)
}
proto = "tcp"
}
if proto != "unix" && strings.Contains(addr, ":") {
hostParts := strings.Split(addr, ":")
if len(hostParts) != 2 {
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}
if hostParts[0] != "" {
host = hostParts[0]
} else {
host = defaultHost
}
if p, err := strconv.Atoi(hostParts[1]); err == nil && p != 0 {
port = p
} else {
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}
} else if proto == "tcp" && !strings.Contains(addr, ":") {
return "", fmt.Errorf("Invalid bind address format: %s", addr)
} else {
host = addr
}
if proto == "unix" {
return fmt.Sprintf("%s://%s", proto, host), nil
}
func ParseUnixAddr(addr string, defaultAddr string) (string, error) {
addr = strings.TrimPrefix(addr, "unix://")
if strings.Contains(addr, "://") {
return "", fmt.Errorf("Invalid proto, expected unix: %s", addr)
}
return fmt.Sprintf("%s://%s:%d", proto, host, port), nil
if addr == "" {
addr = defaultAddr
}
return fmt.Sprintf("unix://%s", addr), nil
}
func ParseTCPAddr(addr string, defaultAddr string) (string, error) {
addr = strings.TrimPrefix(addr, "tcp://")
if strings.Contains(addr, "://") || addr == "" {
return "", fmt.Errorf("Invalid proto, expected tcp: %s", addr)
}
hostParts := strings.Split(addr, ":")
if len(hostParts) != 2 {
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}
host := hostParts[0]
if host == "" {
host = defaultAddr
}
p, err := strconv.Atoi(hostParts[1])
if err != nil && p == 0 {
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}
return fmt.Sprintf("tcp://%s:%d", host, p), nil
}
// Get a repos name and returns the right reposName + tag

View file

@ -2,9 +2,10 @@ package proxy
import (
"io"
"log"
"net"
"syscall"
log "github.com/Sirupsen/logrus"
)
type TCPProxy struct {

View file

@ -2,17 +2,18 @@ package proxy
import (
"encoding/binary"
"log"
"net"
"strings"
"sync"
"syscall"
"time"
log "github.com/Sirupsen/logrus"
)
const (
UDPConnTrackTimeout = 90 * time.Second
UDPBufSize = 2048
UDPBufSize = 65507
)
// A net.Addr where the IP is split into two fields so you can use it as a key

12
signal/signal_unix.go Normal file
View file

@ -0,0 +1,12 @@
// +build !windows
package signal
import (
"syscall"
)
// Signals used in api/client (no windows equivalent, use
// invalid signals so they don't get handled)
const SIGCHLD = syscall.SIGCHLD
const SIGWINCH = syscall.SIGWINCH

12
signal/signal_windows.go Normal file
View file

@ -0,0 +1,12 @@
// +build windows
package signal
import (
"syscall"
)
// Signals used in api/client (no windows equivalent, use
// invalid signals so they don't get handled)
const SIGCHLD = syscall.Signal(0xff)
const SIGWINCH = syscall.Signal(0xff)

View file

@ -1,11 +1,12 @@
package signal
import (
"log"
"os"
gosignal "os/signal"
"sync/atomic"
"syscall"
log "github.com/Sirupsen/logrus"
)
// Trap sets up a simplified signal "trap", appropriate for common
@ -28,14 +29,13 @@ func Trap(cleanup func()) {
interruptCount := uint32(0)
for sig := range c {
go func(sig os.Signal) {
log.Printf("Received signal '%v', starting shutdown of docker...\n", sig)
log.Infof("Received signal '%v', starting shutdown of docker...", sig)
switch sig {
case os.Interrupt, syscall.SIGTERM:
// If the user really wants to interrupt, let him do so.
if atomic.LoadUint32(&interruptCount) < 3 {
atomic.AddUint32(&interruptCount, 1)
// Initiate the cleanup only once
if atomic.LoadUint32(&interruptCount) == 1 {
if atomic.AddUint32(&interruptCount, 1) == 1 {
// Call cleanup handler
cleanup()
os.Exit(0)
@ -43,7 +43,7 @@ func Trap(cleanup func()) {
return
}
} else {
log.Printf("Force shutdown of docker, interrupting cleanup\n")
log.Infof("Force shutdown of docker, interrupting cleanup")
}
case syscall.SIGQUIT:
}

View file

@ -5,7 +5,7 @@ import (
"errors"
"io"
"github.com/docker/docker/pkg/log"
log "github.com/Sirupsen/logrus"
)
const (

View file

@ -2,10 +2,10 @@ package sysinfo
import (
"io/ioutil"
"log"
"os"
"path"
log "github.com/Sirupsen/logrus"
"github.com/docker/libcontainer/cgroups"
)

16
system/lstat.go Normal file
View file

@ -0,0 +1,16 @@
// +build !windows
package system
import (
"syscall"
)
func Lstat(path string) (*Stat, error) {
s := &syscall.Stat_t{}
err := syscall.Lstat(path, s)
if err != nil {
return nil, err
}
return fromStatT(s)
}

27
system/lstat_test.go Normal file
View file

@ -0,0 +1,27 @@
package system
import (
"os"
"testing"
)
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")
}
}

8
system/lstat_windows.go Normal file
View file

@ -0,0 +1,8 @@
// +build windows
package system
func Lstat(path string) (*Stat, error) {
// should not be called on cli code path
return nil, ErrNotSupportedPlatform
}

17
system/meminfo.go Normal file
View 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
}

67
system/meminfo_linux.go Normal file
View file

@ -0,0 +1,67 @@
package system
import (
"bufio"
"errors"
"io"
"os"
"strconv"
"strings"
"github.com/docker/docker/pkg/units"
)
var (
ErrMalformed = errors.New("malformed file")
)
// Retrieve memory statistics of the host system and parse them into 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)
}
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
}

View file

@ -0,0 +1,37 @@
package system
import (
"strings"
"testing"
"github.com/docker/docker/pkg/units"
)
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)
}
}

View file

@ -0,0 +1,7 @@
// +build !linux
package system
func ReadMemInfo() (*MemInfo, error) {
return nil, ErrNotSupportedPlatform
}

18
system/mknod.go Normal file
View file

@ -0,0 +1,18 @@
// +build !windows
package system
import (
"syscall"
)
func Mknod(path string, mode uint32, dev int) error {
return syscall.Mknod(path, mode, dev)
}
// 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))
}

12
system/mknod_windows.go Normal file
View file

@ -0,0 +1,12 @@
// +build windows
package system
func Mknod(path string, mode uint32, dev int) error {
// should not be called on cli code path
return ErrNotSupportedPlatform
}
func Mkdev(major int64, minor int64) uint32 {
panic("Mkdev not implemented on windows, should not be called on cli code")
}

42
system/stat.go Normal file
View file

@ -0,0 +1,42 @@
package system
import (
"syscall"
)
type Stat struct {
mode uint32
uid uint32
gid uint32
rdev uint64
size int64
mtim syscall.Timespec
}
func (s Stat) Mode() uint32 {
return s.mode
}
func (s Stat) Uid() uint32 {
return s.uid
}
func (s Stat) Gid() uint32 {
return s.gid
}
func (s Stat) Rdev() uint64 {
return s.rdev
}
func (s Stat) Size() int64 {
return s.size
}
func (s Stat) Mtim() syscall.Timespec {
return s.mtim
}
func (s Stat) GetLastModification() syscall.Timespec {
return s.Mtim()
}

View file

@ -4,10 +4,11 @@ import (
"syscall"
)
func GetLastAccess(stat *syscall.Stat_t) syscall.Timespec {
return stat.Atim
}
func GetLastModification(stat *syscall.Stat_t) syscall.Timespec {
return stat.Mtim
func fromStatT(s *syscall.Stat_t) (*Stat, error) {
return &Stat{size: s.Size,
mode: s.Mode,
uid: s.Uid,
gid: s.Gid,
rdev: s.Rdev,
mtim: s.Mtim}, nil
}

36
system/stat_test.go Normal file
View file

@ -0,0 +1,36 @@
package system
import (
"os"
"syscall"
"testing"
)
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")
}
}

View file

@ -1,13 +1,16 @@
// +build !linux
// +build !linux,!windows
package system
import "syscall"
import (
"syscall"
)
func GetLastAccess(stat *syscall.Stat_t) syscall.Timespec {
return stat.Atimespec
}
func GetLastModification(stat *syscall.Stat_t) syscall.Timespec {
return stat.Mtimespec
func fromStatT(s *syscall.Stat_t) (*Stat, error) {
return &Stat{size: s.Size,
mode: uint32(s.Mode),
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
mtim: s.Mtimespec}, nil
}

12
system/stat_windows.go Normal file
View file

@ -0,0 +1,12 @@
// +build windows
package system
import (
"errors"
"syscall"
)
func fromStatT(s *syscall.Win32FileAttributeData) (*Stat, error) {
return nil, errors.New("fromStatT should not be called on windows path")
}

11
system/umask.go Normal file
View file

@ -0,0 +1,11 @@
// +build !windows
package system
import (
"syscall"
)
func Umask(newmask int) (oldmask int, err error) {
return syscall.Umask(newmask), nil
}

8
system/umask_windows.go Normal file
View file

@ -0,0 +1,8 @@
// +build windows
package system
func Umask(newmask int) (oldmask int, err error) {
// should not be called on cli code path
return 0, ErrNotSupportedPlatform
}

View file

@ -8,7 +8,7 @@ import (
"testing"
)
func prepareFiles(t *testing.T) (string, string, string) {
func prepareFiles(t *testing.T) (string, string, string, string) {
dir, err := ioutil.TempDir("", "docker-system-test")
if err != nil {
t.Fatal(err)
@ -26,11 +26,12 @@ func prepareFiles(t *testing.T) (string, string, string) {
t.Fatal(err)
}
return file, invalid, symlink
return file, invalid, symlink, dir
}
func TestLUtimesNano(t *testing.T) {
file, invalid, symlink := prepareFiles(t)
file, invalid, symlink, dir := prepareFiles(t)
defer os.RemoveAll(dir)
before, err := os.Stat(file)
if err != nil {

View file

@ -1 +0,0 @@
Eric Windisch <ewindisch@docker.com> (@ewindisch)

4
tarsum/MAINTAINERS Normal file
View file

@ -0,0 +1,4 @@
Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
Eric Windisch <ewindisch@docker.com> (github: ewindisch)
Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
Vincent Batts <vbatts@redhat.com> (github: vbatts)

View file

@ -7,13 +7,11 @@ import (
"encoding/hex"
"hash"
"io"
"sort"
"strconv"
"strings"
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
"github.com/docker/docker/pkg/log"
log "github.com/Sirupsen/logrus"
)
const (
@ -29,18 +27,18 @@ const (
// including the byte payload of the image's json metadata as well, and for
// calculating the checksums for buildcache.
func NewTarSum(r io.Reader, dc bool, v Version) (TarSum, error) {
if _, ok := tarSumVersions[v]; !ok {
return nil, ErrVersionNotImplemented
}
return &tarSum{Reader: r, DisableCompression: dc, tarSumVersion: v}, nil
return NewTarSumHash(r, dc, v, DefaultTHash)
}
// Create a new TarSum, providing a THash to use rather than the DefaultTHash
func NewTarSumHash(r io.Reader, dc bool, v Version, tHash THash) (TarSum, error) {
if _, ok := tarSumVersions[v]; !ok {
return nil, ErrVersionNotImplemented
headerSelector, err := getTarHeaderSelector(v)
if err != nil {
return nil, err
}
return &tarSum{Reader: r, DisableCompression: dc, tarSumVersion: v, tHash: tHash}, nil
ts := &tarSum{Reader: r, DisableCompression: dc, tarSumVersion: v, headerSelector: headerSelector, tHash: tHash}
err = ts.initTarSum()
return ts, err
}
// TarSum is the generic interface for calculating fixed time
@ -69,8 +67,9 @@ type tarSum struct {
currentFile string
finished bool
first bool
DisableCompression bool // false by default. When false, the output gzip compressed.
tarSumVersion Version // this field is not exported so it can not be mutated during use
DisableCompression bool // false by default. When false, the output gzip compressed.
tarSumVersion Version // this field is not exported so it can not be mutated during use
headerSelector tarHeaderSelector // handles selecting and ordering headers for files in the archive
}
func (ts tarSum) Hash() THash {
@ -103,49 +102,12 @@ type simpleTHash struct {
func (sth simpleTHash) Name() string { return sth.n }
func (sth simpleTHash) Hash() hash.Hash { return sth.h() }
func (ts tarSum) selectHeaders(h *tar.Header, v Version) (set [][2]string) {
for _, elem := range [][2]string{
{"name", h.Name},
{"mode", strconv.Itoa(int(h.Mode))},
{"uid", strconv.Itoa(h.Uid)},
{"gid", strconv.Itoa(h.Gid)},
{"size", strconv.Itoa(int(h.Size))},
{"mtime", strconv.Itoa(int(h.ModTime.UTC().Unix()))},
{"typeflag", string([]byte{h.Typeflag})},
{"linkname", h.Linkname},
{"uname", h.Uname},
{"gname", h.Gname},
{"devmajor", strconv.Itoa(int(h.Devmajor))},
{"devminor", strconv.Itoa(int(h.Devminor))},
} {
if v >= VersionDev && elem[0] == "mtime" {
continue
}
set = append(set, elem)
}
return
}
func (ts *tarSum) encodeHeader(h *tar.Header) error {
for _, elem := range ts.selectHeaders(h, ts.Version()) {
for _, elem := range ts.headerSelector.selectHeaders(h) {
if _, err := ts.h.Write([]byte(elem[0] + elem[1])); err != nil {
return err
}
}
// include the additional pax headers, from an ordered list
if ts.Version() >= VersionDev {
var keys []string
for k := range h.Xattrs {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if _, err := ts.h.Write([]byte(k + h.Xattrs[k])); err != nil {
return err
}
}
}
return nil
}
@ -170,12 +132,6 @@ func (ts *tarSum) initTarSum() error {
}
func (ts *tarSum) Read(buf []byte) (int, error) {
if ts.writer == nil {
if err := ts.initTarSum(); err != nil {
return 0, err
}
}
if ts.finished {
return ts.bufWriter.Read(buf)
}

225
tarsum/tarsum_spec.md Normal file
View file

@ -0,0 +1,225 @@
page_title: TarSum checksum specification
page_description: Documentation for algorithms used in the TarSum checksum calculation
page_keywords: docker, checksum, validation, tarsum
# TarSum Checksum Specification
## Abstract
This document describes the algorithms used in performing the TarSum checksum
calculation on filesystem layers, the need for this method over existing
methods, and the versioning of this calculation.
## Introduction
The transportation of filesystems, regarding Docker, is done with tar(1)
archives. There are a variety of tar serialization formats [2], and a key
concern here is ensuring a repeatable checksum given a set of inputs from a
generic tar archive. Types of transportation include distribution to and from a
registry endpoint, saving and loading through commands or Docker daemon APIs,
transferring the build context from client to Docker daemon, and committing the
filesystem of a container to become an image.
As tar archives are used for transit, but not preserved in many situations, the
focus of the algorithm is to ensure the integrity of the preserved filesystem,
while maintaining a deterministic accountability. This includes neither
constraining the ordering or manipulation of the files during the creation or
unpacking of the archive, nor include additional metadata state about the file
system attributes.
## Intended Audience
This document is outlining the methods used for consistent checksum calculation
for filesystems transported via tar archives.
Auditing these methodologies is an open and iterative process. This document
should accommodate the review of source code. Ultimately, this document should
be the starting point of further refinements to the algorithm and its future
versions.
## Concept
The checksum mechanism must ensure the integrity and assurance of the
filesystem payload.
## Checksum Algorithm Profile
A checksum mechanism must define the following operations and attributes:
* Associated hashing cipher - used to checksum each file payload and attribute
information.
* Checksum list - each file of the filesystem archive has its checksum
calculated from the payload and attributes of the file. The final checksum is
calculated from this list, with specific ordering.
* Version - as the algorithm adapts to requirements, there are behaviors of the
algorithm to manage by versioning.
* Archive being calculated - the tar archive having its checksum calculated
## Elements of TarSum checksum
The calculated sum output is a text string. The elements included in the output
of the calculated sum comprise the information needed for validation of the sum
(TarSum version and hashing cipher used) and the expected checksum in hexadecimal
form.
There are two delimiters used:
* '+' separates TarSum version from hashing cipher
* ':' separates calculation mechanics from expected hash
Example:
```
"tarsum.v1+sha256:220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e"
| | \ |
| | \ |
|_version_|_cipher__|__ |
| \ |
|_calculation_mechanics_|______________________expected_sum_______________________|
```
## Versioning
Versioning was introduced [0] to accommodate differences in calculation needed,
and ability to maintain reverse compatibility.
The general algorithm will be describe further in the 'Calculation'.
### Version0
This is the initial version of TarSum.
Its element in the TarSum checksum string is `tarsum`.
### Version1
Its element in the TarSum checksum is `tarsum.v1`.
The notable changes in this version:
* Exclusion of file `mtime` from the file information headers, in each file
checksum calculation
* Inclusion of extended attributes (`xattrs`. Also seen as `SCHILY.xattr.` prefixed Pax
tar file info headers) keys and values in each file checksum calculation
### VersionDev
*Do not use unless validating refinements to the checksum algorithm*
Its element in the TarSum checksum is `tarsum.dev`.
This is a floating place holder for a next version and grounds for testing
changes. The methods used for calculation are subject to change without notice,
and this version is for testing and not for production use.
## Ciphers
The official default and standard hashing cipher used in the calculation mechanic
is `sha256`. This refers to SHA256 hash algorithm as defined in FIPS 180-4.
Though the TarSum algorithm itself is not exclusively bound to the single
hashing cipher `sha256`, support for alternate hashing ciphers was later added
[1]. Use cases for alternate cipher could include future-proofing TarSum
checksum format and using faster cipher hashes for tar filesystem checksums.
## Calculation
### Requirement
As mentioned earlier, the calculation is such that it takes into consideration
the lifecycle of the tar archive. In that the tar archive is not an immutable,
permanent artifact. Otherwise options like relying on a known hashing cipher
checksum of the archive itself would be reliable enough. The tar archive of the
filesystem is used as a transportation medium for Docker images, and the
archive is discarded once its contents are extracted. Therefore, for consistent
validation items such as order of files in the tar archive and time stamps are
subject to change once an image is received.
### Process
The method is typically iterative due to reading tar info headers from the
archive stream, though this is not a strict requirement.
#### Files
Each file in the tar archive have their contents (headers and body) checksummed
individually using the designated associated hashing cipher. The ordered
headers of the file are written to the checksum calculation first, and then the
payload of the file body.
The resulting checksum of the file is appended to the list of file sums. The
sum is encoded as a string of the hexadecimal digest. Additionally, the file
name and position in the archive is kept as reference for special ordering.
#### Headers
The following headers are read, in this
order ( and the corresponding representation of its value):
* 'name' - string
* 'mode' - string of the base10 integer
* 'uid' - string of the integer
* 'gid' - string of the integer
* 'size' - string of the integer
* 'mtime' (_Version0 only_) - string of integer of the seconds since 1970-01-01 00:00:00 UTC
* 'typeflag' - string of the char
* 'linkname' - string
* 'uname' - string
* 'gname' - string
* 'devmajor' - string of the integer
* 'devminor' - string of the integer
For >= Version1, the extented attribute headers ("SCHILY.xattr." prefixed pax
headers) included after the above list. These xattrs key/values are first
sorted by the keys.
#### Header Format
The ordered headers are written to the hash in the format of
"{.key}{.value}"
with no newline.
#### Body
After the order headers of the file have been added to the checksum for the
file, the body of the file is written to the hash.
#### List of file sums
The list of file sums is sorted by the string of the hexadecimal digest.
If there are two files in the tar with matching paths, the order of occurrence
for that path is reflected for the sums of the corresponding file header and
body.
#### Final Checksum
Begin with a fresh or initial state of the associated hash cipher. If there is
additional payload to include in the TarSum calculation for the archive, it is
written first. Then each checksum from the ordered list of file sums is written
to the hash.
The resulting digest is formatted per the Elements of TarSum checksum,
including the TarSum version, the associated hash cipher and the hexadecimal
encoded checksum digest.
## Security Considerations
The initial version of TarSum has undergone one update that could invalidate
handcrafted tar archives. The tar archive format supports appending of files
with same names as prior files in the archive. The latter file will clobber the
prior file of the same path. Due to this the algorithm now accounts for files
with matching paths, and orders the list of file sums accordingly [3].
## Footnotes
* [0] Versioning https://github.com/docker/docker/commit/747f89cd327db9d50251b17797c4d825162226d0
* [1] Alternate ciphers https://github.com/docker/docker/commit/4e9925d780665149b8bc940d5ba242ada1973c4e
* [2] Tar http://en.wikipedia.org/wiki/Tar_%28computing%29
* [3] Name collision https://github.com/docker/docker/commit/c5e6362c53cbbc09ddbabd5a7323e04438b57d31
## Acknowledgements
Joffrey F (shin-) and Guillaume J. Charmes (creack) on the initial work of the
TarSum calculation.

View file

@ -132,6 +132,7 @@ func sizedTar(opts sizedOptions) io.Reader {
fh = bytes.NewBuffer([]byte{})
}
tarW := tar.NewWriter(fh)
defer tarW.Close()
for i := int64(0); i < opts.num; i++ {
err := tarW.WriteHeader(&tar.Header{
Name: fmt.Sprintf("/testdata%d", i),
@ -230,6 +231,17 @@ func TestEmptyTar(t *testing.T) {
if resultSum != expectedSum {
t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum)
}
// Test without ever actually writing anything.
if ts, err = NewTarSum(bytes.NewReader([]byte{}), true, Version0); err != nil {
t.Fatal(err)
}
resultSum = ts.Sum(nil)
if resultSum != expectedSum {
t.Fatalf("expected [%s] but got [%s]", expectedSum, resultSum)
}
}
var (
@ -318,6 +330,153 @@ func TestTarSums(t *testing.T) {
}
}
func TestIteration(t *testing.T) {
headerTests := []struct {
expectedSum string // TODO(vbatts) it would be nice to get individual sums of each
version Version
hdr *tar.Header
data []byte
}{
{
"tarsum+sha256:626c4a2e9a467d65c33ae81f7f3dedd4de8ccaee72af73223c4bc4718cbc7bbd",
Version0,
&tar.Header{
Name: "file.txt",
Size: 0,
Typeflag: tar.TypeReg,
Devminor: 0,
Devmajor: 0,
},
[]byte(""),
},
{
"tarsum.dev+sha256:6ffd43a1573a9913325b4918e124ee982a99c0f3cba90fc032a65f5e20bdd465",
VersionDev,
&tar.Header{
Name: "file.txt",
Size: 0,
Typeflag: tar.TypeReg,
Devminor: 0,
Devmajor: 0,
},
[]byte(""),
},
{
"tarsum.dev+sha256:b38166c059e11fb77bef30bf16fba7584446e80fcc156ff46d47e36c5305d8ef",
VersionDev,
&tar.Header{
Name: "another.txt",
Uid: 1000,
Gid: 1000,
Uname: "slartibartfast",
Gname: "users",
Size: 4,
Typeflag: tar.TypeReg,
Devminor: 0,
Devmajor: 0,
},
[]byte("test"),
},
{
"tarsum.dev+sha256:4cc2e71ac5d31833ab2be9b4f7842a14ce595ec96a37af4ed08f87bc374228cd",
VersionDev,
&tar.Header{
Name: "xattrs.txt",
Uid: 1000,
Gid: 1000,
Uname: "slartibartfast",
Gname: "users",
Size: 4,
Typeflag: tar.TypeReg,
Xattrs: map[string]string{
"user.key1": "value1",
"user.key2": "value2",
},
},
[]byte("test"),
},
{
"tarsum.dev+sha256:65f4284fa32c0d4112dd93c3637697805866415b570587e4fd266af241503760",
VersionDev,
&tar.Header{
Name: "xattrs.txt",
Uid: 1000,
Gid: 1000,
Uname: "slartibartfast",
Gname: "users",
Size: 4,
Typeflag: tar.TypeReg,
Xattrs: map[string]string{
"user.KEY1": "value1", // adding different case to ensure different sum
"user.key2": "value2",
},
},
[]byte("test"),
},
{
"tarsum+sha256:c12bb6f1303a9ddbf4576c52da74973c00d14c109bcfa76b708d5da1154a07fa",
Version0,
&tar.Header{
Name: "xattrs.txt",
Uid: 1000,
Gid: 1000,
Uname: "slartibartfast",
Gname: "users",
Size: 4,
Typeflag: tar.TypeReg,
Xattrs: map[string]string{
"user.NOT": "CALCULATED",
},
},
[]byte("test"),
},
}
for _, htest := range headerTests {
s, err := renderSumForHeader(htest.version, htest.hdr, htest.data)
if err != nil {
t.Fatal(err)
}
if s != htest.expectedSum {
t.Errorf("expected sum: %q, got: %q", htest.expectedSum, s)
}
}
}
func renderSumForHeader(v Version, h *tar.Header, data []byte) (string, error) {
buf := bytes.NewBuffer(nil)
// first build our test tar
tw := tar.NewWriter(buf)
if err := tw.WriteHeader(h); err != nil {
return "", err
}
if _, err := tw.Write(data); err != nil {
return "", err
}
tw.Close()
ts, err := NewTarSum(buf, true, v)
if err != nil {
return "", err
}
tr := tar.NewReader(ts)
for {
hdr, err := tr.Next()
if hdr == nil || err == io.EOF {
// Signals the end of the archive.
break
}
if err != nil {
return "", err
}
if _, err = io.Copy(ioutil.Discard, tr); err != nil {
return "", err
}
}
return ts.Sum(nil), nil
}
func Benchmark9kTar(b *testing.B) {
buf := bytes.NewBuffer([]byte{})
fh, err := os.Open("testdata/46af0962ab5afeb5ce6740d4d91652e69206fc991fd5328c1a94d364ad00e457/layer.tar")
@ -328,10 +487,13 @@ func Benchmark9kTar(b *testing.B) {
n, err := io.Copy(buf, fh)
fh.Close()
reader := bytes.NewReader(buf.Bytes())
b.SetBytes(n)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ts, err := NewTarSum(buf, true, Version0)
reader.Seek(0, 0)
ts, err := NewTarSum(reader, true, Version0)
if err != nil {
b.Error(err)
return
@ -351,10 +513,13 @@ func Benchmark9kTarGzip(b *testing.B) {
n, err := io.Copy(buf, fh)
fh.Close()
reader := bytes.NewReader(buf.Bytes())
b.SetBytes(n)
b.ResetTimer()
for i := 0; i < b.N; i++ {
ts, err := NewTarSum(buf, false, Version0)
reader.Seek(0, 0)
ts, err := NewTarSum(reader, false, Version0)
if err != nil {
b.Error(err)
return

View file

@ -2,7 +2,11 @@ package tarsum
import (
"errors"
"sort"
"strconv"
"strings"
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
// versioning of the TarSum algorithm
@ -10,11 +14,11 @@ import (
// i.e. "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"
type Version int
// Prefix of "tarsum"
const (
// Prefix of "tarsum"
Version0 Version = iota
// Prefix of "tarsum.dev"
// NOTE: this variable will be of an unsettled next-version of the TarSum calculation
Version1
// NOTE: this variable will be either the latest or an unsettled next-version of the TarSum calculation
VersionDev
)
@ -28,8 +32,9 @@ func GetVersions() []Version {
}
var tarSumVersions = map[Version]string{
0: "tarsum",
1: "tarsum.dev",
Version0: "tarsum",
Version1: "tarsum.v1",
VersionDev: "tarsum.dev",
}
func (tsv Version) String() string {
@ -50,7 +55,78 @@ func GetVersionFromTarsum(tarsum string) (Version, error) {
return -1, ErrNotVersion
}
// Errors that may be returned by functions in this package
var (
ErrNotVersion = errors.New("string does not include a TarSum Version")
ErrVersionNotImplemented = errors.New("TarSum Version is not yet implemented")
)
// tarHeaderSelector is the interface which different versions
// of tarsum should use for selecting and ordering tar headers
// for each item in the archive.
type tarHeaderSelector interface {
selectHeaders(h *tar.Header) (orderedHeaders [][2]string)
}
type tarHeaderSelectFunc func(h *tar.Header) (orderedHeaders [][2]string)
func (f tarHeaderSelectFunc) selectHeaders(h *tar.Header) (orderedHeaders [][2]string) {
return f(h)
}
func v0TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
return [][2]string{
{"name", h.Name},
{"mode", strconv.Itoa(int(h.Mode))},
{"uid", strconv.Itoa(h.Uid)},
{"gid", strconv.Itoa(h.Gid)},
{"size", strconv.Itoa(int(h.Size))},
{"mtime", strconv.Itoa(int(h.ModTime.UTC().Unix()))},
{"typeflag", string([]byte{h.Typeflag})},
{"linkname", h.Linkname},
{"uname", h.Uname},
{"gname", h.Gname},
{"devmajor", strconv.Itoa(int(h.Devmajor))},
{"devminor", strconv.Itoa(int(h.Devminor))},
}
}
func v1TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
// Get extended attributes.
xAttrKeys := make([]string, len(h.Xattrs))
for k := range h.Xattrs {
xAttrKeys = append(xAttrKeys, k)
}
sort.Strings(xAttrKeys)
// Make the slice with enough capacity to hold the 11 basic headers
// we want from the v0 selector plus however many xattrs we have.
orderedHeaders = make([][2]string, 0, 11+len(xAttrKeys))
// Copy all headers from v0 excluding the 'mtime' header (the 5th element).
v0headers := v0TarHeaderSelect(h)
orderedHeaders = append(orderedHeaders, v0headers[0:5]...)
orderedHeaders = append(orderedHeaders, v0headers[6:]...)
// Finally, append the sorted xattrs.
for _, k := range xAttrKeys {
orderedHeaders = append(orderedHeaders, [2]string{k, h.Xattrs[k]})
}
return
}
var registeredHeaderSelectors = map[Version]tarHeaderSelectFunc{
Version0: v0TarHeaderSelect,
Version1: v1TarHeaderSelect,
VersionDev: v1TarHeaderSelect,
}
func getTarHeaderSelector(v Version) (tarHeaderSelector, error) {
headerSelector, ok := registeredHeaderSelectors[v]
if !ok {
return nil, ErrVersionNotImplemented
}
return headerSelector, nil
}

View file

@ -11,11 +11,17 @@ func TestVersion(t *testing.T) {
t.Errorf("expected %q, got %q", expected, v.String())
}
expected = "tarsum.dev"
expected = "tarsum.v1"
v = 1
if v.String() != expected {
t.Errorf("expected %q, got %q", expected, v.String())
}
expected = "tarsum.dev"
v = 2
if v.String() != expected {
t.Errorf("expected %q, got %q", expected, v.String())
}
}
func TestGetVersion(t *testing.T) {

87
term/console_windows.go Normal file
View file

@ -0,0 +1,87 @@
// +build windows
package term
import (
"syscall"
"unsafe"
)
const (
// Consts for Get/SetConsoleMode function
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
ENABLE_ECHO_INPUT = 0x0004
ENABLE_INSERT_MODE = 0x0020
ENABLE_LINE_INPUT = 0x0002
ENABLE_MOUSE_INPUT = 0x0010
ENABLE_PROCESSED_INPUT = 0x0001
ENABLE_QUICK_EDIT_MODE = 0x0040
ENABLE_WINDOW_INPUT = 0x0008
// If parameter is a screen buffer handle, additional values
ENABLE_PROCESSED_OUTPUT = 0x0001
ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
)
var kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
var (
setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
)
func GetConsoleMode(fileDesc uintptr) (uint32, error) {
var mode uint32
err := syscall.GetConsoleMode(syscall.Handle(fileDesc), &mode)
return mode, err
}
func SetConsoleMode(fileDesc uintptr, mode uint32) error {
r, _, err := setConsoleModeProc.Call(fileDesc, uintptr(mode), 0)
if r == 0 {
if err != nil {
return err
}
return syscall.EINVAL
}
return nil
}
// types for calling GetConsoleScreenBufferInfo
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
type (
SHORT int16
SMALL_RECT struct {
Left SHORT
Top SHORT
Right SHORT
Bottom SHORT
}
COORD struct {
X SHORT
Y SHORT
}
WORD uint16
CONSOLE_SCREEN_BUFFER_INFO struct {
dwSize COORD
dwCursorPosition COORD
wAttributes WORD
srWindow SMALL_RECT
dwMaximumWindowSize COORD
}
)
func GetConsoleScreenBufferInfo(fileDesc uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
var info CONSOLE_SCREEN_BUFFER_INFO
r, _, err := getConsoleScreenBufferInfoProc.Call(uintptr(fileDesc), uintptr(unsafe.Pointer(&info)), 0)
if r == 0 {
if err != nil {
return nil, err
}
return nil, syscall.EINVAL
}
return &info, nil
}

47
term/tc_linux_cgo.go Normal file
View file

@ -0,0 +1,47 @@
// +build linux,cgo
package term
import (
"syscall"
"unsafe"
)
// #include <termios.h>
import "C"
type Termios syscall.Termios
// MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd uintptr) (*State, error) {
var oldState State
if err := tcget(fd, &oldState.termios); err != 0 {
return nil, err
}
newState := oldState.termios
C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&newState)))
if err := tcset(fd, &newState); err != 0 {
return nil, err
}
return &oldState, nil
}
func tcget(fd uintptr, p *Termios) syscall.Errno {
ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p)))
if ret != 0 {
return err.(syscall.Errno)
}
return 0
}
func tcset(fd uintptr, p *Termios) syscall.Errno {
ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p)))
if ret != 0 {
return err.(syscall.Errno)
}
return 0
}

19
term/tc_other.go Normal file
View file

@ -0,0 +1,19 @@
// +build !windows
// +build !linux !cgo
package term
import (
"syscall"
"unsafe"
)
func tcget(fd uintptr, p *Termios) syscall.Errno {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(p)))
return err
}
func tcset(fd uintptr, p *Termios) syscall.Errno {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(p)))
return err
}

View file

@ -1,3 +1,5 @@
// +build !windows
package term
import (
@ -45,8 +47,7 @@ func SetWinsize(fd uintptr, ws *Winsize) error {
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
var termios Termios
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&termios)))
return err == 0
return tcget(fd, &termios) == 0
}
// Restore restores the terminal connected to the given file descriptor to a
@ -55,8 +56,7 @@ func RestoreTerminal(fd uintptr, state *State) error {
if state == nil {
return ErrInvalidState
}
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&state.termios)))
if err != 0 {
if err := tcset(fd, &state.termios); err != 0 {
return err
}
return nil
@ -64,7 +64,7 @@ func RestoreTerminal(fd uintptr, state *State) error {
func SaveState(fd uintptr) (*State, error) {
var oldState State
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
if err := tcget(fd, &oldState.termios); err != 0 {
return nil, err
}
@ -75,7 +75,7 @@ func DisableEcho(fd uintptr, state *State) error {
newState := state.termios
newState.Lflag &^= syscall.ECHO
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
if err := tcset(fd, &newState); err != 0 {
return err
}
handleInterrupt(fd, state)

89
term/term_windows.go Normal file
View file

@ -0,0 +1,89 @@
// +build windows
package term
type State struct {
mode uint32
}
type Winsize struct {
Height uint16
Width uint16
x uint16
y uint16
}
func GetWinsize(fd uintptr) (*Winsize, error) {
ws := &Winsize{}
var info *CONSOLE_SCREEN_BUFFER_INFO
info, err := GetConsoleScreenBufferInfo(fd)
if err != nil {
return nil, err
}
ws.Height = uint16(info.srWindow.Right - info.srWindow.Left + 1)
ws.Width = uint16(info.srWindow.Bottom - info.srWindow.Top + 1)
ws.x = 0 // todo azlinux -- this is the pixel size of the Window, and not currently used by any caller
ws.y = 0
return ws, nil
}
func SetWinsize(fd uintptr, ws *Winsize) error {
return nil
}
// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
_, e := GetConsoleMode(fd)
return e == nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func RestoreTerminal(fd uintptr, state *State) error {
return SetConsoleMode(fd, state.mode)
}
func SaveState(fd uintptr) (*State, error) {
mode, e := GetConsoleMode(fd)
if e != nil {
return nil, e
}
return &State{mode}, nil
}
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx for these flag settings
func DisableEcho(fd uintptr, state *State) error {
state.mode &^= (ENABLE_ECHO_INPUT)
state.mode |= (ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)
return SetConsoleMode(fd, state.mode)
}
func SetRawTerminal(fd uintptr) (*State, error) {
oldState, err := MakeRaw(fd)
if err != nil {
return nil, err
}
// TODO (azlinux): implement handling interrupt and restore state of terminal
return oldState, err
}
// MakeRaw puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd uintptr) (*State, error) {
var state *State
state, err := SaveState(fd)
if err != nil {
return nil, err
}
// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx for these flag settings
state.mode &^= (ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT)
err = SetConsoleMode(fd, state.mode)
if err != nil {
return nil, err
}
return state, nil
}

View file

@ -1,3 +1,5 @@
// +build !cgo
package term
import (

View file

@ -6,18 +6,21 @@ import (
)
const (
// Define our own version of RFC339Nano because we want one
// RFC3339NanoFixed is our own version of RFC339Nano because we want one
// that pads the nano seconds part with zeros to ensure
// the timestamps are aligned in the logs.
RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
JSONFormat = `"` + time.RFC3339Nano + `"`
// JSONFormat is the format used by FastMarshalJSON
JSONFormat = `"` + time.RFC3339Nano + `"`
)
// FastMarshalJSON avoids one of the extra allocations that
// time.MarshalJSON is making.
func FastMarshalJSON(t time.Time) (string, error) {
if y := t.Year(); y < 0 || y >= 10000 {
// RFC 3339 is clear that years are 4 digits exactly.
// See golang.org/issue/4556#c15 for more discussion.
return "", errors.New("Time.MarshalJSON: year outside of range [0,9999]")
return "", errors.New("time.MarshalJSON: year outside of range [0,9999]")
}
return t.Format(JSONFormat), nil
}

View file

@ -10,7 +10,9 @@ import (
)
var (
ErrNoID = errors.New("prefix can't be empty")
// ErrNoID is thrown when attempting to use empty prefixes
ErrNoID = errors.New("prefix can't be empty")
errDuplicateID = errors.New("multiple IDs were found")
)
func init() {
@ -27,56 +29,62 @@ type TruncIndex struct {
ids map[string]struct{}
}
// NewTruncIndex creates a new TruncIndex and initializes with a list of IDs
func NewTruncIndex(ids []string) (idx *TruncIndex) {
idx = &TruncIndex{
ids: make(map[string]struct{}),
trie: patricia.NewTrie(),
}
for _, id := range ids {
idx.addId(id)
idx.addID(id)
}
return
}
func (idx *TruncIndex) addId(id string) error {
func (idx *TruncIndex) addID(id string) error {
if strings.Contains(id, " ") {
return fmt.Errorf("Illegal character: ' '")
return fmt.Errorf("illegal character: ' '")
}
if id == "" {
return ErrNoID
}
if _, exists := idx.ids[id]; exists {
return fmt.Errorf("Id already exists: '%s'", id)
return fmt.Errorf("id already exists: '%s'", id)
}
idx.ids[id] = struct{}{}
if inserted := idx.trie.Insert(patricia.Prefix(id), struct{}{}); !inserted {
return fmt.Errorf("Failed to insert id: %s", id)
return fmt.Errorf("failed to insert id: %s", id)
}
return nil
}
// Add adds a new ID to the TruncIndex
func (idx *TruncIndex) Add(id string) error {
idx.Lock()
defer idx.Unlock()
if err := idx.addId(id); err != nil {
if err := idx.addID(id); err != nil {
return err
}
return nil
}
// Delete removes an ID from the TruncIndex. If there are multiple IDs
// with the given prefix, an error is thrown.
func (idx *TruncIndex) Delete(id string) error {
idx.Lock()
defer idx.Unlock()
if _, exists := idx.ids[id]; !exists || id == "" {
return fmt.Errorf("No such id: '%s'", id)
return fmt.Errorf("no such id: '%s'", id)
}
delete(idx.ids, id)
if deleted := idx.trie.Delete(patricia.Prefix(id)); !deleted {
return fmt.Errorf("No such id: '%s'", id)
return fmt.Errorf("no such id: '%s'", id)
}
return nil
}
// Get retrieves an ID from the TruncIndex. If there are multiple IDs
// with the given prefix, an error is thrown.
func (idx *TruncIndex) Get(s string) (string, error) {
idx.RLock()
defer idx.RUnlock()
@ -90,17 +98,17 @@ func (idx *TruncIndex) Get(s string) (string, error) {
if id != "" {
// we haven't found the ID if there are two or more IDs
id = ""
return fmt.Errorf("we've found two entries")
return errDuplicateID
}
id = string(prefix)
return nil
}
if err := idx.trie.VisitSubtree(patricia.Prefix(s), subTreeVisitFunc); err != nil {
return "", fmt.Errorf("No such id: %s", s)
return "", fmt.Errorf("no such id: %s", s)
}
if id != "" {
return id, nil
}
return "", fmt.Errorf("No such id: %s", s)
return "", fmt.Errorf("no such id: %s", s)
}

View file

@ -1,2 +1,2 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
Victor Vieux <vieux@docker.com> (@vieux)
Jessie Frazelle <jess@docker.com> (@jfrazelle)

View file

@ -10,6 +10,7 @@ import (
// See: http://en.wikipedia.org/wiki/Binary_prefix
const (
// Decimal
KB = 1000
MB = 1000 * KB
GB = 1000 * MB
@ -17,6 +18,7 @@ const (
PB = 1000 * TB
// Binary
KiB = 1024
MiB = 1024 * KiB
GiB = 1024 * MiB
@ -32,18 +34,26 @@ var (
sizeRegex = regexp.MustCompile(`^(\d+)([kKmMgGtTpP])?[bB]?$`)
)
var unitAbbrs = [...]string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
// HumanSize returns a human-readable approximation of a size
// using SI standard (eg. "44kB", "17MB")
func HumanSize(size int64) string {
return intToString(float64(size), 1000.0, decimapAbbrs)
}
func BytesSize(size float64) string {
return intToString(size, 1024.0, binaryAbbrs)
}
func intToString(size, unit float64, _map []string) string {
i := 0
sizef := float64(size)
for sizef >= 1000.0 {
sizef = sizef / 1000.0
for size >= unit {
size = size / unit
i++
}
return fmt.Sprintf("%.4g %s", sizef, unitAbbrs[i])
return fmt.Sprintf("%.4g %s", size, _map[i])
}
// FromHumanSize returns an integer from a human-readable specification of a
@ -52,7 +62,7 @@ func FromHumanSize(size string) (int64, error) {
return parseSize(size, decimalMap)
}
// Parses a human-readable string representing an amount of RAM
// RAMInBytes parses a human-readable string representing an amount of RAM
// in bytes, kibibytes, mebibytes, gibibytes, or tebibytes and
// returns the number of bytes, or -1 if the string is unparseable.
// Units are case-insensitive, and the 'b' suffix is optional.
@ -64,7 +74,7 @@ func RAMInBytes(size string) (int64, error) {
func parseSize(sizeStr string, uMap unitMap) (int64, error) {
matches := sizeRegex.FindStringSubmatch(sizeStr)
if len(matches) != 3 {
return -1, fmt.Errorf("Invalid size: '%s'", sizeStr)
return -1, fmt.Errorf("invalid size: '%s'", sizeStr)
}
size, err := strconv.ParseInt(matches[1], 10, 0)

View file

@ -7,6 +7,16 @@ import (
"testing"
)
func TestBytesSize(t *testing.T) {
assertEquals(t, "1 KiB", BytesSize(1024))
assertEquals(t, "1 MiB", BytesSize(1024*1024))
assertEquals(t, "1 MiB", BytesSize(1048576))
assertEquals(t, "2 MiB", BytesSize(2*MiB))
assertEquals(t, "3.42 GiB", BytesSize(3.42*GiB))
assertEquals(t, "5.372 TiB", BytesSize(5.372*TiB))
assertEquals(t, "2.22 PiB", BytesSize(2.22*PiB))
}
func TestHumanSize(t *testing.T) {
assertEquals(t, "1 kB", HumanSize(1000))
assertEquals(t, "1.024 kB", HumanSize(1024))

30
urlutil/git.go Normal file
View file

@ -0,0 +1,30 @@
package urlutil
import "strings"
var (
validPrefixes = []string{
"git://",
"github.com/",
"git@",
}
)
// IsGitURL returns true if the provided str is a git repository URL.
func IsGitURL(str string) bool {
if IsURL(str) && strings.HasSuffix(str, ".git") {
return true
}
for _, prefix := range validPrefixes {
if strings.HasPrefix(str, prefix) {
return true
}
}
return false
}
// IsGitTransport returns true if the provided str is a git transport by inspecting
// the prefix of the string for known protocols used in git.
func IsGitTransport(str string) bool {
return IsURL(str) || strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "git@")
}

43
urlutil/git_test.go Normal file
View file

@ -0,0 +1,43 @@
package urlutil
import "testing"
var (
gitUrls = []string{
"git://github.com/docker/docker",
"git@github.com:docker/docker.git",
"git@bitbucket.org:atlassianlabs/atlassian-docker.git",
"https://github.com/docker/docker.git",
"http://github.com/docker/docker.git",
}
incompleteGitUrls = []string{
"github.com/docker/docker",
}
)
func TestValidGitTransport(t *testing.T) {
for _, url := range gitUrls {
if IsGitTransport(url) == false {
t.Fatalf("%q should be detected as valid Git prefix", url)
}
}
for _, url := range incompleteGitUrls {
if IsGitTransport(url) == true {
t.Fatalf("%q should not be detected as valid Git prefix", url)
}
}
}
func TestIsGIT(t *testing.T) {
for _, url := range gitUrls {
if IsGitURL(url) == false {
t.Fatalf("%q should be detected as valid Git url", url)
}
}
for _, url := range incompleteGitUrls {
if IsGitURL(url) == false {
t.Fatalf("%q should be detected as valid Git url", url)
}
}
}

19
urlutil/url.go Normal file
View file

@ -0,0 +1,19 @@
package urlutil
import "strings"
var validUrlPrefixes = []string{
"http://",
"https://",
}
// IsURL returns true if the provided str is a valid URL by doing
// a simple change for the transport of the url.
func IsURL(str string) bool {
for _, prefix := range validUrlPrefixes {
if strings.HasPrefix(str, prefix) {
return true
}
}
return false
}

View file

@ -5,53 +5,59 @@ import (
"strings"
)
// Version provides utility methods for comparing versions.
type Version string
func (me Version) compareTo(other Version) int {
func (v Version) compareTo(other Version) int {
var (
meTab = strings.Split(string(me), ".")
currTab = strings.Split(string(v), ".")
otherTab = strings.Split(string(other), ".")
)
max := len(meTab)
max := len(currTab)
if len(otherTab) > max {
max = len(otherTab)
}
for i := 0; i < max; i++ {
var meInt, otherInt int
var currInt, otherInt int
if len(meTab) > i {
meInt, _ = strconv.Atoi(meTab[i])
if len(currTab) > i {
currInt, _ = strconv.Atoi(currTab[i])
}
if len(otherTab) > i {
otherInt, _ = strconv.Atoi(otherTab[i])
}
if meInt > otherInt {
if currInt > otherInt {
return 1
}
if otherInt > meInt {
if otherInt > currInt {
return -1
}
}
return 0
}
func (me Version) LessThan(other Version) bool {
return me.compareTo(other) == -1
// LessThan checks if a version is less than another version
func (v Version) LessThan(other Version) bool {
return v.compareTo(other) == -1
}
func (me Version) LessThanOrEqualTo(other Version) bool {
return me.compareTo(other) <= 0
// LessThanOrEqualTo checks if a version is less than or equal to another
func (v Version) LessThanOrEqualTo(other Version) bool {
return v.compareTo(other) <= 0
}
func (me Version) GreaterThan(other Version) bool {
return me.compareTo(other) == 1
// GreaterThan checks if a version is greater than another one
func (v Version) GreaterThan(other Version) bool {
return v.compareTo(other) == 1
}
func (me Version) GreaterThanOrEqualTo(other Version) bool {
return me.compareTo(other) >= 0
// GreaterThanOrEqualTo checks ia version is greater than or equal to another
func (v Version) GreaterThanOrEqualTo(other Version) bool {
return v.compareTo(other) >= 0
}
func (me Version) Equal(other Version) bool {
return me.compareTo(other) == 0
// Equal checks if a version is equal to another
func (v Version) Equal(other Version) bool {
return v.compareTo(other) == 0
}