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:
commit
e4b13e4efb
88 changed files with 3624 additions and 496 deletions
|
@ -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
|
||||
|
|
|
@ -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
39
archive/archive_unix.go
Normal 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)
|
||||
}
|
12
archive/archive_windows.go
Normal file
12
archive/archive_windows.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
97
archive/example_changes.go
Normal file
97
archive/example_changes.go
Normal 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
|
||||
}
|
|
@ -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
1
devicemapper/MAINTAINERS
Normal file
|
@ -0,0 +1 @@
|
|||
Vincent Batts <vbatts@redhat.com> (@vbatts)
|
129
devicemapper/attach_loopback.go
Normal file
129
devicemapper/attach_loopback.go
Normal 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
669
devicemapper/devmapper.go
Normal 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, ¶ms),
|
||||
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
|
||||
}
|
30
devicemapper/devmapper_log.go
Normal file
30
devicemapper/devmapper_log.go
Normal 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)
|
||||
}
|
||||
}
|
260
devicemapper/devmapper_wrapper.go
Normal file
260
devicemapper/devmapper_wrapper.go
Normal 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
72
devicemapper/ioctl.go
Normal 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
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package fileutils
|
||||
|
||||
import (
|
||||
"github.com/docker/docker/pkg/log"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/log"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
type resumableRequestReader struct {
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
Jessie Frazelle <jess@docker.com> (@jfrazelle)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -4,8 +4,9 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
type JSONLog struct {
|
||||
|
|
83
log/log.go
83
log/log.go
|
@ -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...)
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
|
54
mount/sharedsubtree_linux.go
Normal file
54
mount/sharedsubtree_linux.go
Normal 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)
|
||||
}
|
331
mount/sharedsubtree_linux_test.go
Normal file
331
mount/sharedsubtree_linux_test.go
Normal 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()
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,9 +2,10 @@ package proxy
|
|||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
type TCPProxy struct {
|
||||
|
|
|
@ -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
12
signal/signal_unix.go
Normal 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
12
signal/signal_windows.go
Normal 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)
|
|
@ -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:
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/docker/docker/pkg/log"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -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
16
system/lstat.go
Normal 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
27
system/lstat_test.go
Normal 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
8
system/lstat_windows.go
Normal 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
17
system/meminfo.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package system
|
||||
|
||||
// MemInfo contains memory statistics of the host system.
|
||||
type MemInfo struct {
|
||||
// Total usable RAM (i.e. physical RAM minus a few reserved bits and the
|
||||
// kernel binary code).
|
||||
MemTotal int64
|
||||
|
||||
// Amount of free memory.
|
||||
MemFree int64
|
||||
|
||||
// Total amount of swap space available.
|
||||
SwapTotal int64
|
||||
|
||||
// Amount of swap space that is currently unused.
|
||||
SwapFree int64
|
||||
}
|
67
system/meminfo_linux.go
Normal file
67
system/meminfo_linux.go
Normal 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
|
||||
}
|
37
system/meminfo_linux_test.go
Normal file
37
system/meminfo_linux_test.go
Normal 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)
|
||||
}
|
||||
}
|
7
system/meminfo_unsupported.go
Normal file
7
system/meminfo_unsupported.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
// +build !linux
|
||||
|
||||
package system
|
||||
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
return nil, ErrNotSupportedPlatform
|
||||
}
|
18
system/mknod.go
Normal file
18
system/mknod.go
Normal 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
12
system/mknod_windows.go
Normal 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
42
system/stat.go
Normal 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()
|
||||
}
|
|
@ -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
36
system/stat_test.go
Normal 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")
|
||||
}
|
||||
}
|
|
@ -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
12
system/stat_windows.go
Normal 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
11
system/umask.go
Normal 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
8
system/umask_windows.go
Normal 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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Eric Windisch <ewindisch@docker.com> (@ewindisch)
|
4
tarsum/MAINTAINERS
Normal file
4
tarsum/MAINTAINERS
Normal 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)
|
|
@ -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
225
tarsum/tarsum_spec.md
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
87
term/console_windows.go
Normal 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
47
term/tc_linux_cgo.go
Normal 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
19
term/tc_other.go
Normal 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
|
||||
}
|
12
term/term.go
12
term/term.go
|
@ -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
89
term/term_windows.go
Normal 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
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
// +build !cgo
|
||||
|
||||
package term
|
||||
|
||||
import (
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
|
||||
Victor Vieux <vieux@docker.com> (@vieux)
|
||||
Jessie Frazelle <jess@docker.com> (@jfrazelle)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
30
urlutil/git.go
Normal 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
43
urlutil/git_test.go
Normal 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
19
urlutil/url.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue