update deps
This commit is contained in:
60
Godeps/Godeps.json
generated
60
Godeps/Godeps.json
generated
@@ -1,10 +1,16 @@
|
|||||||
{
|
{
|
||||||
"ImportPath": "github.com/gostor/gotgt",
|
"ImportPath": "github.com/gostor/gotgt",
|
||||||
"GoVersion": "go1.6",
|
"GoVersion": "go1.6",
|
||||||
|
"GodepVersion": "v63",
|
||||||
"Packages": [
|
"Packages": [
|
||||||
"./..."
|
"./..."
|
||||||
],
|
],
|
||||||
"Deps": [
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/Microsoft/go-winio",
|
||||||
|
"Comment": "v0.1.0",
|
||||||
|
"Rev": "8f9387ea7efabb228a981b9c381142be7667967f"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/Sirupsen/logrus",
|
"ImportPath": "github.com/Sirupsen/logrus",
|
||||||
"Comment": "v0.10.0-19-gf3cfb45",
|
"Comment": "v0.10.0-19-gf3cfb45",
|
||||||
@@ -15,51 +21,6 @@
|
|||||||
"Comment": "v9",
|
"Comment": "v9",
|
||||||
"Rev": "6dc8b843c670f2027cc26b164935635840a40526"
|
"Rev": "6dc8b843c670f2027cc26b164935635840a40526"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/distribution/registry/api/errcode",
|
|
||||||
"Comment": "v2.1.1-2-g77c6d9d",
|
|
||||||
"Rev": "77c6d9deb9b26653b4a264bbb2fd634ac927df7f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/archive",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/fileutils",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/idtools",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/ioutils",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/pools",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/promise",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/random",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/stringid",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/system",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/utils",
|
|
||||||
"Rev": "325cacb1e0edb4286b042cdb22d8bba76b8743d4"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/go-connections/sockets",
|
"ImportPath": "github.com/docker/go-connections/sockets",
|
||||||
"Comment": "v0.2.0-8-g990a1a1",
|
"Comment": "v0.2.0-8-g990a1a1",
|
||||||
@@ -77,6 +38,15 @@
|
|||||||
"ImportPath": "github.com/gorilla/mux",
|
"ImportPath": "github.com/gorilla/mux",
|
||||||
"Rev": "8096f47503459bcc74d1f4c487b7e6e42e5746b5"
|
"Rev": "8096f47503459bcc74d1f4c487b7e6e42e5746b5"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/inconshreveable/mousetrap",
|
||||||
|
"Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/opencontainers/runc/libcontainer/user",
|
||||||
|
"Comment": "v0.0.6-22-g47e3f83",
|
||||||
|
"Rev": "47e3f834d73e76bc2a6a585b48d2a93325b34979"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/spf13/cobra",
|
"ImportPath": "github.com/spf13/cobra",
|
||||||
"Rev": "1238ba19d24b0b9ceee2094e1cb31947d45c3e86"
|
"Rev": "1238ba19d24b0b9ceee2094e1cb31947d45c3e86"
|
||||||
|
|||||||
22
Godeps/_workspace/src/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
22
Godeps/_workspace/src/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Microsoft
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
15
Godeps/_workspace/src/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# go-winio
|
||||||
|
|
||||||
|
This repository contains utilities for efficiently performing Win32 IO operations in
|
||||||
|
Go. Currently, this is focused on accessing named pipes and other file handles, and
|
||||||
|
for using named pipes as a net transport.
|
||||||
|
|
||||||
|
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go
|
||||||
|
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and
|
||||||
|
newer operating systems. This is similar to the implementation of network sockets in Go's net
|
||||||
|
package.
|
||||||
|
|
||||||
|
Please see the LICENSE file for licensing information.
|
||||||
|
|
||||||
|
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
|
||||||
|
for another named pipe implementation.
|
||||||
241
Godeps/_workspace/src/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
241
Godeps/_workspace/src/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
||||||
|
//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
|
||||||
|
|
||||||
|
const (
|
||||||
|
BackupData = uint32(iota + 1)
|
||||||
|
BackupEaData
|
||||||
|
BackupSecurity
|
||||||
|
BackupAlternateData
|
||||||
|
BackupLink
|
||||||
|
BackupPropertyData
|
||||||
|
BackupObjectId
|
||||||
|
BackupReparseData
|
||||||
|
BackupSparseBlock
|
||||||
|
BackupTxfsData
|
||||||
|
|
||||||
|
StreamSparseAttributes = uint32(8)
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackupHeader represents a backup stream of a file.
|
||||||
|
type BackupHeader struct {
|
||||||
|
Id uint32 // The backup stream ID
|
||||||
|
Attributes uint32 // Stream attributes
|
||||||
|
Size int64 // The size of the stream in bytes
|
||||||
|
Name string // The name of the stream (for BackupAlternateData only).
|
||||||
|
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32StreamId struct {
|
||||||
|
StreamId uint32
|
||||||
|
Attributes uint32
|
||||||
|
Size uint64
|
||||||
|
NameSize uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
|
||||||
|
// of BackupHeader values.
|
||||||
|
type BackupStreamReader struct {
|
||||||
|
r io.Reader
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
|
||||||
|
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
|
||||||
|
return &BackupStreamReader{r, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next backup stream and prepares for calls to Write(). It skips the remainder of the current stream if
|
||||||
|
// it was not completely read.
|
||||||
|
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
||||||
|
if r.bytesLeft > 0 {
|
||||||
|
if _, err := io.Copy(ioutil.Discard, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var wsi win32StreamId
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr := &BackupHeader{
|
||||||
|
Id: wsi.StreamId,
|
||||||
|
Attributes: wsi.Attributes,
|
||||||
|
Size: int64(wsi.Size),
|
||||||
|
}
|
||||||
|
if wsi.NameSize != 0 {
|
||||||
|
name := make([]uint16, int(wsi.NameSize/2))
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Name = syscall.UTF16ToString(name)
|
||||||
|
}
|
||||||
|
if wsi.StreamId == BackupSparseBlock {
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Size -= 8
|
||||||
|
}
|
||||||
|
r.bytesLeft = hdr.Size
|
||||||
|
return hdr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from the current backup stream.
|
||||||
|
func (r *BackupStreamReader) Read(b []byte) (int, error) {
|
||||||
|
if r.bytesLeft == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if int64(len(b)) > r.bytesLeft {
|
||||||
|
b = b[:r.bytesLeft]
|
||||||
|
}
|
||||||
|
n, err := r.r.Read(b)
|
||||||
|
r.bytesLeft -= int64(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
} else if r.bytesLeft == 0 && err == nil {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
|
||||||
|
type BackupStreamWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
|
||||||
|
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
|
||||||
|
return &BackupStreamWriter{w, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes the next backup stream header and prepares for calls to Write().
|
||||||
|
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
|
||||||
|
if w.bytesLeft != 0 {
|
||||||
|
return fmt.Errorf("missing %d bytes", w.bytesLeft)
|
||||||
|
}
|
||||||
|
name := utf16.Encode([]rune(hdr.Name))
|
||||||
|
wsi := win32StreamId{
|
||||||
|
StreamId: hdr.Id,
|
||||||
|
Attributes: hdr.Attributes,
|
||||||
|
Size: uint64(hdr.Size),
|
||||||
|
NameSize: uint32(len(name) * 2),
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
// Include space for the int64 block offset
|
||||||
|
wsi.Size += 8
|
||||||
|
}
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(name) != 0 {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.bytesLeft = hdr.Size
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to the current backup stream.
|
||||||
|
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
|
||||||
|
if w.bytesLeft < int64(len(b)) {
|
||||||
|
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
|
||||||
|
}
|
||||||
|
n, err := w.w.Write(b)
|
||||||
|
w.bytesLeft -= int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
|
||||||
|
type BackupFileReader struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
|
||||||
|
// Read will attempt to read the security descriptor of the file.
|
||||||
|
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
|
||||||
|
r := &BackupFileReader{f, includeSecurity, 0}
|
||||||
|
runtime.SetFinalizer(r, func(r *BackupFileReader) { r.Close() })
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
|
||||||
|
func (r *BackupFileReader) Read(b []byte) (int, error) {
|
||||||
|
var bytesRead uint32
|
||||||
|
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{"BackupRead", r.f.Name(), err}
|
||||||
|
}
|
||||||
|
if bytesRead == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return int(bytesRead), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileReader. It does not close
|
||||||
|
// the underlying file.
|
||||||
|
func (r *BackupFileReader) Close() error {
|
||||||
|
if r.ctx != 0 {
|
||||||
|
backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
|
||||||
|
r.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
|
||||||
|
type BackupFileWriter struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileWrtier returns a new BackupFileWriter from a file handle. If includeSecurity is true,
|
||||||
|
// Write() will attempt to restore the security descriptor from the stream.
|
||||||
|
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
||||||
|
w := &BackupFileWriter{f, includeSecurity, 0}
|
||||||
|
runtime.SetFinalizer(w, func(w *BackupFileWriter) { w.Close() })
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write restores a portion of the file using the provided backup stream.
|
||||||
|
func (w *BackupFileWriter) Write(b []byte) (int, error) {
|
||||||
|
var bytesWritten uint32
|
||||||
|
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{"BackupWrite", w.f.Name(), err}
|
||||||
|
}
|
||||||
|
if int(bytesWritten) != len(b) {
|
||||||
|
return int(bytesWritten), errors.New("not all bytes could be written")
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileWriter. It does not
|
||||||
|
// close the underlying file.
|
||||||
|
func (w *BackupFileWriter) Close() error {
|
||||||
|
if w.ctx != 0 {
|
||||||
|
backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
|
||||||
|
w.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
219
Godeps/_workspace/src/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
219
Godeps/_workspace/src/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
|
||||||
|
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
||||||
|
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||||
|
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||||
|
//sys timeBeginPeriod(period uint32) (n int32) = winmm.timeBeginPeriod
|
||||||
|
|
||||||
|
const (
|
||||||
|
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
|
||||||
|
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileClosed = errors.New("file has already been closed")
|
||||||
|
ErrTimeout = &timeoutError{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeoutError struct{}
|
||||||
|
|
||||||
|
func (e *timeoutError) Error() string { return "i/o timeout" }
|
||||||
|
func (e *timeoutError) Timeout() bool { return true }
|
||||||
|
func (e *timeoutError) Temporary() bool { return true }
|
||||||
|
|
||||||
|
var ioInitOnce sync.Once
|
||||||
|
var ioCompletionPort syscall.Handle
|
||||||
|
|
||||||
|
// ioResult contains the result of an asynchronous IO operation
|
||||||
|
type ioResult struct {
|
||||||
|
bytes uint32
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioOperation represents an outstanding asynchronous Win32 IO
|
||||||
|
type ioOperation struct {
|
||||||
|
o syscall.Overlapped
|
||||||
|
ch chan ioResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func initIo() {
|
||||||
|
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ioCompletionPort = h
|
||||||
|
go ioCompletionProcessor(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
|
||||||
|
// It takes ownership of this handle and will close it if it is garbage collected.
|
||||||
|
type win32File struct {
|
||||||
|
handle syscall.Handle
|
||||||
|
wg sync.WaitGroup
|
||||||
|
closing bool
|
||||||
|
readDeadline time.Time
|
||||||
|
writeDeadline time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeWin32File makes a new win32File from an existing file handle
|
||||||
|
func makeWin32File(h syscall.Handle) (*win32File, error) {
|
||||||
|
f := &win32File{handle: h}
|
||||||
|
ioInitOnce.Do(initIo)
|
||||||
|
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
runtime.SetFinalizer(f, (*win32File).closeHandle)
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||||
|
return makeWin32File(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeHandle closes the resources associated with a Win32 handle
|
||||||
|
func (f *win32File) closeHandle() {
|
||||||
|
if !f.closing {
|
||||||
|
// cancel all IO and wait for it to complete
|
||||||
|
f.closing = true
|
||||||
|
cancelIoEx(f.handle, nil)
|
||||||
|
f.wg.Wait()
|
||||||
|
// at this point, no new IO can start
|
||||||
|
syscall.Close(f.handle)
|
||||||
|
f.handle = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a win32File.
|
||||||
|
func (f *win32File) Close() error {
|
||||||
|
f.closeHandle()
|
||||||
|
runtime.SetFinalizer(f, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareIo prepares for a new IO operation
|
||||||
|
func (f *win32File) prepareIo() (*ioOperation, error) {
|
||||||
|
f.wg.Add(1)
|
||||||
|
if f.closing {
|
||||||
|
return nil, ErrFileClosed
|
||||||
|
}
|
||||||
|
c := &ioOperation{}
|
||||||
|
c.ch = make(chan ioResult)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioCompletionProcessor processes completed async IOs forever
|
||||||
|
func ioCompletionProcessor(h syscall.Handle) {
|
||||||
|
// Set the timer resolution to 1. This fixes a performance regression in golang 1.6.
|
||||||
|
timeBeginPeriod(1)
|
||||||
|
for {
|
||||||
|
var bytes uint32
|
||||||
|
var key uintptr
|
||||||
|
var op *ioOperation
|
||||||
|
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
|
||||||
|
if op == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
op.ch <- ioResult{bytes, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncIo processes the return value from ReadFile or WriteFile, blocking until
|
||||||
|
// the operation has actually completed.
|
||||||
|
func (f *win32File) asyncIo(c *ioOperation, deadline time.Time, bytes uint32, err error) (int, error) {
|
||||||
|
if err != syscall.ERROR_IO_PENDING {
|
||||||
|
f.wg.Done()
|
||||||
|
return int(bytes), err
|
||||||
|
} else {
|
||||||
|
var r ioResult
|
||||||
|
wait := true
|
||||||
|
timedout := false
|
||||||
|
if f.closing {
|
||||||
|
cancelIoEx(f.handle, &c.o)
|
||||||
|
} else if !deadline.IsZero() {
|
||||||
|
now := time.Now()
|
||||||
|
if !deadline.After(now) {
|
||||||
|
timedout = true
|
||||||
|
} else {
|
||||||
|
timeout := time.After(deadline.Sub(now))
|
||||||
|
select {
|
||||||
|
case r = <-c.ch:
|
||||||
|
wait = false
|
||||||
|
case <-timeout:
|
||||||
|
timedout = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if timedout {
|
||||||
|
cancelIoEx(f.handle, &c.o)
|
||||||
|
}
|
||||||
|
if wait {
|
||||||
|
r = <-c.ch
|
||||||
|
}
|
||||||
|
err = r.err
|
||||||
|
if err == syscall.ERROR_OPERATION_ABORTED {
|
||||||
|
if f.closing {
|
||||||
|
err = ErrFileClosed
|
||||||
|
} else if timedout {
|
||||||
|
err = ErrTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.wg.Done()
|
||||||
|
return int(r.bytes), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from a file handle.
|
||||||
|
func (f *win32File) Read(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIo(c, f.readDeadline, bytes, err)
|
||||||
|
|
||||||
|
// Handle EOF conditions.
|
||||||
|
if err == nil && n == 0 && len(b) != 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else if err == syscall.ERROR_BROKEN_PIPE {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to a file handle.
|
||||||
|
func (f *win32File) Write(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
|
||||||
|
return f.asyncIo(c, f.writeDeadline, bytes, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetReadDeadline(t time.Time) error {
|
||||||
|
f.readDeadline = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetWriteDeadline(t time.Time) error {
|
||||||
|
f.writeDeadline = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
30
Godeps/_workspace/src/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
30
Godeps/_workspace/src/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
|
||||||
|
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
|
||||||
|
|
||||||
|
type FileBasicInfo struct {
|
||||||
|
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
|
||||||
|
FileAttributes uintptr // includes padding
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||||
|
bi := &FileBasicInfo{}
|
||||||
|
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), 0, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||||
|
return nil, &os.PathError{"GetFileInformationByHandleEx", f.Name(), err}
|
||||||
|
}
|
||||||
|
return bi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
||||||
|
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), 0, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||||
|
return &os.PathError{"SetFileInformationByHandle", f.Name(), err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
398
Godeps/_workspace/src/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
398
Godeps/_workspace/src/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
|
||||||
|
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
|
||||||
|
//sys createFile(name string, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
|
||||||
|
//sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW
|
||||||
|
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
||||||
|
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||||
|
|
||||||
|
type securityAttributes struct {
|
||||||
|
Length uint32
|
||||||
|
SecurityDescriptor *byte
|
||||||
|
InheritHandle uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
cERROR_PIPE_BUSY = syscall.Errno(231)
|
||||||
|
cERROR_PIPE_CONNECTED = syscall.Errno(535)
|
||||||
|
cERROR_SEM_TIMEOUT = syscall.Errno(121)
|
||||||
|
|
||||||
|
cPIPE_ACCESS_DUPLEX = 0x3
|
||||||
|
cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000
|
||||||
|
cSECURITY_SQOS_PRESENT = 0x100000
|
||||||
|
cSECURITY_ANONYMOUS = 0
|
||||||
|
|
||||||
|
cPIPE_REJECT_REMOTE_CLIENTS = 0x8
|
||||||
|
|
||||||
|
cPIPE_UNLIMITED_INSTANCES = 255
|
||||||
|
|
||||||
|
cNMPWAIT_USE_DEFAULT_WAIT = 0
|
||||||
|
cNMPWAIT_NOWAIT = 1
|
||||||
|
|
||||||
|
cPIPE_TYPE_MESSAGE = 4
|
||||||
|
|
||||||
|
cPIPE_READMODE_MESSAGE = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||||
|
// This error should match net.errClosing since docker takes a dependency on its text.
|
||||||
|
ErrPipeListenerClosed = errors.New("use of closed network connection")
|
||||||
|
|
||||||
|
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
||||||
|
)
|
||||||
|
|
||||||
|
type win32Pipe struct {
|
||||||
|
*win32File
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32MessageBytePipe struct {
|
||||||
|
win32Pipe
|
||||||
|
writeClosed bool
|
||||||
|
readEOF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeAddress string
|
||||||
|
|
||||||
|
func (f *win32Pipe) LocalAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) RemoteAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) SetDeadline(t time.Time) error {
|
||||||
|
f.SetReadDeadline(t)
|
||||||
|
f.SetWriteDeadline(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite closes the write side of a message pipe in byte mode.
|
||||||
|
func (f *win32MessageBytePipe) CloseWrite() error {
|
||||||
|
if f.writeClosed {
|
||||||
|
return errPipeWriteClosed
|
||||||
|
}
|
||||||
|
_, err := f.win32File.Write(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writeClosed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
||||||
|
// they are used to implement CloseWrite().
|
||||||
|
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
||||||
|
if f.writeClosed {
|
||||||
|
return 0, errPipeWriteClosed
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return f.win32File.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
|
||||||
|
// mode pipe will return io.EOF, as will all subsequent reads.
|
||||||
|
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
|
||||||
|
if f.readEOF {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n, err := f.win32File.Read(b)
|
||||||
|
if err == io.EOF {
|
||||||
|
// If this was the result of a zero-byte read, then
|
||||||
|
// it is possible that the read was due to a zero-size
|
||||||
|
// message. Since we are simulating CloseWrite with a
|
||||||
|
// zero-byte message, ensure that all future Read() calls
|
||||||
|
// also return EOF.
|
||||||
|
f.readEOF = true
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) Network() string {
|
||||||
|
return "pipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipe connects to a named pipe by path, timing out if the connection
|
||||||
|
// takes longer than the specified duration. If timeout is nil, then the timeout
|
||||||
|
// is the default timeout established by the pipe server.
|
||||||
|
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||||
|
var absTimeout time.Time
|
||||||
|
if timeout != nil {
|
||||||
|
absTimeout = time.Now().Add(*timeout)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var h syscall.Handle
|
||||||
|
for {
|
||||||
|
h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||||
|
if err != cERROR_PIPE_BUSY {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
var ms uint32
|
||||||
|
if absTimeout.IsZero() {
|
||||||
|
ms = cNMPWAIT_USE_DEFAULT_WAIT
|
||||||
|
} else if now.After(absTimeout) {
|
||||||
|
ms = cNMPWAIT_NOWAIT
|
||||||
|
} else {
|
||||||
|
ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000)
|
||||||
|
}
|
||||||
|
err = waitNamedPipe(path, ms)
|
||||||
|
if err != nil {
|
||||||
|
if err == cERROR_SEM_TIMEOUT {
|
||||||
|
return nil, ErrTimeout
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32
|
||||||
|
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var state uint32
|
||||||
|
err = getNamedPipeHandleState(h, &state, nil, nil, nil, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if state&cPIPE_READMODE_MESSAGE != 0 {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("message readmode pipes not supported")}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pipe is in message mode, return a message byte pipe, which
|
||||||
|
// supports CloseWrite().
|
||||||
|
if flags&cPIPE_TYPE_MESSAGE != 0 {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: f, path: path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: f, path: path}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type acceptResponse struct {
|
||||||
|
f *win32File
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32PipeListener struct {
|
||||||
|
firstHandle syscall.Handle
|
||||||
|
path string
|
||||||
|
securityDescriptor []byte
|
||||||
|
config PipeConfig
|
||||||
|
acceptCh chan (chan acceptResponse)
|
||||||
|
closeCh chan int
|
||||||
|
doneCh chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
|
||||||
|
var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED
|
||||||
|
if first {
|
||||||
|
flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS
|
||||||
|
if c.MessageMode {
|
||||||
|
mode |= cPIPE_TYPE_MESSAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
var sa securityAttributes
|
||||||
|
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||||
|
if securityDescriptor != nil {
|
||||||
|
sa.SecurityDescriptor = &securityDescriptor[0]
|
||||||
|
}
|
||||||
|
h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, &sa)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
||||||
|
h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) listenerRoutine() {
|
||||||
|
closed := false
|
||||||
|
for !closed {
|
||||||
|
select {
|
||||||
|
case <-l.closeCh:
|
||||||
|
closed = true
|
||||||
|
case responseCh := <-l.acceptCh:
|
||||||
|
p, err := l.makeServerPipe()
|
||||||
|
if err == nil {
|
||||||
|
// Wait for the client to connect.
|
||||||
|
ch := make(chan error)
|
||||||
|
go func() {
|
||||||
|
ch <- connectPipe(p)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case err = <-ch:
|
||||||
|
if err != nil {
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
case <-l.closeCh:
|
||||||
|
// Abort the connect request by closing the handle.
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
err = <-ch
|
||||||
|
if err == nil || err == ErrFileClosed {
|
||||||
|
err = ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseCh <- acceptResponse{p, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syscall.Close(l.firstHandle)
|
||||||
|
l.firstHandle = 0
|
||||||
|
// Notify Close() and Accept() callers that the handle has been closed.
|
||||||
|
close(l.doneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeConfig contain configuration for the pipe listener.
|
||||||
|
type PipeConfig struct {
|
||||||
|
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
|
||||||
|
SecurityDescriptor string
|
||||||
|
|
||||||
|
// MessageMode determines whether the pipe is in byte or message mode. In either
|
||||||
|
// case the pipe is read in byte mode by default. The only practical difference in
|
||||||
|
// this implementation is that CloseWrite() is only supported for message mode pipes;
|
||||||
|
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
|
||||||
|
// transferred to the reader (and returned as io.EOF in this implementation)
|
||||||
|
// when the pipe is in message mode.
|
||||||
|
MessageMode bool
|
||||||
|
|
||||||
|
// InputBufferSize specifies the size the input buffer, in bytes.
|
||||||
|
InputBufferSize int32
|
||||||
|
|
||||||
|
// OutputBufferSize specifies the size the input buffer, in bytes.
|
||||||
|
OutputBufferSize int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
|
||||||
|
// The pipe must not already exist.
|
||||||
|
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
||||||
|
var (
|
||||||
|
sd []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if c == nil {
|
||||||
|
c = &PipeConfig{}
|
||||||
|
}
|
||||||
|
if c.SecurityDescriptor != "" {
|
||||||
|
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h, err := makeServerPipeHandle(path, sd, c, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Immediately open and then close a client handle so that the named pipe is
|
||||||
|
// created but not currently accepting connections.
|
||||||
|
h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
syscall.Close(h2)
|
||||||
|
l := &win32PipeListener{
|
||||||
|
firstHandle: h,
|
||||||
|
path: path,
|
||||||
|
securityDescriptor: sd,
|
||||||
|
config: *c,
|
||||||
|
acceptCh: make(chan (chan acceptResponse)),
|
||||||
|
closeCh: make(chan int),
|
||||||
|
doneCh: make(chan int),
|
||||||
|
}
|
||||||
|
go l.listenerRoutine()
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectPipe(p *win32File) error {
|
||||||
|
c, err := p.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = connectNamedPipe(p.handle, &c.o)
|
||||||
|
_, err = p.asyncIo(c, time.Time{}, 0, err)
|
||||||
|
if err != nil && err != cERROR_PIPE_CONNECTED {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Accept() (net.Conn, error) {
|
||||||
|
ch := make(chan acceptResponse)
|
||||||
|
select {
|
||||||
|
case l.acceptCh <- ch:
|
||||||
|
response := <-ch
|
||||||
|
err := response.err
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if l.config.MessageMode {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
||||||
|
case <-l.doneCh:
|
||||||
|
return nil, ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Close() error {
|
||||||
|
select {
|
||||||
|
case l.closeCh <- 1:
|
||||||
|
<-l.doneCh
|
||||||
|
case <-l.doneCh:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Addr() net.Addr {
|
||||||
|
return pipeAddress(l.path)
|
||||||
|
}
|
||||||
150
Godeps/_workspace/src/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
150
Godeps/_workspace/src/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys adjustTokenPrivileges(token syscall.Handle, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
||||||
|
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
|
||||||
|
//sys revertToSelf() (err error) = advapi32.RevertToSelf
|
||||||
|
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Handle) (err error) = advapi32.OpenThreadToken
|
||||||
|
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
|
||||||
|
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
|
||||||
|
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
|
||||||
|
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
|
||||||
|
|
||||||
|
const (
|
||||||
|
SE_PRIVILEGE_ENABLED = 2
|
||||||
|
|
||||||
|
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
|
||||||
|
|
||||||
|
SeBackupPrivilege = "SeBackupPrivilege"
|
||||||
|
SeRestorePrivilege = "SeRestorePrivilege"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
securityAnonymous = iota
|
||||||
|
securityIdentification
|
||||||
|
securityImpersonation
|
||||||
|
securityDelegation
|
||||||
|
)
|
||||||
|
|
||||||
|
type PrivilegeError struct {
|
||||||
|
privileges []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PrivilegeError) Error() string {
|
||||||
|
s := ""
|
||||||
|
if len(e.privileges) > 1 {
|
||||||
|
s = "Could not enable privileges "
|
||||||
|
} else {
|
||||||
|
s = "Could not enable privilege "
|
||||||
|
}
|
||||||
|
for i, p := range e.privileges {
|
||||||
|
if i != 0 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
s += `"`
|
||||||
|
s += getPrivilegeName(p)
|
||||||
|
s += `"`
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunWithPrivilege(name string, fn func() error) error {
|
||||||
|
return RunWithPrivileges([]string{name}, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunWithPrivileges(names []string, fn func() error) error {
|
||||||
|
var privileges []uint64
|
||||||
|
for _, name := range names {
|
||||||
|
p := uint64(0)
|
||||||
|
err := lookupPrivilegeValue("", name, &p)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
privileges = append(privileges, p)
|
||||||
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
token, err := newThreadToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer releaseThreadToken(token)
|
||||||
|
err = adjustPrivileges(token, privileges)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustPrivileges(token syscall.Handle, privileges []uint64) error {
|
||||||
|
var b bytes.Buffer
|
||||||
|
binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
|
||||||
|
for _, p := range privileges {
|
||||||
|
binary.Write(&b, binary.LittleEndian, p)
|
||||||
|
binary.Write(&b, binary.LittleEndian, uint32(SE_PRIVILEGE_ENABLED))
|
||||||
|
}
|
||||||
|
prevState := make([]byte, b.Len())
|
||||||
|
reqSize := uint32(0)
|
||||||
|
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
|
||||||
|
if !success {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == ERROR_NOT_ALL_ASSIGNED {
|
||||||
|
return &PrivilegeError{privileges}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrivilegeName(luid uint64) string {
|
||||||
|
var nameBuffer [256]uint16
|
||||||
|
bufSize := uint32(len(nameBuffer))
|
||||||
|
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %d>", luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayNameBuffer [256]uint16
|
||||||
|
displayBufSize := uint32(len(displayNameBuffer))
|
||||||
|
var langId uint32
|
||||||
|
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %s>", utf16.Decode(nameBuffer[:bufSize]))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newThreadToken() (syscall.Handle, error) {
|
||||||
|
err := impersonateSelf(securityImpersonation)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var token syscall.Handle
|
||||||
|
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
|
||||||
|
if err != nil {
|
||||||
|
rerr := revertToSelf()
|
||||||
|
if rerr != nil {
|
||||||
|
panic(rerr)
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseThreadToken(h syscall.Handle) {
|
||||||
|
err := revertToSelf()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
syscall.Close(h)
|
||||||
|
}
|
||||||
124
Godeps/_workspace/src/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
124
Godeps/_workspace/src/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
reparseTagMountPoint = 0xA0000003
|
||||||
|
reparseTagSymlink = 0xA000000C
|
||||||
|
)
|
||||||
|
|
||||||
|
type reparseDataBuffer struct {
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseDataLength uint16
|
||||||
|
Reserved uint16
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReparsePoint describes a Win32 symlink or mount point.
|
||||||
|
type ReparsePoint struct {
|
||||||
|
Target string
|
||||||
|
IsMountPoint bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
|
||||||
|
// mount point reparse point.
|
||||||
|
type UnsupportedReparsePointError struct {
|
||||||
|
Tag uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnsupportedReparsePointError) Error() string {
|
||||||
|
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
|
||||||
|
// or a mount point.
|
||||||
|
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
|
||||||
|
isMountPoint := false
|
||||||
|
tag := binary.LittleEndian.Uint32(b[0:4])
|
||||||
|
switch tag {
|
||||||
|
case reparseTagMountPoint:
|
||||||
|
isMountPoint = true
|
||||||
|
case reparseTagSymlink:
|
||||||
|
default:
|
||||||
|
return nil, &UnsupportedReparsePointError{tag}
|
||||||
|
}
|
||||||
|
nameOffset := 16 + binary.LittleEndian.Uint16(b[12:14])
|
||||||
|
if !isMountPoint {
|
||||||
|
nameOffset += 4
|
||||||
|
}
|
||||||
|
nameLength := binary.LittleEndian.Uint16(b[14:16])
|
||||||
|
name := make([]uint16, nameLength/2)
|
||||||
|
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDriveLetter(c byte) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
|
||||||
|
// mount point.
|
||||||
|
func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
||||||
|
// Generate an NT path and determine if this is a relative path.
|
||||||
|
var ntTarget string
|
||||||
|
relative := false
|
||||||
|
if strings.HasPrefix(rp.Target, `\\?\`) {
|
||||||
|
ntTarget = rp.Target
|
||||||
|
} else if strings.HasPrefix(rp.Target, `\\`) {
|
||||||
|
ntTarget = `\??\UNC\` + rp.Target[2:]
|
||||||
|
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
|
||||||
|
ntTarget = `\??\` + rp.Target
|
||||||
|
} else {
|
||||||
|
ntTarget = rp.Target
|
||||||
|
relative = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The paths must be NUL-terminated even though they are counted strings.
|
||||||
|
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
|
||||||
|
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
|
||||||
|
|
||||||
|
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
|
||||||
|
size += len(ntTarget16)*2 + len(target16)*2
|
||||||
|
|
||||||
|
tag := uint32(reparseTagMountPoint)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
tag = reparseTagSymlink
|
||||||
|
size += 4 // Add room for symlink flags
|
||||||
|
}
|
||||||
|
|
||||||
|
data := reparseDataBuffer{
|
||||||
|
ReparseTag: tag,
|
||||||
|
ReparseDataLength: uint16(size),
|
||||||
|
SubstituteNameOffset: 0,
|
||||||
|
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
|
||||||
|
PrintNameOffset: uint16(len(ntTarget16) * 2),
|
||||||
|
PrintNameLength: uint16((len(target16) - 1) * 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
binary.Write(&b, binary.LittleEndian, &data)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
flags := uint32(0)
|
||||||
|
if relative {
|
||||||
|
flags |= 1
|
||||||
|
}
|
||||||
|
binary.Write(&b, binary.LittleEndian, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.Write(&b, binary.LittleEndian, ntTarget16)
|
||||||
|
binary.Write(&b, binary.LittleEndian, target16)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
96
Godeps/_workspace/src/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
96
Godeps/_workspace/src/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
|
||||||
|
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
|
||||||
|
//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
|
||||||
|
//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW
|
||||||
|
//sys localFree(mem uintptr) = LocalFree
|
||||||
|
//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength
|
||||||
|
|
||||||
|
const (
|
||||||
|
cERROR_NONE_MAPPED = syscall.Errno(1332)
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountLookupError struct {
|
||||||
|
Name string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AccountLookupError) Error() string {
|
||||||
|
if e.Name == "" {
|
||||||
|
return "lookup account: empty account name specified"
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
switch e.Err {
|
||||||
|
case cERROR_NONE_MAPPED:
|
||||||
|
s = "not found"
|
||||||
|
default:
|
||||||
|
s = e.Err.Error()
|
||||||
|
}
|
||||||
|
return "lookup account " + e.Name + ": " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
type SddlConversionError struct {
|
||||||
|
Sddl string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SddlConversionError) Error() string {
|
||||||
|
return "convert " + e.Sddl + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupSidByName looks up the SID of an account by name
|
||||||
|
func LookupSidByName(name string) (sid string, err error) {
|
||||||
|
if name == "" {
|
||||||
|
return "", &AccountLookupError{name, cERROR_NONE_MAPPED}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidSize, sidNameUse, refDomainSize uint32
|
||||||
|
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sidBuffer := make([]byte, sidSize)
|
||||||
|
refDomainBuffer := make([]uint16, refDomainSize)
|
||||||
|
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
var strBuffer *uint16
|
||||||
|
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sid = syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
|
||||||
|
localFree(uintptr(unsafe.Pointer(strBuffer)))
|
||||||
|
return sid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
|
||||||
|
var sdBuffer uintptr
|
||||||
|
err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &SddlConversionError{sddl, err}
|
||||||
|
}
|
||||||
|
defer localFree(sdBuffer)
|
||||||
|
sd := make([]byte, getSecurityDescriptorLength(sdBuffer))
|
||||||
|
copy(sd, (*[0xffff]byte)(unsafe.Pointer(sdBuffer))[:len(sd)])
|
||||||
|
return sd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SecurityDescriptorToSddl(sd []byte) (string, error) {
|
||||||
|
var sddl *uint16
|
||||||
|
// The returned string length seems to including an aribtrary number of terminating NULs.
|
||||||
|
// Don't use it.
|
||||||
|
err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer localFree(uintptr(unsafe.Pointer(sddl)))
|
||||||
|
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil
|
||||||
|
}
|
||||||
3
Godeps/_workspace/src/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package winio
|
||||||
|
|
||||||
|
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go
|
||||||
492
Godeps/_workspace/src/github.com/Microsoft/go-winio/zsyscall.go
generated
vendored
Normal file
492
Godeps/_workspace/src/github.com/Microsoft/go-winio/zsyscall.go
generated
vendored
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
var (
|
||||||
|
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||||
|
modwinmm = syscall.NewLazyDLL("winmm.dll")
|
||||||
|
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||||
|
|
||||||
|
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||||
|
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||||
|
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
||||||
|
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
||||||
|
proctimeBeginPeriod = modwinmm.NewProc("timeBeginPeriod")
|
||||||
|
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||||
|
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||||
|
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||||
|
procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
|
||||||
|
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
||||||
|
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
||||||
|
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
||||||
|
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
||||||
|
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
|
||||||
|
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
|
||||||
|
procLocalFree = modkernel32.NewProc("LocalFree")
|
||||||
|
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
|
||||||
|
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
|
||||||
|
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
|
||||||
|
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||||
|
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
||||||
|
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||||
|
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
||||||
|
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||||
|
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
||||||
|
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
|
||||||
|
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||||
|
procBackupRead = modkernel32.NewProc("BackupRead")
|
||||||
|
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||||
|
)
|
||||||
|
|
||||||
|
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
|
||||||
|
newport = syscall.Handle(r0)
|
||||||
|
if newport == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func timeBeginPeriod(period uint32) (n int32) {
|
||||||
|
r0, _, _ := syscall.Syscall(proctimeBeginPeriod.Addr(), 1, uintptr(period), 0, 0)
|
||||||
|
n = int32(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *securityAttributes) (handle syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
|
||||||
|
handle = syscall.Handle(r0)
|
||||||
|
if handle == syscall.InvalidHandle {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFile(name string, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createFile(name *uint16, access uint32, mode uint32, sa *securityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
|
||||||
|
handle = syscall.Handle(r0)
|
||||||
|
if handle == syscall.InvalidHandle {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitNamedPipe(name string, timeout uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _waitNamedPipe(_p0, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _waitNamedPipe(name *uint16, timeout uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(accountName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(str)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _convertStringSecurityDescriptorToSecurityDescriptor(_p0, revision, sd, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func localFree(mem uintptr) {
|
||||||
|
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
|
||||||
|
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
|
||||||
|
len = uint32(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustTokenPrivileges(token syscall.Handle, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if releaseAll {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
|
||||||
|
success = r0 != 0
|
||||||
|
if true {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func impersonateSelf(level uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func revertToSelf() (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *syscall.Handle) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if openAsSelf {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentThread() (h syscall.Handle) {
|
||||||
|
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
|
||||||
|
h = syscall.Handle(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *uint16
|
||||||
|
_p1, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeValue(_p0, _p1, luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeName(_p0, luid, buffer, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
} else {
|
||||||
|
_p1 = 0
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
} else {
|
||||||
|
_p2 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
} else {
|
||||||
|
_p1 = 0
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
} else {
|
||||||
|
_p2 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
259
Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors.go
generated
vendored
259
Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors.go
generated
vendored
@@ -1,259 +0,0 @@
|
|||||||
package errcode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrorCoder is the base interface for ErrorCode and Error allowing
|
|
||||||
// users of each to just call ErrorCode to get the real ID of each
|
|
||||||
type ErrorCoder interface {
|
|
||||||
ErrorCode() ErrorCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorCode represents the error type. The errors are serialized via strings
|
|
||||||
// and the integer format may change and should *never* be exported.
|
|
||||||
type ErrorCode int
|
|
||||||
|
|
||||||
var _ error = ErrorCode(0)
|
|
||||||
|
|
||||||
// ErrorCode just returns itself
|
|
||||||
func (ec ErrorCode) ErrorCode() ErrorCode {
|
|
||||||
return ec
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the ID/Value
|
|
||||||
func (ec ErrorCode) Error() string {
|
|
||||||
return ec.Descriptor().Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Descriptor returns the descriptor for the error code.
|
|
||||||
func (ec ErrorCode) Descriptor() ErrorDescriptor {
|
|
||||||
d, ok := errorCodeToDescriptors[ec]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return ErrorCodeUnknown.Descriptor()
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the canonical identifier for this error code.
|
|
||||||
func (ec ErrorCode) String() string {
|
|
||||||
return ec.Descriptor().Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message returned the human-readable error message for this error code.
|
|
||||||
func (ec ErrorCode) Message() string {
|
|
||||||
return ec.Descriptor().Message
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalText encodes the receiver into UTF-8-encoded text and returns the
|
|
||||||
// result.
|
|
||||||
func (ec ErrorCode) MarshalText() (text []byte, err error) {
|
|
||||||
return []byte(ec.String()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalText decodes the form generated by MarshalText.
|
|
||||||
func (ec *ErrorCode) UnmarshalText(text []byte) error {
|
|
||||||
desc, ok := idToDescriptors[string(text)]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
desc = ErrorCodeUnknown.Descriptor()
|
|
||||||
}
|
|
||||||
|
|
||||||
*ec = desc.Code
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDetail creates a new Error struct based on the passed-in info and
|
|
||||||
// set the Detail property appropriately
|
|
||||||
func (ec ErrorCode) WithDetail(detail interface{}) Error {
|
|
||||||
return Error{
|
|
||||||
Code: ec,
|
|
||||||
Message: ec.Message(),
|
|
||||||
}.WithDetail(detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithArgs creates a new Error struct and sets the Args slice
|
|
||||||
func (ec ErrorCode) WithArgs(args ...interface{}) Error {
|
|
||||||
return Error{
|
|
||||||
Code: ec,
|
|
||||||
Message: ec.Message(),
|
|
||||||
}.WithArgs(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error provides a wrapper around ErrorCode with extra Details provided.
|
|
||||||
type Error struct {
|
|
||||||
Code ErrorCode `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
Detail interface{} `json:"detail,omitempty"`
|
|
||||||
|
|
||||||
// TODO(duglin): See if we need an "args" property so we can do the
|
|
||||||
// variable substitution right before showing the message to the user
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ error = Error{}
|
|
||||||
|
|
||||||
// ErrorCode returns the ID/Value of this Error
|
|
||||||
func (e Error) ErrorCode() ErrorCode {
|
|
||||||
return e.Code
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns a human readable representation of the error.
|
|
||||||
func (e Error) Error() string {
|
|
||||||
return fmt.Sprintf("%s: %s",
|
|
||||||
strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)),
|
|
||||||
e.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithDetail will return a new Error, based on the current one, but with
|
|
||||||
// some Detail info added
|
|
||||||
func (e Error) WithDetail(detail interface{}) Error {
|
|
||||||
return Error{
|
|
||||||
Code: e.Code,
|
|
||||||
Message: e.Message,
|
|
||||||
Detail: detail,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithArgs uses the passed-in list of interface{} as the substitution
|
|
||||||
// variables in the Error's Message string, but returns a new Error
|
|
||||||
func (e Error) WithArgs(args ...interface{}) Error {
|
|
||||||
return Error{
|
|
||||||
Code: e.Code,
|
|
||||||
Message: fmt.Sprintf(e.Code.Message(), args...),
|
|
||||||
Detail: e.Detail,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrorDescriptor provides relevant information about a given error code.
|
|
||||||
type ErrorDescriptor struct {
|
|
||||||
// Code is the error code that this descriptor describes.
|
|
||||||
Code ErrorCode
|
|
||||||
|
|
||||||
// Value provides a unique, string key, often captilized with
|
|
||||||
// underscores, to identify the error code. This value is used as the
|
|
||||||
// keyed value when serializing api errors.
|
|
||||||
Value string
|
|
||||||
|
|
||||||
// Message is a short, human readable decription of the error condition
|
|
||||||
// included in API responses.
|
|
||||||
Message string
|
|
||||||
|
|
||||||
// Description provides a complete account of the errors purpose, suitable
|
|
||||||
// for use in documentation.
|
|
||||||
Description string
|
|
||||||
|
|
||||||
// HTTPStatusCode provides the http status code that is associated with
|
|
||||||
// this error condition.
|
|
||||||
HTTPStatusCode int
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseErrorCode returns the value by the string error code.
|
|
||||||
// `ErrorCodeUnknown` will be returned if the error is not known.
|
|
||||||
func ParseErrorCode(value string) ErrorCode {
|
|
||||||
ed, ok := idToDescriptors[value]
|
|
||||||
if ok {
|
|
||||||
return ed.Code
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrorCodeUnknown
|
|
||||||
}
|
|
||||||
|
|
||||||
// Errors provides the envelope for multiple errors and a few sugar methods
|
|
||||||
// for use within the application.
|
|
||||||
type Errors []error
|
|
||||||
|
|
||||||
var _ error = Errors{}
|
|
||||||
|
|
||||||
func (errs Errors) Error() string {
|
|
||||||
switch len(errs) {
|
|
||||||
case 0:
|
|
||||||
return "<nil>"
|
|
||||||
case 1:
|
|
||||||
return errs[0].Error()
|
|
||||||
default:
|
|
||||||
msg := "errors:\n"
|
|
||||||
for _, err := range errs {
|
|
||||||
msg += err.Error() + "\n"
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the current number of errors.
|
|
||||||
func (errs Errors) Len() int {
|
|
||||||
return len(errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON converts slice of error, ErrorCode or Error into a
|
|
||||||
// slice of Error - then serializes
|
|
||||||
func (errs Errors) MarshalJSON() ([]byte, error) {
|
|
||||||
var tmpErrs struct {
|
|
||||||
Errors []Error `json:"errors,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, daErr := range errs {
|
|
||||||
var err Error
|
|
||||||
|
|
||||||
switch daErr.(type) {
|
|
||||||
case ErrorCode:
|
|
||||||
err = daErr.(ErrorCode).WithDetail(nil)
|
|
||||||
case Error:
|
|
||||||
err = daErr.(Error)
|
|
||||||
default:
|
|
||||||
err = ErrorCodeUnknown.WithDetail(daErr)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the Error struct was setup and they forgot to set the
|
|
||||||
// Message field (meaning its "") then grab it from the ErrCode
|
|
||||||
msg := err.Message
|
|
||||||
if msg == "" {
|
|
||||||
msg = err.Code.Message()
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpErrs.Errors = append(tmpErrs.Errors, Error{
|
|
||||||
Code: err.Code,
|
|
||||||
Message: msg,
|
|
||||||
Detail: err.Detail,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.Marshal(tmpErrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON deserializes []Error and then converts it into slice of
|
|
||||||
// Error or ErrorCode
|
|
||||||
func (errs *Errors) UnmarshalJSON(data []byte) error {
|
|
||||||
var tmpErrs struct {
|
|
||||||
Errors []Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(data, &tmpErrs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var newErrs Errors
|
|
||||||
for _, daErr := range tmpErrs.Errors {
|
|
||||||
// If Message is empty or exactly matches the Code's message string
|
|
||||||
// then just use the Code, no need for a full Error struct
|
|
||||||
if daErr.Detail == nil && (daErr.Message == "" || daErr.Message == daErr.Code.Message()) {
|
|
||||||
// Error's w/o details get converted to ErrorCode
|
|
||||||
newErrs = append(newErrs, daErr.Code)
|
|
||||||
} else {
|
|
||||||
// Error's w/ details are untouched
|
|
||||||
newErrs = append(newErrs, Error{
|
|
||||||
Code: daErr.Code,
|
|
||||||
Message: daErr.Message,
|
|
||||||
Detail: daErr.Detail,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*errs = newErrs
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
179
Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors_test.go
generated
vendored
179
Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/errors_test.go
generated
vendored
@@ -1,179 +0,0 @@
|
|||||||
package errcode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestErrorCodes ensures that error code format, mappings and
|
|
||||||
// marshaling/unmarshaling. round trips are stable.
|
|
||||||
func TestErrorCodes(t *testing.T) {
|
|
||||||
if len(errorCodeToDescriptors) == 0 {
|
|
||||||
t.Fatal("errors aren't loaded!")
|
|
||||||
}
|
|
||||||
|
|
||||||
for ec, desc := range errorCodeToDescriptors {
|
|
||||||
if ec != desc.Code {
|
|
||||||
t.Fatalf("error code in descriptor isn't correct, %q != %q", ec, desc.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
if idToDescriptors[desc.Value].Code != ec {
|
|
||||||
t.Fatalf("error code in idToDesc isn't correct, %q != %q", idToDescriptors[desc.Value].Code, ec)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ec.Message() != desc.Message {
|
|
||||||
t.Fatalf("ec.Message doesn't mtach desc.Message: %q != %q", ec.Message(), desc.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test (de)serializing the ErrorCode
|
|
||||||
p, err := json.Marshal(ec)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("couldn't marshal ec %v: %v", ec, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p) <= 0 {
|
|
||||||
t.Fatalf("expected content in marshaled before for error code %v", ec)
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, unmarshal to interface and ensure we have a string.
|
|
||||||
var ecUnspecified interface{}
|
|
||||||
if err := json.Unmarshal(p, &ecUnspecified); err != nil {
|
|
||||||
t.Fatalf("error unmarshaling error code %v: %v", ec, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := ecUnspecified.(string); !ok {
|
|
||||||
t.Fatalf("expected a string for error code %v on unmarshal got a %T", ec, ecUnspecified)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, unmarshal with the error code type and ensure they are equal
|
|
||||||
var ecUnmarshaled ErrorCode
|
|
||||||
if err := json.Unmarshal(p, &ecUnmarshaled); err != nil {
|
|
||||||
t.Fatalf("error unmarshaling error code %v: %v", ec, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ecUnmarshaled != ec {
|
|
||||||
t.Fatalf("unexpected error code during error code marshal/unmarshal: %v != %v", ecUnmarshaled, ec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestErrorsManagement does a quick check of the Errors type to ensure that
|
|
||||||
// members are properly pushed and marshaled.
|
|
||||||
var ErrorCodeTest1 = Register("v2.errors", ErrorDescriptor{
|
|
||||||
Value: "TEST1",
|
|
||||||
Message: "test error 1",
|
|
||||||
Description: `Just a test message #1.`,
|
|
||||||
HTTPStatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
|
|
||||||
var ErrorCodeTest2 = Register("v2.errors", ErrorDescriptor{
|
|
||||||
Value: "TEST2",
|
|
||||||
Message: "test error 2",
|
|
||||||
Description: `Just a test message #2.`,
|
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
|
||||||
})
|
|
||||||
|
|
||||||
var ErrorCodeTest3 = Register("v2.errors", ErrorDescriptor{
|
|
||||||
Value: "TEST3",
|
|
||||||
Message: "Sorry %q isn't valid",
|
|
||||||
Description: `Just a test message #3.`,
|
|
||||||
HTTPStatusCode: http.StatusNotFound,
|
|
||||||
})
|
|
||||||
|
|
||||||
func TestErrorsManagement(t *testing.T) {
|
|
||||||
var errs Errors
|
|
||||||
|
|
||||||
errs = append(errs, ErrorCodeTest1)
|
|
||||||
errs = append(errs, ErrorCodeTest2.WithDetail(
|
|
||||||
map[string]interface{}{"digest": "sometestblobsumdoesntmatter"}))
|
|
||||||
errs = append(errs, ErrorCodeTest3.WithArgs("BOOGIE"))
|
|
||||||
errs = append(errs, ErrorCodeTest3.WithArgs("BOOGIE").WithDetail("data"))
|
|
||||||
|
|
||||||
p, err := json.Marshal(errs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error marashaling errors: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedJSON := `{"errors":[` +
|
|
||||||
`{"code":"TEST1","message":"test error 1"},` +
|
|
||||||
`{"code":"TEST2","message":"test error 2","detail":{"digest":"sometestblobsumdoesntmatter"}},` +
|
|
||||||
`{"code":"TEST3","message":"Sorry \"BOOGIE\" isn't valid"},` +
|
|
||||||
`{"code":"TEST3","message":"Sorry \"BOOGIE\" isn't valid","detail":"data"}` +
|
|
||||||
`]}`
|
|
||||||
|
|
||||||
if string(p) != expectedJSON {
|
|
||||||
t.Fatalf("unexpected json:\ngot:\n%q\n\nexpected:\n%q", string(p), expectedJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now test the reverse
|
|
||||||
var unmarshaled Errors
|
|
||||||
if err := json.Unmarshal(p, &unmarshaled); err != nil {
|
|
||||||
t.Fatalf("unexpected error unmarshaling error envelope: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(unmarshaled, errs) {
|
|
||||||
t.Fatalf("errors not equal after round trip:\nunmarshaled:\n%#v\n\nerrs:\n%#v", unmarshaled, errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the arg substitution stuff
|
|
||||||
e1 := unmarshaled[3].(Error)
|
|
||||||
exp1 := `Sorry "BOOGIE" isn't valid`
|
|
||||||
if e1.Message != exp1 {
|
|
||||||
t.Fatalf("Wrong msg, got:\n%q\n\nexpected:\n%q", e1.Message, exp1)
|
|
||||||
}
|
|
||||||
|
|
||||||
exp1 = "test3: " + exp1
|
|
||||||
if e1.Error() != exp1 {
|
|
||||||
t.Fatalf("Error() didn't return the right string, got:%s\nexpected:%s", e1.Error(), exp1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test again with a single value this time
|
|
||||||
errs = Errors{ErrorCodeUnknown}
|
|
||||||
expectedJSON = "{\"errors\":[{\"code\":\"UNKNOWN\",\"message\":\"unknown error\"}]}"
|
|
||||||
p, err = json.Marshal(errs)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error marashaling errors: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(p) != expectedJSON {
|
|
||||||
t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now test the reverse
|
|
||||||
unmarshaled = nil
|
|
||||||
if err := json.Unmarshal(p, &unmarshaled); err != nil {
|
|
||||||
t.Fatalf("unexpected error unmarshaling error envelope: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(unmarshaled, errs) {
|
|
||||||
t.Fatalf("errors not equal after round trip:\nunmarshaled:\n%#v\n\nerrs:\n%#v", unmarshaled, errs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that calling WithArgs() more than once does the right thing.
|
|
||||||
// Meaning creates a new Error and uses the ErrorCode Message
|
|
||||||
e1 = ErrorCodeTest3.WithArgs("test1")
|
|
||||||
e2 := e1.WithArgs("test2")
|
|
||||||
if &e1 == &e2 {
|
|
||||||
t.Fatalf("args: e2 and e1 should not be the same, but they are")
|
|
||||||
}
|
|
||||||
if e2.Message != `Sorry "test2" isn't valid` {
|
|
||||||
t.Fatalf("e2 had wrong message: %q", e2.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that calling WithDetail() more than once does the right thing.
|
|
||||||
// Meaning creates a new Error and overwrites the old detail field
|
|
||||||
e1 = ErrorCodeTest3.WithDetail("stuff1")
|
|
||||||
e2 = e1.WithDetail("stuff2")
|
|
||||||
if &e1 == &e2 {
|
|
||||||
t.Fatalf("detail: e2 and e1 should not be the same, but they are")
|
|
||||||
}
|
|
||||||
if e2.Detail != `stuff2` {
|
|
||||||
t.Fatalf("e2 had wrong detail: %q", e2.Detail)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
44
Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/handler.go
generated
vendored
44
Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/handler.go
generated
vendored
@@ -1,44 +0,0 @@
|
|||||||
package errcode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ServeJSON attempts to serve the errcode in a JSON envelope. It marshals err
|
|
||||||
// and sets the content-type header to 'application/json'. It will handle
|
|
||||||
// ErrorCoder and Errors, and if necessary will create an envelope.
|
|
||||||
func ServeJSON(w http.ResponseWriter, err error) error {
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
var sc int
|
|
||||||
|
|
||||||
switch errs := err.(type) {
|
|
||||||
case Errors:
|
|
||||||
if len(errs) < 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err, ok := errs[0].(ErrorCoder); ok {
|
|
||||||
sc = err.ErrorCode().Descriptor().HTTPStatusCode
|
|
||||||
}
|
|
||||||
case ErrorCoder:
|
|
||||||
sc = errs.ErrorCode().Descriptor().HTTPStatusCode
|
|
||||||
err = Errors{err} // create an envelope.
|
|
||||||
default:
|
|
||||||
// We just have an unhandled error type, so just place in an envelope
|
|
||||||
// and move along.
|
|
||||||
err = Errors{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sc == 0 {
|
|
||||||
sc = http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(sc)
|
|
||||||
|
|
||||||
if err := json.NewEncoder(w).Encode(err); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
116
Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/register.go
generated
vendored
116
Godeps/_workspace/src/github.com/docker/distribution/registry/api/errcode/register.go
generated
vendored
@@ -1,116 +0,0 @@
|
|||||||
package errcode
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errorCodeToDescriptors = map[ErrorCode]ErrorDescriptor{}
|
|
||||||
idToDescriptors = map[string]ErrorDescriptor{}
|
|
||||||
groupToDescriptors = map[string][]ErrorDescriptor{}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrorCodeUnknown is a generic error that can be used as a last
|
|
||||||
// resort if there is no situation-specific error message that can be used
|
|
||||||
ErrorCodeUnknown = Register("errcode", ErrorDescriptor{
|
|
||||||
Value: "UNKNOWN",
|
|
||||||
Message: "unknown error",
|
|
||||||
Description: `Generic error returned when the error does not have an
|
|
||||||
API classification.`,
|
|
||||||
HTTPStatusCode: http.StatusInternalServerError,
|
|
||||||
})
|
|
||||||
|
|
||||||
// ErrorCodeUnsupported is returned when an operation is not supported.
|
|
||||||
ErrorCodeUnsupported = Register("errcode", ErrorDescriptor{
|
|
||||||
Value: "UNSUPPORTED",
|
|
||||||
Message: "The operation is unsupported.",
|
|
||||||
Description: `The operation was unsupported due to a missing
|
|
||||||
implementation or invalid set of parameters.`,
|
|
||||||
HTTPStatusCode: http.StatusMethodNotAllowed,
|
|
||||||
})
|
|
||||||
|
|
||||||
// ErrorCodeUnauthorized is returned if a request is not authorized.
|
|
||||||
ErrorCodeUnauthorized = Register("errcode", ErrorDescriptor{
|
|
||||||
Value: "UNAUTHORIZED",
|
|
||||||
Message: "access to the requested resource is not authorized",
|
|
||||||
Description: `The access controller denied access for the operation on
|
|
||||||
a resource. Often this will be accompanied by a 401 Unauthorized
|
|
||||||
response status.`,
|
|
||||||
HTTPStatusCode: http.StatusUnauthorized,
|
|
||||||
})
|
|
||||||
|
|
||||||
// ErrorCodeUnavailable provides a common error to report unavialability
|
|
||||||
// of a service or endpoint.
|
|
||||||
ErrorCodeUnavailable = Register("errcode", ErrorDescriptor{
|
|
||||||
Value: "UNAVAILABLE",
|
|
||||||
Message: "service unavailable",
|
|
||||||
Description: "Returned when a service is not available",
|
|
||||||
HTTPStatusCode: http.StatusServiceUnavailable,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
var nextCode = 1000
|
|
||||||
var registerLock sync.Mutex
|
|
||||||
|
|
||||||
// Register will make the passed-in error known to the environment and
|
|
||||||
// return a new ErrorCode
|
|
||||||
func Register(group string, descriptor ErrorDescriptor) ErrorCode {
|
|
||||||
registerLock.Lock()
|
|
||||||
defer registerLock.Unlock()
|
|
||||||
|
|
||||||
descriptor.Code = ErrorCode(nextCode)
|
|
||||||
|
|
||||||
if _, ok := idToDescriptors[descriptor.Value]; ok {
|
|
||||||
panic(fmt.Sprintf("ErrorValue %q is already registered", descriptor.Value))
|
|
||||||
}
|
|
||||||
if _, ok := errorCodeToDescriptors[descriptor.Code]; ok {
|
|
||||||
panic(fmt.Sprintf("ErrorCode %v is already registered", descriptor.Code))
|
|
||||||
}
|
|
||||||
|
|
||||||
groupToDescriptors[group] = append(groupToDescriptors[group], descriptor)
|
|
||||||
errorCodeToDescriptors[descriptor.Code] = descriptor
|
|
||||||
idToDescriptors[descriptor.Value] = descriptor
|
|
||||||
|
|
||||||
nextCode++
|
|
||||||
return descriptor.Code
|
|
||||||
}
|
|
||||||
|
|
||||||
type byValue []ErrorDescriptor
|
|
||||||
|
|
||||||
func (a byValue) Len() int { return len(a) }
|
|
||||||
func (a byValue) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
||||||
func (a byValue) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
|
||||||
|
|
||||||
// GetGroupNames returns the list of Error group names that are registered
|
|
||||||
func GetGroupNames() []string {
|
|
||||||
keys := []string{}
|
|
||||||
|
|
||||||
for k := range groupToDescriptors {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetErrorCodeGroup returns the named group of error descriptors
|
|
||||||
func GetErrorCodeGroup(name string) []ErrorDescriptor {
|
|
||||||
desc := groupToDescriptors[name]
|
|
||||||
sort.Sort(byValue(desc))
|
|
||||||
return desc
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetErrorAllDescriptors returns a slice of all ErrorDescriptors that are
|
|
||||||
// registered, irrespective of what group they're in
|
|
||||||
func GetErrorAllDescriptors() []ErrorDescriptor {
|
|
||||||
result := []ErrorDescriptor{}
|
|
||||||
|
|
||||||
for _, group := range GetGroupNames() {
|
|
||||||
result = append(result, GetErrorCodeGroup(group)...)
|
|
||||||
}
|
|
||||||
sort.Sort(byValue(result))
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@@ -176,7 +176,7 @@
|
|||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
Copyright 2015 Docker, Inc.
|
Copyright 2013-2016 Docker, Inc.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
19
Godeps/_workspace/src/github.com/docker/docker/NOTICE
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/docker/docker/NOTICE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Docker
|
||||||
|
Copyright 2012-2016 Docker, Inc.
|
||||||
|
|
||||||
|
This product includes software developed at Docker, Inc. (https://www.docker.com).
|
||||||
|
|
||||||
|
This product contains software (https://github.com/kr/pty) developed
|
||||||
|
by Keith Rarick, licensed under the MIT License.
|
||||||
|
|
||||||
|
The following is courtesy of our legal counsel:
|
||||||
|
|
||||||
|
|
||||||
|
Use and transfer of Docker may be subject to certain restrictions by the
|
||||||
|
United States and other governments.
|
||||||
|
It is your responsibility to ensure that your use and/or transfer does not
|
||||||
|
violate applicable laws.
|
||||||
|
|
||||||
|
For more information, please see https://www.bis.doc.gov
|
||||||
|
|
||||||
|
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
||||||
1
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/README.md
generated
vendored
1
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/README.md
generated
vendored
@@ -1 +0,0 @@
|
|||||||
This code provides helper functions for dealing with archive files.
|
|
||||||
1049
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive.go
generated
vendored
1049
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive.go
generated
vendored
File diff suppressed because it is too large
Load Diff
1248
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_test.go
generated
vendored
1248
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
112
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_unix.go
generated
vendored
112
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_unix.go
generated
vendored
@@ -1,112 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fixVolumePathPrefix does platform specific processing to ensure that if
|
|
||||||
// the path being passed in is not in a volume path format, convert it to one.
|
|
||||||
func fixVolumePathPrefix(srcPath string) string {
|
|
||||||
return srcPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// getWalkRoot calculates the root path when performing a TarWithOptions.
|
|
||||||
// We use a separate function as this is platform specific. On Linux, we
|
|
||||||
// can't use filepath.Join(srcPath,include) because this will clean away
|
|
||||||
// a trailing "." or "/" which may be important.
|
|
||||||
func getWalkRoot(srcPath string, include string) string {
|
|
||||||
return srcPath + string(filepath.Separator) + include
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanonicalTarNameForPath returns platform-specific filepath
|
|
||||||
// to canonical posix-style path for tar archival. p is relative
|
|
||||||
// path.
|
|
||||||
func CanonicalTarNameForPath(p string) (string, error) {
|
|
||||||
return p, nil // already unix-style
|
|
||||||
}
|
|
||||||
|
|
||||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
|
||||||
// on the platform the archival is done.
|
|
||||||
|
|
||||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
|
||||||
return perm // noop for unix as golang APIs provide perm bits correctly
|
|
||||||
}
|
|
||||||
|
|
||||||
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) {
|
|
||||||
s, ok := stat.(*syscall.Stat_t)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
err = errors.New("cannot convert stat value to syscall.Stat_t")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
inode = uint64(s.Ino)
|
|
||||||
|
|
||||||
// Currently go does not fill in the major/minors
|
|
||||||
if s.Mode&syscall.S_IFBLK != 0 ||
|
|
||||||
s.Mode&syscall.S_IFCHR != 0 {
|
|
||||||
hdr.Devmajor = int64(major(uint64(s.Rdev)))
|
|
||||||
hdr.Devminor = int64(minor(uint64(s.Rdev)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFileUIDGID(stat interface{}) (int, int, error) {
|
|
||||||
s, ok := stat.(*syscall.Stat_t)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return -1, -1, errors.New("cannot convert stat value to syscall.Stat_t")
|
|
||||||
}
|
|
||||||
return int(s.Uid), int(s.Gid), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func major(device uint64) uint64 {
|
|
||||||
return (device >> 8) & 0xfff
|
|
||||||
}
|
|
||||||
|
|
||||||
func minor(device uint64) uint64 {
|
|
||||||
return (device & 0xff) | ((device >> 12) & 0xfff00)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
|
|
||||||
// createTarFile to handle the following types of header: Block; Char; Fifo
|
|
||||||
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
|
||||||
mode := uint32(hdr.Mode & 07777)
|
|
||||||
switch hdr.Typeflag {
|
|
||||||
case tar.TypeBlock:
|
|
||||||
mode |= syscall.S_IFBLK
|
|
||||||
case tar.TypeChar:
|
|
||||||
mode |= syscall.S_IFCHR
|
|
||||||
case tar.TypeFifo:
|
|
||||||
mode |= syscall.S_IFIFO
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
|
|
||||||
if hdr.Typeflag == tar.TypeLink {
|
|
||||||
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
|
|
||||||
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if hdr.Typeflag != tar.TypeSymlink {
|
|
||||||
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
60
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_unix_test.go
generated
vendored
60
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_unix_test.go
generated
vendored
@@ -1,60 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCanonicalTarNameForPath(t *testing.T) {
|
|
||||||
cases := []struct{ in, expected string }{
|
|
||||||
{"foo", "foo"},
|
|
||||||
{"foo/bar", "foo/bar"},
|
|
||||||
{"foo/dir/", "foo/dir/"},
|
|
||||||
}
|
|
||||||
for _, v := range cases {
|
|
||||||
if out, err := CanonicalTarNameForPath(v.in); err != nil {
|
|
||||||
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
|
|
||||||
} else if out != v.expected {
|
|
||||||
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCanonicalTarName(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
in string
|
|
||||||
isDir bool
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"foo", false, "foo"},
|
|
||||||
{"foo", true, "foo/"},
|
|
||||||
{"foo/bar", false, "foo/bar"},
|
|
||||||
{"foo/bar", true, "foo/bar/"},
|
|
||||||
}
|
|
||||||
for _, v := range cases {
|
|
||||||
if out, err := canonicalTarName(v.in, v.isDir); err != nil {
|
|
||||||
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
|
|
||||||
} else if out != v.expected {
|
|
||||||
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChmodTarEntry(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
in, expected os.FileMode
|
|
||||||
}{
|
|
||||||
{0000, 0000},
|
|
||||||
{0777, 0777},
|
|
||||||
{0644, 0644},
|
|
||||||
{0755, 0755},
|
|
||||||
{0444, 0444},
|
|
||||||
}
|
|
||||||
for _, v := range cases {
|
|
||||||
if out := chmodTarEntry(v.in); out != v.expected {
|
|
||||||
t.Fatalf("wrong chmod. expected:%v got:%v", v.expected, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
70
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_windows.go
generated
vendored
70
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_windows.go
generated
vendored
@@ -1,70 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/longpath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// fixVolumePathPrefix does platform specific processing to ensure that if
|
|
||||||
// the path being passed in is not in a volume path format, convert it to one.
|
|
||||||
func fixVolumePathPrefix(srcPath string) string {
|
|
||||||
return longpath.AddPrefix(srcPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getWalkRoot calculates the root path when performing a TarWithOptions.
|
|
||||||
// We use a separate function as this is platform specific.
|
|
||||||
func getWalkRoot(srcPath string, include string) string {
|
|
||||||
return filepath.Join(srcPath, include)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CanonicalTarNameForPath returns platform-specific filepath
|
|
||||||
// to canonical posix-style path for tar archival. p is relative
|
|
||||||
// path.
|
|
||||||
func CanonicalTarNameForPath(p string) (string, error) {
|
|
||||||
// windows: convert windows style relative path with backslashes
|
|
||||||
// into forward slashes. Since windows does not allow '/' or '\'
|
|
||||||
// in file names, it is mostly safe to replace however we must
|
|
||||||
// check just in case
|
|
||||||
if strings.Contains(p, "/") {
|
|
||||||
return "", fmt.Errorf("Windows path contains forward slash: %s", p)
|
|
||||||
}
|
|
||||||
return strings.Replace(p, string(os.PathSeparator), "/", -1), nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// chmodTarEntry is used to adjust the file permissions used in tar header based
|
|
||||||
// on the platform the archival is done.
|
|
||||||
func chmodTarEntry(perm os.FileMode) os.FileMode {
|
|
||||||
perm &= 0755
|
|
||||||
// Add the x bit: make everything +x from windows
|
|
||||||
perm |= 0111
|
|
||||||
|
|
||||||
return perm
|
|
||||||
}
|
|
||||||
|
|
||||||
func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, stat interface{}) (inode uint64, err error) {
|
|
||||||
// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
|
|
||||||
// createTarFile to handle the following types of header: Block; Char; Fifo
|
|
||||||
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFileUIDGID(stat interface{}) (int, int, error) {
|
|
||||||
// no notion of file ownership mapping yet on Windows
|
|
||||||
return 0, 0, nil
|
|
||||||
}
|
|
||||||
87
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_windows_test.go
generated
vendored
87
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/archive_windows_test.go
generated
vendored
@@ -1,87 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCopyFileWithInvalidDest(t *testing.T) {
|
|
||||||
folder, err := ioutil.TempDir("", "docker-archive-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(folder)
|
|
||||||
dest := "c:dest"
|
|
||||||
srcFolder := filepath.Join(folder, "src")
|
|
||||||
src := filepath.Join(folder, "src", "src")
|
|
||||||
err = os.MkdirAll(srcFolder, 0740)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
ioutil.WriteFile(src, []byte("content"), 0777)
|
|
||||||
err = CopyWithTar(src, dest)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("archiver.CopyWithTar should throw an error on invalid dest.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCanonicalTarNameForPath(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
in, expected string
|
|
||||||
shouldFail bool
|
|
||||||
}{
|
|
||||||
{"foo", "foo", false},
|
|
||||||
{"foo/bar", "___", true}, // unix-styled windows path must fail
|
|
||||||
{`foo\bar`, "foo/bar", false},
|
|
||||||
}
|
|
||||||
for _, v := range cases {
|
|
||||||
if out, err := CanonicalTarNameForPath(v.in); err != nil && !v.shouldFail {
|
|
||||||
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
|
|
||||||
} else if v.shouldFail && err == nil {
|
|
||||||
t.Fatalf("canonical path call should have failed with error. in=%s out=%s", v.in, out)
|
|
||||||
} else if !v.shouldFail && out != v.expected {
|
|
||||||
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCanonicalTarName(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
in string
|
|
||||||
isDir bool
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{"foo", false, "foo"},
|
|
||||||
{"foo", true, "foo/"},
|
|
||||||
{`foo\bar`, false, "foo/bar"},
|
|
||||||
{`foo\bar`, true, "foo/bar/"},
|
|
||||||
}
|
|
||||||
for _, v := range cases {
|
|
||||||
if out, err := canonicalTarName(v.in, v.isDir); err != nil {
|
|
||||||
t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err)
|
|
||||||
} else if out != v.expected {
|
|
||||||
t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChmodTarEntry(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
in, expected os.FileMode
|
|
||||||
}{
|
|
||||||
{0000, 0111},
|
|
||||||
{0777, 0755},
|
|
||||||
{0644, 0755},
|
|
||||||
{0755, 0755},
|
|
||||||
{0444, 0555},
|
|
||||||
}
|
|
||||||
for _, v := range cases {
|
|
||||||
if out := chmodTarEntry(v.in); out != v.expected {
|
|
||||||
t.Fatalf("wrong chmod. expected:%v got:%v", v.expected, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
416
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes.go
generated
vendored
416
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes.go
generated
vendored
@@ -1,416 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/pkg/idtools"
|
|
||||||
"github.com/docker/docker/pkg/pools"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ChangeType represents the change type.
|
|
||||||
type ChangeType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ChangeModify represents the modify operation.
|
|
||||||
ChangeModify = iota
|
|
||||||
// ChangeAdd represents the add operation.
|
|
||||||
ChangeAdd
|
|
||||||
// ChangeDelete represents the delete operation.
|
|
||||||
ChangeDelete
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c ChangeType) String() string {
|
|
||||||
switch c {
|
|
||||||
case ChangeModify:
|
|
||||||
return "C"
|
|
||||||
case ChangeAdd:
|
|
||||||
return "A"
|
|
||||||
case ChangeDelete:
|
|
||||||
return "D"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change represents a change, it wraps the change type and path.
|
|
||||||
// It describes changes of the files in the path respect to the
|
|
||||||
// parent layers. The change could be modify, add, delete.
|
|
||||||
// This is used for layer diff.
|
|
||||||
type Change struct {
|
|
||||||
Path string
|
|
||||||
Kind ChangeType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (change *Change) String() string {
|
|
||||||
return fmt.Sprintf("%s %s", change.Kind, change.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for sort.Sort
|
|
||||||
type changesByPath []Change
|
|
||||||
|
|
||||||
func (c changesByPath) Less(i, j int) bool { return c[i].Path < c[j].Path }
|
|
||||||
func (c changesByPath) Len() int { return len(c) }
|
|
||||||
func (c changesByPath) Swap(i, j int) { c[j], c[i] = c[i], c[j] }
|
|
||||||
|
|
||||||
// Gnu tar and the go tar writer don't have sub-second mtime
|
|
||||||
// precision, which is problematic when we apply changes via tar
|
|
||||||
// files, we handle this by comparing for exact times, *or* same
|
|
||||||
// second count and either a or b having exactly 0 nanoseconds
|
|
||||||
func sameFsTime(a, b time.Time) bool {
|
|
||||||
return a == b ||
|
|
||||||
(a.Unix() == b.Unix() &&
|
|
||||||
(a.Nanosecond() == 0 || b.Nanosecond() == 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
func sameFsTimeSpec(a, b syscall.Timespec) bool {
|
|
||||||
return a.Sec == b.Sec &&
|
|
||||||
(a.Nsec == b.Nsec || a.Nsec == 0 || b.Nsec == 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes walks the path rw and determines changes for the files in the path,
|
|
||||||
// with respect to the parent layers
|
|
||||||
func Changes(layers []string, rw string) ([]Change, error) {
|
|
||||||
var (
|
|
||||||
changes []Change
|
|
||||||
changedDirs = make(map[string]struct{})
|
|
||||||
)
|
|
||||||
|
|
||||||
err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebase path
|
|
||||||
path, err = filepath.Rel(rw, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// As this runs on the daemon side, file paths are OS specific.
|
|
||||||
path = filepath.Join(string(os.PathSeparator), path)
|
|
||||||
|
|
||||||
// Skip root
|
|
||||||
if path == string(os.PathSeparator) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip AUFS metadata
|
|
||||||
if matched, err := filepath.Match(string(os.PathSeparator)+WhiteoutMetaPrefix+"*", path); err != nil || matched {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
change := Change{
|
|
||||||
Path: path,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find out what kind of modification happened
|
|
||||||
file := filepath.Base(path)
|
|
||||||
// If there is a whiteout, then the file was removed
|
|
||||||
if strings.HasPrefix(file, WhiteoutPrefix) {
|
|
||||||
originalFile := file[len(WhiteoutPrefix):]
|
|
||||||
change.Path = filepath.Join(filepath.Dir(path), originalFile)
|
|
||||||
change.Kind = ChangeDelete
|
|
||||||
} else {
|
|
||||||
// Otherwise, the file was added
|
|
||||||
change.Kind = ChangeAdd
|
|
||||||
|
|
||||||
// ...Unless it already existed in a top layer, in which case, it's a modification
|
|
||||||
for _, layer := range layers {
|
|
||||||
stat, err := os.Stat(filepath.Join(layer, path))
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
// The file existed in the top layer, so that's a modification
|
|
||||||
|
|
||||||
// However, if it's a directory, maybe it wasn't actually modified.
|
|
||||||
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
|
|
||||||
if stat.IsDir() && f.IsDir() {
|
|
||||||
if f.Size() == stat.Size() && f.Mode() == stat.Mode() && sameFsTime(f.ModTime(), stat.ModTime()) {
|
|
||||||
// Both directories are the same, don't record the change
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
change.Kind = ChangeModify
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If /foo/bar/file.txt is modified, then /foo/bar must be part of the changed files.
|
|
||||||
// This block is here to ensure the change is recorded even if the
|
|
||||||
// modify time, mode and size of the parent directory in the rw and ro layers are all equal.
|
|
||||||
// Check https://github.com/docker/docker/pull/13590 for details.
|
|
||||||
if f.IsDir() {
|
|
||||||
changedDirs[path] = struct{}{}
|
|
||||||
}
|
|
||||||
if change.Kind == ChangeAdd || change.Kind == ChangeDelete {
|
|
||||||
parent := filepath.Dir(path)
|
|
||||||
if _, ok := changedDirs[parent]; !ok && parent != "/" {
|
|
||||||
changes = append(changes, Change{Path: parent, Kind: ChangeModify})
|
|
||||||
changedDirs[parent] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record change
|
|
||||||
changes = append(changes, change)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return changes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileInfo describes the information of a file.
|
|
||||||
type FileInfo struct {
|
|
||||||
parent *FileInfo
|
|
||||||
name string
|
|
||||||
stat *system.StatT
|
|
||||||
children map[string]*FileInfo
|
|
||||||
capability []byte
|
|
||||||
added bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookUp looks up the file information of a file.
|
|
||||||
func (info *FileInfo) LookUp(path string) *FileInfo {
|
|
||||||
// As this runs on the daemon side, file paths are OS specific.
|
|
||||||
parent := info
|
|
||||||
if path == string(os.PathSeparator) {
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
pathElements := strings.Split(path, string(os.PathSeparator))
|
|
||||||
for _, elem := range pathElements {
|
|
||||||
if elem != "" {
|
|
||||||
child := parent.children[elem]
|
|
||||||
if child == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
parent = child
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return parent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *FileInfo) path() string {
|
|
||||||
if info.parent == nil {
|
|
||||||
// As this runs on the daemon side, file paths are OS specific.
|
|
||||||
return string(os.PathSeparator)
|
|
||||||
}
|
|
||||||
return filepath.Join(info.parent.path(), info.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
|
|
||||||
|
|
||||||
sizeAtEntry := len(*changes)
|
|
||||||
|
|
||||||
if oldInfo == nil {
|
|
||||||
// add
|
|
||||||
change := Change{
|
|
||||||
Path: info.path(),
|
|
||||||
Kind: ChangeAdd,
|
|
||||||
}
|
|
||||||
*changes = append(*changes, change)
|
|
||||||
info.added = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// We make a copy so we can modify it to detect additions
|
|
||||||
// also, we only recurse on the old dir if the new info is a directory
|
|
||||||
// otherwise any previous delete/change is considered recursive
|
|
||||||
oldChildren := make(map[string]*FileInfo)
|
|
||||||
if oldInfo != nil && info.isDir() {
|
|
||||||
for k, v := range oldInfo.children {
|
|
||||||
oldChildren[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, newChild := range info.children {
|
|
||||||
oldChild, _ := oldChildren[name]
|
|
||||||
if oldChild != nil {
|
|
||||||
// change?
|
|
||||||
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 statDifferent(oldStat, newStat) ||
|
|
||||||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
|
|
||||||
change := Change{
|
|
||||||
Path: newChild.path(),
|
|
||||||
Kind: ChangeModify,
|
|
||||||
}
|
|
||||||
*changes = append(*changes, change)
|
|
||||||
newChild.added = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from copy so we can detect deletions
|
|
||||||
delete(oldChildren, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
newChild.addChanges(oldChild, changes)
|
|
||||||
}
|
|
||||||
for _, oldChild := range oldChildren {
|
|
||||||
// delete
|
|
||||||
change := Change{
|
|
||||||
Path: oldChild.path(),
|
|
||||||
Kind: ChangeDelete,
|
|
||||||
}
|
|
||||||
*changes = append(*changes, change)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there were changes inside this directory, we need to add it, even if the directory
|
|
||||||
// itself wasn't changed. This is needed to properly save and restore filesystem permissions.
|
|
||||||
// As this runs on the daemon side, file paths are OS specific.
|
|
||||||
if len(*changes) > sizeAtEntry && info.isDir() && !info.added && info.path() != string(os.PathSeparator) {
|
|
||||||
change := Change{
|
|
||||||
Path: info.path(),
|
|
||||||
Kind: ChangeModify,
|
|
||||||
}
|
|
||||||
// Let's insert the directory entry before the recently added entries located inside this dir
|
|
||||||
*changes = append(*changes, change) // just to resize the slice, will be overwritten
|
|
||||||
copy((*changes)[sizeAtEntry+1:], (*changes)[sizeAtEntry:])
|
|
||||||
(*changes)[sizeAtEntry] = change
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes add changes to file information.
|
|
||||||
func (info *FileInfo) Changes(oldInfo *FileInfo) []Change {
|
|
||||||
var changes []Change
|
|
||||||
|
|
||||||
info.addChanges(oldInfo, &changes)
|
|
||||||
|
|
||||||
return changes
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRootFileInfo() *FileInfo {
|
|
||||||
// As this runs on the daemon side, file paths are OS specific.
|
|
||||||
root := &FileInfo{
|
|
||||||
name: string(os.PathSeparator),
|
|
||||||
children: make(map[string]*FileInfo),
|
|
||||||
}
|
|
||||||
return root
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangesDirs compares two directories and generates an array of Change objects describing the changes.
|
|
||||||
// If oldDir is "", then all files in newDir will be Add-Changes.
|
|
||||||
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
|
|
||||||
var (
|
|
||||||
oldRoot, newRoot *FileInfo
|
|
||||||
)
|
|
||||||
if oldDir == "" {
|
|
||||||
emptyDir, err := ioutil.TempDir("", "empty")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer os.Remove(emptyDir)
|
|
||||||
oldDir = emptyDir
|
|
||||||
}
|
|
||||||
oldRoot, newRoot, err := collectFileInfoForChanges(oldDir, newDir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newRoot.Changes(oldRoot), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChangesSize calculates the size in bytes of the provided changes, based on newDir.
|
|
||||||
func ChangesSize(newDir string, changes []Change) int64 {
|
|
||||||
var (
|
|
||||||
size int64
|
|
||||||
sf = make(map[uint64]struct{})
|
|
||||||
)
|
|
||||||
for _, change := range changes {
|
|
||||||
if change.Kind == ChangeModify || change.Kind == ChangeAdd {
|
|
||||||
file := filepath.Join(newDir, change.Path)
|
|
||||||
fileInfo, err := os.Lstat(file)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Can not stat %q: %s", file, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileInfo != nil && !fileInfo.IsDir() {
|
|
||||||
if hasHardlinks(fileInfo) {
|
|
||||||
inode := getIno(fileInfo)
|
|
||||||
if _, ok := sf[inode]; !ok {
|
|
||||||
size += fileInfo.Size()
|
|
||||||
sf[inode] = struct{}{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
size += fileInfo.Size()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExportChanges produces an Archive from the provided changes, relative to dir.
|
|
||||||
func ExportChanges(dir string, changes []Change, uidMaps, gidMaps []idtools.IDMap) (Archive, error) {
|
|
||||||
reader, writer := io.Pipe()
|
|
||||||
go func() {
|
|
||||||
ta := &tarAppender{
|
|
||||||
TarWriter: tar.NewWriter(writer),
|
|
||||||
Buffer: pools.BufioWriter32KPool.Get(nil),
|
|
||||||
SeenFiles: make(map[uint64]string),
|
|
||||||
UIDMaps: uidMaps,
|
|
||||||
GIDMaps: gidMaps,
|
|
||||||
}
|
|
||||||
// this buffer is needed for the duration of this piped stream
|
|
||||||
defer pools.BufioWriter32KPool.Put(ta.Buffer)
|
|
||||||
|
|
||||||
sort.Sort(changesByPath(changes))
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// from this
|
|
||||||
for _, change := range changes {
|
|
||||||
if change.Kind == ChangeDelete {
|
|
||||||
whiteOutDir := filepath.Dir(change.Path)
|
|
||||||
whiteOutBase := filepath.Base(change.Path)
|
|
||||||
whiteOut := filepath.Join(whiteOutDir, WhiteoutPrefix+whiteOutBase)
|
|
||||||
timestamp := time.Now()
|
|
||||||
hdr := &tar.Header{
|
|
||||||
Name: whiteOut[1:],
|
|
||||||
Size: 0,
|
|
||||||
ModTime: timestamp,
|
|
||||||
AccessTime: timestamp,
|
|
||||||
ChangeTime: timestamp,
|
|
||||||
}
|
|
||||||
if err := ta.TarWriter.WriteHeader(hdr); err != nil {
|
|
||||||
logrus.Debugf("Can't write whiteout header: %s", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
path := filepath.Join(dir, change.Path)
|
|
||||||
if err := ta.addTarFile(path, change.Path[1:]); err != nil {
|
|
||||||
logrus.Debugf("Can't add file %s to tar: %s", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure to check the error on Close.
|
|
||||||
if err := ta.TarWriter.Close(); err != nil {
|
|
||||||
logrus.Debugf("Can't close layer: %s", err)
|
|
||||||
}
|
|
||||||
if err := writer.Close(); err != nil {
|
|
||||||
logrus.Debugf("failed close Changes writer: %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return reader, nil
|
|
||||||
}
|
|
||||||
285
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_linux.go
generated
vendored
285
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_linux.go
generated
vendored
@@ -1,285 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// walker is used to implement collectFileInfoForChanges on linux. Where this
|
|
||||||
// method in general returns the entire contents of two directory trees, we
|
|
||||||
// optimize some FS calls out on linux. In particular, we take advantage of the
|
|
||||||
// fact that getdents(2) returns the inode of each file in the directory being
|
|
||||||
// walked, which, when walking two trees in parallel to generate a list of
|
|
||||||
// changes, can be used to prune subtrees without ever having to lstat(2) them
|
|
||||||
// directly. Eliminating stat calls in this way can save up to seconds on large
|
|
||||||
// images.
|
|
||||||
type walker struct {
|
|
||||||
dir1 string
|
|
||||||
dir2 string
|
|
||||||
root1 *FileInfo
|
|
||||||
root2 *FileInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// collectFileInfoForChanges returns a complete representation of the trees
|
|
||||||
// rooted at dir1 and dir2, with one important exception: any subtree or
|
|
||||||
// leaf where the inode and device numbers are an exact match between dir1
|
|
||||||
// and dir2 will be pruned from the results. This method is *only* to be used
|
|
||||||
// to generating a list of changes between the two directories, as it does not
|
|
||||||
// reflect the full contents.
|
|
||||||
func collectFileInfoForChanges(dir1, dir2 string) (*FileInfo, *FileInfo, error) {
|
|
||||||
w := &walker{
|
|
||||||
dir1: dir1,
|
|
||||||
dir2: dir2,
|
|
||||||
root1: newRootFileInfo(),
|
|
||||||
root2: newRootFileInfo(),
|
|
||||||
}
|
|
||||||
|
|
||||||
i1, err := os.Lstat(w.dir1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
i2, err := os.Lstat(w.dir2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.walk("/", i1, i2); err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.root1, w.root2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a FileInfo, its path info, and a reference to the root of the tree
|
|
||||||
// being constructed, register this file with the tree.
|
|
||||||
func walkchunk(path string, fi os.FileInfo, dir string, root *FileInfo) error {
|
|
||||||
if fi == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
parent := root.LookUp(filepath.Dir(path))
|
|
||||||
if parent == nil {
|
|
||||||
return fmt.Errorf("collectFileInfoForChanges: Unexpectedly no parent for %s", path)
|
|
||||||
}
|
|
||||||
info := &FileInfo{
|
|
||||||
name: filepath.Base(path),
|
|
||||||
children: make(map[string]*FileInfo),
|
|
||||||
parent: parent,
|
|
||||||
}
|
|
||||||
cpath := filepath.Join(dir, path)
|
|
||||||
stat, err := system.FromStatT(fi.Sys().(*syscall.Stat_t))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info.stat = stat
|
|
||||||
info.capability, _ = system.Lgetxattr(cpath, "security.capability") // lgetxattr(2): fs access
|
|
||||||
parent.children[info.name] = info
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk a subtree rooted at the same path in both trees being iterated. For
|
|
||||||
// example, /docker/overlay/1234/a/b/c/d and /docker/overlay/8888/a/b/c/d
|
|
||||||
func (w *walker) walk(path string, i1, i2 os.FileInfo) (err error) {
|
|
||||||
// Register these nodes with the return trees, unless we're still at the
|
|
||||||
// (already-created) roots:
|
|
||||||
if path != "/" {
|
|
||||||
if err := walkchunk(path, i1, w.dir1, w.root1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := walkchunk(path, i2, w.dir2, w.root2); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is1Dir := i1 != nil && i1.IsDir()
|
|
||||||
is2Dir := i2 != nil && i2.IsDir()
|
|
||||||
|
|
||||||
sameDevice := false
|
|
||||||
if i1 != nil && i2 != nil {
|
|
||||||
si1 := i1.Sys().(*syscall.Stat_t)
|
|
||||||
si2 := i2.Sys().(*syscall.Stat_t)
|
|
||||||
if si1.Dev == si2.Dev {
|
|
||||||
sameDevice = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If these files are both non-existent, or leaves (non-dirs), we are done.
|
|
||||||
if !is1Dir && !is2Dir {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the names of all the files contained in both directories being walked:
|
|
||||||
var names1, names2 []nameIno
|
|
||||||
if is1Dir {
|
|
||||||
names1, err = readdirnames(filepath.Join(w.dir1, path)) // getdents(2): fs access
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if is2Dir {
|
|
||||||
names2, err = readdirnames(filepath.Join(w.dir2, path)) // getdents(2): fs access
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have lists of the files contained in both parallel directories, sorted
|
|
||||||
// in the same order. Walk them in parallel, generating a unique merged list
|
|
||||||
// of all items present in either or both directories.
|
|
||||||
var names []string
|
|
||||||
ix1 := 0
|
|
||||||
ix2 := 0
|
|
||||||
|
|
||||||
for {
|
|
||||||
if ix1 >= len(names1) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if ix2 >= len(names2) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
ni1 := names1[ix1]
|
|
||||||
ni2 := names2[ix2]
|
|
||||||
|
|
||||||
switch bytes.Compare([]byte(ni1.name), []byte(ni2.name)) {
|
|
||||||
case -1: // ni1 < ni2 -- advance ni1
|
|
||||||
// we will not encounter ni1 in names2
|
|
||||||
names = append(names, ni1.name)
|
|
||||||
ix1++
|
|
||||||
case 0: // ni1 == ni2
|
|
||||||
if ni1.ino != ni2.ino || !sameDevice {
|
|
||||||
names = append(names, ni1.name)
|
|
||||||
}
|
|
||||||
ix1++
|
|
||||||
ix2++
|
|
||||||
case 1: // ni1 > ni2 -- advance ni2
|
|
||||||
// we will not encounter ni2 in names1
|
|
||||||
names = append(names, ni2.name)
|
|
||||||
ix2++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for ix1 < len(names1) {
|
|
||||||
names = append(names, names1[ix1].name)
|
|
||||||
ix1++
|
|
||||||
}
|
|
||||||
for ix2 < len(names2) {
|
|
||||||
names = append(names, names2[ix2].name)
|
|
||||||
ix2++
|
|
||||||
}
|
|
||||||
|
|
||||||
// For each of the names present in either or both of the directories being
|
|
||||||
// iterated, stat the name under each root, and recurse the pair of them:
|
|
||||||
for _, name := range names {
|
|
||||||
fname := filepath.Join(path, name)
|
|
||||||
var cInfo1, cInfo2 os.FileInfo
|
|
||||||
if is1Dir {
|
|
||||||
cInfo1, err = os.Lstat(filepath.Join(w.dir1, fname)) // lstat(2): fs access
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if is2Dir {
|
|
||||||
cInfo2, err = os.Lstat(filepath.Join(w.dir2, fname)) // lstat(2): fs access
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err = w.walk(fname, cInfo1, cInfo2); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// {name,inode} pairs used to support the early-pruning logic of the walker type
|
|
||||||
type nameIno struct {
|
|
||||||
name string
|
|
||||||
ino uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type nameInoSlice []nameIno
|
|
||||||
|
|
||||||
func (s nameInoSlice) Len() int { return len(s) }
|
|
||||||
func (s nameInoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
||||||
func (s nameInoSlice) Less(i, j int) bool { return s[i].name < s[j].name }
|
|
||||||
|
|
||||||
// readdirnames is a hacked-apart version of the Go stdlib code, exposing inode
|
|
||||||
// numbers further up the stack when reading directory contents. Unlike
|
|
||||||
// os.Readdirnames, which returns a list of filenames, this function returns a
|
|
||||||
// list of {filename,inode} pairs.
|
|
||||||
func readdirnames(dirname string) (names []nameIno, err error) {
|
|
||||||
var (
|
|
||||||
size = 100
|
|
||||||
buf = make([]byte, 4096)
|
|
||||||
nbuf int
|
|
||||||
bufp int
|
|
||||||
nb int
|
|
||||||
)
|
|
||||||
|
|
||||||
f, err := os.Open(dirname)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
names = make([]nameIno, 0, size) // Empty with room to grow.
|
|
||||||
for {
|
|
||||||
// Refill the buffer if necessary
|
|
||||||
if bufp >= nbuf {
|
|
||||||
bufp = 0
|
|
||||||
nbuf, err = syscall.ReadDirent(int(f.Fd()), buf) // getdents on linux
|
|
||||||
if nbuf < 0 {
|
|
||||||
nbuf = 0
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, os.NewSyscallError("readdirent", err)
|
|
||||||
}
|
|
||||||
if nbuf <= 0 {
|
|
||||||
break // EOF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drain the buffer
|
|
||||||
nb, names = parseDirent(buf[bufp:nbuf], names)
|
|
||||||
bufp += nb
|
|
||||||
}
|
|
||||||
|
|
||||||
sl := nameInoSlice(names)
|
|
||||||
sort.Sort(sl)
|
|
||||||
return sl, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDirent is a minor modification of syscall.ParseDirent (linux version)
|
|
||||||
// which returns {name,inode} pairs instead of just names.
|
|
||||||
func parseDirent(buf []byte, names []nameIno) (consumed int, newnames []nameIno) {
|
|
||||||
origlen := len(buf)
|
|
||||||
for len(buf) > 0 {
|
|
||||||
dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0]))
|
|
||||||
buf = buf[dirent.Reclen:]
|
|
||||||
if dirent.Ino == 0 { // File absent in directory.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
bytes := (*[10000]byte)(unsafe.Pointer(&dirent.Name[0]))
|
|
||||||
var name = string(bytes[0:clen(bytes[:])])
|
|
||||||
if name == "." || name == ".." { // Useless names
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
names = append(names, nameIno{name, dirent.Ino})
|
|
||||||
}
|
|
||||||
return origlen - len(buf), names
|
|
||||||
}
|
|
||||||
|
|
||||||
func clen(n []byte) int {
|
|
||||||
for i := 0; i < len(n); i++ {
|
|
||||||
if n[i] == 0 {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(n)
|
|
||||||
}
|
|
||||||
97
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_other.go
generated
vendored
97
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_other.go
generated
vendored
@@ -1,97 +0,0 @@
|
|||||||
// +build !linux
|
|
||||||
|
|
||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
func collectFileInfoForChanges(oldDir, newDir string) (*FileInfo, *FileInfo, error) {
|
|
||||||
var (
|
|
||||||
oldRoot, newRoot *FileInfo
|
|
||||||
err1, err2 error
|
|
||||||
errs = make(chan error, 2)
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
oldRoot, err1 = collectFileInfo(oldDir)
|
|
||||||
errs <- err1
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
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, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return oldRoot, newRoot, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectFileInfo(sourceDir string) (*FileInfo, error) {
|
|
||||||
root := newRootFileInfo()
|
|
||||||
|
|
||||||
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebase path
|
|
||||||
relPath, err := filepath.Rel(sourceDir, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// As this runs on the daemon side, file paths are OS specific.
|
|
||||||
relPath = filepath.Join(string(os.PathSeparator), relPath)
|
|
||||||
|
|
||||||
// See https://github.com/golang/go/issues/9168 - bug in filepath.Join.
|
|
||||||
// Temporary workaround. If the returned path starts with two backslashes,
|
|
||||||
// trim it down to a single backslash. Only relevant on Windows.
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
if strings.HasPrefix(relPath, `\\`) {
|
|
||||||
relPath = relPath[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if relPath == string(os.PathSeparator) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parent := root.LookUp(filepath.Dir(relPath))
|
|
||||||
if parent == nil {
|
|
||||||
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
info := &FileInfo{
|
|
||||||
name: filepath.Base(relPath),
|
|
||||||
children: make(map[string]*FileInfo),
|
|
||||||
parent: parent,
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := system.Lstat(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
info.stat = s
|
|
||||||
|
|
||||||
info.capability, _ = system.Lgetxattr(path, "security.capability")
|
|
||||||
|
|
||||||
parent.children[info.name] = info
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return root, nil
|
|
||||||
}
|
|
||||||
127
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_posix_test.go
generated
vendored
127
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_posix_test.go
generated
vendored
@@ -1,127 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHardLinkOrder(t *testing.T) {
|
|
||||||
names := []string{"file1.txt", "file2.txt", "file3.txt"}
|
|
||||||
msg := []byte("Hey y'all")
|
|
||||||
|
|
||||||
// Create dir
|
|
||||||
src, err := ioutil.TempDir("", "docker-hardlink-test-src-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
//defer os.RemoveAll(src)
|
|
||||||
for _, name := range names {
|
|
||||||
func() {
|
|
||||||
fh, err := os.Create(path.Join(src, name))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer fh.Close()
|
|
||||||
if _, err = fh.Write(msg); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
// Create dest, with changes that includes hardlinks
|
|
||||||
dest, err := ioutil.TempDir("", "docker-hardlink-test-dest-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
os.RemoveAll(dest) // we just want the name, at first
|
|
||||||
if err := copyDir(src, dest); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dest)
|
|
||||||
for _, name := range names {
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
if err := os.Link(path.Join(dest, name), path.Join(dest, fmt.Sprintf("%s.link%d", name, i))); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get changes
|
|
||||||
changes, err := ChangesDirs(dest, src)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort
|
|
||||||
sort.Sort(changesByPath(changes))
|
|
||||||
|
|
||||||
// ExportChanges
|
|
||||||
ar, err := ExportChanges(dest, changes, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
hdrs, err := walkHeaders(ar)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// reverse sort
|
|
||||||
sort.Sort(sort.Reverse(changesByPath(changes)))
|
|
||||||
// ExportChanges
|
|
||||||
arRev, err := ExportChanges(dest, changes, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
hdrsRev, err := walkHeaders(arRev)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// line up the two sets
|
|
||||||
sort.Sort(tarHeaders(hdrs))
|
|
||||||
sort.Sort(tarHeaders(hdrsRev))
|
|
||||||
|
|
||||||
// compare Size and LinkName
|
|
||||||
for i := range hdrs {
|
|
||||||
if hdrs[i].Name != hdrsRev[i].Name {
|
|
||||||
t.Errorf("headers - expected name %q; but got %q", hdrs[i].Name, hdrsRev[i].Name)
|
|
||||||
}
|
|
||||||
if hdrs[i].Size != hdrsRev[i].Size {
|
|
||||||
t.Errorf("headers - %q expected size %d; but got %d", hdrs[i].Name, hdrs[i].Size, hdrsRev[i].Size)
|
|
||||||
}
|
|
||||||
if hdrs[i].Typeflag != hdrsRev[i].Typeflag {
|
|
||||||
t.Errorf("headers - %q expected type %d; but got %d", hdrs[i].Name, hdrs[i].Typeflag, hdrsRev[i].Typeflag)
|
|
||||||
}
|
|
||||||
if hdrs[i].Linkname != hdrsRev[i].Linkname {
|
|
||||||
t.Errorf("headers - %q expected linkname %q; but got %q", hdrs[i].Name, hdrs[i].Linkname, hdrsRev[i].Linkname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type tarHeaders []tar.Header
|
|
||||||
|
|
||||||
func (th tarHeaders) Len() int { return len(th) }
|
|
||||||
func (th tarHeaders) Swap(i, j int) { th[j], th[i] = th[i], th[j] }
|
|
||||||
func (th tarHeaders) Less(i, j int) bool { return th[i].Name < th[j].Name }
|
|
||||||
|
|
||||||
func walkHeaders(r io.Reader) ([]tar.Header, error) {
|
|
||||||
t := tar.NewReader(r)
|
|
||||||
headers := []tar.Header{}
|
|
||||||
for {
|
|
||||||
hdr, err := t.Next()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return headers, err
|
|
||||||
}
|
|
||||||
headers = append(headers, *hdr)
|
|
||||||
}
|
|
||||||
return headers, nil
|
|
||||||
}
|
|
||||||
527
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_test.go
generated
vendored
527
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_test.go
generated
vendored
@@ -1,527 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func max(x, y int) int {
|
|
||||||
if x >= y {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyDir(src, dst string) error {
|
|
||||||
cmd := exec.Command("cp", "-a", src, dst)
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileType uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
Regular FileType = iota
|
|
||||||
Dir
|
|
||||||
Symlink
|
|
||||||
)
|
|
||||||
|
|
||||||
type FileData struct {
|
|
||||||
filetype FileType
|
|
||||||
path string
|
|
||||||
contents string
|
|
||||||
permissions os.FileMode
|
|
||||||
}
|
|
||||||
|
|
||||||
func createSampleDir(t *testing.T, root string) {
|
|
||||||
files := []FileData{
|
|
||||||
{Regular, "file1", "file1\n", 0600},
|
|
||||||
{Regular, "file2", "file2\n", 0666},
|
|
||||||
{Regular, "file3", "file3\n", 0404},
|
|
||||||
{Regular, "file4", "file4\n", 0600},
|
|
||||||
{Regular, "file5", "file5\n", 0600},
|
|
||||||
{Regular, "file6", "file6\n", 0600},
|
|
||||||
{Regular, "file7", "file7\n", 0600},
|
|
||||||
{Dir, "dir1", "", 0740},
|
|
||||||
{Regular, "dir1/file1-1", "file1-1\n", 01444},
|
|
||||||
{Regular, "dir1/file1-2", "file1-2\n", 0666},
|
|
||||||
{Dir, "dir2", "", 0700},
|
|
||||||
{Regular, "dir2/file2-1", "file2-1\n", 0666},
|
|
||||||
{Regular, "dir2/file2-2", "file2-2\n", 0666},
|
|
||||||
{Dir, "dir3", "", 0700},
|
|
||||||
{Regular, "dir3/file3-1", "file3-1\n", 0666},
|
|
||||||
{Regular, "dir3/file3-2", "file3-2\n", 0666},
|
|
||||||
{Dir, "dir4", "", 0700},
|
|
||||||
{Regular, "dir4/file3-1", "file4-1\n", 0666},
|
|
||||||
{Regular, "dir4/file3-2", "file4-2\n", 0666},
|
|
||||||
{Symlink, "symlink1", "target1", 0666},
|
|
||||||
{Symlink, "symlink2", "target2", 0666},
|
|
||||||
{Symlink, "symlink3", root + "/file1", 0666},
|
|
||||||
{Symlink, "symlink4", root + "/symlink3", 0666},
|
|
||||||
{Symlink, "dirSymlink", root + "/dir1", 0740},
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
for _, info := range files {
|
|
||||||
p := path.Join(root, info.path)
|
|
||||||
if info.filetype == Dir {
|
|
||||||
if err := os.MkdirAll(p, info.permissions); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
} else if info.filetype == Regular {
|
|
||||||
if err := ioutil.WriteFile(p, []byte(info.contents), info.permissions); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
} else if info.filetype == Symlink {
|
|
||||||
if err := os.Symlink(info.contents, p); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.filetype != Symlink {
|
|
||||||
// Set a consistent ctime, atime for all files and dirs
|
|
||||||
if err := os.Chtimes(p, now, now); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangeString(t *testing.T) {
|
|
||||||
modifiyChange := Change{"change", ChangeModify}
|
|
||||||
toString := modifiyChange.String()
|
|
||||||
if toString != "C change" {
|
|
||||||
t.Fatalf("String() of a change with ChangeModifiy Kind should have been %s but was %s", "C change", toString)
|
|
||||||
}
|
|
||||||
addChange := Change{"change", ChangeAdd}
|
|
||||||
toString = addChange.String()
|
|
||||||
if toString != "A change" {
|
|
||||||
t.Fatalf("String() of a change with ChangeAdd Kind should have been %s but was %s", "A change", toString)
|
|
||||||
}
|
|
||||||
deleteChange := Change{"change", ChangeDelete}
|
|
||||||
toString = deleteChange.String()
|
|
||||||
if toString != "D change" {
|
|
||||||
t.Fatalf("String() of a change with ChangeDelete Kind should have been %s but was %s", "D change", toString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangesWithNoChanges(t *testing.T) {
|
|
||||||
rwLayer, err := ioutil.TempDir("", "docker-changes-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(rwLayer)
|
|
||||||
layer, err := ioutil.TempDir("", "docker-changes-test-layer")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(layer)
|
|
||||||
createSampleDir(t, layer)
|
|
||||||
changes, err := Changes([]string{layer}, rwLayer)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(changes) != 0 {
|
|
||||||
t.Fatalf("Changes with no difference should have detect no changes, but detected %d", len(changes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangesWithChanges(t *testing.T) {
|
|
||||||
// Mock the readonly layer
|
|
||||||
layer, err := ioutil.TempDir("", "docker-changes-test-layer")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(layer)
|
|
||||||
createSampleDir(t, layer)
|
|
||||||
os.MkdirAll(path.Join(layer, "dir1/subfolder"), 0740)
|
|
||||||
|
|
||||||
// Mock the RW layer
|
|
||||||
rwLayer, err := ioutil.TempDir("", "docker-changes-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(rwLayer)
|
|
||||||
|
|
||||||
// Create a folder in RW layer
|
|
||||||
dir1 := path.Join(rwLayer, "dir1")
|
|
||||||
os.MkdirAll(dir1, 0740)
|
|
||||||
deletedFile := path.Join(dir1, ".wh.file1-2")
|
|
||||||
ioutil.WriteFile(deletedFile, []byte{}, 0600)
|
|
||||||
modifiedFile := path.Join(dir1, "file1-1")
|
|
||||||
ioutil.WriteFile(modifiedFile, []byte{0x00}, 01444)
|
|
||||||
// Let's add a subfolder for a newFile
|
|
||||||
subfolder := path.Join(dir1, "subfolder")
|
|
||||||
os.MkdirAll(subfolder, 0740)
|
|
||||||
newFile := path.Join(subfolder, "newFile")
|
|
||||||
ioutil.WriteFile(newFile, []byte{}, 0740)
|
|
||||||
|
|
||||||
changes, err := Changes([]string{layer}, rwLayer)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedChanges := []Change{
|
|
||||||
{"/dir1", ChangeModify},
|
|
||||||
{"/dir1/file1-1", ChangeModify},
|
|
||||||
{"/dir1/file1-2", ChangeDelete},
|
|
||||||
{"/dir1/subfolder", ChangeModify},
|
|
||||||
{"/dir1/subfolder/newFile", ChangeAdd},
|
|
||||||
}
|
|
||||||
checkChanges(expectedChanges, changes, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://github.com/docker/docker/pull/13590
|
|
||||||
func TestChangesWithChangesGH13590(t *testing.T) {
|
|
||||||
baseLayer, err := ioutil.TempDir("", "docker-changes-test.")
|
|
||||||
defer os.RemoveAll(baseLayer)
|
|
||||||
|
|
||||||
dir3 := path.Join(baseLayer, "dir1/dir2/dir3")
|
|
||||||
os.MkdirAll(dir3, 07400)
|
|
||||||
|
|
||||||
file := path.Join(dir3, "file.txt")
|
|
||||||
ioutil.WriteFile(file, []byte("hello"), 0666)
|
|
||||||
|
|
||||||
layer, err := ioutil.TempDir("", "docker-changes-test2.")
|
|
||||||
defer os.RemoveAll(layer)
|
|
||||||
|
|
||||||
// Test creating a new file
|
|
||||||
if err := copyDir(baseLayer+"/dir1", layer+"/"); err != nil {
|
|
||||||
t.Fatalf("Cmd failed: %q", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Remove(path.Join(layer, "dir1/dir2/dir3/file.txt"))
|
|
||||||
file = path.Join(layer, "dir1/dir2/dir3/file1.txt")
|
|
||||||
ioutil.WriteFile(file, []byte("bye"), 0666)
|
|
||||||
|
|
||||||
changes, err := Changes([]string{baseLayer}, layer)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedChanges := []Change{
|
|
||||||
{"/dir1/dir2/dir3", ChangeModify},
|
|
||||||
{"/dir1/dir2/dir3/file1.txt", ChangeAdd},
|
|
||||||
}
|
|
||||||
checkChanges(expectedChanges, changes, t)
|
|
||||||
|
|
||||||
// Now test changing a file
|
|
||||||
layer, err = ioutil.TempDir("", "docker-changes-test3.")
|
|
||||||
defer os.RemoveAll(layer)
|
|
||||||
|
|
||||||
if err := copyDir(baseLayer+"/dir1", layer+"/"); err != nil {
|
|
||||||
t.Fatalf("Cmd failed: %q", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file = path.Join(layer, "dir1/dir2/dir3/file.txt")
|
|
||||||
ioutil.WriteFile(file, []byte("bye"), 0666)
|
|
||||||
|
|
||||||
changes, err = Changes([]string{baseLayer}, layer)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedChanges = []Change{
|
|
||||||
{"/dir1/dir2/dir3/file.txt", ChangeModify},
|
|
||||||
}
|
|
||||||
checkChanges(expectedChanges, changes, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an directory, copy it, make sure we report no changes between the two
|
|
||||||
func TestChangesDirsEmpty(t *testing.T) {
|
|
||||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(src)
|
|
||||||
createSampleDir(t, src)
|
|
||||||
dst := src + "-copy"
|
|
||||||
if err := copyDir(src, dst); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dst)
|
|
||||||
changes, err := ChangesDirs(dst, src)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(changes) != 0 {
|
|
||||||
t.Fatalf("Reported changes for identical dirs: %v", changes)
|
|
||||||
}
|
|
||||||
os.RemoveAll(src)
|
|
||||||
os.RemoveAll(dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mutateSampleDir(t *testing.T, root string) {
|
|
||||||
// Remove a regular file
|
|
||||||
if err := os.RemoveAll(path.Join(root, "file1")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a directory
|
|
||||||
if err := os.RemoveAll(path.Join(root, "dir1")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a symlink
|
|
||||||
if err := os.RemoveAll(path.Join(root, "symlink1")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rewrite a file
|
|
||||||
if err := ioutil.WriteFile(path.Join(root, "file2"), []byte("fileNN\n"), 0777); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace a file
|
|
||||||
if err := os.RemoveAll(path.Join(root, "file3")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(path.Join(root, "file3"), []byte("fileMM\n"), 0404); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Touch file
|
|
||||||
if err := os.Chtimes(path.Join(root, "file4"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace file with dir
|
|
||||||
if err := os.RemoveAll(path.Join(root, "file5")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(path.Join(root, "file5"), 0666); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new file
|
|
||||||
if err := ioutil.WriteFile(path.Join(root, "filenew"), []byte("filenew\n"), 0777); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new dir
|
|
||||||
if err := os.MkdirAll(path.Join(root, "dirnew"), 0766); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new symlink
|
|
||||||
if err := os.Symlink("targetnew", path.Join(root, "symlinknew")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change a symlink
|
|
||||||
if err := os.RemoveAll(path.Join(root, "symlink2")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.Symlink("target2change", path.Join(root, "symlink2")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace dir with file
|
|
||||||
if err := os.RemoveAll(path.Join(root, "dir2")); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(path.Join(root, "dir2"), []byte("dir2\n"), 0777); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Touch dir
|
|
||||||
if err := os.Chtimes(path.Join(root, "dir3"), time.Now().Add(time.Second), time.Now().Add(time.Second)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangesDirsMutated(t *testing.T) {
|
|
||||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
createSampleDir(t, src)
|
|
||||||
dst := src + "-copy"
|
|
||||||
if err := copyDir(src, dst); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(src)
|
|
||||||
defer os.RemoveAll(dst)
|
|
||||||
|
|
||||||
mutateSampleDir(t, dst)
|
|
||||||
|
|
||||||
changes, err := ChangesDirs(dst, src)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(changesByPath(changes))
|
|
||||||
|
|
||||||
expectedChanges := []Change{
|
|
||||||
{"/dir1", ChangeDelete},
|
|
||||||
{"/dir2", ChangeModify},
|
|
||||||
{"/dirnew", ChangeAdd},
|
|
||||||
{"/file1", ChangeDelete},
|
|
||||||
{"/file2", ChangeModify},
|
|
||||||
{"/file3", ChangeModify},
|
|
||||||
{"/file4", ChangeModify},
|
|
||||||
{"/file5", ChangeModify},
|
|
||||||
{"/filenew", ChangeAdd},
|
|
||||||
{"/symlink1", ChangeDelete},
|
|
||||||
{"/symlink2", ChangeModify},
|
|
||||||
{"/symlinknew", ChangeAdd},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
|
|
||||||
if i >= len(expectedChanges) {
|
|
||||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
|
||||||
}
|
|
||||||
if i >= len(changes) {
|
|
||||||
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
|
|
||||||
}
|
|
||||||
if changes[i].Path == expectedChanges[i].Path {
|
|
||||||
if changes[i] != expectedChanges[i] {
|
|
||||||
t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
|
|
||||||
}
|
|
||||||
} else if changes[i].Path < expectedChanges[i].Path {
|
|
||||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
|
||||||
} else {
|
|
||||||
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApplyLayer(t *testing.T) {
|
|
||||||
src, err := ioutil.TempDir("", "docker-changes-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
createSampleDir(t, src)
|
|
||||||
defer os.RemoveAll(src)
|
|
||||||
dst := src + "-copy"
|
|
||||||
if err := copyDir(src, dst); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
mutateSampleDir(t, dst)
|
|
||||||
defer os.RemoveAll(dst)
|
|
||||||
|
|
||||||
changes, err := ChangesDirs(dst, src)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
layer, err := ExportChanges(dst, changes, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
layerCopy, err := NewTempArchive(layer, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := ApplyLayer(src, layerCopy); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
changes2, err := ChangesDirs(src, dst)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(changes2) != 0 {
|
|
||||||
t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangesSizeWithHardlinks(t *testing.T) {
|
|
||||||
srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(srcDir)
|
|
||||||
|
|
||||||
destDir, err := ioutil.TempDir("", "docker-test-destDir")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(destDir)
|
|
||||||
|
|
||||||
creationSize, err := prepareUntarSourceDirectory(100, destDir, true)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
changes, err := ChangesDirs(destDir, srcDir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := ChangesSize(destDir, changes)
|
|
||||||
if got != int64(creationSize) {
|
|
||||||
t.Errorf("Expected %d bytes of changes, got %d", creationSize, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangesSizeWithNoChanges(t *testing.T) {
|
|
||||||
size := ChangesSize("/tmp", nil)
|
|
||||||
if size != 0 {
|
|
||||||
t.Fatalf("ChangesSizes with no changes should be 0, was %d", size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangesSizeWithOnlyDeleteChanges(t *testing.T) {
|
|
||||||
changes := []Change{
|
|
||||||
{Path: "deletedPath", Kind: ChangeDelete},
|
|
||||||
}
|
|
||||||
size := ChangesSize("/tmp", changes)
|
|
||||||
if size != 0 {
|
|
||||||
t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangesSize(t *testing.T) {
|
|
||||||
parentPath, err := ioutil.TempDir("", "docker-changes-test")
|
|
||||||
defer os.RemoveAll(parentPath)
|
|
||||||
addition := path.Join(parentPath, "addition")
|
|
||||||
if err := ioutil.WriteFile(addition, []byte{0x01, 0x01, 0x01}, 0744); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
modification := path.Join(parentPath, "modification")
|
|
||||||
if err = ioutil.WriteFile(modification, []byte{0x01, 0x01, 0x01}, 0744); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
changes := []Change{
|
|
||||||
{Path: "addition", Kind: ChangeAdd},
|
|
||||||
{Path: "modification", Kind: ChangeModify},
|
|
||||||
}
|
|
||||||
size := ChangesSize(parentPath, changes)
|
|
||||||
if size != 6 {
|
|
||||||
t.Fatalf("Expected 6 bytes of changes, got %d", size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkChanges(expectedChanges, changes []Change, t *testing.T) {
|
|
||||||
sort.Sort(changesByPath(expectedChanges))
|
|
||||||
sort.Sort(changesByPath(changes))
|
|
||||||
for i := 0; i < max(len(changes), len(expectedChanges)); i++ {
|
|
||||||
if i >= len(expectedChanges) {
|
|
||||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
|
||||||
}
|
|
||||||
if i >= len(changes) {
|
|
||||||
t.Fatalf("no change for expected change %s\n", expectedChanges[i].String())
|
|
||||||
}
|
|
||||||
if changes[i].Path == expectedChanges[i].Path {
|
|
||||||
if changes[i] != expectedChanges[i] {
|
|
||||||
t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String())
|
|
||||||
}
|
|
||||||
} else if changes[i].Path < expectedChanges[i].Path {
|
|
||||||
t.Fatalf("unexpected change %s\n", changes[i].String())
|
|
||||||
} else {
|
|
||||||
t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
36
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_unix.go
generated
vendored
36
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_unix.go
generated
vendored
@@ -1,36 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool {
|
|
||||||
// Don't look at size for dirs, its not a good measure of change
|
|
||||||
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.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
|
|
||||||
(!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *FileInfo) isDir() bool {
|
|
||||||
return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIno(fi os.FileInfo) uint64 {
|
|
||||||
return uint64(fi.Sys().(*syscall.Stat_t).Ino)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasHardlinks(fi os.FileInfo) bool {
|
|
||||||
return fi.Sys().(*syscall.Stat_t).Nlink > 1
|
|
||||||
}
|
|
||||||
30
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_windows.go
generated
vendored
30
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/changes_windows.go
generated
vendored
@@ -1,30 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
func statDifferent(oldStat *system.StatT, newStat *system.StatT) bool {
|
|
||||||
|
|
||||||
// Don't look at size for dirs, its not a good measure of change
|
|
||||||
if oldStat.ModTime() != newStat.ModTime() ||
|
|
||||||
oldStat.Mode() != newStat.Mode() ||
|
|
||||||
oldStat.Size() != newStat.Size() && !oldStat.IsDir() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (info *FileInfo) isDir() bool {
|
|
||||||
return info.parent == nil || info.stat.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
func getIno(fi os.FileInfo) (inode uint64) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasHardlinks(fi os.FileInfo) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
458
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/copy.go
generated
vendored
458
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/copy.go
generated
vendored
@@ -1,458 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Errors used or returned by this file.
|
|
||||||
var (
|
|
||||||
ErrNotDirectory = errors.New("not a directory")
|
|
||||||
ErrDirNotExists = errors.New("no such directory")
|
|
||||||
ErrCannotCopyDir = errors.New("cannot copy directory")
|
|
||||||
ErrInvalidCopySource = errors.New("invalid copy source content")
|
|
||||||
)
|
|
||||||
|
|
||||||
// PreserveTrailingDotOrSeparator returns the given cleaned path (after
|
|
||||||
// processing using any utility functions from the path or filepath stdlib
|
|
||||||
// packages) and appends a trailing `/.` or `/` if its corresponding original
|
|
||||||
// path (from before being processed by utility functions from the path or
|
|
||||||
// filepath stdlib packages) ends with a trailing `/.` or `/`. If the cleaned
|
|
||||||
// path already ends in a `.` path segment, then another is not added. If the
|
|
||||||
// clean path already ends in a path separator, then another is not added.
|
|
||||||
func PreserveTrailingDotOrSeparator(cleanedPath, originalPath string) string {
|
|
||||||
// Ensure paths are in platform semantics
|
|
||||||
cleanedPath = normalizePath(cleanedPath)
|
|
||||||
originalPath = normalizePath(originalPath)
|
|
||||||
|
|
||||||
if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) {
|
|
||||||
if !hasTrailingPathSeparator(cleanedPath) {
|
|
||||||
// Add a separator if it doesn't already end with one (a cleaned
|
|
||||||
// path would only end in a separator if it is the root).
|
|
||||||
cleanedPath += string(filepath.Separator)
|
|
||||||
}
|
|
||||||
cleanedPath += "."
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hasTrailingPathSeparator(cleanedPath) && hasTrailingPathSeparator(originalPath) {
|
|
||||||
cleanedPath += string(filepath.Separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleanedPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// assertsDirectory returns whether the given path is
|
|
||||||
// asserted to be a directory, i.e., the path ends with
|
|
||||||
// a trailing '/' or `/.`, assuming a path separator of `/`.
|
|
||||||
func assertsDirectory(path string) bool {
|
|
||||||
return hasTrailingPathSeparator(path) || specifiesCurrentDir(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasTrailingPathSeparator returns whether the given
|
|
||||||
// path ends with the system's path separator character.
|
|
||||||
func hasTrailingPathSeparator(path string) bool {
|
|
||||||
return len(path) > 0 && os.IsPathSeparator(path[len(path)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// specifiesCurrentDir returns whether the given path specifies
|
|
||||||
// a "current directory", i.e., the last path segment is `.`.
|
|
||||||
func specifiesCurrentDir(path string) bool {
|
|
||||||
return filepath.Base(path) == "."
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitPathDirEntry splits the given path between its directory name and its
|
|
||||||
// basename by first cleaning the path but preserves a trailing "." if the
|
|
||||||
// original path specified the current directory.
|
|
||||||
func SplitPathDirEntry(path string) (dir, base string) {
|
|
||||||
cleanedPath := filepath.Clean(normalizePath(path))
|
|
||||||
|
|
||||||
if specifiesCurrentDir(path) {
|
|
||||||
cleanedPath += string(filepath.Separator) + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Dir(cleanedPath), filepath.Base(cleanedPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TarResource archives the resource described by the given CopyInfo to a Tar
|
|
||||||
// archive. A non-nil error is returned if sourcePath does not exist or is
|
|
||||||
// asserted to be a directory but exists as another type of file.
|
|
||||||
//
|
|
||||||
// This function acts as a convenient wrapper around TarWithOptions, which
|
|
||||||
// requires a directory as the source path. TarResource accepts either a
|
|
||||||
// directory or a file path and correctly sets the Tar options.
|
|
||||||
func TarResource(sourceInfo CopyInfo) (content Archive, err error) {
|
|
||||||
return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TarResourceRebase is like TarResource but renames the first path element of
|
|
||||||
// items in the resulting tar archive to match the given rebaseName if not "".
|
|
||||||
func TarResourceRebase(sourcePath, rebaseName string) (content Archive, err error) {
|
|
||||||
sourcePath = normalizePath(sourcePath)
|
|
||||||
if _, err = os.Lstat(sourcePath); err != nil {
|
|
||||||
// Catches the case where the source does not exist or is not a
|
|
||||||
// directory if asserted to be a directory, as this also causes an
|
|
||||||
// error.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Separate the source path between it's directory and
|
|
||||||
// the entry in that directory which we are archiving.
|
|
||||||
sourceDir, sourceBase := SplitPathDirEntry(sourcePath)
|
|
||||||
|
|
||||||
filter := []string{sourceBase}
|
|
||||||
|
|
||||||
logrus.Debugf("copying %q from %q", sourceBase, sourceDir)
|
|
||||||
|
|
||||||
return TarWithOptions(sourceDir, &TarOptions{
|
|
||||||
Compression: Uncompressed,
|
|
||||||
IncludeFiles: filter,
|
|
||||||
IncludeSourceDir: true,
|
|
||||||
RebaseNames: map[string]string{
|
|
||||||
sourceBase: rebaseName,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyInfo holds basic info about the source
|
|
||||||
// or destination path of a copy operation.
|
|
||||||
type CopyInfo struct {
|
|
||||||
Path string
|
|
||||||
Exists bool
|
|
||||||
IsDir bool
|
|
||||||
RebaseName string
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyInfoSourcePath stats the given path to create a CopyInfo
|
|
||||||
// struct representing that resource for the source of an archive copy
|
|
||||||
// operation. The given path should be an absolute local path. A source path
|
|
||||||
// has all symlinks evaluated that appear before the last path separator ("/"
|
|
||||||
// on Unix). As it is to be a copy source, the path must exist.
|
|
||||||
func CopyInfoSourcePath(path string, followLink bool) (CopyInfo, error) {
|
|
||||||
// normalize the file path and then evaluate the symbol link
|
|
||||||
// we will use the target file instead of the symbol link if
|
|
||||||
// followLink is set
|
|
||||||
path = normalizePath(path)
|
|
||||||
|
|
||||||
resolvedPath, rebaseName, err := ResolveHostSourcePath(path, followLink)
|
|
||||||
if err != nil {
|
|
||||||
return CopyInfo{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, err := os.Lstat(resolvedPath)
|
|
||||||
if err != nil {
|
|
||||||
return CopyInfo{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return CopyInfo{
|
|
||||||
Path: resolvedPath,
|
|
||||||
Exists: true,
|
|
||||||
IsDir: stat.IsDir(),
|
|
||||||
RebaseName: rebaseName,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyInfoDestinationPath stats the given path to create a CopyInfo
|
|
||||||
// struct representing that resource for the destination of an archive copy
|
|
||||||
// operation. The given path should be an absolute local path.
|
|
||||||
func CopyInfoDestinationPath(path string) (info CopyInfo, err error) {
|
|
||||||
maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot.
|
|
||||||
path = normalizePath(path)
|
|
||||||
originalPath := path
|
|
||||||
|
|
||||||
stat, err := os.Lstat(path)
|
|
||||||
|
|
||||||
if err == nil && stat.Mode()&os.ModeSymlink == 0 {
|
|
||||||
// The path exists and is not a symlink.
|
|
||||||
return CopyInfo{
|
|
||||||
Path: path,
|
|
||||||
Exists: true,
|
|
||||||
IsDir: stat.IsDir(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// While the path is a symlink.
|
|
||||||
for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ {
|
|
||||||
if n > maxSymlinkIter {
|
|
||||||
// Don't follow symlinks more than this arbitrary number of times.
|
|
||||||
return CopyInfo{}, errors.New("too many symlinks in " + originalPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The path is a symbolic link. We need to evaluate it so that the
|
|
||||||
// destination of the copy operation is the link target and not the
|
|
||||||
// link itself. This is notably different than CopyInfoSourcePath which
|
|
||||||
// only evaluates symlinks before the last appearing path separator.
|
|
||||||
// Also note that it is okay if the last path element is a broken
|
|
||||||
// symlink as the copy operation should create the target.
|
|
||||||
var linkTarget string
|
|
||||||
|
|
||||||
linkTarget, err = os.Readlink(path)
|
|
||||||
if err != nil {
|
|
||||||
return CopyInfo{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !system.IsAbs(linkTarget) {
|
|
||||||
// Join with the parent directory.
|
|
||||||
dstParent, _ := SplitPathDirEntry(path)
|
|
||||||
linkTarget = filepath.Join(dstParent, linkTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
path = linkTarget
|
|
||||||
stat, err = os.Lstat(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// It's okay if the destination path doesn't exist. We can still
|
|
||||||
// continue the copy operation if the parent directory exists.
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return CopyInfo{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure destination parent dir exists.
|
|
||||||
dstParent, _ := SplitPathDirEntry(path)
|
|
||||||
|
|
||||||
parentDirStat, err := os.Lstat(dstParent)
|
|
||||||
if err != nil {
|
|
||||||
return CopyInfo{}, err
|
|
||||||
}
|
|
||||||
if !parentDirStat.IsDir() {
|
|
||||||
return CopyInfo{}, ErrNotDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
return CopyInfo{Path: path}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The path exists after resolving symlinks.
|
|
||||||
return CopyInfo{
|
|
||||||
Path: path,
|
|
||||||
Exists: true,
|
|
||||||
IsDir: stat.IsDir(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrepareArchiveCopy prepares the given srcContent archive, which should
|
|
||||||
// contain the archived resource described by srcInfo, to the destination
|
|
||||||
// described by dstInfo. Returns the possibly modified content archive along
|
|
||||||
// with the path to the destination directory which it should be extracted to.
|
|
||||||
func PrepareArchiveCopy(srcContent Reader, srcInfo, dstInfo CopyInfo) (dstDir string, content Archive, err error) {
|
|
||||||
// Ensure in platform semantics
|
|
||||||
srcInfo.Path = normalizePath(srcInfo.Path)
|
|
||||||
dstInfo.Path = normalizePath(dstInfo.Path)
|
|
||||||
|
|
||||||
// Separate the destination path between its directory and base
|
|
||||||
// components in case the source archive contents need to be rebased.
|
|
||||||
dstDir, dstBase := SplitPathDirEntry(dstInfo.Path)
|
|
||||||
_, srcBase := SplitPathDirEntry(srcInfo.Path)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case dstInfo.Exists && dstInfo.IsDir:
|
|
||||||
// The destination exists as a directory. No alteration
|
|
||||||
// to srcContent is needed as its contents can be
|
|
||||||
// simply extracted to the destination directory.
|
|
||||||
return dstInfo.Path, ioutil.NopCloser(srcContent), nil
|
|
||||||
case dstInfo.Exists && srcInfo.IsDir:
|
|
||||||
// The destination exists as some type of file and the source
|
|
||||||
// content is a directory. This is an error condition since
|
|
||||||
// you cannot copy a directory to an existing file location.
|
|
||||||
return "", nil, ErrCannotCopyDir
|
|
||||||
case dstInfo.Exists:
|
|
||||||
// The destination exists as some type of file and the source content
|
|
||||||
// is also a file. The source content entry will have to be renamed to
|
|
||||||
// have a basename which matches the destination path's basename.
|
|
||||||
if len(srcInfo.RebaseName) != 0 {
|
|
||||||
srcBase = srcInfo.RebaseName
|
|
||||||
}
|
|
||||||
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
|
||||||
case srcInfo.IsDir:
|
|
||||||
// The destination does not exist and the source content is an archive
|
|
||||||
// of a directory. The archive should be extracted to the parent of
|
|
||||||
// the destination path instead, and when it is, the directory that is
|
|
||||||
// created as a result should take the name of the destination path.
|
|
||||||
// The source content entries will have to be renamed to have a
|
|
||||||
// basename which matches the destination path's basename.
|
|
||||||
if len(srcInfo.RebaseName) != 0 {
|
|
||||||
srcBase = srcInfo.RebaseName
|
|
||||||
}
|
|
||||||
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
|
||||||
case assertsDirectory(dstInfo.Path):
|
|
||||||
// The destination does not exist and is asserted to be created as a
|
|
||||||
// directory, but the source content is not a directory. This is an
|
|
||||||
// error condition since you cannot create a directory from a file
|
|
||||||
// source.
|
|
||||||
return "", nil, ErrDirNotExists
|
|
||||||
default:
|
|
||||||
// The last remaining case is when the destination does not exist, is
|
|
||||||
// not asserted to be a directory, and the source content is not an
|
|
||||||
// archive of a directory. It this case, the destination file will need
|
|
||||||
// to be created when the archive is extracted and the source content
|
|
||||||
// entry will have to be renamed to have a basename which matches the
|
|
||||||
// destination path's basename.
|
|
||||||
if len(srcInfo.RebaseName) != 0 {
|
|
||||||
srcBase = srcInfo.RebaseName
|
|
||||||
}
|
|
||||||
return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// RebaseArchiveEntries rewrites the given srcContent archive replacing
|
|
||||||
// an occurrence of oldBase with newBase at the beginning of entry names.
|
|
||||||
func RebaseArchiveEntries(srcContent Reader, oldBase, newBase string) Archive {
|
|
||||||
if oldBase == string(os.PathSeparator) {
|
|
||||||
// If oldBase specifies the root directory, use an empty string as
|
|
||||||
// oldBase instead so that newBase doesn't replace the path separator
|
|
||||||
// that all paths will start with.
|
|
||||||
oldBase = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
rebased, w := io.Pipe()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
srcTar := tar.NewReader(srcContent)
|
|
||||||
rebasedTar := tar.NewWriter(w)
|
|
||||||
|
|
||||||
for {
|
|
||||||
hdr, err := srcTar.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
// Signals end of archive.
|
|
||||||
rebasedTar.Close()
|
|
||||||
w.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hdr.Name = strings.Replace(hdr.Name, oldBase, newBase, 1)
|
|
||||||
|
|
||||||
if err = rebasedTar.WriteHeader(hdr); err != nil {
|
|
||||||
w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = io.Copy(rebasedTar, srcTar); err != nil {
|
|
||||||
w.CloseWithError(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return rebased
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyResource performs an archive copy from the given source path to the
|
|
||||||
// given destination path. The source path MUST exist and the destination
|
|
||||||
// path's parent directory must exist.
|
|
||||||
func CopyResource(srcPath, dstPath string, followLink bool) error {
|
|
||||||
var (
|
|
||||||
srcInfo CopyInfo
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure in platform semantics
|
|
||||||
srcPath = normalizePath(srcPath)
|
|
||||||
dstPath = normalizePath(dstPath)
|
|
||||||
|
|
||||||
// Clean the source and destination paths.
|
|
||||||
srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath)
|
|
||||||
dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath)
|
|
||||||
|
|
||||||
if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
content, err := TarResource(srcInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer content.Close()
|
|
||||||
|
|
||||||
return CopyTo(content, srcInfo, dstPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyTo handles extracting the given content whose
|
|
||||||
// entries should be sourced from srcInfo to dstPath.
|
|
||||||
func CopyTo(content Reader, srcInfo CopyInfo, dstPath string) error {
|
|
||||||
// The destination path need not exist, but CopyInfoDestinationPath will
|
|
||||||
// ensure that at least the parent directory exists.
|
|
||||||
dstInfo, err := CopyInfoDestinationPath(normalizePath(dstPath))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer copyArchive.Close()
|
|
||||||
|
|
||||||
options := &TarOptions{
|
|
||||||
NoLchown: true,
|
|
||||||
NoOverwriteDirNonDir: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
return Untar(copyArchive, dstDir, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveHostSourcePath decides real path need to be copied with parameters such as
|
|
||||||
// whether to follow symbol link or not, if followLink is true, resolvedPath will return
|
|
||||||
// link target of any symbol link file, else it will only resolve symlink of directory
|
|
||||||
// but return symbol link file itself without resolving.
|
|
||||||
func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, err error) {
|
|
||||||
if followLink {
|
|
||||||
resolvedPath, err = filepath.EvalSymlinks(path)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resolvedPath, rebaseName = GetRebaseName(path, resolvedPath)
|
|
||||||
} else {
|
|
||||||
dirPath, basePath := filepath.Split(path)
|
|
||||||
|
|
||||||
// if not follow symbol link, then resolve symbol link of parent dir
|
|
||||||
var resolvedDirPath string
|
|
||||||
resolvedDirPath, err = filepath.EvalSymlinks(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// resolvedDirPath will have been cleaned (no trailing path separators) so
|
|
||||||
// we can manually join it with the base path element.
|
|
||||||
resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
|
|
||||||
if hasTrailingPathSeparator(path) && filepath.Base(path) != filepath.Base(resolvedPath) {
|
|
||||||
rebaseName = filepath.Base(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resolvedPath, rebaseName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRebaseName normalizes and compares path and resolvedPath,
|
|
||||||
// return completed resolved path and rebased file name
|
|
||||||
func GetRebaseName(path, resolvedPath string) (string, string) {
|
|
||||||
// linkTarget will have been cleaned (no trailing path separators and dot) so
|
|
||||||
// we can manually join it with them
|
|
||||||
var rebaseName string
|
|
||||||
if specifiesCurrentDir(path) && !specifiesCurrentDir(resolvedPath) {
|
|
||||||
resolvedPath += string(filepath.Separator) + "."
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasTrailingPathSeparator(path) && !hasTrailingPathSeparator(resolvedPath) {
|
|
||||||
resolvedPath += string(filepath.Separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
if filepath.Base(path) != filepath.Base(resolvedPath) {
|
|
||||||
// In the case where the path had a trailing separator and a symlink
|
|
||||||
// evaluation has changed the last path component, we will need to
|
|
||||||
// rebase the name in the archive that is being copied to match the
|
|
||||||
// originally requested name.
|
|
||||||
rebaseName = filepath.Base(path)
|
|
||||||
}
|
|
||||||
return resolvedPath, rebaseName
|
|
||||||
}
|
|
||||||
974
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/copy_test.go
generated
vendored
974
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/copy_test.go
generated
vendored
@@ -1,974 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func removeAllPaths(paths ...string) {
|
|
||||||
for _, path := range paths {
|
|
||||||
os.RemoveAll(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTestTempDirs(t *testing.T) (tmpDirA, tmpDirB string) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if tmpDirA, err = ioutil.TempDir("", "archive-copy-test"); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpDirB, err = ioutil.TempDir("", "archive-copy-test"); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNotDir(err error) bool {
|
|
||||||
return strings.Contains(err.Error(), "not a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinTrailingSep(pathElements ...string) string {
|
|
||||||
joined := filepath.Join(pathElements...)
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s%c", joined, filepath.Separator)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileContentsEqual(t *testing.T, filenameA, filenameB string) (err error) {
|
|
||||||
t.Logf("checking for equal file contents: %q and %q\n", filenameA, filenameB)
|
|
||||||
|
|
||||||
fileA, err := os.Open(filenameA)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer fileA.Close()
|
|
||||||
|
|
||||||
fileB, err := os.Open(filenameB)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer fileB.Close()
|
|
||||||
|
|
||||||
hasher := sha256.New()
|
|
||||||
|
|
||||||
if _, err = io.Copy(hasher, fileA); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hashA := hasher.Sum(nil)
|
|
||||||
hasher.Reset()
|
|
||||||
|
|
||||||
if _, err = io.Copy(hasher, fileB); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
hashB := hasher.Sum(nil)
|
|
||||||
|
|
||||||
if !bytes.Equal(hashA, hashB) {
|
|
||||||
err = fmt.Errorf("file content hashes not equal - expected %s, got %s", hex.EncodeToString(hashA), hex.EncodeToString(hashB))
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func dirContentsEqual(t *testing.T, newDir, oldDir string) (err error) {
|
|
||||||
t.Logf("checking for equal directory contents: %q and %q\n", newDir, oldDir)
|
|
||||||
|
|
||||||
var changes []Change
|
|
||||||
|
|
||||||
if changes, err = ChangesDirs(newDir, oldDir); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(changes) != 0 {
|
|
||||||
err = fmt.Errorf("expected no changes between directories, but got: %v", changes)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func logDirContents(t *testing.T, dirPath string) {
|
|
||||||
logWalkedPaths := filepath.WalkFunc(func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("stat error for path %q: %s", path, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsDir() {
|
|
||||||
path = joinTrailingSep(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("\t%s", path)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Logf("logging directory contents: %q", dirPath)
|
|
||||||
|
|
||||||
if err := filepath.Walk(dirPath, logWalkedPaths); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCopyHelper(t *testing.T, srcPath, dstPath string) (err error) {
|
|
||||||
t.Logf("copying from %q to %q (not follow symbol link)", srcPath, dstPath)
|
|
||||||
|
|
||||||
return CopyResource(srcPath, dstPath, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCopyHelperFSym(t *testing.T, srcPath, dstPath string) (err error) {
|
|
||||||
t.Logf("copying from %q to %q (follow symbol link)", srcPath, dstPath)
|
|
||||||
|
|
||||||
return CopyResource(srcPath, dstPath, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic assumptions about SRC and DST:
|
|
||||||
// 1. SRC must exist.
|
|
||||||
// 2. If SRC ends with a trailing separator, it must be a directory.
|
|
||||||
// 3. DST parent directory must exist.
|
|
||||||
// 4. If DST exists as a file, it must not end with a trailing separator.
|
|
||||||
|
|
||||||
// First get these easy error cases out of the way.
|
|
||||||
|
|
||||||
// Test for error when SRC does not exist.
|
|
||||||
func TestCopyErrSrcNotExists(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
if _, err := CopyInfoSourcePath(filepath.Join(tmpDirA, "file1"), false); !os.IsNotExist(err) {
|
|
||||||
t.Fatalf("expected IsNotExist error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for error when SRC ends in a trailing
|
|
||||||
// path separator but it exists as a file.
|
|
||||||
func TestCopyErrSrcNotDir(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
|
|
||||||
if _, err := CopyInfoSourcePath(joinTrailingSep(tmpDirA, "file1"), false); !isNotDir(err) {
|
|
||||||
t.Fatalf("expected IsNotDir error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for error when SRC is a valid file or directory,
|
|
||||||
// but the DST parent directory does not exist.
|
|
||||||
func TestCopyErrDstParentNotExists(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
|
|
||||||
srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false}
|
|
||||||
|
|
||||||
// Try with a file source.
|
|
||||||
content, err := TarResource(srcInfo)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
defer content.Close()
|
|
||||||
|
|
||||||
// Copy to a file whose parent does not exist.
|
|
||||||
if err = CopyTo(content, srcInfo, filepath.Join(tmpDirB, "fakeParentDir", "file1")); err == nil {
|
|
||||||
t.Fatal("expected IsNotExist error, but got nil instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
t.Fatalf("expected IsNotExist error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try with a directory source.
|
|
||||||
srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true}
|
|
||||||
|
|
||||||
content, err = TarResource(srcInfo)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
defer content.Close()
|
|
||||||
|
|
||||||
// Copy to a directory whose parent does not exist.
|
|
||||||
if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "fakeParentDir", "fakeDstDir")); err == nil {
|
|
||||||
t.Fatal("expected IsNotExist error, but got nil instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
t.Fatalf("expected IsNotExist error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test for error when DST ends in a trailing
|
|
||||||
// path separator but exists as a file.
|
|
||||||
func TestCopyErrDstNotDir(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
// Try with a file source.
|
|
||||||
srcInfo := CopyInfo{Path: filepath.Join(tmpDirA, "file1"), Exists: true, IsDir: false}
|
|
||||||
|
|
||||||
content, err := TarResource(srcInfo)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
defer content.Close()
|
|
||||||
|
|
||||||
if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "file1")); err == nil {
|
|
||||||
t.Fatal("expected IsNotDir error, but got nil instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isNotDir(err) {
|
|
||||||
t.Fatalf("expected IsNotDir error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try with a directory source.
|
|
||||||
srcInfo = CopyInfo{Path: filepath.Join(tmpDirA, "dir1"), Exists: true, IsDir: true}
|
|
||||||
|
|
||||||
content, err = TarResource(srcInfo)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
defer content.Close()
|
|
||||||
|
|
||||||
if err = CopyTo(content, srcInfo, joinTrailingSep(tmpDirB, "file1")); err == nil {
|
|
||||||
t.Fatal("expected IsNotDir error, but got nil instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isNotDir(err) {
|
|
||||||
t.Fatalf("expected IsNotDir error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Possibilities are reduced to the remaining 10 cases:
|
|
||||||
//
|
|
||||||
// case | srcIsDir | onlyDirContents | dstExists | dstIsDir | dstTrSep | action
|
|
||||||
// ===================================================================================================
|
|
||||||
// A | no | - | no | - | no | create file
|
|
||||||
// B | no | - | no | - | yes | error
|
|
||||||
// C | no | - | yes | no | - | overwrite file
|
|
||||||
// D | no | - | yes | yes | - | create file in dst dir
|
|
||||||
// E | yes | no | no | - | - | create dir, copy contents
|
|
||||||
// F | yes | no | yes | no | - | error
|
|
||||||
// G | yes | no | yes | yes | - | copy dir and contents
|
|
||||||
// H | yes | yes | no | - | - | create dir, copy contents
|
|
||||||
// I | yes | yes | yes | no | - | error
|
|
||||||
// J | yes | yes | yes | yes | - | copy dir contents
|
|
||||||
//
|
|
||||||
|
|
||||||
// A. SRC specifies a file and DST (no trailing path separator) doesn't
|
|
||||||
// exist. This should create a file with the name DST and copy the
|
|
||||||
// contents of the source file into it.
|
|
||||||
func TestCopyCaseA(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
|
|
||||||
srcPath := filepath.Join(tmpDirA, "file1")
|
|
||||||
dstPath := filepath.Join(tmpDirB, "itWorks.txt")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcPath, dstPath); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
os.Remove(dstPath)
|
|
||||||
|
|
||||||
symlinkPath := filepath.Join(tmpDirA, "symlink3")
|
|
||||||
symlinkPath1 := filepath.Join(tmpDirA, "symlink4")
|
|
||||||
linkTarget := filepath.Join(tmpDirA, "file1")
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, symlinkPath, dstPath); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
os.Remove(dstPath)
|
|
||||||
if err = testCopyHelperFSym(t, symlinkPath1, dstPath); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// B. SRC specifies a file and DST (with trailing path separator) doesn't
|
|
||||||
// exist. This should cause an error because the copy operation cannot
|
|
||||||
// create a directory when copying a single file.
|
|
||||||
func TestCopyCaseB(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
|
|
||||||
srcPath := filepath.Join(tmpDirA, "file1")
|
|
||||||
dstDir := joinTrailingSep(tmpDirB, "testDir")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcPath, dstDir); err == nil {
|
|
||||||
t.Fatal("expected ErrDirNotExists error, but got nil instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != ErrDirNotExists {
|
|
||||||
t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
symlinkPath := filepath.Join(tmpDirA, "symlink3")
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, symlinkPath, dstDir); err == nil {
|
|
||||||
t.Fatal("expected ErrDirNotExists error, but got nil instead")
|
|
||||||
}
|
|
||||||
if err != ErrDirNotExists {
|
|
||||||
t.Fatalf("expected ErrDirNotExists error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// C. SRC specifies a file and DST exists as a file. This should overwrite
|
|
||||||
// the file at DST with the contents of the source file.
|
|
||||||
func TestCopyCaseC(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
srcPath := filepath.Join(tmpDirA, "file1")
|
|
||||||
dstPath := filepath.Join(tmpDirB, "file2")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Ensure they start out different.
|
|
||||||
if err = fileContentsEqual(t, srcPath, dstPath); err == nil {
|
|
||||||
t.Fatal("expected different file contents")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcPath, dstPath); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// C. Symbol link following version:
|
|
||||||
// SRC specifies a file and DST exists as a file. This should overwrite
|
|
||||||
// the file at DST with the contents of the source file.
|
|
||||||
func TestCopyCaseCFSym(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
symlinkPathBad := filepath.Join(tmpDirA, "symlink1")
|
|
||||||
symlinkPath := filepath.Join(tmpDirA, "symlink3")
|
|
||||||
linkTarget := filepath.Join(tmpDirA, "file1")
|
|
||||||
dstPath := filepath.Join(tmpDirB, "file2")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// first to test broken link
|
|
||||||
if err = testCopyHelperFSym(t, symlinkPathBad, dstPath); err == nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test symbol link -> symbol link -> target
|
|
||||||
// Ensure they start out different.
|
|
||||||
if err = fileContentsEqual(t, linkTarget, dstPath); err == nil {
|
|
||||||
t.Fatal("expected different file contents")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, symlinkPath, dstPath); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// D. SRC specifies a file and DST exists as a directory. This should place
|
|
||||||
// a copy of the source file inside it using the basename from SRC. Ensure
|
|
||||||
// this works whether DST has a trailing path separator or not.
|
|
||||||
func TestCopyCaseD(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
srcPath := filepath.Join(tmpDirA, "file1")
|
|
||||||
dstDir := filepath.Join(tmpDirB, "dir1")
|
|
||||||
dstPath := filepath.Join(dstDir, "file1")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Ensure that dstPath doesn't exist.
|
|
||||||
if _, err = os.Stat(dstPath); !os.IsNotExist(err) {
|
|
||||||
t.Fatalf("did not expect dstPath %q to exist", dstPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcPath, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
|
||||||
t.Fatalf("unable to make dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "dir1")
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcPath, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fileContentsEqual(t, srcPath, dstPath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// D. Symbol link following version:
|
|
||||||
// SRC specifies a file and DST exists as a directory. This should place
|
|
||||||
// a copy of the source file inside it using the basename from SRC. Ensure
|
|
||||||
// this works whether DST has a trailing path separator or not.
|
|
||||||
func TestCopyCaseDFSym(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
srcPath := filepath.Join(tmpDirA, "symlink4")
|
|
||||||
linkTarget := filepath.Join(tmpDirA, "file1")
|
|
||||||
dstDir := filepath.Join(tmpDirB, "dir1")
|
|
||||||
dstPath := filepath.Join(dstDir, "symlink4")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Ensure that dstPath doesn't exist.
|
|
||||||
if _, err = os.Stat(dstPath); !os.IsNotExist(err) {
|
|
||||||
t.Fatalf("did not expect dstPath %q to exist", dstPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcPath, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
|
||||||
t.Fatalf("unable to make dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "dir1")
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcPath, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = fileContentsEqual(t, linkTarget, dstPath); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// E. SRC specifies a directory and DST does not exist. This should create a
|
|
||||||
// directory at DST and copy the contents of the SRC directory into the DST
|
|
||||||
// directory. Ensure this works whether DST has a trailing path separator or
|
|
||||||
// not.
|
|
||||||
func TestCopyCaseE(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
|
|
||||||
srcDir := filepath.Join(tmpDirA, "dir1")
|
|
||||||
dstDir := filepath.Join(tmpDirB, "testDir")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
|
|
||||||
t.Log("dir contents not equal")
|
|
||||||
logDirContents(t, tmpDirA)
|
|
||||||
logDirContents(t, tmpDirB)
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "testDir")
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// E. Symbol link following version:
|
|
||||||
// SRC specifies a directory and DST does not exist. This should create a
|
|
||||||
// directory at DST and copy the contents of the SRC directory into the DST
|
|
||||||
// directory. Ensure this works whether DST has a trailing path separator or
|
|
||||||
// not.
|
|
||||||
func TestCopyCaseEFSym(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
|
|
||||||
srcDir := filepath.Join(tmpDirA, "dirSymlink")
|
|
||||||
linkTarget := filepath.Join(tmpDirA, "dir1")
|
|
||||||
dstDir := filepath.Join(tmpDirB, "testDir")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
|
||||||
t.Log("dir contents not equal")
|
|
||||||
logDirContents(t, tmpDirA)
|
|
||||||
logDirContents(t, tmpDirB)
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "testDir")
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// F. SRC specifies a directory and DST exists as a file. This should cause an
|
|
||||||
// error as it is not possible to overwrite a file with a directory.
|
|
||||||
func TestCopyCaseF(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
srcDir := filepath.Join(tmpDirA, "dir1")
|
|
||||||
symSrcDir := filepath.Join(tmpDirA, "dirSymlink")
|
|
||||||
dstFile := filepath.Join(tmpDirB, "file1")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstFile); err == nil {
|
|
||||||
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != ErrCannotCopyDir {
|
|
||||||
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now test with symbol link
|
|
||||||
if err = testCopyHelperFSym(t, symSrcDir, dstFile); err == nil {
|
|
||||||
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != ErrCannotCopyDir {
|
|
||||||
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// G. SRC specifies a directory and DST exists as a directory. This should copy
|
|
||||||
// the SRC directory and all its contents to the DST directory. Ensure this
|
|
||||||
// works whether DST has a trailing path separator or not.
|
|
||||||
func TestCopyCaseG(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
srcDir := filepath.Join(tmpDirA, "dir1")
|
|
||||||
dstDir := filepath.Join(tmpDirB, "dir2")
|
|
||||||
resultDir := filepath.Join(dstDir, "dir1")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, resultDir, srcDir); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
|
||||||
t.Fatalf("unable to make dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "dir2")
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, resultDir, srcDir); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// G. Symbol link version:
|
|
||||||
// SRC specifies a directory and DST exists as a directory. This should copy
|
|
||||||
// the SRC directory and all its contents to the DST directory. Ensure this
|
|
||||||
// works whether DST has a trailing path separator or not.
|
|
||||||
func TestCopyCaseGFSym(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
srcDir := filepath.Join(tmpDirA, "dirSymlink")
|
|
||||||
linkTarget := filepath.Join(tmpDirA, "dir1")
|
|
||||||
dstDir := filepath.Join(tmpDirB, "dir2")
|
|
||||||
resultDir := filepath.Join(dstDir, "dirSymlink")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, resultDir, linkTarget); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
|
||||||
t.Fatalf("unable to make dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "dir2")
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, resultDir, linkTarget); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// H. SRC specifies a directory's contents only and DST does not exist. This
|
|
||||||
// should create a directory at DST and copy the contents of the SRC
|
|
||||||
// directory (but not the directory itself) into the DST directory. Ensure
|
|
||||||
// this works whether DST has a trailing path separator or not.
|
|
||||||
func TestCopyCaseH(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
|
|
||||||
srcDir := joinTrailingSep(tmpDirA, "dir1") + "."
|
|
||||||
dstDir := filepath.Join(tmpDirB, "testDir")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
|
|
||||||
t.Log("dir contents not equal")
|
|
||||||
logDirContents(t, tmpDirA)
|
|
||||||
logDirContents(t, tmpDirB)
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "testDir")
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
|
|
||||||
t.Log("dir contents not equal")
|
|
||||||
logDirContents(t, tmpDirA)
|
|
||||||
logDirContents(t, tmpDirB)
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// H. Symbol link following version:
|
|
||||||
// SRC specifies a directory's contents only and DST does not exist. This
|
|
||||||
// should create a directory at DST and copy the contents of the SRC
|
|
||||||
// directory (but not the directory itself) into the DST directory. Ensure
|
|
||||||
// this works whether DST has a trailing path separator or not.
|
|
||||||
func TestCopyCaseHFSym(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
|
|
||||||
srcDir := joinTrailingSep(tmpDirA, "dirSymlink") + "."
|
|
||||||
linkTarget := filepath.Join(tmpDirA, "dir1")
|
|
||||||
dstDir := filepath.Join(tmpDirB, "testDir")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
|
||||||
t.Log("dir contents not equal")
|
|
||||||
logDirContents(t, tmpDirA)
|
|
||||||
logDirContents(t, tmpDirB)
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "testDir")
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
|
||||||
t.Log("dir contents not equal")
|
|
||||||
logDirContents(t, tmpDirA)
|
|
||||||
logDirContents(t, tmpDirB)
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// I. SRC specifies a directory's contents only and DST exists as a file. This
|
|
||||||
// should cause an error as it is not possible to overwrite a file with a
|
|
||||||
// directory.
|
|
||||||
func TestCopyCaseI(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
srcDir := joinTrailingSep(tmpDirA, "dir1") + "."
|
|
||||||
symSrcDir := filepath.Join(tmpDirB, "dirSymlink")
|
|
||||||
dstFile := filepath.Join(tmpDirB, "file1")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstFile); err == nil {
|
|
||||||
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != ErrCannotCopyDir {
|
|
||||||
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now try with symbol link of dir
|
|
||||||
if err = testCopyHelperFSym(t, symSrcDir, dstFile); err == nil {
|
|
||||||
t.Fatal("expected ErrCannotCopyDir error, but got nil instead")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != ErrCannotCopyDir {
|
|
||||||
t.Fatalf("expected ErrCannotCopyDir error, but got %T: %s", err, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// J. SRC specifies a directory's contents only and DST exists as a directory.
|
|
||||||
// This should copy the contents of the SRC directory (but not the directory
|
|
||||||
// itself) into the DST directory. Ensure this works whether DST has a
|
|
||||||
// trailing path separator or not.
|
|
||||||
func TestCopyCaseJ(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
srcDir := joinTrailingSep(tmpDirA, "dir1") + "."
|
|
||||||
dstDir := filepath.Join(tmpDirB, "dir5")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// first to create an empty dir
|
|
||||||
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
|
||||||
t.Fatalf("unable to make dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
|
||||||
t.Fatalf("unable to make dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "dir5")
|
|
||||||
|
|
||||||
if err = testCopyHelper(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, srcDir); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// J. Symbol link following version:
|
|
||||||
// SRC specifies a directory's contents only and DST exists as a directory.
|
|
||||||
// This should copy the contents of the SRC directory (but not the directory
|
|
||||||
// itself) into the DST directory. Ensure this works whether DST has a
|
|
||||||
// trailing path separator or not.
|
|
||||||
func TestCopyCaseJFSym(t *testing.T) {
|
|
||||||
tmpDirA, tmpDirB := getTestTempDirs(t)
|
|
||||||
defer removeAllPaths(tmpDirA, tmpDirB)
|
|
||||||
|
|
||||||
// Load A and B with some sample files and directories.
|
|
||||||
createSampleDir(t, tmpDirA)
|
|
||||||
createSampleDir(t, tmpDirB)
|
|
||||||
|
|
||||||
srcDir := joinTrailingSep(tmpDirA, "dirSymlink") + "."
|
|
||||||
linkTarget := filepath.Join(tmpDirA, "dir1")
|
|
||||||
dstDir := filepath.Join(tmpDirB, "dir5")
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// first to create an empty dir
|
|
||||||
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
|
||||||
t.Fatalf("unable to make dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try again but using a trailing path separator for dstDir.
|
|
||||||
|
|
||||||
if err = os.RemoveAll(dstDir); err != nil {
|
|
||||||
t.Fatalf("unable to remove dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.MkdirAll(dstDir, os.FileMode(0755)); err != nil {
|
|
||||||
t.Fatalf("unable to make dstDir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDir = joinTrailingSep(tmpDirB, "dir5")
|
|
||||||
|
|
||||||
if err = testCopyHelperFSym(t, srcDir, dstDir); err != nil {
|
|
||||||
t.Fatalf("unexpected error %T: %s", err, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = dirContentsEqual(t, dstDir, linkTarget); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/copy_unix.go
generated
vendored
11
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/copy_unix.go
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func normalizePath(path string) string {
|
|
||||||
return filepath.ToSlash(path)
|
|
||||||
}
|
|
||||||
9
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/copy_windows.go
generated
vendored
9
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/copy_windows.go
generated
vendored
@@ -1,9 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func normalizePath(path string) string {
|
|
||||||
return filepath.FromSlash(path)
|
|
||||||
}
|
|
||||||
279
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/diff.go
generated
vendored
279
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/diff.go
generated
vendored
@@ -1,279 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/pkg/idtools"
|
|
||||||
"github.com/docker/docker/pkg/pools"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnpackLayer unpack `layer` to a `dest`. The stream `layer` can be
|
|
||||||
// compressed or uncompressed.
|
|
||||||
// Returns the size in bytes of the contents of the layer.
|
|
||||||
func UnpackLayer(dest string, layer Reader, options *TarOptions) (size int64, err error) {
|
|
||||||
tr := tar.NewReader(layer)
|
|
||||||
trBuf := pools.BufioReader32KPool.Get(tr)
|
|
||||||
defer pools.BufioReader32KPool.Put(trBuf)
|
|
||||||
|
|
||||||
var dirs []*tar.Header
|
|
||||||
unpackedPaths := make(map[string]struct{})
|
|
||||||
|
|
||||||
if options == nil {
|
|
||||||
options = &TarOptions{}
|
|
||||||
}
|
|
||||||
if options.ExcludePatterns == nil {
|
|
||||||
options.ExcludePatterns = []string{}
|
|
||||||
}
|
|
||||||
remappedRootUID, remappedRootGID, err := idtools.GetRootUIDGID(options.UIDMaps, options.GIDMaps)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
aufsTempdir := ""
|
|
||||||
aufsHardlinks := make(map[string]*tar.Header)
|
|
||||||
|
|
||||||
if options == nil {
|
|
||||||
options = &TarOptions{}
|
|
||||||
}
|
|
||||||
// Iterate through the files in the archive.
|
|
||||||
for {
|
|
||||||
hdr, err := tr.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
// end of tar archive
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
size += hdr.Size
|
|
||||||
|
|
||||||
// Normalize name, for safety and for a simple is-root check
|
|
||||||
hdr.Name = filepath.Clean(hdr.Name)
|
|
||||||
|
|
||||||
// Windows does not support filenames with colons in them. Ignore
|
|
||||||
// these files. This is not a problem though (although it might
|
|
||||||
// appear that it is). Let's suppose a client is running docker pull.
|
|
||||||
// The daemon it points to is Windows. Would it make sense for the
|
|
||||||
// client to be doing a docker pull Ubuntu for example (which has files
|
|
||||||
// with colons in the name under /usr/share/man/man3)? No, absolutely
|
|
||||||
// not as it would really only make sense that they were pulling a
|
|
||||||
// Windows image. However, for development, it is necessary to be able
|
|
||||||
// to pull Linux images which are in the repository.
|
|
||||||
//
|
|
||||||
// TODO Windows. Once the registry is aware of what images are Windows-
|
|
||||||
// specific or Linux-specific, this warning should be changed to an error
|
|
||||||
// to cater for the situation where someone does manage to upload a Linux
|
|
||||||
// image but have it tagged as Windows inadvertently.
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
if strings.Contains(hdr.Name, ":") {
|
|
||||||
logrus.Warnf("Windows: Ignoring %s (is this a Linux image?)", hdr.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note as these operations are platform specific, so must the slash be.
|
|
||||||
if !strings.HasSuffix(hdr.Name, string(os.PathSeparator)) {
|
|
||||||
// Not the root directory, ensure that the parent directory exists.
|
|
||||||
// This happened in some tests where an image had a tarfile without any
|
|
||||||
// parent directories.
|
|
||||||
parent := filepath.Dir(hdr.Name)
|
|
||||||
parentPath := filepath.Join(dest, parent)
|
|
||||||
|
|
||||||
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
|
|
||||||
err = system.MkdirAll(parentPath, 0600)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip AUFS metadata dirs
|
|
||||||
if strings.HasPrefix(hdr.Name, WhiteoutMetaPrefix) {
|
|
||||||
// Regular files inside /.wh..wh.plnk can be used as hardlink targets
|
|
||||||
// We don't want this directory, but we need the files in them so that
|
|
||||||
// such hardlinks can be resolved.
|
|
||||||
if strings.HasPrefix(hdr.Name, WhiteoutLinkDir) && hdr.Typeflag == tar.TypeReg {
|
|
||||||
basename := filepath.Base(hdr.Name)
|
|
||||||
aufsHardlinks[basename] = hdr
|
|
||||||
if aufsTempdir == "" {
|
|
||||||
if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(aufsTempdir)
|
|
||||||
}
|
|
||||||
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true, nil); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hdr.Name != WhiteoutOpaqueDir {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path := filepath.Join(dest, hdr.Name)
|
|
||||||
rel, err := filepath.Rel(dest, path)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note as these operations are platform specific, so must the slash be.
|
|
||||||
if strings.HasPrefix(rel, ".."+string(os.PathSeparator)) {
|
|
||||||
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
|
|
||||||
}
|
|
||||||
base := filepath.Base(path)
|
|
||||||
|
|
||||||
if strings.HasPrefix(base, WhiteoutPrefix) {
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
if base == WhiteoutOpaqueDir {
|
|
||||||
_, err := os.Lstat(dir)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
err = nil // parent was deleted
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if path == dir {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if _, exists := unpackedPaths[path]; !exists {
|
|
||||||
err := os.RemoveAll(path)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
originalBase := base[len(WhiteoutPrefix):]
|
|
||||||
originalPath := filepath.Join(dir, originalBase)
|
|
||||||
if err := os.RemoveAll(originalPath); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If path exits we almost always just want to remove and replace it.
|
|
||||||
// The only exception is when it is a directory *and* the file from
|
|
||||||
// the layer is also a directory. Then we want to merge them (i.e.
|
|
||||||
// just apply the metadata from the layer).
|
|
||||||
if fi, err := os.Lstat(path); err == nil {
|
|
||||||
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
|
|
||||||
if err := os.RemoveAll(path); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trBuf.Reset(tr)
|
|
||||||
srcData := io.Reader(trBuf)
|
|
||||||
srcHdr := hdr
|
|
||||||
|
|
||||||
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
|
|
||||||
// we manually retarget these into the temporary files we extracted them into
|
|
||||||
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), WhiteoutLinkDir) {
|
|
||||||
linkBasename := filepath.Base(hdr.Linkname)
|
|
||||||
srcHdr = aufsHardlinks[linkBasename]
|
|
||||||
if srcHdr == nil {
|
|
||||||
return 0, fmt.Errorf("Invalid aufs hardlink")
|
|
||||||
}
|
|
||||||
tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer tmpFile.Close()
|
|
||||||
srcData = tmpFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the options contain a uid & gid maps, convert header uid/gid
|
|
||||||
// entries using the maps such that lchown sets the proper mapped
|
|
||||||
// uid/gid after writing the file. We only perform this mapping if
|
|
||||||
// the file isn't already owned by the remapped root UID or GID, as
|
|
||||||
// that specific uid/gid has no mapping from container -> host, and
|
|
||||||
// those files already have the proper ownership for inside the
|
|
||||||
// container.
|
|
||||||
if srcHdr.Uid != remappedRootUID {
|
|
||||||
xUID, err := idtools.ToHost(srcHdr.Uid, options.UIDMaps)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
srcHdr.Uid = xUID
|
|
||||||
}
|
|
||||||
if srcHdr.Gid != remappedRootGID {
|
|
||||||
xGID, err := idtools.ToHost(srcHdr.Gid, options.GIDMaps)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
srcHdr.Gid = xGID
|
|
||||||
}
|
|
||||||
if err := createTarFile(path, dest, srcHdr, srcData, true, nil); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directory mtimes must be handled at the end to avoid further
|
|
||||||
// file creation in them to modify the directory mtime
|
|
||||||
if hdr.Typeflag == tar.TypeDir {
|
|
||||||
dirs = append(dirs, hdr)
|
|
||||||
}
|
|
||||||
unpackedPaths[path] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, hdr := range dirs {
|
|
||||||
path := filepath.Join(dest, hdr.Name)
|
|
||||||
if err := system.Chtimes(path, hdr.AccessTime, hdr.ModTime); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyLayer parses a diff in the standard layer format from `layer`,
|
|
||||||
// and applies it to the directory `dest`. The stream `layer` can be
|
|
||||||
// compressed or uncompressed.
|
|
||||||
// Returns the size in bytes of the contents of the layer.
|
|
||||||
func ApplyLayer(dest string, layer Reader) (int64, error) {
|
|
||||||
return applyLayerHandler(dest, layer, &TarOptions{}, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyUncompressedLayer parses a diff in the standard layer format from
|
|
||||||
// `layer`, and applies it to the directory `dest`. The stream `layer`
|
|
||||||
// can only be uncompressed.
|
|
||||||
// Returns the size in bytes of the contents of the layer.
|
|
||||||
func ApplyUncompressedLayer(dest string, layer Reader, options *TarOptions) (int64, error) {
|
|
||||||
return applyLayerHandler(dest, layer, options, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// do the bulk load of ApplyLayer, but allow for not calling DecompressStream
|
|
||||||
func applyLayerHandler(dest string, layer Reader, options *TarOptions, decompress bool) (int64, error) {
|
|
||||||
dest = filepath.Clean(dest)
|
|
||||||
|
|
||||||
// We need to be able to set any perms
|
|
||||||
oldmask, err := system.Umask(0)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
|
|
||||||
|
|
||||||
if decompress {
|
|
||||||
layer, err = DecompressStream(layer)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return UnpackLayer(dest, layer, options)
|
|
||||||
}
|
|
||||||
370
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/diff_test.go
generated
vendored
370
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/diff_test.go
generated
vendored
@@ -1,370 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestApplyLayerInvalidFilenames(t *testing.T) {
|
|
||||||
for i, headers := range [][]*tar.Header{
|
|
||||||
{
|
|
||||||
{
|
|
||||||
Name: "../victim/dotdot",
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{
|
|
||||||
// Note the leading slash
|
|
||||||
Name: "/../victim/slash-dotdot",
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidFilenames", headers); err != nil {
|
|
||||||
t.Fatalf("i=%d. %v", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApplyLayerInvalidHardlink(t *testing.T) {
|
|
||||||
for i, headers := range [][]*tar.Header{
|
|
||||||
{ // try reading victim/hello (../)
|
|
||||||
{
|
|
||||||
Name: "dotdot",
|
|
||||||
Typeflag: tar.TypeLink,
|
|
||||||
Linkname: "../victim/hello",
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // try reading victim/hello (/../)
|
|
||||||
{
|
|
||||||
Name: "slash-dotdot",
|
|
||||||
Typeflag: tar.TypeLink,
|
|
||||||
// Note the leading slash
|
|
||||||
Linkname: "/../victim/hello",
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // try writing victim/file
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeLink,
|
|
||||||
Linkname: "../victim",
|
|
||||||
Mode: 0755,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "loophole-victim/file",
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // try reading victim/hello (hardlink, symlink)
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeLink,
|
|
||||||
Linkname: "../victim",
|
|
||||||
Mode: 0755,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "symlink",
|
|
||||||
Typeflag: tar.TypeSymlink,
|
|
||||||
Linkname: "loophole-victim/hello",
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Try reading victim/hello (hardlink, hardlink)
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeLink,
|
|
||||||
Linkname: "../victim",
|
|
||||||
Mode: 0755,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "hardlink",
|
|
||||||
Typeflag: tar.TypeLink,
|
|
||||||
Linkname: "loophole-victim/hello",
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // Try removing victim directory (hardlink)
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeLink,
|
|
||||||
Linkname: "../victim",
|
|
||||||
Mode: 0755,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidHardlink", headers); err != nil {
|
|
||||||
t.Fatalf("i=%d. %v", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApplyLayerInvalidSymlink(t *testing.T) {
|
|
||||||
for i, headers := range [][]*tar.Header{
|
|
||||||
{ // try reading victim/hello (../)
|
|
||||||
{
|
|
||||||
Name: "dotdot",
|
|
||||||
Typeflag: tar.TypeSymlink,
|
|
||||||
Linkname: "../victim/hello",
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // try reading victim/hello (/../)
|
|
||||||
{
|
|
||||||
Name: "slash-dotdot",
|
|
||||||
Typeflag: tar.TypeSymlink,
|
|
||||||
// Note the leading slash
|
|
||||||
Linkname: "/../victim/hello",
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // try writing victim/file
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeSymlink,
|
|
||||||
Linkname: "../victim",
|
|
||||||
Mode: 0755,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "loophole-victim/file",
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // try reading victim/hello (symlink, symlink)
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeSymlink,
|
|
||||||
Linkname: "../victim",
|
|
||||||
Mode: 0755,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "symlink",
|
|
||||||
Typeflag: tar.TypeSymlink,
|
|
||||||
Linkname: "loophole-victim/hello",
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // try reading victim/hello (symlink, hardlink)
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeSymlink,
|
|
||||||
Linkname: "../victim",
|
|
||||||
Mode: 0755,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "hardlink",
|
|
||||||
Typeflag: tar.TypeLink,
|
|
||||||
Linkname: "loophole-victim/hello",
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ // try removing victim directory (symlink)
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeSymlink,
|
|
||||||
Linkname: "../victim",
|
|
||||||
Mode: 0755,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "loophole-victim",
|
|
||||||
Typeflag: tar.TypeReg,
|
|
||||||
Mode: 0644,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidSymlink", headers); err != nil {
|
|
||||||
t.Fatalf("i=%d. %v", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestApplyLayerWhiteouts(t *testing.T) {
|
|
||||||
wd, err := ioutil.TempDir("", "graphdriver-test-whiteouts")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(wd)
|
|
||||||
|
|
||||||
base := []string{
|
|
||||||
".baz",
|
|
||||||
"bar/",
|
|
||||||
"bar/bax",
|
|
||||||
"bar/bay/",
|
|
||||||
"baz",
|
|
||||||
"foo/",
|
|
||||||
"foo/.abc",
|
|
||||||
"foo/.bcd/",
|
|
||||||
"foo/.bcd/a",
|
|
||||||
"foo/cde/",
|
|
||||||
"foo/cde/def",
|
|
||||||
"foo/cde/efg",
|
|
||||||
"foo/fgh",
|
|
||||||
"foobar",
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcase struct {
|
|
||||||
change, expected []string
|
|
||||||
}
|
|
||||||
|
|
||||||
tcases := []tcase{
|
|
||||||
{
|
|
||||||
base,
|
|
||||||
base,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{
|
|
||||||
".bay",
|
|
||||||
".wh.baz",
|
|
||||||
"foo/",
|
|
||||||
"foo/.bce",
|
|
||||||
"foo/.wh..wh..opq",
|
|
||||||
"foo/cde/",
|
|
||||||
"foo/cde/efg",
|
|
||||||
},
|
|
||||||
[]string{
|
|
||||||
".bay",
|
|
||||||
".baz",
|
|
||||||
"bar/",
|
|
||||||
"bar/bax",
|
|
||||||
"bar/bay/",
|
|
||||||
"foo/",
|
|
||||||
"foo/.bce",
|
|
||||||
"foo/cde/",
|
|
||||||
"foo/cde/efg",
|
|
||||||
"foobar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{
|
|
||||||
".bay",
|
|
||||||
".wh..baz",
|
|
||||||
".wh.foobar",
|
|
||||||
"foo/",
|
|
||||||
"foo/.abc",
|
|
||||||
"foo/.wh.cde",
|
|
||||||
"bar/",
|
|
||||||
},
|
|
||||||
[]string{
|
|
||||||
".bay",
|
|
||||||
"bar/",
|
|
||||||
"bar/bax",
|
|
||||||
"bar/bay/",
|
|
||||||
"foo/",
|
|
||||||
"foo/.abc",
|
|
||||||
"foo/.bce",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]string{
|
|
||||||
".abc",
|
|
||||||
".wh..wh..opq",
|
|
||||||
"foobar",
|
|
||||||
},
|
|
||||||
[]string{
|
|
||||||
".abc",
|
|
||||||
"foobar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tc := range tcases {
|
|
||||||
l, err := makeTestLayer(tc.change)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = UnpackLayer(wd, l, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
err = l.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
paths, err := readDirContents(wd)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(tc.expected, paths) {
|
|
||||||
t.Fatalf("invalid files for layer %d: expected %q, got %q", i, tc.expected, paths)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTestLayer(paths []string) (rc io.ReadCloser, err error) {
|
|
||||||
tmpDir, err := ioutil.TempDir("", "graphdriver-test-mklayer")
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err != nil {
|
|
||||||
os.RemoveAll(tmpDir)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for _, p := range paths {
|
|
||||||
if p[len(p)-1] == filepath.Separator {
|
|
||||||
if err = os.MkdirAll(filepath.Join(tmpDir, p), 0700); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err = ioutil.WriteFile(filepath.Join(tmpDir, p), nil, 0600); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
archive, err := Tar(tmpDir, Uncompressed)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return ioutils.NewReadCloserWrapper(archive, func() error {
|
|
||||||
err := archive.Close()
|
|
||||||
os.RemoveAll(tmpDir)
|
|
||||||
return err
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readDirContents(root string) ([]string, error) {
|
|
||||||
var files []string
|
|
||||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if path == root {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
rel, err := filepath.Rel(root, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
rel = rel + "/"
|
|
||||||
}
|
|
||||||
files = append(files, rel)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
97
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/example_changes.go
generated
vendored
97
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/example_changes.go
generated
vendored
@@ -1,97 +0,0 @@
|
|||||||
// +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
|
|
||||||
}
|
|
||||||
BIN
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/testdata/broken.tar
generated
vendored
BIN
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/testdata/broken.tar
generated
vendored
Binary file not shown.
16
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/time_linux.go
generated
vendored
16
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/time_linux.go
generated
vendored
@@ -1,16 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
|
||||||
if time.IsZero() {
|
|
||||||
// Return UTIME_OMIT special value
|
|
||||||
ts.Sec = 0
|
|
||||||
ts.Nsec = ((1 << 30) - 2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return syscall.NsecToTimespec(time.UnixNano())
|
|
||||||
}
|
|
||||||
16
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/time_unsupported.go
generated
vendored
16
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/time_unsupported.go
generated
vendored
@@ -1,16 +0,0 @@
|
|||||||
// +build !linux
|
|
||||||
|
|
||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
|
|
||||||
nsec := int64(0)
|
|
||||||
if !time.IsZero() {
|
|
||||||
nsec = time.UnixNano()
|
|
||||||
}
|
|
||||||
return syscall.NsecToTimespec(nsec)
|
|
||||||
}
|
|
||||||
166
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/utils_test.go
generated
vendored
166
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/utils_test.go
generated
vendored
@@ -1,166 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testUntarFns = map[string]func(string, io.Reader) error{
|
|
||||||
"untar": func(dest string, r io.Reader) error {
|
|
||||||
return Untar(r, dest, nil)
|
|
||||||
},
|
|
||||||
"applylayer": func(dest string, r io.Reader) error {
|
|
||||||
_, err := ApplyLayer(dest, Reader(r))
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// testBreakout is a helper function that, within the provided `tmpdir` directory,
|
|
||||||
// creates a `victim` folder with a generated `hello` file in it.
|
|
||||||
// `untar` extracts to a directory named `dest`, the tar file created from `headers`.
|
|
||||||
//
|
|
||||||
// Here are the tested scenarios:
|
|
||||||
// - removed `victim` folder (write)
|
|
||||||
// - removed files from `victim` folder (write)
|
|
||||||
// - new files in `victim` folder (write)
|
|
||||||
// - modified files in `victim` folder (write)
|
|
||||||
// - file in `dest` with same content as `victim/hello` (read)
|
|
||||||
//
|
|
||||||
// When using testBreakout make sure you cover one of the scenarios listed above.
|
|
||||||
func testBreakout(untarFn string, tmpdir string, headers []*tar.Header) error {
|
|
||||||
tmpdir, err := ioutil.TempDir("", tmpdir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpdir)
|
|
||||||
|
|
||||||
dest := filepath.Join(tmpdir, "dest")
|
|
||||||
if err := os.Mkdir(dest, 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
victim := filepath.Join(tmpdir, "victim")
|
|
||||||
if err := os.Mkdir(victim, 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hello := filepath.Join(victim, "hello")
|
|
||||||
helloData, err := time.Now().MarshalText()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(hello, helloData, 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
helloStat, err := os.Stat(hello)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reader, writer := io.Pipe()
|
|
||||||
go func() {
|
|
||||||
t := tar.NewWriter(writer)
|
|
||||||
for _, hdr := range headers {
|
|
||||||
t.WriteHeader(hdr)
|
|
||||||
}
|
|
||||||
t.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
untar := testUntarFns[untarFn]
|
|
||||||
if untar == nil {
|
|
||||||
return fmt.Errorf("could not find untar function %q in testUntarFns", untarFn)
|
|
||||||
}
|
|
||||||
if err := untar(dest, reader); err != nil {
|
|
||||||
if _, ok := err.(breakoutError); !ok {
|
|
||||||
// If untar returns an error unrelated to an archive breakout,
|
|
||||||
// then consider this an unexpected error and abort.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Here, untar detected the breakout.
|
|
||||||
// Let's move on verifying that indeed there was no breakout.
|
|
||||||
fmt.Printf("breakoutError: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check victim folder
|
|
||||||
f, err := os.Open(victim)
|
|
||||||
if err != nil {
|
|
||||||
// codepath taken if victim folder was removed
|
|
||||||
return fmt.Errorf("archive breakout: error reading %q: %v", victim, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Check contents of victim folder
|
|
||||||
//
|
|
||||||
// We are only interested in getting 2 files from the victim folder, because if all is well
|
|
||||||
// we expect only one result, the `hello` file. If there is a second result, it cannot
|
|
||||||
// hold the same name `hello` and we assume that a new file got created in the victim folder.
|
|
||||||
// That is enough to detect an archive breakout.
|
|
||||||
names, err := f.Readdirnames(2)
|
|
||||||
if err != nil {
|
|
||||||
// codepath taken if victim is not a folder
|
|
||||||
return fmt.Errorf("archive breakout: error reading directory content of %q: %v", victim, err)
|
|
||||||
}
|
|
||||||
for _, name := range names {
|
|
||||||
if name != "hello" {
|
|
||||||
// codepath taken if new file was created in victim folder
|
|
||||||
return fmt.Errorf("archive breakout: new file %q", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check victim/hello
|
|
||||||
f, err = os.Open(hello)
|
|
||||||
if err != nil {
|
|
||||||
// codepath taken if read permissions were removed
|
|
||||||
return fmt.Errorf("archive breakout: could not lstat %q: %v", hello, err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
b, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fi, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if helloStat.IsDir() != fi.IsDir() ||
|
|
||||||
// TODO: cannot check for fi.ModTime() change
|
|
||||||
helloStat.Mode() != fi.Mode() ||
|
|
||||||
helloStat.Size() != fi.Size() ||
|
|
||||||
!bytes.Equal(helloData, b) {
|
|
||||||
// codepath taken if hello has been modified
|
|
||||||
return fmt.Errorf("archive breakout: file %q has been modified. Contents: expected=%q, got=%q. FileInfo: expected=%#v, got=%#v", hello, helloData, b, helloStat, fi)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that nothing in dest/ has the same content as victim/hello.
|
|
||||||
// Since victim/hello was generated with time.Now(), it is safe to assume
|
|
||||||
// that any file whose content matches exactly victim/hello, managed somehow
|
|
||||||
// to access victim/hello.
|
|
||||||
return filepath.Walk(dest, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if info.IsDir() {
|
|
||||||
if err != nil {
|
|
||||||
// skip directory if error
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
// enter directory
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
// skip file if error
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
b, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
// Houston, we have a problem. Aborting (space)walk.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if bytes.Equal(helloData, b) {
|
|
||||||
return fmt.Errorf("archive breakout: file %q has been accessed via %q", hello, path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
23
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/whiteouts.go
generated
vendored
23
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/whiteouts.go
generated
vendored
@@ -1,23 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
// Whiteouts are files with a special meaning for the layered filesystem.
|
|
||||||
// Docker uses AUFS whiteout files inside exported archives. In other
|
|
||||||
// filesystems these files are generated/handled on tar creation/extraction.
|
|
||||||
|
|
||||||
// WhiteoutPrefix prefix means file is a whiteout. If this is followed by a
|
|
||||||
// filename this means that file has been removed from the base layer.
|
|
||||||
const WhiteoutPrefix = ".wh."
|
|
||||||
|
|
||||||
// WhiteoutMetaPrefix prefix means whiteout has a special meaning and is not
|
|
||||||
// for removing an actual file. Normally these files are excluded from exported
|
|
||||||
// archives.
|
|
||||||
const WhiteoutMetaPrefix = WhiteoutPrefix + WhiteoutPrefix
|
|
||||||
|
|
||||||
// WhiteoutLinkDir is a directory AUFS uses for storing hardlink links to other
|
|
||||||
// layers. Normally these should not go into exported archives and all changed
|
|
||||||
// hardlinks should be copied to the top layer.
|
|
||||||
const WhiteoutLinkDir = WhiteoutMetaPrefix + "plnk"
|
|
||||||
|
|
||||||
// WhiteoutOpaqueDir file means directory has been made opaque - meaning
|
|
||||||
// readdir calls to this directory do not follow to lower layers.
|
|
||||||
const WhiteoutOpaqueDir = WhiteoutMetaPrefix + ".opq"
|
|
||||||
59
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/wrap.go
generated
vendored
59
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/wrap.go
generated
vendored
@@ -1,59 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Generate generates a new archive from the content provided
|
|
||||||
// as input.
|
|
||||||
//
|
|
||||||
// `files` is a sequence of path/content pairs. A new file is
|
|
||||||
// added to the archive for each pair.
|
|
||||||
// If the last pair is incomplete, the file is created with an
|
|
||||||
// empty content. For example:
|
|
||||||
//
|
|
||||||
// Generate("foo.txt", "hello world", "emptyfile")
|
|
||||||
//
|
|
||||||
// The above call will return an archive with 2 files:
|
|
||||||
// * ./foo.txt with content "hello world"
|
|
||||||
// * ./empty with empty content
|
|
||||||
//
|
|
||||||
// FIXME: stream content instead of buffering
|
|
||||||
// FIXME: specify permissions and other archive metadata
|
|
||||||
func Generate(input ...string) (Archive, error) {
|
|
||||||
files := parseStringPairs(input...)
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
tw := tar.NewWriter(buf)
|
|
||||||
for _, file := range files {
|
|
||||||
name, content := file[0], file[1]
|
|
||||||
hdr := &tar.Header{
|
|
||||||
Name: name,
|
|
||||||
Size: int64(len(content)),
|
|
||||||
}
|
|
||||||
if err := tw.WriteHeader(hdr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := tw.Write([]byte(content)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := tw.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return ioutil.NopCloser(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseStringPairs(input ...string) (output [][2]string) {
|
|
||||||
output = make([][2]string, 0, len(input)/2+1)
|
|
||||||
for i := 0; i < len(input); i += 2 {
|
|
||||||
var pair [2]string
|
|
||||||
pair[0] = input[i]
|
|
||||||
if i+1 < len(input) {
|
|
||||||
pair[1] = input[i+1]
|
|
||||||
}
|
|
||||||
output = append(output, pair)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
98
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/wrap_test.go
generated
vendored
98
Godeps/_workspace/src/github.com/docker/docker/pkg/archive/wrap_test.go
generated
vendored
@@ -1,98 +0,0 @@
|
|||||||
package archive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/tar"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGenerateEmptyFile(t *testing.T) {
|
|
||||||
archive, err := Generate("emptyFile")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if archive == nil {
|
|
||||||
t.Fatal("The generated archive should not be nil.")
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedFiles := [][]string{
|
|
||||||
{"emptyFile", ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := tar.NewReader(archive)
|
|
||||||
actualFiles := make([][]string, 0, 10)
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
hdr, err := tr.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.ReadFrom(tr)
|
|
||||||
content := buf.String()
|
|
||||||
actualFiles = append(actualFiles, []string{hdr.Name, content})
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if len(actualFiles) != len(expectedFiles) {
|
|
||||||
t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles))
|
|
||||||
}
|
|
||||||
for i := 0; i < len(expectedFiles); i++ {
|
|
||||||
actual := actualFiles[i]
|
|
||||||
expected := expectedFiles[i]
|
|
||||||
if actual[0] != expected[0] {
|
|
||||||
t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0])
|
|
||||||
}
|
|
||||||
if actual[1] != expected[1] {
|
|
||||||
t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateWithContent(t *testing.T) {
|
|
||||||
archive, err := Generate("file", "content")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if archive == nil {
|
|
||||||
t.Fatal("The generated archive should not be nil.")
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedFiles := [][]string{
|
|
||||||
{"file", "content"},
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := tar.NewReader(archive)
|
|
||||||
actualFiles := make([][]string, 0, 10)
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
hdr, err := tr.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
buf.ReadFrom(tr)
|
|
||||||
content := buf.String()
|
|
||||||
actualFiles = append(actualFiles, []string{hdr.Name, content})
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if len(actualFiles) != len(expectedFiles) {
|
|
||||||
t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles))
|
|
||||||
}
|
|
||||||
for i := 0; i < len(expectedFiles); i++ {
|
|
||||||
actual := actualFiles[i]
|
|
||||||
expected := expectedFiles[i]
|
|
||||||
if actual[0] != expected[0] {
|
|
||||||
t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0])
|
|
||||||
}
|
|
||||||
if actual[1] != expected[1] {
|
|
||||||
t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
279
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils.go
generated
vendored
279
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils.go
generated
vendored
@@ -1,279 +0,0 @@
|
|||||||
package fileutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"text/scanner"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// exclusion return true if the specified pattern is an exclusion
|
|
||||||
func exclusion(pattern string) bool {
|
|
||||||
return pattern[0] == '!'
|
|
||||||
}
|
|
||||||
|
|
||||||
// empty return true if the specified pattern is empty
|
|
||||||
func empty(pattern string) bool {
|
|
||||||
return pattern == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanPatterns takes a slice of patterns returns a new
|
|
||||||
// slice of patterns cleaned with filepath.Clean, stripped
|
|
||||||
// of any empty patterns and lets the caller know whether the
|
|
||||||
// slice contains any exception patterns (prefixed with !).
|
|
||||||
func CleanPatterns(patterns []string) ([]string, [][]string, bool, error) {
|
|
||||||
// Loop over exclusion patterns and:
|
|
||||||
// 1. Clean them up.
|
|
||||||
// 2. Indicate whether we are dealing with any exception rules.
|
|
||||||
// 3. Error if we see a single exclusion marker on it's own (!).
|
|
||||||
cleanedPatterns := []string{}
|
|
||||||
patternDirs := [][]string{}
|
|
||||||
exceptions := false
|
|
||||||
for _, pattern := range patterns {
|
|
||||||
// Eliminate leading and trailing whitespace.
|
|
||||||
pattern = strings.TrimSpace(pattern)
|
|
||||||
if empty(pattern) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if exclusion(pattern) {
|
|
||||||
if len(pattern) == 1 {
|
|
||||||
return nil, nil, false, errors.New("Illegal exclusion pattern: !")
|
|
||||||
}
|
|
||||||
exceptions = true
|
|
||||||
}
|
|
||||||
pattern = filepath.Clean(pattern)
|
|
||||||
cleanedPatterns = append(cleanedPatterns, pattern)
|
|
||||||
if exclusion(pattern) {
|
|
||||||
pattern = pattern[1:]
|
|
||||||
}
|
|
||||||
patternDirs = append(patternDirs, strings.Split(pattern, "/"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleanedPatterns, patternDirs, exceptions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matches returns true if file matches any of the patterns
|
|
||||||
// and isn't excluded by any of the subsequent patterns.
|
|
||||||
func Matches(file string, patterns []string) (bool, error) {
|
|
||||||
file = filepath.Clean(file)
|
|
||||||
|
|
||||||
if file == "." {
|
|
||||||
// Don't let them exclude everything, kind of silly.
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
patterns, patDirs, _, err := CleanPatterns(patterns)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return OptimizedMatches(file, patterns, patDirs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go.
|
|
||||||
// It will assume that the inputs have been preprocessed and therefore the function
|
|
||||||
// doesn't need to do as much error checking and clean-up. This was done to avoid
|
|
||||||
// repeating these steps on each file being checked during the archive process.
|
|
||||||
// The more generic fileutils.Matches() can't make these assumptions.
|
|
||||||
func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) {
|
|
||||||
matched := false
|
|
||||||
parentPath := filepath.Dir(file)
|
|
||||||
parentPathDirs := strings.Split(parentPath, "/")
|
|
||||||
|
|
||||||
for i, pattern := range patterns {
|
|
||||||
negative := false
|
|
||||||
|
|
||||||
if exclusion(pattern) {
|
|
||||||
negative = true
|
|
||||||
pattern = pattern[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
match, err := regexpMatch(pattern, file)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("Error in pattern (%s): %s", pattern, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !match && parentPath != "." {
|
|
||||||
// Check to see if the pattern matches one of our parent dirs.
|
|
||||||
if len(patDirs[i]) <= len(parentPathDirs) {
|
|
||||||
match, _ = regexpMatch(strings.Join(patDirs[i], "/"),
|
|
||||||
strings.Join(parentPathDirs[:len(patDirs[i])], "/"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if match {
|
|
||||||
matched = !negative
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matched {
|
|
||||||
logrus.Debugf("Skipping excluded path: %s", file)
|
|
||||||
}
|
|
||||||
|
|
||||||
return matched, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// regexpMatch tries to match the logic of filepath.Match but
|
|
||||||
// does so using regexp logic. We do this so that we can expand the
|
|
||||||
// wildcard set to include other things, like "**" to mean any number
|
|
||||||
// of directories. This means that we should be backwards compatible
|
|
||||||
// with filepath.Match(). We'll end up supporting more stuff, due to
|
|
||||||
// the fact that we're using regexp, but that's ok - it does no harm.
|
|
||||||
func regexpMatch(pattern, path string) (bool, error) {
|
|
||||||
regStr := "^"
|
|
||||||
|
|
||||||
// Do some syntax checking on the pattern.
|
|
||||||
// filepath's Match() has some really weird rules that are inconsistent
|
|
||||||
// so instead of trying to dup their logic, just call Match() for its
|
|
||||||
// error state and if there is an error in the pattern return it.
|
|
||||||
// If this becomes an issue we can remove this since its really only
|
|
||||||
// needed in the error (syntax) case - which isn't really critical.
|
|
||||||
if _, err := filepath.Match(pattern, path); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Go through the pattern and convert it to a regexp.
|
|
||||||
// We use a scanner so we can support utf-8 chars.
|
|
||||||
var scan scanner.Scanner
|
|
||||||
scan.Init(strings.NewReader(pattern))
|
|
||||||
|
|
||||||
sl := string(os.PathSeparator)
|
|
||||||
escSL := sl
|
|
||||||
if sl == `\` {
|
|
||||||
escSL += `\`
|
|
||||||
}
|
|
||||||
|
|
||||||
for scan.Peek() != scanner.EOF {
|
|
||||||
ch := scan.Next()
|
|
||||||
|
|
||||||
if ch == '*' {
|
|
||||||
if scan.Peek() == '*' {
|
|
||||||
// is some flavor of "**"
|
|
||||||
scan.Next()
|
|
||||||
|
|
||||||
if scan.Peek() == scanner.EOF {
|
|
||||||
// is "**EOF" - to align with .gitignore just accept all
|
|
||||||
regStr += ".*"
|
|
||||||
} else {
|
|
||||||
// is "**"
|
|
||||||
regStr += "((.*" + escSL + ")|([^" + escSL + "]*))"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Treat **/ as ** so eat the "/"
|
|
||||||
if string(scan.Peek()) == sl {
|
|
||||||
scan.Next()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// is "*" so map it to anything but "/"
|
|
||||||
regStr += "[^" + escSL + "]*"
|
|
||||||
}
|
|
||||||
} else if ch == '?' {
|
|
||||||
// "?" is any char except "/"
|
|
||||||
regStr += "[^" + escSL + "]"
|
|
||||||
} else if strings.Index(".$", string(ch)) != -1 {
|
|
||||||
// Escape some regexp special chars that have no meaning
|
|
||||||
// in golang's filepath.Match
|
|
||||||
regStr += `\` + string(ch)
|
|
||||||
} else if ch == '\\' {
|
|
||||||
// escape next char. Note that a trailing \ in the pattern
|
|
||||||
// will be left alone (but need to escape it)
|
|
||||||
if sl == `\` {
|
|
||||||
// On windows map "\" to "\\", meaning an escaped backslash,
|
|
||||||
// and then just continue because filepath.Match on
|
|
||||||
// Windows doesn't allow escaping at all
|
|
||||||
regStr += escSL
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if scan.Peek() != scanner.EOF {
|
|
||||||
regStr += `\` + string(scan.Next())
|
|
||||||
} else {
|
|
||||||
regStr += `\`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
regStr += string(ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
regStr += "$"
|
|
||||||
|
|
||||||
res, err := regexp.MatchString(regStr, path)
|
|
||||||
|
|
||||||
// Map regexp's error to filepath's so no one knows we're not using filepath
|
|
||||||
if err != nil {
|
|
||||||
err = filepath.ErrBadPattern
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyFile copies from src to dst until either EOF is reached
|
|
||||||
// on src or an error occurs. It verifies src exists and remove
|
|
||||||
// the dst if it exists.
|
|
||||||
func CopyFile(src, dst string) (int64, error) {
|
|
||||||
cleanSrc := filepath.Clean(src)
|
|
||||||
cleanDst := filepath.Clean(dst)
|
|
||||||
if cleanSrc == cleanDst {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
sf, err := os.Open(cleanSrc)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer sf.Close()
|
|
||||||
if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
df, err := os.Create(cleanDst)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer df.Close()
|
|
||||||
return io.Copy(df, sf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadSymlinkedDirectory returns the target directory of a symlink.
|
|
||||||
// The target of the symbolic link may not be a file.
|
|
||||||
func ReadSymlinkedDirectory(path string) (string, error) {
|
|
||||||
var realPath string
|
|
||||||
var err error
|
|
||||||
if realPath, err = filepath.Abs(path); err != nil {
|
|
||||||
return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err)
|
|
||||||
}
|
|
||||||
if realPath, err = filepath.EvalSymlinks(realPath); err != nil {
|
|
||||||
return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err)
|
|
||||||
}
|
|
||||||
realPathInfo, err := os.Stat(realPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err)
|
|
||||||
}
|
|
||||||
if !realPathInfo.Mode().IsDir() {
|
|
||||||
return "", fmt.Errorf("canonical path points to a file '%s'", realPath)
|
|
||||||
}
|
|
||||||
return realPath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIfNotExists creates a file or a directory only if it does not already exist.
|
|
||||||
func CreateIfNotExists(path string, isDir bool) error {
|
|
||||||
if _, err := os.Stat(path); err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
if isDir {
|
|
||||||
return os.MkdirAll(path, 0755)
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f, err := os.OpenFile(path, os.O_CREATE, 0755)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
573
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils_test.go
generated
vendored
573
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils_test.go
generated
vendored
@@ -1,573 +0,0 @@
|
|||||||
package fileutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CopyFile with invalid src
|
|
||||||
func TestCopyFileWithInvalidSrc(t *testing.T) {
|
|
||||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
|
||||||
defer os.RemoveAll(tempFolder)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
bytes, err := CopyFile("/invalid/file/path", path.Join(tempFolder, "dest"))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Should have fail to copy an invalid src file")
|
|
||||||
}
|
|
||||||
if bytes != 0 {
|
|
||||||
t.Fatal("Should have written 0 bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyFile with invalid dest
|
|
||||||
func TestCopyFileWithInvalidDest(t *testing.T) {
|
|
||||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
|
||||||
defer os.RemoveAll(tempFolder)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
src := path.Join(tempFolder, "file")
|
|
||||||
err = ioutil.WriteFile(src, []byte("content"), 0740)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
bytes, err := CopyFile(src, path.Join(tempFolder, "/invalid/dest/path"))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Should have fail to copy an invalid src file")
|
|
||||||
}
|
|
||||||
if bytes != 0 {
|
|
||||||
t.Fatal("Should have written 0 bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyFile with same src and dest
|
|
||||||
func TestCopyFileWithSameSrcAndDest(t *testing.T) {
|
|
||||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
|
||||||
defer os.RemoveAll(tempFolder)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
file := path.Join(tempFolder, "file")
|
|
||||||
err = ioutil.WriteFile(file, []byte("content"), 0740)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
bytes, err := CopyFile(file, file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if bytes != 0 {
|
|
||||||
t.Fatal("Should have written 0 bytes as it is the same file.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyFile with same src and dest but path is different and not clean
|
|
||||||
func TestCopyFileWithSameSrcAndDestWithPathNameDifferent(t *testing.T) {
|
|
||||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
|
||||||
defer os.RemoveAll(tempFolder)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testFolder := path.Join(tempFolder, "test")
|
|
||||||
err = os.MkdirAll(testFolder, 0740)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
file := path.Join(testFolder, "file")
|
|
||||||
sameFile := testFolder + "/../test/file"
|
|
||||||
err = ioutil.WriteFile(file, []byte("content"), 0740)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
bytes, err := CopyFile(file, sameFile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if bytes != 0 {
|
|
||||||
t.Fatal("Should have written 0 bytes as it is the same file.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCopyFile(t *testing.T) {
|
|
||||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
|
||||||
defer os.RemoveAll(tempFolder)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
src := path.Join(tempFolder, "src")
|
|
||||||
dest := path.Join(tempFolder, "dest")
|
|
||||||
ioutil.WriteFile(src, []byte("content"), 0777)
|
|
||||||
ioutil.WriteFile(dest, []byte("destContent"), 0777)
|
|
||||||
bytes, err := CopyFile(src, dest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if bytes != 7 {
|
|
||||||
t.Fatalf("Should have written %d bytes but wrote %d", 7, bytes)
|
|
||||||
}
|
|
||||||
actual, err := ioutil.ReadFile(dest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if string(actual) != "content" {
|
|
||||||
t.Fatalf("Dest content was '%s', expected '%s'", string(actual), "content")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reading a symlink to a directory must return the directory
|
|
||||||
func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) {
|
|
||||||
var err error
|
|
||||||
if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil {
|
|
||||||
t.Errorf("failed to create directory: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil {
|
|
||||||
t.Errorf("failed to create symlink: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var path string
|
|
||||||
if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil {
|
|
||||||
t.Fatalf("failed to read symlink to directory: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if path != "/tmp/testReadSymlinkToExistingDirectory" {
|
|
||||||
t.Fatalf("symlink returned unexpected directory: %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil {
|
|
||||||
t.Errorf("failed to remove temporary directory: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.Remove("/tmp/dirLinkTest"); err != nil {
|
|
||||||
t.Errorf("failed to remove symlink: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reading a non-existing symlink must fail
|
|
||||||
func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) {
|
|
||||||
var path string
|
|
||||||
var err error
|
|
||||||
if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil {
|
|
||||||
t.Fatalf("error expected for non-existing symlink")
|
|
||||||
}
|
|
||||||
|
|
||||||
if path != "" {
|
|
||||||
t.Fatalf("expected empty path, but '%s' was returned", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reading a symlink to a file must fail
|
|
||||||
func TestReadSymlinkedDirectoryToFile(t *testing.T) {
|
|
||||||
var err error
|
|
||||||
var file *os.File
|
|
||||||
|
|
||||||
if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil {
|
|
||||||
t.Fatalf("failed to create file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file.Close()
|
|
||||||
|
|
||||||
if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil {
|
|
||||||
t.Errorf("failed to create symlink: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var path string
|
|
||||||
if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil {
|
|
||||||
t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if path != "" {
|
|
||||||
t.Fatalf("path should've been empty: %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil {
|
|
||||||
t.Errorf("failed to remove file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.Remove("/tmp/fileLinkTest"); err != nil {
|
|
||||||
t.Errorf("failed to remove symlink: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWildcardMatches(t *testing.T) {
|
|
||||||
match, _ := Matches("fileutils.go", []string{"*"})
|
|
||||||
if match != true {
|
|
||||||
t.Errorf("failed to get a wildcard match, got %v", match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A simple pattern match should return true.
|
|
||||||
func TestPatternMatches(t *testing.T) {
|
|
||||||
match, _ := Matches("fileutils.go", []string{"*.go"})
|
|
||||||
if match != true {
|
|
||||||
t.Errorf("failed to get a match, got %v", match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// An exclusion followed by an inclusion should return true.
|
|
||||||
func TestExclusionPatternMatchesPatternBefore(t *testing.T) {
|
|
||||||
match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"})
|
|
||||||
if match != true {
|
|
||||||
t.Errorf("failed to get true match on exclusion pattern, got %v", match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A folder pattern followed by an exception should return false.
|
|
||||||
func TestPatternMatchesFolderExclusions(t *testing.T) {
|
|
||||||
match, _ := Matches("docs/README.md", []string{"docs", "!docs/README.md"})
|
|
||||||
if match != false {
|
|
||||||
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A folder pattern followed by an exception should return false.
|
|
||||||
func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) {
|
|
||||||
match, _ := Matches("docs/README.md", []string{"docs/", "!docs/README.md"})
|
|
||||||
if match != false {
|
|
||||||
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A folder pattern followed by an exception should return false.
|
|
||||||
func TestPatternMatchesFolderWildcardExclusions(t *testing.T) {
|
|
||||||
match, _ := Matches("docs/README.md", []string{"docs/*", "!docs/README.md"})
|
|
||||||
if match != false {
|
|
||||||
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A pattern followed by an exclusion should return false.
|
|
||||||
func TestExclusionPatternMatchesPatternAfter(t *testing.T) {
|
|
||||||
match, _ := Matches("fileutils.go", []string{"*.go", "!fileutils.go"})
|
|
||||||
if match != false {
|
|
||||||
t.Errorf("failed to get false match on exclusion pattern, got %v", match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A filename evaluating to . should return false.
|
|
||||||
func TestExclusionPatternMatchesWholeDirectory(t *testing.T) {
|
|
||||||
match, _ := Matches(".", []string{"*.go"})
|
|
||||||
if match != false {
|
|
||||||
t.Errorf("failed to get false match on ., got %v", match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A single ! pattern should return an error.
|
|
||||||
func TestSingleExclamationError(t *testing.T) {
|
|
||||||
_, err := Matches("fileutils.go", []string{"!"})
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("failed to get an error for a single exclamation point, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A string preceded with a ! should return true from Exclusion.
|
|
||||||
func TestExclusion(t *testing.T) {
|
|
||||||
exclusion := exclusion("!")
|
|
||||||
if !exclusion {
|
|
||||||
t.Errorf("failed to get true for a single !, got %v", exclusion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matches with no patterns
|
|
||||||
func TestMatchesWithNoPatterns(t *testing.T) {
|
|
||||||
matches, err := Matches("/any/path/there", []string{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if matches {
|
|
||||||
t.Fatalf("Should not have match anything")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Matches with malformed patterns
|
|
||||||
func TestMatchesWithMalformedPatterns(t *testing.T) {
|
|
||||||
matches, err := Matches("/any/path/there", []string{"["})
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Should have failed because of a malformed syntax in the pattern")
|
|
||||||
}
|
|
||||||
if matches {
|
|
||||||
t.Fatalf("Should not have match anything")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test lots of variants of patterns & strings
|
|
||||||
func TestMatches(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
pattern string
|
|
||||||
text string
|
|
||||||
pass bool
|
|
||||||
}{
|
|
||||||
{"**", "file", true},
|
|
||||||
{"**", "file/", true},
|
|
||||||
{"**/", "file", true}, // weird one
|
|
||||||
{"**/", "file/", true},
|
|
||||||
{"**", "/", true},
|
|
||||||
{"**/", "/", true},
|
|
||||||
{"**", "dir/file", true},
|
|
||||||
{"**/", "dir/file", false},
|
|
||||||
{"**", "dir/file/", true},
|
|
||||||
{"**/", "dir/file/", true},
|
|
||||||
{"**/**", "dir/file", true},
|
|
||||||
{"**/**", "dir/file/", true},
|
|
||||||
{"dir/**", "dir/file", true},
|
|
||||||
{"dir/**", "dir/file/", true},
|
|
||||||
{"dir/**", "dir/dir2/file", true},
|
|
||||||
{"dir/**", "dir/dir2/file/", true},
|
|
||||||
{"**/dir2/*", "dir/dir2/file", true},
|
|
||||||
{"**/dir2/*", "dir/dir2/file/", false},
|
|
||||||
{"**/dir2/**", "dir/dir2/dir3/file", true},
|
|
||||||
{"**/dir2/**", "dir/dir2/dir3/file/", true},
|
|
||||||
{"**file", "file", true},
|
|
||||||
{"**file", "dir/file", true},
|
|
||||||
{"**/file", "dir/file", true},
|
|
||||||
{"**file", "dir/dir/file", true},
|
|
||||||
{"**/file", "dir/dir/file", true},
|
|
||||||
{"**/file*", "dir/dir/file", true},
|
|
||||||
{"**/file*", "dir/dir/file.txt", true},
|
|
||||||
{"**/file*txt", "dir/dir/file.txt", true},
|
|
||||||
{"**/file*.txt", "dir/dir/file.txt", true},
|
|
||||||
{"**/file*.txt*", "dir/dir/file.txt", true},
|
|
||||||
{"**/**/*.txt", "dir/dir/file.txt", true},
|
|
||||||
{"**/**/*.txt2", "dir/dir/file.txt", false},
|
|
||||||
{"**/*.txt", "file.txt", true},
|
|
||||||
{"**/**/*.txt", "file.txt", true},
|
|
||||||
{"a**/*.txt", "a/file.txt", true},
|
|
||||||
{"a**/*.txt", "a/dir/file.txt", true},
|
|
||||||
{"a**/*.txt", "a/dir/dir/file.txt", true},
|
|
||||||
{"a/*.txt", "a/dir/file.txt", false},
|
|
||||||
{"a/*.txt", "a/file.txt", true},
|
|
||||||
{"a/*.txt**", "a/file.txt", true},
|
|
||||||
{"a[b-d]e", "ae", false},
|
|
||||||
{"a[b-d]e", "ace", true},
|
|
||||||
{"a[b-d]e", "aae", false},
|
|
||||||
{"a[^b-d]e", "aze", true},
|
|
||||||
{".*", ".foo", true},
|
|
||||||
{".*", "foo", false},
|
|
||||||
{"abc.def", "abcdef", false},
|
|
||||||
{"abc.def", "abc.def", true},
|
|
||||||
{"abc.def", "abcZdef", false},
|
|
||||||
{"abc?def", "abcZdef", true},
|
|
||||||
{"abc?def", "abcdef", false},
|
|
||||||
{"a\\*b", "a*b", true},
|
|
||||||
{"a\\", "a", false},
|
|
||||||
{"a\\", "a\\", false},
|
|
||||||
{"a\\\\", "a\\", true},
|
|
||||||
{"**/foo/bar", "foo/bar", true},
|
|
||||||
{"**/foo/bar", "dir/foo/bar", true},
|
|
||||||
{"**/foo/bar", "dir/dir2/foo/bar", true},
|
|
||||||
{"abc/**", "abc", false},
|
|
||||||
{"abc/**", "abc/def", true},
|
|
||||||
{"abc/**", "abc/def/ghi", true},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
res, _ := regexpMatch(test.pattern, test.text)
|
|
||||||
if res != test.pass {
|
|
||||||
t.Fatalf("Failed: %v - res:%v", test, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// An empty string should return true from Empty.
|
|
||||||
func TestEmpty(t *testing.T) {
|
|
||||||
empty := empty("")
|
|
||||||
if !empty {
|
|
||||||
t.Errorf("failed to get true for an empty string, got %v", empty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanPatterns(t *testing.T) {
|
|
||||||
cleaned, _, _, _ := CleanPatterns([]string{"docs", "config"})
|
|
||||||
if len(cleaned) != 2 {
|
|
||||||
t.Errorf("expected 2 element slice, got %v", len(cleaned))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanPatternsStripEmptyPatterns(t *testing.T) {
|
|
||||||
cleaned, _, _, _ := CleanPatterns([]string{"docs", "config", ""})
|
|
||||||
if len(cleaned) != 2 {
|
|
||||||
t.Errorf("expected 2 element slice, got %v", len(cleaned))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanPatternsExceptionFlag(t *testing.T) {
|
|
||||||
_, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md"})
|
|
||||||
if !exceptions {
|
|
||||||
t.Errorf("expected exceptions to be true, got %v", exceptions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanPatternsLeadingSpaceTrimmed(t *testing.T) {
|
|
||||||
_, _, exceptions, _ := CleanPatterns([]string{"docs", " !docs/README.md"})
|
|
||||||
if !exceptions {
|
|
||||||
t.Errorf("expected exceptions to be true, got %v", exceptions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanPatternsTrailingSpaceTrimmed(t *testing.T) {
|
|
||||||
_, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md "})
|
|
||||||
if !exceptions {
|
|
||||||
t.Errorf("expected exceptions to be true, got %v", exceptions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanPatternsErrorSingleException(t *testing.T) {
|
|
||||||
_, _, _, err := CleanPatterns([]string{"!"})
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("expected error on single exclamation point, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanPatternsFolderSplit(t *testing.T) {
|
|
||||||
_, dirs, _, _ := CleanPatterns([]string{"docs/config/CONFIG.md"})
|
|
||||||
if dirs[0][0] != "docs" {
|
|
||||||
t.Errorf("expected first element in dirs slice to be docs, got %v", dirs[0][1])
|
|
||||||
}
|
|
||||||
if dirs[0][1] != "config" {
|
|
||||||
t.Errorf("expected first element in dirs slice to be config, got %v", dirs[0][1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateIfNotExistsDir(t *testing.T) {
|
|
||||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tempFolder)
|
|
||||||
|
|
||||||
folderToCreate := filepath.Join(tempFolder, "tocreate")
|
|
||||||
|
|
||||||
if err := CreateIfNotExists(folderToCreate, true); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fileinfo, err := os.Stat(folderToCreate)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Should have create a folder, got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fileinfo.IsDir() {
|
|
||||||
t.Fatalf("Should have been a dir, seems it's not")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateIfNotExistsFile(t *testing.T) {
|
|
||||||
tempFolder, err := ioutil.TempDir("", "docker-fileutils-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tempFolder)
|
|
||||||
|
|
||||||
fileToCreate := filepath.Join(tempFolder, "file/to/create")
|
|
||||||
|
|
||||||
if err := CreateIfNotExists(fileToCreate, false); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
fileinfo, err := os.Stat(fileToCreate)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Should have create a file, got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fileinfo.IsDir() {
|
|
||||||
t.Fatalf("Should have been a file, seems it's not")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// These matchTests are stolen from go's filepath Match tests.
|
|
||||||
type matchTest struct {
|
|
||||||
pattern, s string
|
|
||||||
match bool
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchTests = []matchTest{
|
|
||||||
{"abc", "abc", true, nil},
|
|
||||||
{"*", "abc", true, nil},
|
|
||||||
{"*c", "abc", true, nil},
|
|
||||||
{"a*", "a", true, nil},
|
|
||||||
{"a*", "abc", true, nil},
|
|
||||||
{"a*", "ab/c", false, nil},
|
|
||||||
{"a*/b", "abc/b", true, nil},
|
|
||||||
{"a*/b", "a/c/b", false, nil},
|
|
||||||
{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
|
|
||||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
|
|
||||||
{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
|
|
||||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
|
|
||||||
{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
|
|
||||||
{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
|
|
||||||
{"ab[c]", "abc", true, nil},
|
|
||||||
{"ab[b-d]", "abc", true, nil},
|
|
||||||
{"ab[e-g]", "abc", false, nil},
|
|
||||||
{"ab[^c]", "abc", false, nil},
|
|
||||||
{"ab[^b-d]", "abc", false, nil},
|
|
||||||
{"ab[^e-g]", "abc", true, nil},
|
|
||||||
{"a\\*b", "a*b", true, nil},
|
|
||||||
{"a\\*b", "ab", false, nil},
|
|
||||||
{"a?b", "a☺b", true, nil},
|
|
||||||
{"a[^a]b", "a☺b", true, nil},
|
|
||||||
{"a???b", "a☺b", false, nil},
|
|
||||||
{"a[^a][^a][^a]b", "a☺b", false, nil},
|
|
||||||
{"[a-ζ]*", "α", true, nil},
|
|
||||||
{"*[a-ζ]", "A", false, nil},
|
|
||||||
{"a?b", "a/b", false, nil},
|
|
||||||
{"a*b", "a/b", false, nil},
|
|
||||||
{"[\\]a]", "]", true, nil},
|
|
||||||
{"[\\-]", "-", true, nil},
|
|
||||||
{"[x\\-]", "x", true, nil},
|
|
||||||
{"[x\\-]", "-", true, nil},
|
|
||||||
{"[x\\-]", "z", false, nil},
|
|
||||||
{"[\\-x]", "x", true, nil},
|
|
||||||
{"[\\-x]", "-", true, nil},
|
|
||||||
{"[\\-x]", "a", false, nil},
|
|
||||||
{"[]a]", "]", false, filepath.ErrBadPattern},
|
|
||||||
{"[-]", "-", false, filepath.ErrBadPattern},
|
|
||||||
{"[x-]", "x", false, filepath.ErrBadPattern},
|
|
||||||
{"[x-]", "-", false, filepath.ErrBadPattern},
|
|
||||||
{"[x-]", "z", false, filepath.ErrBadPattern},
|
|
||||||
{"[-x]", "x", false, filepath.ErrBadPattern},
|
|
||||||
{"[-x]", "-", false, filepath.ErrBadPattern},
|
|
||||||
{"[-x]", "a", false, filepath.ErrBadPattern},
|
|
||||||
{"\\", "a", false, filepath.ErrBadPattern},
|
|
||||||
{"[a-b-c]", "a", false, filepath.ErrBadPattern},
|
|
||||||
{"[", "a", false, filepath.ErrBadPattern},
|
|
||||||
{"[^", "a", false, filepath.ErrBadPattern},
|
|
||||||
{"[^bc", "a", false, filepath.ErrBadPattern},
|
|
||||||
{"a[", "a", false, filepath.ErrBadPattern}, // was nil but IMO its wrong
|
|
||||||
{"a[", "ab", false, filepath.ErrBadPattern},
|
|
||||||
{"*x", "xxx", true, nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
func errp(e error) string {
|
|
||||||
if e == nil {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
return e.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestMatch test's our version of filepath.Match, called regexpMatch.
|
|
||||||
func TestMatch(t *testing.T) {
|
|
||||||
for _, tt := range matchTests {
|
|
||||||
pattern := tt.pattern
|
|
||||||
s := tt.s
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
if strings.Index(pattern, "\\") >= 0 {
|
|
||||||
// no escape allowed on windows.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pattern = filepath.Clean(pattern)
|
|
||||||
s = filepath.Clean(s)
|
|
||||||
}
|
|
||||||
ok, err := regexpMatch(pattern, s)
|
|
||||||
if ok != tt.match || err != tt.err {
|
|
||||||
t.Fatalf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils_unix.go
generated
vendored
22
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils_unix.go
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
// +build linux freebsd
|
|
||||||
|
|
||||||
package fileutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetTotalUsedFds Returns the number of used File Descriptors by
|
|
||||||
// reading it via /proc filesystem.
|
|
||||||
func GetTotalUsedFds() int {
|
|
||||||
if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil {
|
|
||||||
logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err)
|
|
||||||
} else {
|
|
||||||
return len(fds)
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
7
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils_windows.go
generated
vendored
7
Godeps/_workspace/src/github.com/docker/docker/pkg/fileutils/fileutils_windows.go
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
package fileutils
|
|
||||||
|
|
||||||
// GetTotalUsedFds Returns the number of used File Descriptors. Not supported
|
|
||||||
// on Windows.
|
|
||||||
func GetTotalUsedFds() int {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
195
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/idtools.go
generated
vendored
195
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/idtools.go
generated
vendored
@@ -1,195 +0,0 @@
|
|||||||
package idtools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IDMap contains a single entry for user namespace range remapping. An array
|
|
||||||
// of IDMap entries represents the structure that will be provided to the Linux
|
|
||||||
// kernel for creating a user namespace.
|
|
||||||
type IDMap struct {
|
|
||||||
ContainerID int `json:"container_id"`
|
|
||||||
HostID int `json:"host_id"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type subIDRange struct {
|
|
||||||
Start int
|
|
||||||
Length int
|
|
||||||
}
|
|
||||||
|
|
||||||
type ranges []subIDRange
|
|
||||||
|
|
||||||
func (e ranges) Len() int { return len(e) }
|
|
||||||
func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
|
||||||
func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
|
|
||||||
|
|
||||||
const (
|
|
||||||
subuidFileName string = "/etc/subuid"
|
|
||||||
subgidFileName string = "/etc/subgid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MkdirAllAs creates a directory (include any along the path) and then modifies
|
|
||||||
// ownership to the requested uid/gid. If the directory already exists, this
|
|
||||||
// function will still change ownership to the requested uid/gid pair.
|
|
||||||
func MkdirAllAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
|
||||||
return mkdirAs(path, mode, ownerUID, ownerGID, true, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MkdirAllNewAs creates a directory (include any along the path) and then modifies
|
|
||||||
// ownership ONLY of newly created directories to the requested uid/gid. If the
|
|
||||||
// directories along the path exist, no change of ownership will be performed
|
|
||||||
func MkdirAllNewAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
|
||||||
return mkdirAs(path, mode, ownerUID, ownerGID, true, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MkdirAs creates a directory and then modifies ownership to the requested uid/gid.
|
|
||||||
// If the directory already exists, this function still changes ownership
|
|
||||||
func MkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int) error {
|
|
||||||
return mkdirAs(path, mode, ownerUID, ownerGID, false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
|
|
||||||
// If the maps are empty, then the root uid/gid will default to "real" 0/0
|
|
||||||
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
|
|
||||||
var uid, gid int
|
|
||||||
|
|
||||||
if uidMap != nil {
|
|
||||||
xUID, err := ToHost(0, uidMap)
|
|
||||||
if err != nil {
|
|
||||||
return -1, -1, err
|
|
||||||
}
|
|
||||||
uid = xUID
|
|
||||||
}
|
|
||||||
if gidMap != nil {
|
|
||||||
xGID, err := ToHost(0, gidMap)
|
|
||||||
if err != nil {
|
|
||||||
return -1, -1, err
|
|
||||||
}
|
|
||||||
gid = xGID
|
|
||||||
}
|
|
||||||
return uid, gid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToContainer takes an id mapping, and uses it to translate a
|
|
||||||
// host ID to the remapped ID. If no map is provided, then the translation
|
|
||||||
// assumes a 1-to-1 mapping and returns the passed in id
|
|
||||||
func ToContainer(hostID int, idMap []IDMap) (int, error) {
|
|
||||||
if idMap == nil {
|
|
||||||
return hostID, nil
|
|
||||||
}
|
|
||||||
for _, m := range idMap {
|
|
||||||
if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
|
|
||||||
contID := m.ContainerID + (hostID - m.HostID)
|
|
||||||
return contID, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToHost takes an id mapping and a remapped ID, and translates the
|
|
||||||
// ID to the mapped host ID. If no map is provided, then the translation
|
|
||||||
// assumes a 1-to-1 mapping and returns the passed in id #
|
|
||||||
func ToHost(contID int, idMap []IDMap) (int, error) {
|
|
||||||
if idMap == nil {
|
|
||||||
return contID, nil
|
|
||||||
}
|
|
||||||
for _, m := range idMap {
|
|
||||||
if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
|
|
||||||
hostID := m.HostID + (contID - m.ContainerID)
|
|
||||||
return hostID, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateIDMappings takes a requested user and group name and
|
|
||||||
// using the data from /etc/sub{uid,gid} ranges, creates the
|
|
||||||
// proper uid and gid remapping ranges for that user/group pair
|
|
||||||
func CreateIDMappings(username, groupname string) ([]IDMap, []IDMap, error) {
|
|
||||||
subuidRanges, err := parseSubuid(username)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
subgidRanges, err := parseSubgid(groupname)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if len(subuidRanges) == 0 {
|
|
||||||
return nil, nil, fmt.Errorf("No subuid ranges found for user %q", username)
|
|
||||||
}
|
|
||||||
if len(subgidRanges) == 0 {
|
|
||||||
return nil, nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
|
|
||||||
}
|
|
||||||
|
|
||||||
return createIDMap(subuidRanges), createIDMap(subgidRanges), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createIDMap(subidRanges ranges) []IDMap {
|
|
||||||
idMap := []IDMap{}
|
|
||||||
|
|
||||||
// sort the ranges by lowest ID first
|
|
||||||
sort.Sort(subidRanges)
|
|
||||||
containerID := 0
|
|
||||||
for _, idrange := range subidRanges {
|
|
||||||
idMap = append(idMap, IDMap{
|
|
||||||
ContainerID: containerID,
|
|
||||||
HostID: idrange.Start,
|
|
||||||
Size: idrange.Length,
|
|
||||||
})
|
|
||||||
containerID = containerID + idrange.Length
|
|
||||||
}
|
|
||||||
return idMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSubuid(username string) (ranges, error) {
|
|
||||||
return parseSubidFile(subuidFileName, username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSubgid(username string) (ranges, error) {
|
|
||||||
return parseSubidFile(subgidFileName, username)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSubidFile(path, username string) (ranges, error) {
|
|
||||||
var rangeList ranges
|
|
||||||
|
|
||||||
subidFile, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return rangeList, err
|
|
||||||
}
|
|
||||||
defer subidFile.Close()
|
|
||||||
|
|
||||||
s := bufio.NewScanner(subidFile)
|
|
||||||
for s.Scan() {
|
|
||||||
if err := s.Err(); err != nil {
|
|
||||||
return rangeList, err
|
|
||||||
}
|
|
||||||
|
|
||||||
text := strings.TrimSpace(s.Text())
|
|
||||||
if text == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.Split(text, ":")
|
|
||||||
if len(parts) != 3 {
|
|
||||||
return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
|
|
||||||
}
|
|
||||||
if parts[0] == username {
|
|
||||||
// return the first entry for a user; ignores potential for multiple ranges per user
|
|
||||||
startid, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
|
|
||||||
}
|
|
||||||
length, err := strconv.Atoi(parts[2])
|
|
||||||
if err != nil {
|
|
||||||
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
|
|
||||||
}
|
|
||||||
rangeList = append(rangeList, subIDRange{startid, length})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rangeList, nil
|
|
||||||
}
|
|
||||||
60
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/idtools_unix.go
generated
vendored
60
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/idtools_unix.go
generated
vendored
@@ -1,60 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package idtools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
|
||||||
// make an array containing the original path asked for, plus (for mkAll == true)
|
|
||||||
// all path components leading up to the complete path that don't exist before we MkdirAll
|
|
||||||
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
|
|
||||||
// chown the full directory path if it exists
|
|
||||||
var paths []string
|
|
||||||
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
|
||||||
paths = []string{path}
|
|
||||||
} else if err == nil && chownExisting {
|
|
||||||
if err := os.Chown(path, ownerUID, ownerGID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// short-circuit--we were called with an existing directory and chown was requested
|
|
||||||
return nil
|
|
||||||
} else if err == nil {
|
|
||||||
// nothing to do; directory path fully exists already and chown was NOT requested
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if mkAll {
|
|
||||||
// walk back to "/" looking for directories which do not exist
|
|
||||||
// and add them to the paths array for chown after creation
|
|
||||||
dirPath := path
|
|
||||||
for {
|
|
||||||
dirPath = filepath.Dir(dirPath)
|
|
||||||
if dirPath == "/" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
|
|
||||||
paths = append(paths, dirPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// even if it existed, we will chown the requested path + any subpaths that
|
|
||||||
// didn't exist when we called MkdirAll
|
|
||||||
for _, pathComponent := range paths {
|
|
||||||
if err := os.Chown(pathComponent, ownerUID, ownerGID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
243
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/idtools_unix_test.go
generated
vendored
243
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/idtools_unix_test.go
generated
vendored
@@ -1,243 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package idtools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
type node struct {
|
|
||||||
uid int
|
|
||||||
gid int
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMkdirAllAs(t *testing.T) {
|
|
||||||
dirName, err := ioutil.TempDir("", "mkdirall")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Couldn't create temp dir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dirName)
|
|
||||||
|
|
||||||
testTree := map[string]node{
|
|
||||||
"usr": {0, 0},
|
|
||||||
"usr/bin": {0, 0},
|
|
||||||
"lib": {33, 33},
|
|
||||||
"lib/x86_64": {45, 45},
|
|
||||||
"lib/x86_64/share": {1, 1},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := buildTree(dirName, testTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
|
|
||||||
if err := MkdirAllAs(filepath.Join(dirName, "usr", "share"), 0755, 99, 99); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testTree["usr/share"] = node{99, 99}
|
|
||||||
verifyTree, err := readTree(dirName, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test 2-deep new directories--both should be owned by the uid/gid pair
|
|
||||||
if err := MkdirAllAs(filepath.Join(dirName, "lib", "some", "other"), 0755, 101, 101); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testTree["lib/some"] = node{101, 101}
|
|
||||||
testTree["lib/some/other"] = node{101, 101}
|
|
||||||
verifyTree, err = readTree(dirName, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test a directory that already exists; should be chowned, but nothing else
|
|
||||||
if err := MkdirAllAs(filepath.Join(dirName, "usr"), 0755, 102, 102); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testTree["usr"] = node{102, 102}
|
|
||||||
verifyTree, err = readTree(dirName, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMkdirAllNewAs(t *testing.T) {
|
|
||||||
|
|
||||||
dirName, err := ioutil.TempDir("", "mkdirnew")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Couldn't create temp dir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dirName)
|
|
||||||
|
|
||||||
testTree := map[string]node{
|
|
||||||
"usr": {0, 0},
|
|
||||||
"usr/bin": {0, 0},
|
|
||||||
"lib": {33, 33},
|
|
||||||
"lib/x86_64": {45, 45},
|
|
||||||
"lib/x86_64/share": {1, 1},
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := buildTree(dirName, testTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test adding a directory to a pre-existing dir; only the new dir is owned by the uid/gid
|
|
||||||
if err := MkdirAllNewAs(filepath.Join(dirName, "usr", "share"), 0755, 99, 99); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testTree["usr/share"] = node{99, 99}
|
|
||||||
verifyTree, err := readTree(dirName, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test 2-deep new directories--both should be owned by the uid/gid pair
|
|
||||||
if err := MkdirAllNewAs(filepath.Join(dirName, "lib", "some", "other"), 0755, 101, 101); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testTree["lib/some"] = node{101, 101}
|
|
||||||
testTree["lib/some/other"] = node{101, 101}
|
|
||||||
verifyTree, err = readTree(dirName, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test a directory that already exists; should NOT be chowned
|
|
||||||
if err := MkdirAllNewAs(filepath.Join(dirName, "usr"), 0755, 102, 102); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
verifyTree, err = readTree(dirName, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMkdirAs(t *testing.T) {
|
|
||||||
|
|
||||||
dirName, err := ioutil.TempDir("", "mkdir")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Couldn't create temp dir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dirName)
|
|
||||||
|
|
||||||
testTree := map[string]node{
|
|
||||||
"usr": {0, 0},
|
|
||||||
}
|
|
||||||
if err := buildTree(dirName, testTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test a directory that already exists; should just chown to the requested uid/gid
|
|
||||||
if err := MkdirAs(filepath.Join(dirName, "usr"), 0755, 99, 99); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testTree["usr"] = node{99, 99}
|
|
||||||
verifyTree, err := readTree(dirName, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a subdir under a dir which doesn't exist--should fail
|
|
||||||
if err := MkdirAs(filepath.Join(dirName, "usr", "bin", "subdir"), 0755, 102, 102); err == nil {
|
|
||||||
t.Fatalf("Trying to create a directory with Mkdir where the parent doesn't exist should have failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a subdir under an existing dir; should only change the ownership of the new subdir
|
|
||||||
if err := MkdirAs(filepath.Join(dirName, "usr", "bin"), 0755, 102, 102); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testTree["usr/bin"] = node{102, 102}
|
|
||||||
verifyTree, err = readTree(dirName, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := compareTrees(testTree, verifyTree); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildTree(base string, tree map[string]node) error {
|
|
||||||
for path, node := range tree {
|
|
||||||
fullPath := filepath.Join(base, path)
|
|
||||||
if err := os.MkdirAll(fullPath, 0755); err != nil {
|
|
||||||
return fmt.Errorf("Couldn't create path: %s; error: %v", fullPath, err)
|
|
||||||
}
|
|
||||||
if err := os.Chown(fullPath, node.uid, node.gid); err != nil {
|
|
||||||
return fmt.Errorf("Couldn't chown path: %s; error: %v", fullPath, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTree(base, root string) (map[string]node, error) {
|
|
||||||
tree := make(map[string]node)
|
|
||||||
|
|
||||||
dirInfos, err := ioutil.ReadDir(base)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Couldn't read directory entries for %q: %v", base, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, info := range dirInfos {
|
|
||||||
s := &syscall.Stat_t{}
|
|
||||||
if err := syscall.Stat(filepath.Join(base, info.Name()), s); err != nil {
|
|
||||||
return nil, fmt.Errorf("Can't stat file %q: %v", filepath.Join(base, info.Name()), err)
|
|
||||||
}
|
|
||||||
tree[filepath.Join(root, info.Name())] = node{int(s.Uid), int(s.Gid)}
|
|
||||||
if info.IsDir() {
|
|
||||||
// read the subdirectory
|
|
||||||
subtree, err := readTree(filepath.Join(base, info.Name()), filepath.Join(root, info.Name()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for path, nodeinfo := range subtree {
|
|
||||||
tree[path] = nodeinfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareTrees(left, right map[string]node) error {
|
|
||||||
if len(left) != len(right) {
|
|
||||||
return fmt.Errorf("Trees aren't the same size")
|
|
||||||
}
|
|
||||||
for path, nodeLeft := range left {
|
|
||||||
if nodeRight, ok := right[path]; ok {
|
|
||||||
if nodeRight.uid != nodeLeft.uid || nodeRight.gid != nodeLeft.gid {
|
|
||||||
// mismatch
|
|
||||||
return fmt.Errorf("mismatched ownership for %q: expected: %d:%d, got: %d:%d", path,
|
|
||||||
nodeLeft.uid, nodeLeft.gid, nodeRight.uid, nodeRight.gid)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return fmt.Errorf("right tree didn't contain path %q", path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
18
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/idtools_windows.go
generated
vendored
18
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/idtools_windows.go
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package idtools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Platforms such as Windows do not support the UID/GID concept. So make this
|
|
||||||
// just a wrapper around system.MkdirAll.
|
|
||||||
func mkdirAs(path string, mode os.FileMode, ownerUID, ownerGID int, mkAll, chownExisting bool) error {
|
|
||||||
if err := system.MkdirAll(path, mode); err != nil && !os.IsExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
155
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go
generated
vendored
155
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go
generated
vendored
@@ -1,155 +0,0 @@
|
|||||||
package idtools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
|
|
||||||
// Linux distribution commands:
|
|
||||||
// adduser --uid <id> --shell /bin/login --no-create-home --disabled-login --ingroup <groupname> <username>
|
|
||||||
// useradd -M -u <id> -s /bin/nologin -N -g <groupname> <username>
|
|
||||||
// addgroup --gid <id> <groupname>
|
|
||||||
// groupadd -g <id> <groupname>
|
|
||||||
|
|
||||||
const baseUID int = 10000
|
|
||||||
const baseGID int = 10000
|
|
||||||
const idMAX int = 65534
|
|
||||||
|
|
||||||
var (
|
|
||||||
userCommand string
|
|
||||||
groupCommand string
|
|
||||||
|
|
||||||
cmdTemplates = map[string]string{
|
|
||||||
"adduser": "--uid %d --shell /bin/false --no-create-home --disabled-login --ingroup %s %s",
|
|
||||||
"useradd": "-M -u %d -s /bin/false -N -g %s %s",
|
|
||||||
"addgroup": "--gid %d %s",
|
|
||||||
"groupadd": "-g %d %s",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// set up which commands are used for adding users/groups dependent on distro
|
|
||||||
if _, err := resolveBinary("adduser"); err == nil {
|
|
||||||
userCommand = "adduser"
|
|
||||||
} else if _, err := resolveBinary("useradd"); err == nil {
|
|
||||||
userCommand = "useradd"
|
|
||||||
}
|
|
||||||
if _, err := resolveBinary("addgroup"); err == nil {
|
|
||||||
groupCommand = "addgroup"
|
|
||||||
} else if _, err := resolveBinary("groupadd"); err == nil {
|
|
||||||
groupCommand = "groupadd"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveBinary(binname string) (string, error) {
|
|
||||||
binaryPath, err := exec.LookPath(binname)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
resolvedPath, err := filepath.EvalSymlinks(binaryPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
//only return no error if the final resolved binary basename
|
|
||||||
//matches what was searched for
|
|
||||||
if filepath.Base(resolvedPath) == binname {
|
|
||||||
return resolvedPath, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
|
|
||||||
// and calls the appropriate helper function to add the group and then
|
|
||||||
// the user to the group in /etc/group and /etc/passwd respectively.
|
|
||||||
// This new user's /etc/sub{uid,gid} ranges will be used for user namespace
|
|
||||||
// mapping ranges in containers.
|
|
||||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
|
||||||
// Find unused uid, gid pair
|
|
||||||
uid, err := findUnusedUID(baseUID)
|
|
||||||
if err != nil {
|
|
||||||
return -1, -1, fmt.Errorf("Unable to find unused UID: %v", err)
|
|
||||||
}
|
|
||||||
gid, err := findUnusedGID(baseGID)
|
|
||||||
if err != nil {
|
|
||||||
return -1, -1, fmt.Errorf("Unable to find unused GID: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// First add the group that we will use
|
|
||||||
if err := addGroup(name, gid); err != nil {
|
|
||||||
return -1, -1, fmt.Errorf("Error adding group %q: %v", name, err)
|
|
||||||
}
|
|
||||||
// Add the user as a member of the group
|
|
||||||
if err := addUser(name, uid, name); err != nil {
|
|
||||||
return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err)
|
|
||||||
}
|
|
||||||
return uid, gid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func addUser(userName string, uid int, groupName string) error {
|
|
||||||
|
|
||||||
if userCommand == "" {
|
|
||||||
return fmt.Errorf("Cannot add user; no useradd/adduser binary found")
|
|
||||||
}
|
|
||||||
args := fmt.Sprintf(cmdTemplates[userCommand], uid, groupName, userName)
|
|
||||||
return execAddCmd(userCommand, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addGroup(groupName string, gid int) error {
|
|
||||||
|
|
||||||
if groupCommand == "" {
|
|
||||||
return fmt.Errorf("Cannot add group; no groupadd/addgroup binary found")
|
|
||||||
}
|
|
||||||
args := fmt.Sprintf(cmdTemplates[groupCommand], gid, groupName)
|
|
||||||
// only error out if the error isn't that the group already exists
|
|
||||||
// if the group exists then our needs are already met
|
|
||||||
if err := execAddCmd(groupCommand, args); err != nil && !strings.Contains(err.Error(), "already exists") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func execAddCmd(cmd, args string) error {
|
|
||||||
execCmd := exec.Command(cmd, strings.Split(args, " ")...)
|
|
||||||
out, err := execCmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to add user/group with error: %v; output: %q", err, string(out))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findUnusedUID(startUID int) (int, error) {
|
|
||||||
return findUnused("passwd", startUID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findUnusedGID(startGID int) (int, error) {
|
|
||||||
return findUnused("group", startGID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findUnused(file string, id int) (int, error) {
|
|
||||||
for {
|
|
||||||
cmdStr := fmt.Sprintf("cat /etc/%s | cut -d: -f3 | grep '^%d$'", file, id)
|
|
||||||
cmd := exec.Command("sh", "-c", cmdStr)
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
// if a non-zero return code occurs, then we know the ID was not found
|
|
||||||
// and is usable
|
|
||||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
|
||||||
// The program has exited with an exit code != 0
|
|
||||||
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
|
||||||
if status.ExitStatus() == 1 {
|
|
||||||
//no match, we can use this ID
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1, fmt.Errorf("Error looking in /etc/%s for unused ID: %v", file, err)
|
|
||||||
}
|
|
||||||
id++
|
|
||||||
if id > idMAX {
|
|
||||||
return -1, fmt.Errorf("Maximum id in %q reached with finding unused numeric ID", file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
12
Godeps/_workspace/src/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// +build !linux
|
|
||||||
|
|
||||||
package idtools
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
|
|
||||||
// and calls the appropriate helper function to add the group and then
|
|
||||||
// the user to the group in /etc/group and /etc/passwd respectively.
|
|
||||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
|
||||||
return -1, -1, fmt.Errorf("No support for adding users or groups on this OS")
|
|
||||||
}
|
|
||||||
152
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe.go
generated
vendored
152
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe.go
generated
vendored
@@ -1,152 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// maxCap is the highest capacity to use in byte slices that buffer data.
|
|
||||||
const maxCap = 1e6
|
|
||||||
|
|
||||||
// blockThreshold is the minimum number of bytes in the buffer which will cause
|
|
||||||
// a write to BytesPipe to block when allocating a new slice.
|
|
||||||
const blockThreshold = 1e6
|
|
||||||
|
|
||||||
// ErrClosed is returned when Write is called on a closed BytesPipe.
|
|
||||||
var ErrClosed = errors.New("write to closed BytesPipe")
|
|
||||||
|
|
||||||
// BytesPipe is io.ReadWriteCloser which works similarly to pipe(queue).
|
|
||||||
// All written data may be read at most once. Also, BytesPipe allocates
|
|
||||||
// and releases new byte slices to adjust to current needs, so the buffer
|
|
||||||
// won't be overgrown after peak loads.
|
|
||||||
type BytesPipe struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
wait *sync.Cond
|
|
||||||
buf [][]byte // slice of byte-slices of buffered data
|
|
||||||
lastRead int // index in the first slice to a read point
|
|
||||||
bufLen int // length of data buffered over the slices
|
|
||||||
closeErr error // error to return from next Read. set to nil if not closed.
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBytesPipe creates new BytesPipe, initialized by specified slice.
|
|
||||||
// If buf is nil, then it will be initialized with slice which cap is 64.
|
|
||||||
// buf will be adjusted in a way that len(buf) == 0, cap(buf) == cap(buf).
|
|
||||||
func NewBytesPipe(buf []byte) *BytesPipe {
|
|
||||||
if cap(buf) == 0 {
|
|
||||||
buf = make([]byte, 0, 64)
|
|
||||||
}
|
|
||||||
bp := &BytesPipe{
|
|
||||||
buf: [][]byte{buf[:0]},
|
|
||||||
}
|
|
||||||
bp.wait = sync.NewCond(&bp.mu)
|
|
||||||
return bp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes p to BytesPipe.
|
|
||||||
// It can allocate new []byte slices in a process of writing.
|
|
||||||
func (bp *BytesPipe) Write(p []byte) (int, error) {
|
|
||||||
bp.mu.Lock()
|
|
||||||
defer bp.mu.Unlock()
|
|
||||||
written := 0
|
|
||||||
for {
|
|
||||||
if bp.closeErr != nil {
|
|
||||||
return written, ErrClosed
|
|
||||||
}
|
|
||||||
// write data to the last buffer
|
|
||||||
b := bp.buf[len(bp.buf)-1]
|
|
||||||
// copy data to the current empty allocated area
|
|
||||||
n := copy(b[len(b):cap(b)], p)
|
|
||||||
// increment buffered data length
|
|
||||||
bp.bufLen += n
|
|
||||||
// include written data in last buffer
|
|
||||||
bp.buf[len(bp.buf)-1] = b[:len(b)+n]
|
|
||||||
|
|
||||||
written += n
|
|
||||||
|
|
||||||
// if there was enough room to write all then break
|
|
||||||
if len(p) == n {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// more data: write to the next slice
|
|
||||||
p = p[n:]
|
|
||||||
|
|
||||||
// block if too much data is still in the buffer
|
|
||||||
for bp.bufLen >= blockThreshold {
|
|
||||||
bp.wait.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// allocate slice that has twice the size of the last unless maximum reached
|
|
||||||
nextCap := 2 * cap(bp.buf[len(bp.buf)-1])
|
|
||||||
if nextCap > maxCap {
|
|
||||||
nextCap = maxCap
|
|
||||||
}
|
|
||||||
// add new byte slice to the buffers slice and continue writing
|
|
||||||
bp.buf = append(bp.buf, make([]byte, 0, nextCap))
|
|
||||||
}
|
|
||||||
bp.wait.Broadcast()
|
|
||||||
return written, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseWithError causes further reads from a BytesPipe to return immediately.
|
|
||||||
func (bp *BytesPipe) CloseWithError(err error) error {
|
|
||||||
bp.mu.Lock()
|
|
||||||
if err != nil {
|
|
||||||
bp.closeErr = err
|
|
||||||
} else {
|
|
||||||
bp.closeErr = io.EOF
|
|
||||||
}
|
|
||||||
bp.wait.Broadcast()
|
|
||||||
bp.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close causes further reads from a BytesPipe to return immediately.
|
|
||||||
func (bp *BytesPipe) Close() error {
|
|
||||||
return bp.CloseWithError(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bp *BytesPipe) len() int {
|
|
||||||
return bp.bufLen - bp.lastRead
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads bytes from BytesPipe.
|
|
||||||
// Data could be read only once.
|
|
||||||
func (bp *BytesPipe) Read(p []byte) (n int, err error) {
|
|
||||||
bp.mu.Lock()
|
|
||||||
defer bp.mu.Unlock()
|
|
||||||
if bp.len() == 0 {
|
|
||||||
if bp.closeErr != nil {
|
|
||||||
return 0, bp.closeErr
|
|
||||||
}
|
|
||||||
bp.wait.Wait()
|
|
||||||
if bp.len() == 0 && bp.closeErr != nil {
|
|
||||||
return 0, bp.closeErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
read := copy(p, bp.buf[0][bp.lastRead:])
|
|
||||||
n += read
|
|
||||||
bp.lastRead += read
|
|
||||||
if bp.len() == 0 {
|
|
||||||
// we have read everything. reset to the beginning.
|
|
||||||
bp.lastRead = 0
|
|
||||||
bp.bufLen -= len(bp.buf[0])
|
|
||||||
bp.buf[0] = bp.buf[0][:0]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// break if everything was read
|
|
||||||
if len(p) == read {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// more buffered data and more asked. read from next slice.
|
|
||||||
p = p[read:]
|
|
||||||
bp.lastRead = 0
|
|
||||||
bp.bufLen -= len(bp.buf[0])
|
|
||||||
bp.buf[0] = nil // throw away old slice
|
|
||||||
bp.buf = bp.buf[1:] // switch to next
|
|
||||||
}
|
|
||||||
bp.wait.Broadcast()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
158
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe_test.go
generated
vendored
158
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/bytespipe_test.go
generated
vendored
@@ -1,158 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBytesPipeRead(t *testing.T) {
|
|
||||||
buf := NewBytesPipe(nil)
|
|
||||||
buf.Write([]byte("12"))
|
|
||||||
buf.Write([]byte("34"))
|
|
||||||
buf.Write([]byte("56"))
|
|
||||||
buf.Write([]byte("78"))
|
|
||||||
buf.Write([]byte("90"))
|
|
||||||
rd := make([]byte, 4)
|
|
||||||
n, err := buf.Read(rd)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if n != 4 {
|
|
||||||
t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4)
|
|
||||||
}
|
|
||||||
if string(rd) != "1234" {
|
|
||||||
t.Fatalf("Read %s, but must be %s", rd, "1234")
|
|
||||||
}
|
|
||||||
n, err = buf.Read(rd)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if n != 4 {
|
|
||||||
t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 4)
|
|
||||||
}
|
|
||||||
if string(rd) != "5678" {
|
|
||||||
t.Fatalf("Read %s, but must be %s", rd, "5679")
|
|
||||||
}
|
|
||||||
n, err = buf.Read(rd)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if n != 2 {
|
|
||||||
t.Fatalf("Wrong number of bytes read: %d, should be %d", n, 2)
|
|
||||||
}
|
|
||||||
if string(rd[:n]) != "90" {
|
|
||||||
t.Fatalf("Read %s, but must be %s", rd, "90")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBytesPipeWrite(t *testing.T) {
|
|
||||||
buf := NewBytesPipe(nil)
|
|
||||||
buf.Write([]byte("12"))
|
|
||||||
buf.Write([]byte("34"))
|
|
||||||
buf.Write([]byte("56"))
|
|
||||||
buf.Write([]byte("78"))
|
|
||||||
buf.Write([]byte("90"))
|
|
||||||
if string(buf.buf[0]) != "1234567890" {
|
|
||||||
t.Fatalf("Buffer %s, must be %s", buf.buf, "1234567890")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write and read in different speeds/chunk sizes and check valid data is read.
|
|
||||||
func TestBytesPipeWriteRandomChunks(t *testing.T) {
|
|
||||||
cases := []struct{ iterations, writesPerLoop, readsPerLoop int }{
|
|
||||||
{100, 10, 1},
|
|
||||||
{1000, 10, 5},
|
|
||||||
{1000, 100, 0},
|
|
||||||
{1000, 5, 6},
|
|
||||||
{10000, 50, 25},
|
|
||||||
}
|
|
||||||
|
|
||||||
testMessage := []byte("this is a random string for testing")
|
|
||||||
// random slice sizes to read and write
|
|
||||||
writeChunks := []int{25, 35, 15, 20}
|
|
||||||
readChunks := []int{5, 45, 20, 25}
|
|
||||||
|
|
||||||
for _, c := range cases {
|
|
||||||
// first pass: write directly to hash
|
|
||||||
hash := sha1.New()
|
|
||||||
for i := 0; i < c.iterations*c.writesPerLoop; i++ {
|
|
||||||
if _, err := hash.Write(testMessage[:writeChunks[i%len(writeChunks)]]); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expected := hex.EncodeToString(hash.Sum(nil))
|
|
||||||
|
|
||||||
// write/read through buffer
|
|
||||||
buf := NewBytesPipe(nil)
|
|
||||||
hash.Reset()
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// random delay before read starts
|
|
||||||
<-time.After(time.Duration(rand.Intn(10)) * time.Millisecond)
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
p := make([]byte, readChunks[(c.iterations*c.readsPerLoop+i)%len(readChunks)])
|
|
||||||
n, _ := buf.Read(p)
|
|
||||||
if n == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
hash.Write(p[:n])
|
|
||||||
}
|
|
||||||
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for i := 0; i < c.iterations; i++ {
|
|
||||||
for w := 0; w < c.writesPerLoop; w++ {
|
|
||||||
buf.Write(testMessage[:writeChunks[(i*c.writesPerLoop+w)%len(writeChunks)]])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buf.Close()
|
|
||||||
<-done
|
|
||||||
|
|
||||||
actual := hex.EncodeToString(hash.Sum(nil))
|
|
||||||
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("BytesPipe returned invalid data. Expected checksum %v, got %v", expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBytesPipeWrite(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
readBuf := make([]byte, 1024)
|
|
||||||
buf := NewBytesPipe(nil)
|
|
||||||
go func() {
|
|
||||||
var err error
|
|
||||||
for err == nil {
|
|
||||||
_, err = buf.Read(readBuf)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
for j := 0; j < 1000; j++ {
|
|
||||||
buf.Write([]byte("pretty short line, because why not?"))
|
|
||||||
}
|
|
||||||
buf.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBytesPipeRead(b *testing.B) {
|
|
||||||
rd := make([]byte, 512)
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
b.StopTimer()
|
|
||||||
buf := NewBytesPipe(nil)
|
|
||||||
for j := 0; j < 500; j++ {
|
|
||||||
buf.Write(make([]byte, 1024))
|
|
||||||
}
|
|
||||||
b.StartTimer()
|
|
||||||
for j := 0; j < 1000; j++ {
|
|
||||||
if n, _ := buf.Read(rd); n != 512 {
|
|
||||||
b.Fatalf("Wrong number of bytes: %d", n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/fmt.go
generated
vendored
22
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/fmt.go
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FprintfIfNotEmpty prints the string value if it's not empty
|
|
||||||
func FprintfIfNotEmpty(w io.Writer, format, value string) (int, error) {
|
|
||||||
if value != "" {
|
|
||||||
return fmt.Fprintf(w, format, value)
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FprintfIfTrue prints the boolean value if it's true
|
|
||||||
func FprintfIfTrue(w io.Writer, format string, ok bool) (int, error) {
|
|
||||||
if ok {
|
|
||||||
return fmt.Fprintf(w, format, ok)
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
17
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/fmt_test.go
generated
vendored
17
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/fmt_test.go
generated
vendored
@@ -1,17 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestFprintfIfNotEmpty(t *testing.T) {
|
|
||||||
wc := NewWriteCounter(&NopWriter{})
|
|
||||||
n, _ := FprintfIfNotEmpty(wc, "foo%s", "")
|
|
||||||
|
|
||||||
if wc.Count != 0 || n != 0 {
|
|
||||||
t.Errorf("Wrong count: %v vs. %v vs. 0", wc.Count, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, _ = FprintfIfNotEmpty(wc, "foo%s", "bar")
|
|
||||||
if wc.Count != 6 || n != 6 {
|
|
||||||
t.Errorf("Wrong count: %v vs. %v vs. 6", wc.Count, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
226
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader.go
generated
vendored
226
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader.go
generated
vendored
@@ -1,226 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pos struct {
|
|
||||||
idx int
|
|
||||||
offset int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type multiReadSeeker struct {
|
|
||||||
readers []io.ReadSeeker
|
|
||||||
pos *pos
|
|
||||||
posIdx map[io.ReadSeeker]int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *multiReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
var tmpOffset int64
|
|
||||||
switch whence {
|
|
||||||
case os.SEEK_SET:
|
|
||||||
for i, rdr := range r.readers {
|
|
||||||
// get size of the current reader
|
|
||||||
s, err := rdr.Seek(0, os.SEEK_END)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset > tmpOffset+s {
|
|
||||||
if i == len(r.readers)-1 {
|
|
||||||
rdrOffset := s + (offset - tmpOffset)
|
|
||||||
if _, err := rdr.Seek(rdrOffset, os.SEEK_SET); err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
r.pos = &pos{i, rdrOffset}
|
|
||||||
return offset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpOffset += s
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
rdrOffset := offset - tmpOffset
|
|
||||||
idx := i
|
|
||||||
|
|
||||||
rdr.Seek(rdrOffset, os.SEEK_SET)
|
|
||||||
// make sure all following readers are at 0
|
|
||||||
for _, rdr := range r.readers[i+1:] {
|
|
||||||
rdr.Seek(0, os.SEEK_SET)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rdrOffset == s && i != len(r.readers)-1 {
|
|
||||||
idx++
|
|
||||||
rdrOffset = 0
|
|
||||||
}
|
|
||||||
r.pos = &pos{idx, rdrOffset}
|
|
||||||
return offset, nil
|
|
||||||
}
|
|
||||||
case os.SEEK_END:
|
|
||||||
for _, rdr := range r.readers {
|
|
||||||
s, err := rdr.Seek(0, os.SEEK_END)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
tmpOffset += s
|
|
||||||
}
|
|
||||||
r.Seek(tmpOffset+offset, os.SEEK_SET)
|
|
||||||
return tmpOffset + offset, nil
|
|
||||||
case os.SEEK_CUR:
|
|
||||||
if r.pos == nil {
|
|
||||||
return r.Seek(offset, os.SEEK_SET)
|
|
||||||
}
|
|
||||||
// Just return the current offset
|
|
||||||
if offset == 0 {
|
|
||||||
return r.getCurOffset()
|
|
||||||
}
|
|
||||||
|
|
||||||
curOffset, err := r.getCurOffset()
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
rdr, rdrOffset, err := r.getReaderForOffset(curOffset + offset)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.pos = &pos{r.posIdx[rdr], rdrOffset}
|
|
||||||
return curOffset + offset, nil
|
|
||||||
default:
|
|
||||||
return -1, fmt.Errorf("Invalid whence: %d", whence)
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1, fmt.Errorf("Error seeking for whence: %d, offset: %d", whence, offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *multiReadSeeker) getReaderForOffset(offset int64) (io.ReadSeeker, int64, error) {
|
|
||||||
var rdr io.ReadSeeker
|
|
||||||
var rdrOffset int64
|
|
||||||
|
|
||||||
for i, rdr := range r.readers {
|
|
||||||
offsetTo, err := r.getOffsetToReader(rdr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, -1, err
|
|
||||||
}
|
|
||||||
if offsetTo > offset {
|
|
||||||
rdr = r.readers[i-1]
|
|
||||||
rdrOffset = offsetTo - offset
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if rdr == r.readers[len(r.readers)-1] {
|
|
||||||
rdrOffset = offsetTo + offset
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rdr, rdrOffset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *multiReadSeeker) getCurOffset() (int64, error) {
|
|
||||||
var totalSize int64
|
|
||||||
for _, rdr := range r.readers[:r.pos.idx+1] {
|
|
||||||
if r.posIdx[rdr] == r.pos.idx {
|
|
||||||
totalSize += r.pos.offset
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
size, err := getReadSeekerSize(rdr)
|
|
||||||
if err != nil {
|
|
||||||
return -1, fmt.Errorf("error getting seeker size: %v", err)
|
|
||||||
}
|
|
||||||
totalSize += size
|
|
||||||
}
|
|
||||||
return totalSize, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *multiReadSeeker) getOffsetToReader(rdr io.ReadSeeker) (int64, error) {
|
|
||||||
var offset int64
|
|
||||||
for _, r := range r.readers {
|
|
||||||
if r == rdr {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
size, err := getReadSeekerSize(rdr)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
offset += size
|
|
||||||
}
|
|
||||||
return offset, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *multiReadSeeker) Read(b []byte) (int, error) {
|
|
||||||
if r.pos == nil {
|
|
||||||
r.pos = &pos{0, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
bCap := int64(cap(b))
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
var rdr io.ReadSeeker
|
|
||||||
|
|
||||||
for _, rdr = range r.readers[r.pos.idx:] {
|
|
||||||
readBytes, err := io.CopyN(buf, rdr, bCap)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
bCap -= readBytes
|
|
||||||
|
|
||||||
if bCap == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rdrPos, err := rdr.Seek(0, os.SEEK_CUR)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
r.pos = &pos{r.posIdx[rdr], rdrPos}
|
|
||||||
return buf.Read(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getReadSeekerSize(rdr io.ReadSeeker) (int64, error) {
|
|
||||||
// save the current position
|
|
||||||
pos, err := rdr.Seek(0, os.SEEK_CUR)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the size
|
|
||||||
size, err := rdr.Seek(0, os.SEEK_END)
|
|
||||||
if err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset the position
|
|
||||||
if _, err := rdr.Seek(pos, os.SEEK_SET); err != nil {
|
|
||||||
return -1, err
|
|
||||||
}
|
|
||||||
return size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MultiReadSeeker returns a ReadSeeker that's the logical concatenation of the provided
|
|
||||||
// input readseekers. After calling this method the initial position is set to the
|
|
||||||
// beginning of the first ReadSeeker. At the end of a ReadSeeker, Read always advances
|
|
||||||
// to the beginning of the next ReadSeeker and returns EOF at the end of the last ReadSeeker.
|
|
||||||
// Seek can be used over the sum of lengths of all readseekers.
|
|
||||||
//
|
|
||||||
// When a MultiReadSeeker is used, no Read and Seek operations should be made on
|
|
||||||
// its ReadSeeker components. Also, users should make no assumption on the state
|
|
||||||
// of individual readseekers while the MultiReadSeeker is used.
|
|
||||||
func MultiReadSeeker(readers ...io.ReadSeeker) io.ReadSeeker {
|
|
||||||
if len(readers) == 1 {
|
|
||||||
return readers[0]
|
|
||||||
}
|
|
||||||
idx := make(map[io.ReadSeeker]int)
|
|
||||||
for i, rdr := range readers {
|
|
||||||
idx[rdr] = i
|
|
||||||
}
|
|
||||||
return &multiReadSeeker{
|
|
||||||
readers: readers,
|
|
||||||
posIdx: idx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
149
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader_test.go
generated
vendored
149
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/multireader_test.go
generated
vendored
@@ -1,149 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMultiReadSeekerReadAll(t *testing.T) {
|
|
||||||
str := "hello world"
|
|
||||||
s1 := strings.NewReader(str + " 1")
|
|
||||||
s2 := strings.NewReader(str + " 2")
|
|
||||||
s3 := strings.NewReader(str + " 3")
|
|
||||||
mr := MultiReadSeeker(s1, s2, s3)
|
|
||||||
|
|
||||||
expectedSize := int64(s1.Len() + s2.Len() + s3.Len())
|
|
||||||
|
|
||||||
b, err := ioutil.ReadAll(mr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := "hello world 1hello world 2hello world 3"
|
|
||||||
if string(b) != expected {
|
|
||||||
t.Fatalf("ReadAll failed, got: %q, expected %q", string(b), expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
size, err := mr.Seek(0, os.SEEK_END)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if size != expectedSize {
|
|
||||||
t.Fatalf("reader size does not match, got %d, expected %d", size, expectedSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the position and read again
|
|
||||||
pos, err := mr.Seek(0, os.SEEK_SET)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if pos != 0 {
|
|
||||||
t.Fatalf("expected position to be set to 0, got %d", pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err = ioutil.ReadAll(mr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(b) != expected {
|
|
||||||
t.Fatalf("ReadAll failed, got: %q, expected %q", string(b), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiReadSeekerReadEach(t *testing.T) {
|
|
||||||
str := "hello world"
|
|
||||||
s1 := strings.NewReader(str + " 1")
|
|
||||||
s2 := strings.NewReader(str + " 2")
|
|
||||||
s3 := strings.NewReader(str + " 3")
|
|
||||||
mr := MultiReadSeeker(s1, s2, s3)
|
|
||||||
|
|
||||||
var totalBytes int64
|
|
||||||
for i, s := range []*strings.Reader{s1, s2, s3} {
|
|
||||||
sLen := int64(s.Len())
|
|
||||||
buf := make([]byte, s.Len())
|
|
||||||
expected := []byte(fmt.Sprintf("%s %d", str, i+1))
|
|
||||||
|
|
||||||
if _, err := mr.Read(buf); err != nil && err != io.EOF {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(buf, expected) {
|
|
||||||
t.Fatalf("expected %q to be %q", string(buf), string(expected))
|
|
||||||
}
|
|
||||||
|
|
||||||
pos, err := mr.Seek(0, os.SEEK_CUR)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("iteration: %d, error: %v", i+1, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check that the total bytes read is the current position of the seeker
|
|
||||||
totalBytes += sLen
|
|
||||||
if pos != totalBytes {
|
|
||||||
t.Fatalf("expected current position to be: %d, got: %d, iteration: %d", totalBytes, pos, i+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This tests not only that SEEK_SET and SEEK_CUR give the same values, but that the next iteration is in the expected position as well
|
|
||||||
newPos, err := mr.Seek(pos, os.SEEK_SET)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if newPos != pos {
|
|
||||||
t.Fatalf("expected to get same position when calling SEEK_SET with value from SEEK_CUR, cur: %d, set: %d", pos, newPos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiReadSeekerReadSpanningChunks(t *testing.T) {
|
|
||||||
str := "hello world"
|
|
||||||
s1 := strings.NewReader(str + " 1")
|
|
||||||
s2 := strings.NewReader(str + " 2")
|
|
||||||
s3 := strings.NewReader(str + " 3")
|
|
||||||
mr := MultiReadSeeker(s1, s2, s3)
|
|
||||||
|
|
||||||
buf := make([]byte, s1.Len()+3)
|
|
||||||
_, err := mr.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// expected is the contents of s1 + 3 bytes from s2, ie, the `hel` at the end of this string
|
|
||||||
expected := "hello world 1hel"
|
|
||||||
if string(buf) != expected {
|
|
||||||
t.Fatalf("expected %s to be %s", string(buf), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMultiReadSeekerNegativeSeek(t *testing.T) {
|
|
||||||
str := "hello world"
|
|
||||||
s1 := strings.NewReader(str + " 1")
|
|
||||||
s2 := strings.NewReader(str + " 2")
|
|
||||||
s3 := strings.NewReader(str + " 3")
|
|
||||||
mr := MultiReadSeeker(s1, s2, s3)
|
|
||||||
|
|
||||||
s1Len := s1.Len()
|
|
||||||
s2Len := s2.Len()
|
|
||||||
s3Len := s3.Len()
|
|
||||||
|
|
||||||
s, err := mr.Seek(int64(-1*s3.Len()), os.SEEK_END)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if s != int64(s1Len+s2Len) {
|
|
||||||
t.Fatalf("expected %d to be %d", s, s1.Len()+s2.Len())
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, s3Len)
|
|
||||||
if _, err := mr.Read(buf); err != nil && err != io.EOF {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
expected := fmt.Sprintf("%s %d", str, 3)
|
|
||||||
if string(buf) != fmt.Sprintf("%s %d", str, 3) {
|
|
||||||
t.Fatalf("expected %q to be %q", string(buf), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
154
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers.go
generated
vendored
154
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers.go
generated
vendored
@@ -1,154 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type readCloserWrapper struct {
|
|
||||||
io.Reader
|
|
||||||
closer func() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *readCloserWrapper) Close() error {
|
|
||||||
return r.closer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReadCloserWrapper returns a new io.ReadCloser.
|
|
||||||
func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser {
|
|
||||||
return &readCloserWrapper{
|
|
||||||
Reader: r,
|
|
||||||
closer: closer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type readerErrWrapper struct {
|
|
||||||
reader io.Reader
|
|
||||||
closer func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *readerErrWrapper) Read(p []byte) (int, error) {
|
|
||||||
n, err := r.reader.Read(p)
|
|
||||||
if err != nil {
|
|
||||||
r.closer()
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReaderErrWrapper returns a new io.Reader.
|
|
||||||
func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
|
|
||||||
return &readerErrWrapper{
|
|
||||||
reader: r,
|
|
||||||
closer: closer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashData returns the sha256 sum of src.
|
|
||||||
func HashData(src io.Reader) (string, error) {
|
|
||||||
h := sha256.New()
|
|
||||||
if _, err := io.Copy(h, src); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnEOFReader wraps a io.ReadCloser and a function
|
|
||||||
// the function will run at the end of file or close the file.
|
|
||||||
type OnEOFReader struct {
|
|
||||||
Rc io.ReadCloser
|
|
||||||
Fn func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OnEOFReader) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = r.Rc.Read(p)
|
|
||||||
if err == io.EOF {
|
|
||||||
r.runFunc()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the file and run the function.
|
|
||||||
func (r *OnEOFReader) Close() error {
|
|
||||||
err := r.Rc.Close()
|
|
||||||
r.runFunc()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *OnEOFReader) runFunc() {
|
|
||||||
if fn := r.Fn; fn != nil {
|
|
||||||
fn()
|
|
||||||
r.Fn = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// cancelReadCloser wraps an io.ReadCloser with a context for cancelling read
|
|
||||||
// operations.
|
|
||||||
type cancelReadCloser struct {
|
|
||||||
cancel func()
|
|
||||||
pR *io.PipeReader // Stream to read from
|
|
||||||
pW *io.PipeWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCancelReadCloser creates a wrapper that closes the ReadCloser when the
|
|
||||||
// context is cancelled. The returned io.ReadCloser must be closed when it is
|
|
||||||
// no longer needed.
|
|
||||||
func NewCancelReadCloser(ctx context.Context, in io.ReadCloser) io.ReadCloser {
|
|
||||||
pR, pW := io.Pipe()
|
|
||||||
|
|
||||||
// Create a context used to signal when the pipe is closed
|
|
||||||
doneCtx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
p := &cancelReadCloser{
|
|
||||||
cancel: cancel,
|
|
||||||
pR: pR,
|
|
||||||
pW: pW,
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
_, err := io.Copy(pW, in)
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
// If the context was closed, p.closeWithError
|
|
||||||
// was already called. Calling it again would
|
|
||||||
// change the error that Read returns.
|
|
||||||
default:
|
|
||||||
p.closeWithError(err)
|
|
||||||
}
|
|
||||||
in.Close()
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
p.closeWithError(ctx.Err())
|
|
||||||
case <-doneCtx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read wraps the Read method of the pipe that provides data from the wrapped
|
|
||||||
// ReadCloser.
|
|
||||||
func (p *cancelReadCloser) Read(buf []byte) (n int, err error) {
|
|
||||||
return p.pR.Read(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// closeWithError closes the wrapper and its underlying reader. It will
|
|
||||||
// cause future calls to Read to return err.
|
|
||||||
func (p *cancelReadCloser) closeWithError(err error) {
|
|
||||||
p.pW.CloseWithError(err)
|
|
||||||
p.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the wrapper its underlying reader. It will cause
|
|
||||||
// future calls to Read to return io.EOF.
|
|
||||||
func (p *cancelReadCloser) Close() error {
|
|
||||||
p.closeWithError(io.EOF)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
94
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go
generated
vendored
94
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/readers_test.go
generated
vendored
@@ -1,94 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Implement io.Reader
|
|
||||||
type errorReader struct{}
|
|
||||||
|
|
||||||
func (r *errorReader) Read(p []byte) (int, error) {
|
|
||||||
return 0, fmt.Errorf("Error reader always fail.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadCloserWrapperClose(t *testing.T) {
|
|
||||||
reader := strings.NewReader("A string reader")
|
|
||||||
wrapper := NewReadCloserWrapper(reader, func() error {
|
|
||||||
return fmt.Errorf("This will be called when closing")
|
|
||||||
})
|
|
||||||
err := wrapper.Close()
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "This will be called when closing") {
|
|
||||||
t.Fatalf("readCloserWrapper should have call the anonymous func and thus, fail.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReaderErrWrapperReadOnError(t *testing.T) {
|
|
||||||
called := false
|
|
||||||
reader := &errorReader{}
|
|
||||||
wrapper := NewReaderErrWrapper(reader, func() {
|
|
||||||
called = true
|
|
||||||
})
|
|
||||||
_, err := wrapper.Read([]byte{})
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "Error reader always fail.") {
|
|
||||||
t.Fatalf("readErrWrapper should returned an error")
|
|
||||||
}
|
|
||||||
if !called {
|
|
||||||
t.Fatalf("readErrWrapper should have call the anonymous function on failure")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReaderErrWrapperRead(t *testing.T) {
|
|
||||||
reader := strings.NewReader("a string reader.")
|
|
||||||
wrapper := NewReaderErrWrapper(reader, func() {
|
|
||||||
t.Fatalf("readErrWrapper should not have called the anonymous function")
|
|
||||||
})
|
|
||||||
// Read 20 byte (should be ok with the string above)
|
|
||||||
num, err := wrapper.Read(make([]byte, 20))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if num != 16 {
|
|
||||||
t.Fatalf("readerErrWrapper should have read 16 byte, but read %d", num)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHashData(t *testing.T) {
|
|
||||||
reader := strings.NewReader("hash-me")
|
|
||||||
actual, err := HashData(reader)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
expected := "sha256:4d11186aed035cc624d553e10db358492c84a7cd6b9670d92123c144930450aa"
|
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("Expecting %s, got %s", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type perpetualReader struct{}
|
|
||||||
|
|
||||||
func (p *perpetualReader) Read(buf []byte) (n int, err error) {
|
|
||||||
for i := 0; i != len(buf); i++ {
|
|
||||||
buf[i] = 'a'
|
|
||||||
}
|
|
||||||
return len(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCancelReadCloser(t *testing.T) {
|
|
||||||
ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
||||||
cancelReadCloser := NewCancelReadCloser(ctx, ioutil.NopCloser(&perpetualReader{}))
|
|
||||||
for {
|
|
||||||
var buf [128]byte
|
|
||||||
_, err := cancelReadCloser.Read(buf[:])
|
|
||||||
if err == context.DeadlineExceeded {
|
|
||||||
break
|
|
||||||
} else if err != nil {
|
|
||||||
t.Fatalf("got unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
6
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler.go
generated
vendored
6
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler.go
generated
vendored
@@ -1,6 +0,0 @@
|
|||||||
// +build !gccgo
|
|
||||||
|
|
||||||
package ioutils
|
|
||||||
|
|
||||||
func callSchedulerIfNecessary() {
|
|
||||||
}
|
|
||||||
13
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler_gccgo.go
generated
vendored
13
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/scheduler_gccgo.go
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
// +build gccgo
|
|
||||||
|
|
||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
func callSchedulerIfNecessary() {
|
|
||||||
//allow or force Go scheduler to switch context, without explicitly
|
|
||||||
//forcing this will make it hang when using gccgo implementation
|
|
||||||
runtime.Gosched()
|
|
||||||
}
|
|
||||||
10
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/temp_unix.go
generated
vendored
10
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/temp_unix.go
generated
vendored
@@ -1,10 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package ioutils
|
|
||||||
|
|
||||||
import "io/ioutil"
|
|
||||||
|
|
||||||
// TempDir on Unix systems is equivalent to ioutil.TempDir.
|
|
||||||
func TempDir(dir, prefix string) (string, error) {
|
|
||||||
return ioutil.TempDir(dir, prefix)
|
|
||||||
}
|
|
||||||
18
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/temp_windows.go
generated
vendored
18
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/temp_windows.go
generated
vendored
@@ -1,18 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/longpath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TempDir is the equivalent of ioutil.TempDir, except that the result is in Windows longpath format.
|
|
||||||
func TempDir(dir, prefix string) (string, error) {
|
|
||||||
tempDir, err := ioutil.TempDir(dir, prefix)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return longpath.AddPrefix(tempDir), nil
|
|
||||||
}
|
|
||||||
92
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go
generated
vendored
92
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writeflusher.go
generated
vendored
@@ -1,92 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WriteFlusher wraps the Write and Flush operation ensuring that every write
|
|
||||||
// is a flush. In addition, the Close method can be called to intercept
|
|
||||||
// Read/Write calls if the targets lifecycle has already ended.
|
|
||||||
type WriteFlusher struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
w io.Writer
|
|
||||||
flusher http.Flusher
|
|
||||||
flushed bool
|
|
||||||
closed error
|
|
||||||
|
|
||||||
// TODO(stevvooe): Use channel for closed instead, remove mutex. Using a
|
|
||||||
// channel will allow one to properly order the operations.
|
|
||||||
}
|
|
||||||
|
|
||||||
var errWriteFlusherClosed = errors.New("writeflusher: closed")
|
|
||||||
|
|
||||||
func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
|
|
||||||
wf.mu.Lock()
|
|
||||||
defer wf.mu.Unlock()
|
|
||||||
if wf.closed != nil {
|
|
||||||
return 0, wf.closed
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = wf.w.Write(b)
|
|
||||||
wf.flush() // every write is a flush.
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the stream immediately.
|
|
||||||
func (wf *WriteFlusher) Flush() {
|
|
||||||
wf.mu.Lock()
|
|
||||||
defer wf.mu.Unlock()
|
|
||||||
|
|
||||||
wf.flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush the stream immediately without taking a lock. Used internally.
|
|
||||||
func (wf *WriteFlusher) flush() {
|
|
||||||
if wf.closed != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
wf.flushed = true
|
|
||||||
wf.flusher.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flushed returns the state of flushed.
|
|
||||||
// If it's flushed, return true, or else it return false.
|
|
||||||
func (wf *WriteFlusher) Flushed() bool {
|
|
||||||
// BUG(stevvooe): Remove this method. Its use is inherently racy. Seems to
|
|
||||||
// be used to detect whether or a response code has been issued or not.
|
|
||||||
// Another hook should be used instead.
|
|
||||||
wf.mu.Lock()
|
|
||||||
defer wf.mu.Unlock()
|
|
||||||
|
|
||||||
return wf.flushed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the write flusher, disallowing any further writes to the
|
|
||||||
// target. After the flusher is closed, all calls to write or flush will
|
|
||||||
// result in an error.
|
|
||||||
func (wf *WriteFlusher) Close() error {
|
|
||||||
wf.mu.Lock()
|
|
||||||
defer wf.mu.Unlock()
|
|
||||||
|
|
||||||
if wf.closed != nil {
|
|
||||||
return wf.closed
|
|
||||||
}
|
|
||||||
|
|
||||||
wf.closed = errWriteFlusherClosed
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriteFlusher returns a new WriteFlusher.
|
|
||||||
func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
|
||||||
var flusher http.Flusher
|
|
||||||
if f, ok := w.(http.Flusher); ok {
|
|
||||||
flusher = f
|
|
||||||
} else {
|
|
||||||
flusher = &NopFlusher{}
|
|
||||||
}
|
|
||||||
return &WriteFlusher{w: w, flusher: flusher}
|
|
||||||
}
|
|
||||||
66
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers.go
generated
vendored
66
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers.go
generated
vendored
@@ -1,66 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
|
|
||||||
// NopWriter represents a type which write operation is nop.
|
|
||||||
type NopWriter struct{}
|
|
||||||
|
|
||||||
func (*NopWriter) Write(buf []byte) (int, error) {
|
|
||||||
return len(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type nopWriteCloser struct {
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *nopWriteCloser) Close() error { return nil }
|
|
||||||
|
|
||||||
// NopWriteCloser returns a nopWriteCloser.
|
|
||||||
func NopWriteCloser(w io.Writer) io.WriteCloser {
|
|
||||||
return &nopWriteCloser{w}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NopFlusher represents a type which flush operation is nop.
|
|
||||||
type NopFlusher struct{}
|
|
||||||
|
|
||||||
// Flush is a nop operation.
|
|
||||||
func (f *NopFlusher) Flush() {}
|
|
||||||
|
|
||||||
type writeCloserWrapper struct {
|
|
||||||
io.Writer
|
|
||||||
closer func() error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *writeCloserWrapper) Close() error {
|
|
||||||
return r.closer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriteCloserWrapper returns a new io.WriteCloser.
|
|
||||||
func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser {
|
|
||||||
return &writeCloserWrapper{
|
|
||||||
Writer: r,
|
|
||||||
closer: closer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteCounter wraps a concrete io.Writer and hold a count of the number
|
|
||||||
// of bytes written to the writer during a "session".
|
|
||||||
// This can be convenient when write return is masked
|
|
||||||
// (e.g., json.Encoder.Encode())
|
|
||||||
type WriteCounter struct {
|
|
||||||
Count int64
|
|
||||||
Writer io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriteCounter returns a new WriteCounter.
|
|
||||||
func NewWriteCounter(w io.Writer) *WriteCounter {
|
|
||||||
return &WriteCounter{
|
|
||||||
Writer: w,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wc *WriteCounter) Write(p []byte) (count int, err error) {
|
|
||||||
count, err = wc.Writer.Write(p)
|
|
||||||
wc.Count += int64(count)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
65
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers_test.go
generated
vendored
65
Godeps/_workspace/src/github.com/docker/docker/pkg/ioutils/writers_test.go
generated
vendored
@@ -1,65 +0,0 @@
|
|||||||
package ioutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWriteCloserWrapperClose(t *testing.T) {
|
|
||||||
called := false
|
|
||||||
writer := bytes.NewBuffer([]byte{})
|
|
||||||
wrapper := NewWriteCloserWrapper(writer, func() error {
|
|
||||||
called = true
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err := wrapper.Close(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !called {
|
|
||||||
t.Fatalf("writeCloserWrapper should have call the anonymous function.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNopWriteCloser(t *testing.T) {
|
|
||||||
writer := bytes.NewBuffer([]byte{})
|
|
||||||
wrapper := NopWriteCloser(writer)
|
|
||||||
if err := wrapper.Close(); err != nil {
|
|
||||||
t.Fatal("NopWriteCloser always return nil on Close.")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNopWriter(t *testing.T) {
|
|
||||||
nw := &NopWriter{}
|
|
||||||
l, err := nw.Write([]byte{'c'})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if l != 1 {
|
|
||||||
t.Fatalf("Expected 1 got %d", l)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteCounter(t *testing.T) {
|
|
||||||
dummy1 := "This is a dummy string."
|
|
||||||
dummy2 := "This is another dummy string."
|
|
||||||
totalLength := int64(len(dummy1) + len(dummy2))
|
|
||||||
|
|
||||||
reader1 := strings.NewReader(dummy1)
|
|
||||||
reader2 := strings.NewReader(dummy2)
|
|
||||||
|
|
||||||
var buffer bytes.Buffer
|
|
||||||
wc := NewWriteCounter(&buffer)
|
|
||||||
|
|
||||||
reader1.WriteTo(wc)
|
|
||||||
reader2.WriteTo(wc)
|
|
||||||
|
|
||||||
if wc.Count != totalLength {
|
|
||||||
t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength)
|
|
||||||
}
|
|
||||||
|
|
||||||
if buffer.String() != dummy1+dummy2 {
|
|
||||||
t.Error("Wrong message written")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
119
Godeps/_workspace/src/github.com/docker/docker/pkg/pools/pools.go
generated
vendored
119
Godeps/_workspace/src/github.com/docker/docker/pkg/pools/pools.go
generated
vendored
@@ -1,119 +0,0 @@
|
|||||||
// Package pools provides a collection of pools which provide various
|
|
||||||
// data types with buffers. These can be used to lower the number of
|
|
||||||
// memory allocations and reuse buffers.
|
|
||||||
//
|
|
||||||
// New pools should be added to this package to allow them to be
|
|
||||||
// shared across packages.
|
|
||||||
//
|
|
||||||
// Utility functions which operate on pools should be added to this
|
|
||||||
// package to allow them to be reused.
|
|
||||||
package pools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// BufioReader32KPool is a pool which returns bufio.Reader with a 32K buffer.
|
|
||||||
BufioReader32KPool *BufioReaderPool
|
|
||||||
// BufioWriter32KPool is a pool which returns bufio.Writer with a 32K buffer.
|
|
||||||
BufioWriter32KPool *BufioWriterPool
|
|
||||||
)
|
|
||||||
|
|
||||||
const buffer32K = 32 * 1024
|
|
||||||
|
|
||||||
// BufioReaderPool is a bufio reader that uses sync.Pool.
|
|
||||||
type BufioReaderPool struct {
|
|
||||||
pool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
BufioReader32KPool = newBufioReaderPoolWithSize(buffer32K)
|
|
||||||
BufioWriter32KPool = newBufioWriterPoolWithSize(buffer32K)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBufioReaderPoolWithSize is unexported because new pools should be
|
|
||||||
// added here to be shared where required.
|
|
||||||
func newBufioReaderPoolWithSize(size int) *BufioReaderPool {
|
|
||||||
pool := sync.Pool{
|
|
||||||
New: func() interface{} { return bufio.NewReaderSize(nil, size) },
|
|
||||||
}
|
|
||||||
return &BufioReaderPool{pool: pool}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a bufio.Reader which reads from r. The buffer size is that of the pool.
|
|
||||||
func (bufPool *BufioReaderPool) Get(r io.Reader) *bufio.Reader {
|
|
||||||
buf := bufPool.pool.Get().(*bufio.Reader)
|
|
||||||
buf.Reset(r)
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put puts the bufio.Reader back into the pool.
|
|
||||||
func (bufPool *BufioReaderPool) Put(b *bufio.Reader) {
|
|
||||||
b.Reset(nil)
|
|
||||||
bufPool.pool.Put(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy is a convenience wrapper which uses a buffer to avoid allocation in io.Copy.
|
|
||||||
func Copy(dst io.Writer, src io.Reader) (written int64, err error) {
|
|
||||||
buf := BufioReader32KPool.Get(src)
|
|
||||||
written, err = io.Copy(dst, buf)
|
|
||||||
BufioReader32KPool.Put(buf)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReadCloserWrapper returns a wrapper which puts the bufio.Reader back
|
|
||||||
// into the pool and closes the reader if it's an io.ReadCloser.
|
|
||||||
func (bufPool *BufioReaderPool) NewReadCloserWrapper(buf *bufio.Reader, r io.Reader) io.ReadCloser {
|
|
||||||
return ioutils.NewReadCloserWrapper(r, func() error {
|
|
||||||
if readCloser, ok := r.(io.ReadCloser); ok {
|
|
||||||
readCloser.Close()
|
|
||||||
}
|
|
||||||
bufPool.Put(buf)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// BufioWriterPool is a bufio writer that uses sync.Pool.
|
|
||||||
type BufioWriterPool struct {
|
|
||||||
pool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBufioWriterPoolWithSize is unexported because new pools should be
|
|
||||||
// added here to be shared where required.
|
|
||||||
func newBufioWriterPoolWithSize(size int) *BufioWriterPool {
|
|
||||||
pool := sync.Pool{
|
|
||||||
New: func() interface{} { return bufio.NewWriterSize(nil, size) },
|
|
||||||
}
|
|
||||||
return &BufioWriterPool{pool: pool}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a bufio.Writer which writes to w. The buffer size is that of the pool.
|
|
||||||
func (bufPool *BufioWriterPool) Get(w io.Writer) *bufio.Writer {
|
|
||||||
buf := bufPool.pool.Get().(*bufio.Writer)
|
|
||||||
buf.Reset(w)
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put puts the bufio.Writer back into the pool.
|
|
||||||
func (bufPool *BufioWriterPool) Put(b *bufio.Writer) {
|
|
||||||
b.Reset(nil)
|
|
||||||
bufPool.pool.Put(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriteCloserWrapper returns a wrapper which puts the bufio.Writer back
|
|
||||||
// into the pool and closes the writer if it's an io.Writecloser.
|
|
||||||
func (bufPool *BufioWriterPool) NewWriteCloserWrapper(buf *bufio.Writer, w io.Writer) io.WriteCloser {
|
|
||||||
return ioutils.NewWriteCloserWrapper(w, func() error {
|
|
||||||
buf.Flush()
|
|
||||||
if writeCloser, ok := w.(io.WriteCloser); ok {
|
|
||||||
writeCloser.Close()
|
|
||||||
}
|
|
||||||
bufPool.Put(buf)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
162
Godeps/_workspace/src/github.com/docker/docker/pkg/pools/pools_test.go
generated
vendored
162
Godeps/_workspace/src/github.com/docker/docker/pkg/pools/pools_test.go
generated
vendored
@@ -1,162 +0,0 @@
|
|||||||
package pools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBufioReaderPoolGetWithNoReaderShouldCreateOne(t *testing.T) {
|
|
||||||
reader := BufioReader32KPool.Get(nil)
|
|
||||||
if reader == nil {
|
|
||||||
t.Fatalf("BufioReaderPool should have create a bufio.Reader but did not.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBufioReaderPoolPutAndGet(t *testing.T) {
|
|
||||||
sr := bufio.NewReader(strings.NewReader("foobar"))
|
|
||||||
reader := BufioReader32KPool.Get(sr)
|
|
||||||
if reader == nil {
|
|
||||||
t.Fatalf("BufioReaderPool should not return a nil reader.")
|
|
||||||
}
|
|
||||||
// verify the first 3 byte
|
|
||||||
buf1 := make([]byte, 3)
|
|
||||||
_, err := reader.Read(buf1)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if actual := string(buf1); actual != "foo" {
|
|
||||||
t.Fatalf("The first letter should have been 'foo' but was %v", actual)
|
|
||||||
}
|
|
||||||
BufioReader32KPool.Put(reader)
|
|
||||||
// Try to read the next 3 bytes
|
|
||||||
_, err = sr.Read(make([]byte, 3))
|
|
||||||
if err == nil || err != io.EOF {
|
|
||||||
t.Fatalf("The buffer should have been empty, issue an EOF error.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type simpleReaderCloser struct {
|
|
||||||
io.Reader
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *simpleReaderCloser) Close() error {
|
|
||||||
r.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewReadCloserWrapperWithAReadCloser(t *testing.T) {
|
|
||||||
br := bufio.NewReader(strings.NewReader(""))
|
|
||||||
sr := &simpleReaderCloser{
|
|
||||||
Reader: strings.NewReader("foobar"),
|
|
||||||
closed: false,
|
|
||||||
}
|
|
||||||
reader := BufioReader32KPool.NewReadCloserWrapper(br, sr)
|
|
||||||
if reader == nil {
|
|
||||||
t.Fatalf("NewReadCloserWrapper should not return a nil reader.")
|
|
||||||
}
|
|
||||||
// Verify the content of reader
|
|
||||||
buf := make([]byte, 3)
|
|
||||||
_, err := reader.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if actual := string(buf); actual != "foo" {
|
|
||||||
t.Fatalf("The first 3 letter should have been 'foo' but were %v", actual)
|
|
||||||
}
|
|
||||||
reader.Close()
|
|
||||||
// Read 3 more bytes "bar"
|
|
||||||
_, err = reader.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if actual := string(buf); actual != "bar" {
|
|
||||||
t.Fatalf("The first 3 letter should have been 'bar' but were %v", actual)
|
|
||||||
}
|
|
||||||
if !sr.closed {
|
|
||||||
t.Fatalf("The ReaderCloser should have been closed, it is not.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBufioWriterPoolGetWithNoReaderShouldCreateOne(t *testing.T) {
|
|
||||||
writer := BufioWriter32KPool.Get(nil)
|
|
||||||
if writer == nil {
|
|
||||||
t.Fatalf("BufioWriterPool should have create a bufio.Writer but did not.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBufioWriterPoolPutAndGet(t *testing.T) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
bw := bufio.NewWriter(buf)
|
|
||||||
writer := BufioWriter32KPool.Get(bw)
|
|
||||||
if writer == nil {
|
|
||||||
t.Fatalf("BufioReaderPool should not return a nil writer.")
|
|
||||||
}
|
|
||||||
written, err := writer.Write([]byte("foobar"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if written != 6 {
|
|
||||||
t.Fatalf("Should have written 6 bytes, but wrote %v bytes", written)
|
|
||||||
}
|
|
||||||
// Make sure we Flush all the way ?
|
|
||||||
writer.Flush()
|
|
||||||
bw.Flush()
|
|
||||||
if len(buf.Bytes()) != 6 {
|
|
||||||
t.Fatalf("The buffer should contain 6 bytes ('foobar') but contains %v ('%v')", buf.Bytes(), string(buf.Bytes()))
|
|
||||||
}
|
|
||||||
// Reset the buffer
|
|
||||||
buf.Reset()
|
|
||||||
BufioWriter32KPool.Put(writer)
|
|
||||||
// Try to write something
|
|
||||||
written, err = writer.Write([]byte("barfoo"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
// If we now try to flush it, it should panic (the writer is nil)
|
|
||||||
// recover it
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r == nil {
|
|
||||||
t.Fatal("Trying to flush the writter should have 'paniced', did not.")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
writer.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
type simpleWriterCloser struct {
|
|
||||||
io.Writer
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *simpleWriterCloser) Close() error {
|
|
||||||
r.closed = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewWriteCloserWrapperWithAWriteCloser(t *testing.T) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
bw := bufio.NewWriter(buf)
|
|
||||||
sw := &simpleWriterCloser{
|
|
||||||
Writer: new(bytes.Buffer),
|
|
||||||
closed: false,
|
|
||||||
}
|
|
||||||
bw.Flush()
|
|
||||||
writer := BufioWriter32KPool.NewWriteCloserWrapper(bw, sw)
|
|
||||||
if writer == nil {
|
|
||||||
t.Fatalf("BufioReaderPool should not return a nil writer.")
|
|
||||||
}
|
|
||||||
written, err := writer.Write([]byte("foobar"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if written != 6 {
|
|
||||||
t.Fatalf("Should have written 6 bytes, but wrote %v bytes", written)
|
|
||||||
}
|
|
||||||
writer.Close()
|
|
||||||
if !sw.closed {
|
|
||||||
t.Fatalf("The ReaderCloser should have been closed, it is not.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
Godeps/_workspace/src/github.com/docker/docker/pkg/promise/promise.go
generated
vendored
11
Godeps/_workspace/src/github.com/docker/docker/pkg/promise/promise.go
generated
vendored
@@ -1,11 +0,0 @@
|
|||||||
package promise
|
|
||||||
|
|
||||||
// Go is a basic promise implementation: it wraps calls a function in a goroutine,
|
|
||||||
// and returns a channel which will later return the function's return value.
|
|
||||||
func Go(f func() error) chan error {
|
|
||||||
ch := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
ch <- f()
|
|
||||||
}()
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
71
Godeps/_workspace/src/github.com/docker/docker/pkg/random/random.go
generated
vendored
71
Godeps/_workspace/src/github.com/docker/docker/pkg/random/random.go
generated
vendored
@@ -1,71 +0,0 @@
|
|||||||
package random
|
|
||||||
|
|
||||||
import (
|
|
||||||
cryptorand "crypto/rand"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"math/rand"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Rand is a global *rand.Rand instance, which initialized with NewSource() source.
|
|
||||||
var Rand = rand.New(NewSource())
|
|
||||||
|
|
||||||
// Reader is a global, shared instance of a pseudorandom bytes generator.
|
|
||||||
// It doesn't consume entropy.
|
|
||||||
var Reader io.Reader = &reader{rnd: Rand}
|
|
||||||
|
|
||||||
// copypaste from standard math/rand
|
|
||||||
type lockedSource struct {
|
|
||||||
lk sync.Mutex
|
|
||||||
src rand.Source
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *lockedSource) Int63() (n int64) {
|
|
||||||
r.lk.Lock()
|
|
||||||
n = r.src.Int63()
|
|
||||||
r.lk.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *lockedSource) Seed(seed int64) {
|
|
||||||
r.lk.Lock()
|
|
||||||
r.src.Seed(seed)
|
|
||||||
r.lk.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSource returns math/rand.Source safe for concurrent use and initialized
|
|
||||||
// with current unix-nano timestamp
|
|
||||||
func NewSource() rand.Source {
|
|
||||||
var seed int64
|
|
||||||
if cryptoseed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)); err != nil {
|
|
||||||
// This should not happen, but worst-case fallback to time-based seed.
|
|
||||||
seed = time.Now().UnixNano()
|
|
||||||
} else {
|
|
||||||
seed = cryptoseed.Int64()
|
|
||||||
}
|
|
||||||
return &lockedSource{
|
|
||||||
src: rand.NewSource(seed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type reader struct {
|
|
||||||
rnd *rand.Rand
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *reader) Read(b []byte) (int, error) {
|
|
||||||
i := 0
|
|
||||||
for {
|
|
||||||
val := r.rnd.Int63()
|
|
||||||
for val > 0 {
|
|
||||||
b[i] = byte(val)
|
|
||||||
i++
|
|
||||||
if i == len(b) {
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
val >>= 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
Godeps/_workspace/src/github.com/docker/docker/pkg/random/random_test.go
generated
vendored
22
Godeps/_workspace/src/github.com/docker/docker/pkg/random/random_test.go
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
package random
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"sync"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// for go test -v -race
|
|
||||||
func TestConcurrency(t *testing.T) {
|
|
||||||
rnd := rand.New(NewSource())
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
rnd.Int63()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
||||||
1
Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/README.md
generated
vendored
1
Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/README.md
generated
vendored
@@ -1 +0,0 @@
|
|||||||
This package provides helper functions for dealing with string identifiers
|
|
||||||
71
Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go
generated
vendored
71
Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid.go
generated
vendored
@@ -1,71 +0,0 @@
|
|||||||
// Package stringid provides helper functions for dealing with string identifiers
|
|
||||||
package stringid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/random"
|
|
||||||
)
|
|
||||||
|
|
||||||
const shortLen = 12
|
|
||||||
|
|
||||||
var validShortID = regexp.MustCompile("^[a-z0-9]{12}$")
|
|
||||||
|
|
||||||
// IsShortID determines if an arbitrary string *looks like* a short ID.
|
|
||||||
func IsShortID(id string) bool {
|
|
||||||
return validShortID.MatchString(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TruncateID returns a shorthand version of a string identifier for convenience.
|
|
||||||
// A collision with other shorthands is very unlikely, but possible.
|
|
||||||
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
|
|
||||||
// will need to use a langer prefix, or the full-length Id.
|
|
||||||
func TruncateID(id string) string {
|
|
||||||
if i := strings.IndexRune(id, ':'); i >= 0 {
|
|
||||||
id = id[i+1:]
|
|
||||||
}
|
|
||||||
trimTo := shortLen
|
|
||||||
if len(id) < shortLen {
|
|
||||||
trimTo = len(id)
|
|
||||||
}
|
|
||||||
return id[:trimTo]
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateID(crypto bool) string {
|
|
||||||
b := make([]byte, 32)
|
|
||||||
r := random.Reader
|
|
||||||
if crypto {
|
|
||||||
r = rand.Reader
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
if _, err := io.ReadFull(r, b); err != nil {
|
|
||||||
panic(err) // This shouldn't happen
|
|
||||||
}
|
|
||||||
id := hex.EncodeToString(b)
|
|
||||||
// if we try to parse the truncated for as an int and we don't have
|
|
||||||
// an error then the value is all numeric and causes issues when
|
|
||||||
// used as a hostname. ref #3869
|
|
||||||
if _, err := strconv.ParseInt(TruncateID(id), 10, 64); err == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateRandomID returns an unique id.
|
|
||||||
func GenerateRandomID() string {
|
|
||||||
return generateID(true)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateNonCryptoID generates unique id without using cryptographically
|
|
||||||
// secure sources of random.
|
|
||||||
// It helps you to save entropy.
|
|
||||||
func GenerateNonCryptoID() string {
|
|
||||||
return generateID(false)
|
|
||||||
}
|
|
||||||
56
Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go
generated
vendored
56
Godeps/_workspace/src/github.com/docker/docker/pkg/stringid/stringid_test.go
generated
vendored
@@ -1,56 +0,0 @@
|
|||||||
package stringid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGenerateRandomID(t *testing.T) {
|
|
||||||
id := GenerateRandomID()
|
|
||||||
|
|
||||||
if len(id) != 64 {
|
|
||||||
t.Fatalf("Id returned is incorrect: %s", id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShortenId(t *testing.T) {
|
|
||||||
id := GenerateRandomID()
|
|
||||||
truncID := TruncateID(id)
|
|
||||||
if len(truncID) != 12 {
|
|
||||||
t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShortenIdEmpty(t *testing.T) {
|
|
||||||
id := ""
|
|
||||||
truncID := TruncateID(id)
|
|
||||||
if len(truncID) > len(id) {
|
|
||||||
t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShortenIdInvalid(t *testing.T) {
|
|
||||||
id := "1234"
|
|
||||||
truncID := TruncateID(id)
|
|
||||||
if len(truncID) != len(id) {
|
|
||||||
t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsShortIDNonHex(t *testing.T) {
|
|
||||||
id := "some non-hex value"
|
|
||||||
if IsShortID(id) {
|
|
||||||
t.Fatalf("%s is not a short ID", id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsShortIDNotCorrectSize(t *testing.T) {
|
|
||||||
id := strings.Repeat("a", shortLen+1)
|
|
||||||
if IsShortID(id) {
|
|
||||||
t.Fatalf("%s is not a short ID", id)
|
|
||||||
}
|
|
||||||
id = strings.Repeat("a", shortLen-1)
|
|
||||||
if IsShortID(id) {
|
|
||||||
t.Fatalf("%s is not a short ID", id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
47
Godeps/_workspace/src/github.com/docker/docker/pkg/system/chtimes.go
generated
vendored
47
Godeps/_workspace/src/github.com/docker/docker/pkg/system/chtimes.go
generated
vendored
@@ -1,47 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
maxTime time.Time
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
|
|
||||||
// This is a 64 bit timespec
|
|
||||||
// os.Chtimes limits time to the following
|
|
||||||
maxTime = time.Unix(0, 1<<63-1)
|
|
||||||
} else {
|
|
||||||
// This is a 32 bit timespec
|
|
||||||
maxTime = time.Unix(1<<31-1, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chtimes changes the access time and modified time of a file at the given path
|
|
||||||
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
|
||||||
unixMinTime := time.Unix(0, 0)
|
|
||||||
unixMaxTime := maxTime
|
|
||||||
|
|
||||||
// If the modified time is prior to the Unix Epoch, or after the
|
|
||||||
// end of Unix Time, os.Chtimes has undefined behavior
|
|
||||||
// default to Unix Epoch in this case, just in case
|
|
||||||
|
|
||||||
if atime.Before(unixMinTime) || atime.After(unixMaxTime) {
|
|
||||||
atime = unixMinTime
|
|
||||||
}
|
|
||||||
|
|
||||||
if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) {
|
|
||||||
mtime = unixMinTime
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Chtimes(name, atime, mtime); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
94
Godeps/_workspace/src/github.com/docker/docker/pkg/system/chtimes_test.go
generated
vendored
94
Godeps/_workspace/src/github.com/docker/docker/pkg/system/chtimes_test.go
generated
vendored
@@ -1,94 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// prepareTempFile creates a temporary file in a temporary directory.
|
|
||||||
func prepareTempFile(t *testing.T) (string, string) {
|
|
||||||
dir, err := ioutil.TempDir("", "docker-system-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file := filepath.Join(dir, "exist")
|
|
||||||
if err := ioutil.WriteFile(file, []byte("hello"), 0644); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
return file, dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestChtimes tests Chtimes on a tempfile. Test only mTime, because aTime is OS dependent
|
|
||||||
func TestChtimes(t *testing.T) {
|
|
||||||
file, dir := prepareTempFile(t)
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
|
|
||||||
unixEpochTime := time.Unix(0, 0)
|
|
||||||
afterUnixEpochTime := time.Unix(100, 0)
|
|
||||||
unixMaxTime := maxTime
|
|
||||||
|
|
||||||
// Test both aTime and mTime set to Unix Epoch
|
|
||||||
Chtimes(file, unixEpochTime, unixEpochTime)
|
|
||||||
|
|
||||||
f, err := os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.ModTime() != unixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test aTime before Unix Epoch and mTime set to Unix Epoch
|
|
||||||
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.ModTime() != unixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test aTime set to Unix Epoch and mTime before Unix Epoch
|
|
||||||
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.ModTime() != unixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, f.ModTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test both aTime and mTime set to after Unix Epoch (valid time)
|
|
||||||
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.ModTime() != afterUnixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, f.ModTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test both aTime and mTime set to Unix max time
|
|
||||||
Chtimes(file, unixMaxTime, unixMaxTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.ModTime().Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), f.ModTime().Truncate(time.Second))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
91
Godeps/_workspace/src/github.com/docker/docker/pkg/system/chtimes_unix_test.go
generated
vendored
91
Godeps/_workspace/src/github.com/docker/docker/pkg/system/chtimes_unix_test.go
generated
vendored
@@ -1,91 +0,0 @@
|
|||||||
// +build linux freebsd
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestChtimes tests Chtimes access time on a tempfile on Linux
|
|
||||||
func TestChtimesLinux(t *testing.T) {
|
|
||||||
file, dir := prepareTempFile(t)
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
|
|
||||||
unixEpochTime := time.Unix(0, 0)
|
|
||||||
afterUnixEpochTime := time.Unix(100, 0)
|
|
||||||
unixMaxTime := maxTime
|
|
||||||
|
|
||||||
// Test both aTime and mTime set to Unix Epoch
|
|
||||||
Chtimes(file, unixEpochTime, unixEpochTime)
|
|
||||||
|
|
||||||
f, err := os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat := f.Sys().(*syscall.Stat_t)
|
|
||||||
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
|
||||||
if aTime != unixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test aTime before Unix Epoch and mTime set to Unix Epoch
|
|
||||||
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat = f.Sys().(*syscall.Stat_t)
|
|
||||||
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
|
||||||
if aTime != unixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test aTime set to Unix Epoch and mTime before Unix Epoch
|
|
||||||
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat = f.Sys().(*syscall.Stat_t)
|
|
||||||
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
|
||||||
if aTime != unixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test both aTime and mTime set to after Unix Epoch (valid time)
|
|
||||||
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat = f.Sys().(*syscall.Stat_t)
|
|
||||||
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
|
||||||
if aTime != afterUnixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test both aTime and mTime set to Unix max time
|
|
||||||
Chtimes(file, unixMaxTime, unixMaxTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat = f.Sys().(*syscall.Stat_t)
|
|
||||||
aTime = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
|
|
||||||
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
86
Godeps/_workspace/src/github.com/docker/docker/pkg/system/chtimes_windows_test.go
generated
vendored
86
Godeps/_workspace/src/github.com/docker/docker/pkg/system/chtimes_windows_test.go
generated
vendored
@@ -1,86 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestChtimes tests Chtimes access time on a tempfile on Windows
|
|
||||||
func TestChtimesWindows(t *testing.T) {
|
|
||||||
file, dir := prepareTempFile(t)
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
beforeUnixEpochTime := time.Unix(0, 0).Add(-100 * time.Second)
|
|
||||||
unixEpochTime := time.Unix(0, 0)
|
|
||||||
afterUnixEpochTime := time.Unix(100, 0)
|
|
||||||
unixMaxTime := maxTime
|
|
||||||
|
|
||||||
// Test both aTime and mTime set to Unix Epoch
|
|
||||||
Chtimes(file, unixEpochTime, unixEpochTime)
|
|
||||||
|
|
||||||
f, err := os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
aTime := time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
|
||||||
if aTime != unixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test aTime before Unix Epoch and mTime set to Unix Epoch
|
|
||||||
Chtimes(file, beforeUnixEpochTime, unixEpochTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
|
||||||
if aTime != unixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test aTime set to Unix Epoch and mTime before Unix Epoch
|
|
||||||
Chtimes(file, unixEpochTime, beforeUnixEpochTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
|
||||||
if aTime != unixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixEpochTime, aTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test both aTime and mTime set to after Unix Epoch (valid time)
|
|
||||||
Chtimes(file, afterUnixEpochTime, afterUnixEpochTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
|
||||||
if aTime != afterUnixEpochTime {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", afterUnixEpochTime, aTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test both aTime and mTime set to Unix max time
|
|
||||||
Chtimes(file, unixMaxTime, unixMaxTime)
|
|
||||||
|
|
||||||
f, err = os.Stat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
aTime = time.Unix(0, f.Sys().(*syscall.Win32FileAttributeData).LastAccessTime.Nanoseconds())
|
|
||||||
if aTime.Truncate(time.Second) != unixMaxTime.Truncate(time.Second) {
|
|
||||||
t.Fatalf("Expected: %s, got: %s", unixMaxTime.Truncate(time.Second), aTime.Truncate(time.Second))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
10
Godeps/_workspace/src/github.com/docker/docker/pkg/system/errors.go
generated
vendored
10
Godeps/_workspace/src/github.com/docker/docker/pkg/system/errors.go
generated
vendored
@@ -1,10 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNotSupportedPlatform means the platform is not supported.
|
|
||||||
ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
|
|
||||||
)
|
|
||||||
83
Godeps/_workspace/src/github.com/docker/docker/pkg/system/events_windows.go
generated
vendored
83
Godeps/_workspace/src/github.com/docker/docker/pkg/system/events_windows.go
generated
vendored
@@ -1,83 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
// This file implements syscalls for Win32 events which are not implemented
|
|
||||||
// in golang.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
procCreateEvent = modkernel32.NewProc("CreateEventW")
|
|
||||||
procOpenEvent = modkernel32.NewProc("OpenEventW")
|
|
||||||
procSetEvent = modkernel32.NewProc("SetEvent")
|
|
||||||
procResetEvent = modkernel32.NewProc("ResetEvent")
|
|
||||||
procPulseEvent = modkernel32.NewProc("PulseEvent")
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateEvent implements win32 CreateEventW func in golang. It will create an event object.
|
|
||||||
func CreateEvent(eventAttributes *syscall.SecurityAttributes, manualReset bool, initialState bool, name string) (handle syscall.Handle, err error) {
|
|
||||||
namep, _ := syscall.UTF16PtrFromString(name)
|
|
||||||
var _p1 uint32
|
|
||||||
if manualReset {
|
|
||||||
_p1 = 1
|
|
||||||
}
|
|
||||||
var _p2 uint32
|
|
||||||
if initialState {
|
|
||||||
_p2 = 1
|
|
||||||
}
|
|
||||||
r0, _, e1 := procCreateEvent.Call(uintptr(unsafe.Pointer(eventAttributes)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(namep)))
|
|
||||||
use(unsafe.Pointer(namep))
|
|
||||||
handle = syscall.Handle(r0)
|
|
||||||
if handle == syscall.InvalidHandle {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenEvent implements win32 OpenEventW func in golang. It opens an event object.
|
|
||||||
func OpenEvent(desiredAccess uint32, inheritHandle bool, name string) (handle syscall.Handle, err error) {
|
|
||||||
namep, _ := syscall.UTF16PtrFromString(name)
|
|
||||||
var _p1 uint32
|
|
||||||
if inheritHandle {
|
|
||||||
_p1 = 1
|
|
||||||
}
|
|
||||||
r0, _, e1 := procOpenEvent.Call(uintptr(desiredAccess), uintptr(_p1), uintptr(unsafe.Pointer(namep)))
|
|
||||||
use(unsafe.Pointer(namep))
|
|
||||||
handle = syscall.Handle(r0)
|
|
||||||
if handle == syscall.InvalidHandle {
|
|
||||||
err = e1
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEvent implements win32 SetEvent func in golang.
|
|
||||||
func SetEvent(handle syscall.Handle) (err error) {
|
|
||||||
return setResetPulse(handle, procSetEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetEvent implements win32 ResetEvent func in golang.
|
|
||||||
func ResetEvent(handle syscall.Handle) (err error) {
|
|
||||||
return setResetPulse(handle, procResetEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PulseEvent implements win32 PulseEvent func in golang.
|
|
||||||
func PulseEvent(handle syscall.Handle) (err error) {
|
|
||||||
return setResetPulse(handle, procPulseEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setResetPulse(handle syscall.Handle, proc *syscall.LazyProc) (err error) {
|
|
||||||
r0, _, _ := proc.Call(uintptr(handle))
|
|
||||||
if r0 != 0 {
|
|
||||||
err = syscall.Errno(r0)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var temp unsafe.Pointer
|
|
||||||
|
|
||||||
// use ensures a variable is kept alive without the GC freeing while still needed
|
|
||||||
func use(p unsafe.Pointer) {
|
|
||||||
temp = p
|
|
||||||
}
|
|
||||||
19
Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys.go
generated
vendored
19
Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys.go
generated
vendored
@@ -1,19 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MkdirAll creates a directory named path along with any necessary parents,
|
|
||||||
// with permission specified by attribute perm for all dir created.
|
|
||||||
func MkdirAll(path string, perm os.FileMode) error {
|
|
||||||
return os.MkdirAll(path, perm)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAbs is a platform-specific wrapper for filepath.IsAbs.
|
|
||||||
func IsAbs(path string) bool {
|
|
||||||
return filepath.IsAbs(path)
|
|
||||||
}
|
|
||||||
82
Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys_windows.go
generated
vendored
82
Godeps/_workspace/src/github.com/docker/docker/pkg/system/filesys_windows.go
generated
vendored
@@ -1,82 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MkdirAll implementation that is volume path aware for Windows.
|
|
||||||
func MkdirAll(path string, perm os.FileMode) error {
|
|
||||||
if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The rest of this method is copied from os.MkdirAll and should be kept
|
|
||||||
// as-is to ensure compatibility.
|
|
||||||
|
|
||||||
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
|
||||||
dir, err := os.Stat(path)
|
|
||||||
if err == nil {
|
|
||||||
if dir.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &os.PathError{
|
|
||||||
Op: "mkdir",
|
|
||||||
Path: path,
|
|
||||||
Err: syscall.ENOTDIR,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slow path: make sure parent exists and then call Mkdir for path.
|
|
||||||
i := len(path)
|
|
||||||
for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
|
|
||||||
j := i
|
|
||||||
for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
|
|
||||||
if j > 1 {
|
|
||||||
// Create parent
|
|
||||||
err = MkdirAll(path[0:j-1], perm)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent now exists; invoke Mkdir and use its result.
|
|
||||||
err = os.Mkdir(path, perm)
|
|
||||||
if err != nil {
|
|
||||||
// Handle arguments like "foo/." by
|
|
||||||
// double-checking that directory doesn't exist.
|
|
||||||
dir, err1 := os.Lstat(path)
|
|
||||||
if err1 == nil && dir.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAbs is a platform-specific wrapper for filepath.IsAbs. On Windows,
|
|
||||||
// golang filepath.IsAbs does not consider a path \windows\system32 as absolute
|
|
||||||
// as it doesn't start with a drive-letter/colon combination. However, in
|
|
||||||
// docker we need to verify things such as WORKDIR /windows/system32 in
|
|
||||||
// a Dockerfile (which gets translated to \windows\system32 when being processed
|
|
||||||
// by the daemon. This SHOULD be treated as absolute from a docker processing
|
|
||||||
// perspective.
|
|
||||||
func IsAbs(path string) bool {
|
|
||||||
if !filepath.IsAbs(path) {
|
|
||||||
if !strings.HasPrefix(path, string(os.PathSeparator)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
19
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat.go
generated
vendored
19
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat.go
generated
vendored
@@ -1,19 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Lstat takes a path to a file and returns
|
|
||||||
// a system.StatT type pertaining to that file.
|
|
||||||
//
|
|
||||||
// Throws an error if the file does not exist
|
|
||||||
func Lstat(path string) (*StatT, error) {
|
|
||||||
s := &syscall.Stat_t{}
|
|
||||||
if err := syscall.Lstat(path, s); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return fromStatT(s)
|
|
||||||
}
|
|
||||||
30
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_unix_test.go
generated
vendored
30
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_unix_test.go
generated
vendored
@@ -1,30 +0,0 @@
|
|||||||
// +build linux freebsd
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestLstat tests Lstat for existing and non existing files
|
|
||||||
func TestLstat(t *testing.T) {
|
|
||||||
file, invalid, _, dir := prepareFiles(t)
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
statFile, err := Lstat(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if statFile == nil {
|
|
||||||
t.Fatal("returned empty stat for existing file")
|
|
||||||
}
|
|
||||||
|
|
||||||
statInvalid, err := Lstat(invalid)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("did not return error for non-existing file")
|
|
||||||
}
|
|
||||||
if statInvalid != nil {
|
|
||||||
t.Fatal("returned non-nil stat for non-existing file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
25
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_windows.go
generated
vendored
25
Godeps/_workspace/src/github.com/docker/docker/pkg/system/lstat_windows.go
generated
vendored
@@ -1,25 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Lstat calls os.Lstat to get a fileinfo interface back.
|
|
||||||
// This is then copied into our own locally defined structure.
|
|
||||||
// Note the Linux version uses fromStatT to do the copy back,
|
|
||||||
// but that not strictly necessary when already in an OS specific module.
|
|
||||||
func Lstat(path string) (*StatT, error) {
|
|
||||||
fi, err := os.Lstat(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &StatT{
|
|
||||||
name: fi.Name(),
|
|
||||||
size: fi.Size(),
|
|
||||||
mode: fi.Mode(),
|
|
||||||
modTime: fi.ModTime(),
|
|
||||||
isDir: fi.IsDir()}, nil
|
|
||||||
}
|
|
||||||
17
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo.go
generated
vendored
17
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo.go
generated
vendored
@@ -1,17 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
66
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux.go
generated
vendored
66
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_linux.go
generated
vendored
@@ -1,66 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
|
||||||
// MemInfo type.
|
|
||||||
func ReadMemInfo() (*MemInfo, error) {
|
|
||||||
file, err := os.Open("/proc/meminfo")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
return parseMemInfo(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseMemInfo parses the /proc/meminfo file into
|
|
||||||
// a MemInfo object given a io.Reader to the file.
|
|
||||||
//
|
|
||||||
// Throws error if there are problems reading from the file
|
|
||||||
func parseMemInfo(reader io.Reader) (*MemInfo, error) {
|
|
||||||
meminfo := &MemInfo{}
|
|
||||||
scanner := bufio.NewScanner(reader)
|
|
||||||
for scanner.Scan() {
|
|
||||||
// Expected format: ["MemTotal:", "1234", "kB"]
|
|
||||||
parts := strings.Fields(scanner.Text())
|
|
||||||
|
|
||||||
// Sanity checks: Skip malformed entries.
|
|
||||||
if len(parts) < 3 || parts[2] != "kB" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to bytes.
|
|
||||||
size, err := strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
bytes := int64(size) * units.KiB
|
|
||||||
|
|
||||||
switch parts[0] {
|
|
||||||
case "MemTotal:":
|
|
||||||
meminfo.MemTotal = bytes
|
|
||||||
case "MemFree:":
|
|
||||||
meminfo.MemFree = bytes
|
|
||||||
case "SwapTotal:":
|
|
||||||
meminfo.SwapTotal = bytes
|
|
||||||
case "SwapFree:":
|
|
||||||
meminfo.SwapFree = bytes
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle errors that may have occurred during the reading of the file.
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return meminfo, nil
|
|
||||||
}
|
|
||||||
40
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_unix_test.go
generated
vendored
40
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_unix_test.go
generated
vendored
@@ -1,40 +0,0 @@
|
|||||||
// +build linux freebsd
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/go-units"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestMemInfo tests parseMemInfo with a static meminfo string
|
|
||||||
func TestMemInfo(t *testing.T) {
|
|
||||||
const input = `
|
|
||||||
MemTotal: 1 kB
|
|
||||||
MemFree: 2 kB
|
|
||||||
SwapTotal: 3 kB
|
|
||||||
SwapFree: 4 kB
|
|
||||||
Malformed1:
|
|
||||||
Malformed2: 1
|
|
||||||
Malformed3: 2 MB
|
|
||||||
Malformed4: X kB
|
|
||||||
`
|
|
||||||
meminfo, err := parseMemInfo(strings.NewReader(input))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if meminfo.MemTotal != 1*units.KiB {
|
|
||||||
t.Fatalf("Unexpected MemTotal: %d", meminfo.MemTotal)
|
|
||||||
}
|
|
||||||
if meminfo.MemFree != 2*units.KiB {
|
|
||||||
t.Fatalf("Unexpected MemFree: %d", meminfo.MemFree)
|
|
||||||
}
|
|
||||||
if meminfo.SwapTotal != 3*units.KiB {
|
|
||||||
t.Fatalf("Unexpected SwapTotal: %d", meminfo.SwapTotal)
|
|
||||||
}
|
|
||||||
if meminfo.SwapFree != 4*units.KiB {
|
|
||||||
t.Fatalf("Unexpected SwapFree: %d", meminfo.SwapFree)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
8
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_unsupported.go
generated
vendored
8
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_unsupported.go
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
// +build !linux,!windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
// ReadMemInfo is not supported on platforms other than linux and windows.
|
|
||||||
func ReadMemInfo() (*MemInfo, error) {
|
|
||||||
return nil, ErrNotSupportedPlatform
|
|
||||||
}
|
|
||||||
44
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_windows.go
generated
vendored
44
Godeps/_workspace/src/github.com/docker/docker/pkg/system/meminfo_windows.go
generated
vendored
@@ -1,44 +0,0 @@
|
|||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
|
|
||||||
procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx")
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366589(v=vs.85).aspx
|
|
||||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366770(v=vs.85).aspx
|
|
||||||
type memorystatusex struct {
|
|
||||||
dwLength uint32
|
|
||||||
dwMemoryLoad uint32
|
|
||||||
ullTotalPhys uint64
|
|
||||||
ullAvailPhys uint64
|
|
||||||
ullTotalPageFile uint64
|
|
||||||
ullAvailPageFile uint64
|
|
||||||
ullTotalVirtual uint64
|
|
||||||
ullAvailVirtual uint64
|
|
||||||
ullAvailExtendedVirtual uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
|
||||||
// MemInfo type.
|
|
||||||
func ReadMemInfo() (*MemInfo, error) {
|
|
||||||
msi := &memorystatusex{
|
|
||||||
dwLength: 64,
|
|
||||||
}
|
|
||||||
r1, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msi)))
|
|
||||||
if r1 == 0 {
|
|
||||||
return &MemInfo{}, nil
|
|
||||||
}
|
|
||||||
return &MemInfo{
|
|
||||||
MemTotal: int64(msi.ullTotalPhys),
|
|
||||||
MemFree: int64(msi.ullAvailPhys),
|
|
||||||
SwapTotal: int64(msi.ullTotalPageFile),
|
|
||||||
SwapFree: int64(msi.ullAvailPageFile),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
22
Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod.go
generated
vendored
22
Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod.go
generated
vendored
@@ -1,22 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Mknod creates a filesystem node (file, device special file or named pipe) named path
|
|
||||||
// with attributes specified by mode and dev.
|
|
||||||
func Mknod(path string, mode uint32, dev int) error {
|
|
||||||
return syscall.Mknod(path, mode, dev)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mkdev is used to build the value of linux devices (in /dev/) which specifies major
|
|
||||||
// and minor number of the newly created device special file.
|
|
||||||
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
|
|
||||||
// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major,
|
|
||||||
// then the top 12 bits of the minor.
|
|
||||||
func Mkdev(major int64, minor int64) uint32 {
|
|
||||||
return uint32(((minor & 0xfff00) << 12) | ((major & 0xfff) << 8) | (minor & 0xff))
|
|
||||||
}
|
|
||||||
13
Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod_windows.go
generated
vendored
13
Godeps/_workspace/src/github.com/docker/docker/pkg/system/mknod_windows.go
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
// Mknod is not implemented on Windows.
|
|
||||||
func Mknod(path string, mode uint32, dev int) error {
|
|
||||||
return ErrNotSupportedPlatform
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mkdev is not implemented on Windows.
|
|
||||||
func Mkdev(major int64, minor int64) uint32 {
|
|
||||||
panic("Mkdev not implemented on Windows.")
|
|
||||||
}
|
|
||||||
8
Godeps/_workspace/src/github.com/docker/docker/pkg/system/path_unix.go
generated
vendored
8
Godeps/_workspace/src/github.com/docker/docker/pkg/system/path_unix.go
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
// DefaultPathEnv is unix style list of directories to search for
|
|
||||||
// executables. Each directory is separated from the next by a colon
|
|
||||||
// ':' character .
|
|
||||||
const DefaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
|
||||||
7
Godeps/_workspace/src/github.com/docker/docker/pkg/system/path_windows.go
generated
vendored
7
Godeps/_workspace/src/github.com/docker/docker/pkg/system/path_windows.go
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
// DefaultPathEnv is deliberately empty on Windows as the default path will be set by
|
|
||||||
// the container. Docker has no context of what the default path should be.
|
|
||||||
const DefaultPathEnv = ""
|
|
||||||
53
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat.go
generated
vendored
53
Godeps/_workspace/src/github.com/docker/docker/pkg/system/stat.go
generated
vendored
@@ -1,53 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StatT type contains status of a file. It contains metadata
|
|
||||||
// like permission, owner, group, size, etc about a file.
|
|
||||||
type StatT struct {
|
|
||||||
mode uint32
|
|
||||||
uid uint32
|
|
||||||
gid uint32
|
|
||||||
rdev uint64
|
|
||||||
size int64
|
|
||||||
mtim syscall.Timespec
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns file's permission mode.
|
|
||||||
func (s StatT) Mode() uint32 {
|
|
||||||
return s.mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// UID returns file's user id of owner.
|
|
||||||
func (s StatT) UID() uint32 {
|
|
||||||
return s.uid
|
|
||||||
}
|
|
||||||
|
|
||||||
// GID returns file's group id of owner.
|
|
||||||
func (s StatT) GID() uint32 {
|
|
||||||
return s.gid
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rdev returns file's device ID (if it's special file).
|
|
||||||
func (s StatT) Rdev() uint64 {
|
|
||||||
return s.rdev
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns file's size.
|
|
||||||
func (s StatT) Size() int64 {
|
|
||||||
return s.size
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mtim returns file's last modification time.
|
|
||||||
func (s StatT) Mtim() syscall.Timespec {
|
|
||||||
return s.mtim
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastModification returns file's last modification time.
|
|
||||||
func (s StatT) GetLastModification() syscall.Timespec {
|
|
||||||
return s.Mtim()
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user