Commit 7cac3f88 authored by Yifan Gu's avatar Yifan Gu

Godep: Update the godep to remove dependencies on rkt.store

parent 8cbe8938
...@@ -49,21 +49,6 @@ ...@@ -49,21 +49,6 @@
"Rev": "c0ef4539dfab4d21c8ef20ba2924f9fc6f186d35" "Rev": "c0ef4539dfab4d21c8ef20ba2924f9fc6f186d35"
}, },
{ {
"ImportPath": "github.com/appc/spec/aci",
"Comment": "v0.5.1-55-g87808a3",
"Rev": "87808a37061a4a2e6204ccea5fd2fc930576db94"
},
{
"ImportPath": "github.com/appc/spec/pkg/acirenderer",
"Comment": "v0.5.1-55-g87808a3",
"Rev": "87808a37061a4a2e6204ccea5fd2fc930576db94"
},
{
"ImportPath": "github.com/appc/spec/pkg/tarheader",
"Comment": "v0.5.1-55-g87808a3",
"Rev": "87808a37061a4a2e6204ccea5fd2fc930576db94"
},
{
"ImportPath": "github.com/appc/spec/schema", "ImportPath": "github.com/appc/spec/schema",
"Comment": "v0.5.1-55-g87808a3", "Comment": "v0.5.1-55-g87808a3",
"Rev": "87808a37061a4a2e6204ccea5fd2fc930576db94" "Rev": "87808a37061a4a2e6204ccea5fd2fc930576db94"
...@@ -73,25 +58,11 @@ ...@@ -73,25 +58,11 @@
"Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d" "Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d"
}, },
{ {
"ImportPath": "github.com/camlistore/lock",
"Rev": "ae27720f340952636b826119b58130b9c1a847a0"
},
{
"ImportPath": "github.com/codegangsta/negroni", "ImportPath": "github.com/codegangsta/negroni",
"Comment": "v0.1-62-g8d75e11", "Comment": "v0.1-62-g8d75e11",
"Rev": "8d75e11374a1928608c906fe745b538483e7aeb2" "Rev": "8d75e11374a1928608c906fe745b538483e7aeb2"
}, },
{ {
"ImportPath": "github.com/coreos/etcd/etcdserver/etcdhttp/httptypes",
"Comment": "v2.0.4-288-g866a9d4",
"Rev": "866a9d4e41401657ea44bf539b2c5561d6fdcd67"
},
{
"ImportPath": "github.com/coreos/etcd/pkg/types",
"Comment": "v2.0.4-288-g866a9d4",
"Rev": "866a9d4e41401657ea44bf539b2c5561d6fdcd67"
},
{
"ImportPath": "github.com/coreos/go-etcd/etcd", "ImportPath": "github.com/coreos/go-etcd/etcd",
"Comment": "v2.0.0-3-g0424b5f", "Comment": "v2.0.0-3-g0424b5f",
"Rev": "0424b5f86ef0ca57a5309c599f74bbb3e97ecd9d" "Rev": "0424b5f86ef0ca57a5309c599f74bbb3e97ecd9d"
...@@ -111,68 +82,11 @@ ...@@ -111,68 +82,11 @@
"Rev": "97e243d21a8e232e9d8af38ba2366dfcfceebeba" "Rev": "97e243d21a8e232e9d8af38ba2366dfcfceebeba"
}, },
{ {
"ImportPath": "github.com/coreos/rkt/pkg/aci",
"Comment": "v0.5.4",
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
},
{
"ImportPath": "github.com/coreos/rkt/pkg/lock",
"Comment": "v0.5.4",
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
},
{
"ImportPath": "github.com/coreos/rkt/pkg/sys",
"Comment": "v0.5.4",
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
},
{
"ImportPath": "github.com/coreos/rkt/pkg/tar",
"Comment": "v0.5.4",
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
},
{
"ImportPath": "github.com/coreos/rkt/store",
"Comment": "v0.5.4",
"Rev": "c8a7050a883653266137ae05f6e8f166db52eb67"
},
{
"ImportPath": "github.com/cpuguy83/go-md2man/mangen", "ImportPath": "github.com/cpuguy83/go-md2man/mangen",
"Comment": "v1.0.2-5-g2831f11", "Comment": "v1.0.2-5-g2831f11",
"Rev": "2831f11f66ff4008f10e2cd7ed9a85e3d3fc2bed" "Rev": "2831f11f66ff4008f10e2cd7ed9a85e3d3fc2bed"
}, },
{ {
"ImportPath": "github.com/cznic/bufs",
"Rev": "3dcccbd7064a1689f9c093a988ea11ac00e21f51"
},
{
"ImportPath": "github.com/cznic/exp/lldb",
"Rev": "9b0e4be12fbdb7b843e0a658a04c35d160371789"
},
{
"ImportPath": "github.com/cznic/fileutil",
"Rev": "21ae57c9dce724a15e88bd9cd46d5668f3e880a5"
},
{
"ImportPath": "github.com/cznic/mathutil",
"Rev": "250d0b9d3304c5ea0c4cfc7d9efc7ee528b81f3b"
},
{
"ImportPath": "github.com/cznic/ql",
"Rev": "fc1b91b82089d3f132fbed8a7c9f349c3133eb96"
},
{
"ImportPath": "github.com/cznic/sortutil",
"Rev": "d4401851b4c370f979b842fa1e45e0b3b718b391"
},
{
"ImportPath": "github.com/cznic/strutil",
"Rev": "97bc31f80ac4c9fa9c5dc5fea74c383858988ea2"
},
{
"ImportPath": "github.com/cznic/zappy",
"Rev": "47331054e4f96186e3ff772877c0443909368a45"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew", "ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "3e6e67c4dcea3ac2f25fd4731abc0e1deaf36216" "Rev": "3e6e67c4dcea3ac2f25fd4731abc0e1deaf36216"
}, },
...@@ -432,14 +346,6 @@ ...@@ -432,14 +346,6 @@
"Rev": "8adf9e1730c55cdc590de7d49766cb2acc88d8f2" "Rev": "8adf9e1730c55cdc590de7d49766cb2acc88d8f2"
}, },
{ {
"ImportPath": "github.com/petar/GoLLRB/llrb",
"Rev": "53be0d36a84c2a886ca057d34b6aa4468df9ccb4"
},
{
"ImportPath": "github.com/peterbourgon/diskv",
"Rev": "508f5671a72eeaef05cf8c24abe7fbc1c07faf69"
},
{
"ImportPath": "github.com/prometheus/client_golang/model", "ImportPath": "github.com/prometheus/client_golang/model",
"Comment": "0.4.0-1-g692492e", "Comment": "0.4.0-1-g692492e",
"Rev": "692492e54b553a81013254cc1fba4b6dd76fad30" "Rev": "692492e54b553a81013254cc1fba4b6dd76fad30"
...@@ -520,14 +426,6 @@ ...@@ -520,14 +426,6 @@
"Rev": "5292687f5379e01054407da44d7c4590a61fd3de" "Rev": "5292687f5379e01054407da44d7c4590a61fd3de"
}, },
{ {
"ImportPath": "golang.org/x/crypto/cast5",
"Rev": "a7ead6ddf06233883deca151dffaef2effbf498f"
},
{
"ImportPath": "golang.org/x/crypto/openpgp",
"Rev": "a7ead6ddf06233883deca151dffaef2effbf498f"
},
{
"ImportPath": "golang.org/x/crypto/ssh", "ImportPath": "golang.org/x/crypto/ssh",
"Rev": "c84e1f8e3a7e322d497cd16c0e8a13c7e127baf3" "Rev": "c84e1f8e3a7e322d497cd16c0e8a13c7e127baf3"
}, },
......
package aci
import (
"archive/tar"
"io"
"os"
"path/filepath"
"github.com/appc/spec/pkg/tarheader"
)
// BuildWalker creates a filepath.WalkFunc that walks over the given root
// (which should represent an ACI layout on disk) and adds the files in the
// rootfs/ subdirectory to the given ArchiveWriter
func BuildWalker(root string, aw ArchiveWriter) filepath.WalkFunc {
// cache of inode -> filepath, used to leverage hard links in the archive
inos := map[uint64]string{}
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relpath, err := filepath.Rel(root, path)
if err != nil {
return err
}
if relpath == "." {
return nil
}
if relpath == ManifestFile {
// ignore; this will be written by the archive writer
// TODO(jonboulle): does this make sense? maybe just remove from archivewriter?
return nil
}
link := ""
var r io.Reader
switch info.Mode() & os.ModeType {
case os.ModeSocket:
return nil
case os.ModeNamedPipe:
case os.ModeCharDevice:
case os.ModeDevice:
case os.ModeDir:
case os.ModeSymlink:
target, err := os.Readlink(path)
if err != nil {
return err
}
link = target
default:
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
r = file
}
hdr, err := tar.FileInfoHeader(info, link)
if err != nil {
panic(err)
}
// Because os.FileInfo's Name method returns only the base
// name of the file it describes, it may be necessary to
// modify the Name field of the returned header to provide the
// full path name of the file.
hdr.Name = relpath
tarheader.Populate(hdr, info, inos)
// If the file is a hard link to a file we've already seen, we
// don't need the contents
if hdr.Typeflag == tar.TypeLink {
hdr.Size = 0
r = nil
}
if err := aw.AddFile(hdr, r); err != nil {
return err
}
return nil
}
}
// Package aci contains various functions for working with App Container Images.
package aci
package aci
import (
"archive/tar"
"bytes"
"compress/bzip2"
"compress/gzip"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os/exec"
"path/filepath"
"github.com/appc/spec/schema"
)
type FileType string
const (
TypeGzip = FileType("gz")
TypeBzip2 = FileType("bz2")
TypeXz = FileType("xz")
TypeTar = FileType("tar")
TypeText = FileType("text")
TypeUnknown = FileType("unknown")
readLen = 512 // max bytes to sniff
hexHdrGzip = "1f8b"
hexHdrBzip2 = "425a68"
hexHdrXz = "fd377a585a00"
hexSigTar = "7573746172"
tarOffset = 257
textMime = "text/plain; charset=utf-8"
)
var (
hdrGzip []byte
hdrBzip2 []byte
hdrXz []byte
sigTar []byte
tarEnd int
)
func mustDecodeHex(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}
func init() {
hdrGzip = mustDecodeHex(hexHdrGzip)
hdrBzip2 = mustDecodeHex(hexHdrBzip2)
hdrXz = mustDecodeHex(hexHdrXz)
sigTar = mustDecodeHex(hexSigTar)
tarEnd = tarOffset + len(sigTar)
}
// DetectFileType attempts to detect the type of file that the given reader
// represents by comparing it against known file signatures (magic numbers)
func DetectFileType(r io.Reader) (FileType, error) {
var b bytes.Buffer
n, err := io.CopyN(&b, r, readLen)
if err != nil && err != io.EOF {
return TypeUnknown, err
}
bs := b.Bytes()
switch {
case bytes.HasPrefix(bs, hdrGzip):
return TypeGzip, nil
case bytes.HasPrefix(bs, hdrBzip2):
return TypeBzip2, nil
case bytes.HasPrefix(bs, hdrXz):
return TypeXz, nil
case n > int64(tarEnd) && bytes.Equal(bs[tarOffset:tarEnd], sigTar):
return TypeTar, nil
case http.DetectContentType(bs) == textMime:
return TypeText, nil
default:
return TypeUnknown, nil
}
}
// XzReader shells out to a command line xz executable (if
// available) to decompress the given io.Reader using the xz
// compression format
func XzReader(r io.Reader) io.ReadCloser {
rpipe, wpipe := io.Pipe()
ex, err := exec.LookPath("xz")
if err != nil {
log.Fatalf("couldn't find xz executable: %v", err)
}
cmd := exec.Command(ex, "--decompress", "--stdout")
cmd.Stdin = r
cmd.Stdout = wpipe
go func() {
err := cmd.Run()
wpipe.CloseWithError(err)
}()
return rpipe
}
// ManifestFromImage extracts a new schema.ImageManifest from the given ACI image.
func ManifestFromImage(rs io.ReadSeeker) (*schema.ImageManifest, error) {
var im schema.ImageManifest
tr, err := NewCompressedTarReader(rs)
if err != nil {
return nil, err
}
for {
hdr, err := tr.Next()
switch err {
case io.EOF:
return nil, errors.New("missing manifest")
case nil:
if filepath.Clean(hdr.Name) == ManifestFile {
data, err := ioutil.ReadAll(tr)
if err != nil {
return nil, err
}
if err := im.UnmarshalJSON(data); err != nil {
return nil, err
}
return &im, nil
}
default:
return nil, fmt.Errorf("error extracting tarball: %v", err)
}
}
}
// NewCompressedTarReader creates a new tar.Reader reading from the given ACI image.
func NewCompressedTarReader(rs io.ReadSeeker) (*tar.Reader, error) {
cr, err := NewCompressedReader(rs)
if err != nil {
return nil, err
}
return tar.NewReader(cr), nil
}
// NewCompressedReader creates a new io.Reader from the given ACI image.
func NewCompressedReader(rs io.ReadSeeker) (io.Reader, error) {
var (
dr io.Reader
err error
)
_, err = rs.Seek(0, 0)
if err != nil {
return nil, err
}
ftype, err := DetectFileType(rs)
if err != nil {
return nil, err
}
_, err = rs.Seek(0, 0)
if err != nil {
return nil, err
}
switch ftype {
case TypeGzip:
dr, err = gzip.NewReader(rs)
if err != nil {
return nil, err
}
case TypeBzip2:
dr = bzip2.NewReader(rs)
case TypeXz:
dr = XzReader(rs)
case TypeTar:
dr = rs
case TypeUnknown:
return nil, errors.New("error: unknown image filetype")
default:
return nil, errors.New("no type returned from DetectFileType?")
}
return dr, nil
}
package aci
import (
"archive/tar"
"compress/gzip"
"io/ioutil"
"os"
"testing"
)
func newTestACI(usedotslash bool) (*os.File, error) {
tf, err := ioutil.TempFile("", "")
if err != nil {
return nil, err
}
manifestBody := `{"acKind":"ImageManifest","acVersion":"0.5.1","name":"example.com/app"}`
gw := gzip.NewWriter(tf)
tw := tar.NewWriter(gw)
manifestPath := "manifest"
if usedotslash {
manifestPath = "./" + manifestPath
}
hdr := &tar.Header{
Name: manifestPath,
Size: int64(len(manifestBody)),
}
if err := tw.WriteHeader(hdr); err != nil {
return nil, err
}
if _, err := tw.Write([]byte(manifestBody)); err != nil {
return nil, err
}
if err := tw.Close(); err != nil {
return nil, err
}
if err := gw.Close(); err != nil {
return nil, err
}
return tf, nil
}
func newEmptyTestACI() (*os.File, error) {
tf, err := ioutil.TempFile("", "")
if err != nil {
return nil, err
}
gw := gzip.NewWriter(tf)
tw := tar.NewWriter(gw)
if err := tw.Close(); err != nil {
return nil, err
}
if err := gw.Close(); err != nil {
return nil, err
}
return tf, nil
}
func TestManifestFromImage(t *testing.T) {
for _, usedotslash := range []bool{false, true} {
img, err := newTestACI(usedotslash)
if err != nil {
t.Fatalf("newTestACI: unexpected error: %v", err)
}
defer img.Close()
defer os.Remove(img.Name())
im, err := ManifestFromImage(img)
if err != nil {
t.Fatalf("ManifestFromImage: unexpected error: %v", err)
}
if im.Name.String() != "example.com/app" {
t.Errorf("expected %s, got %s", "example.com/app", im.Name.String())
}
emptyImg, err := newEmptyTestACI()
if err != nil {
t.Fatalf("newEmptyTestACI: unexpected error: %v", err)
}
defer emptyImg.Close()
defer os.Remove(emptyImg.Name())
im, err = ManifestFromImage(emptyImg)
if err == nil {
t.Fatalf("ManifestFromImage: expected error")
}
}
}
func TestNewCompressedTarReader(t *testing.T) {
img, err := newTestACI(false)
if err != nil {
t.Fatalf("newTestACI: unexpected error: %v", err)
}
defer img.Close()
defer os.Remove(img.Name())
cr, err := NewCompressedTarReader(img)
if err != nil {
t.Fatalf("NewCompressedTarReader: unexpected error: %v", err)
}
ftype, err := DetectFileType(cr)
if err != nil {
t.Fatalf("DetectFileType: unexpected error: %v", err)
}
if ftype != TypeText {
t.Errorf("expected %v, got %v", TypeText, ftype)
}
}
func TestNewCompressedReader(t *testing.T) {
img, err := newTestACI(false)
if err != nil {
t.Fatalf("newTestACI: unexpected error: %v", err)
}
defer img.Close()
defer os.Remove(img.Name())
cr, err := NewCompressedReader(img)
if err != nil {
t.Fatalf("NewCompressedReader: unexpected error: %v", err)
}
ftype, err := DetectFileType(cr)
if err != nil {
t.Fatalf("DetectFileType: unexpected error: %v", err)
}
if ftype != TypeTar {
t.Errorf("expected %v, got %v", TypeTar, ftype)
}
}
package aci
/*
Image Layout
The on-disk layout of an app container is straightforward.
It includes a rootfs with all of the files that will exist in the root of the app and a manifest describing the image.
The layout MUST contain an image manifest.
/manifest
/rootfs/
/rootfs/usr/bin/mysql
*/
import (
"archive/tar"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/appc/spec/schema"
)
const (
// Path to manifest file inside the layout
ManifestFile = "manifest"
// Path to rootfs directory inside the layout
RootfsDir = "rootfs"
)
var (
ErrNoRootFS = errors.New("no rootfs found in layout")
ErrNoManifest = errors.New("no image manifest found in layout")
)
// ValidateLayout takes a directory and validates that the layout of the directory
// matches that expected by the Application Container Image format.
// If any errors are encountered during the validation, it will abort and
// return the first one.
func ValidateLayout(dir string) error {
fi, err := os.Stat(dir)
if err != nil {
return fmt.Errorf("error accessing layout: %v", err)
}
if !fi.IsDir() {
return fmt.Errorf("given path %q is not a directory", dir)
}
var flist []string
var imOK, rfsOK bool
var im io.Reader
walkLayout := func(fpath string, fi os.FileInfo, err error) error {
rpath, err := filepath.Rel(dir, fpath)
if err != nil {
return err
}
switch rpath {
case ".":
case ManifestFile:
im, err = os.Open(fpath)
if err != nil {
return err
}
imOK = true
case RootfsDir:
if !fi.IsDir() {
return errors.New("rootfs is not a directory")
}
rfsOK = true
default:
flist = append(flist, rpath)
}
return nil
}
if err := filepath.Walk(dir, walkLayout); err != nil {
return err
}
return validate(imOK, im, rfsOK, flist)
}
// ValidateArchive takes a *tar.Reader and validates that the layout of the
// filesystem the reader encapsulates matches that expected by the
// Application Container Image format. If any errors are encountered during
// the validation, it will abort and return the first one.
func ValidateArchive(tr *tar.Reader) error {
var fseen map[string]bool = make(map[string]bool)
var imOK, rfsOK bool
var im bytes.Buffer
Tar:
for {
hdr, err := tr.Next()
switch {
case err == nil:
case err == io.EOF:
break Tar
default:
return err
}
name := filepath.Clean(hdr.Name)
switch name {
case ".":
case ManifestFile:
_, err := io.Copy(&im, tr)
if err != nil {
return err
}
imOK = true
case RootfsDir:
if !hdr.FileInfo().IsDir() {
return fmt.Errorf("rootfs is not a directory")
}
rfsOK = true
default:
if _, seen := fseen[name]; seen {
return fmt.Errorf("duplicate file entry in archive: %s", name)
}
fseen[name] = true
}
}
var flist []string
for key := range fseen {
flist = append(flist, key)
}
return validate(imOK, &im, rfsOK, flist)
}
func validate(imOK bool, im io.Reader, rfsOK bool, files []string) error {
defer func() {
if rc, ok := im.(io.Closer); ok {
rc.Close()
}
}()
if !imOK {
return ErrNoManifest
}
if !rfsOK {
return ErrNoRootFS
}
b, err := ioutil.ReadAll(im)
if err != nil {
return fmt.Errorf("error reading image manifest: %v", err)
}
var a schema.ImageManifest
if err := a.UnmarshalJSON(b); err != nil {
return fmt.Errorf("image manifest validation failed: %v", err)
}
for _, f := range files {
if !strings.HasPrefix(f, "rootfs") {
return fmt.Errorf("unrecognized file path in layout: %q", f)
}
}
return nil
}
package aci
import (
"io/ioutil"
"os"
"path"
"testing"
)
func newValidateLayoutTest() (string, error) {
td, err := ioutil.TempDir("", "")
if err != nil {
return "", err
}
if err := os.MkdirAll(path.Join(td, "rootfs"), 0755); err != nil {
return "", err
}
if err := os.MkdirAll(path.Join(td, "rootfs", "dir", "rootfs"), 0755); err != nil {
return "", err
}
evilManifestBody := "malformedManifest"
manifestBody := `{"acKind":"ImageManifest","acVersion":"0.3.0","name":"example.com/app"}`
evilManifestPath := "rootfs/manifest"
evilManifestPath = path.Join(td, evilManifestPath)
em, err := os.Create(evilManifestPath)
if err != nil {
return "", err
}
em.WriteString(evilManifestBody)
em.Close()
manifestPath := path.Join(td, "manifest")
m, err := os.Create(manifestPath)
if err != nil {
return "", err
}
m.WriteString(manifestBody)
m.Close()
return td, nil
}
func TestValidateLayout(t *testing.T) {
layoutPath, err := newValidateLayoutTest()
if err != nil {
t.Fatalf("newValidateLayoutTest: unexpected error: %v", err)
}
defer os.RemoveAll(layoutPath)
err = ValidateLayout(layoutPath)
if err != nil {
t.Fatalf("ValidateLayout: unexpected error: %v", err)
}
}
package aci
import (
"archive/tar"
"bytes"
"encoding/json"
"io"
"time"
"github.com/appc/spec/schema"
)
// ArchiveWriter writes App Container Images. Users wanting to create an ACI or
// should create an ArchiveWriter and add files to it; the ACI will be written
// to the underlying tar.Writer
type ArchiveWriter interface {
AddFile(hdr *tar.Header, r io.Reader) error
Close() error
}
type imageArchiveWriter struct {
*tar.Writer
am *schema.ImageManifest
}
// NewImageWriter creates a new ArchiveWriter which will generate an App
// Container Image based on the given manifest and write it to the given
// tar.Writer
func NewImageWriter(am schema.ImageManifest, w *tar.Writer) ArchiveWriter {
aw := &imageArchiveWriter{
w,
&am,
}
return aw
}
func (aw *imageArchiveWriter) AddFile(hdr *tar.Header, r io.Reader) error {
err := aw.Writer.WriteHeader(hdr)
if err != nil {
return err
}
if r != nil {
_, err := io.Copy(aw.Writer, r)
if err != nil {
return err
}
}
return nil
}
func (aw *imageArchiveWriter) addFileNow(path string, contents []byte) error {
buf := bytes.NewBuffer(contents)
now := time.Now()
hdr := tar.Header{
Name: path,
Mode: 0644,
Uid: 0,
Gid: 0,
Size: int64(buf.Len()),
ModTime: now,
Typeflag: tar.TypeReg,
Uname: "root",
Gname: "root",
ChangeTime: now,
}
return aw.AddFile(&hdr, buf)
}
func (aw *imageArchiveWriter) addManifest(name string, m json.Marshaler) error {
out, err := m.MarshalJSON()
if err != nil {
return err
}
return aw.addFileNow(name, out)
}
func (aw *imageArchiveWriter) Close() error {
if err := aw.addManifest(ManifestFile, aw.am); err != nil {
return err
}
return aw.Writer.Close()
}
package acirenderer
import (
"archive/tar"
"crypto/sha512"
"fmt"
"hash"
"io"
"io/ioutil"
"path/filepath"
"strings"
"github.com/appc/spec/schema"
"github.com/appc/spec/schema/types"
)
// An ACIRegistry provides all functions of an ACIProvider plus functions to
// search for an aci and get its contents
type ACIRegistry interface {
ACIProvider
GetImageManifest(key string) (*schema.ImageManifest, error)
GetACI(name types.ACName, labels types.Labels) (string, error)
}
// An ACIProvider provides functions to get an ACI contents, to convert an
// ACI hash to the key under which the ACI is known to the provider and to resolve an
// ImageID to the key under which it's known to the provider.
type ACIProvider interface {
// Read the ACI contents stream given the key. Use ResolveKey to
// convert an ImageID to the relative provider's key.
ReadStream(key string) (io.ReadCloser, error)
// Converts an ImageID to the, if existent, key under which the
// ACI is known to the provider
ResolveKey(key string) (string, error)
// Converts a Hash to the provider's key
HashToKey(h hash.Hash) string
}
// An Image contains the ImageManifest, the ACIProvider's key and its Level in
// the dependency tree.
type Image struct {
Im *schema.ImageManifest
Key string
Level uint16
}
// Images encapsulates an ordered slice of Image structs. It represents a flat
// dependency tree.
// The upper Image should be the first in the slice with a level of 0.
// For example if A is the upper image and has two deps (in order B and C). And C has one dep (D),
// the slice (reporting the app name and excluding im and Hash) should be:
// [{A, Level: 0}, {C, Level:1}, {D, Level: 2}, {B, Level: 1}]
type Images []Image
// ACIFiles represents which files to extract for every ACI
type ACIFiles struct {
Key string
FileMap map[string]struct{}
}
// RenderedACI is an (ordered) slice of ACIFiles
type RenderedACI []*ACIFiles
// GetRenderedACIWithImageID, given an imageID, starts with the matching image
// available in the store, creates the dependencies list and returns the
// RenderedACI list.
func GetRenderedACIWithImageID(imageID types.Hash, ap ACIRegistry) (RenderedACI, error) {
imgs, err := CreateDepListFromImageID(imageID, ap)
if err != nil {
return nil, err
}
return GetRenderedACIFromList(imgs, ap)
}
// GetRenderedACI, given an image app name and optional labels, starts with the
// best matching image available in the store, creates the dependencies list
// and returns the RenderedACI list.
func GetRenderedACI(name types.ACName, labels types.Labels, ap ACIRegistry) (RenderedACI, error) {
imgs, err := CreateDepListFromNameLabels(name, labels, ap)
if err != nil {
return nil, err
}
return GetRenderedACIFromList(imgs, ap)
}
// GetRenderedACIFromList returns the RenderedACI list. All file outside rootfs
// are excluded (at the moment only "manifest").
func GetRenderedACIFromList(imgs Images, ap ACIProvider) (RenderedACI, error) {
if len(imgs) == 0 {
return nil, fmt.Errorf("image list empty")
}
allFiles := make(map[string]struct{})
renderedACI := RenderedACI{}
first := true
for i, img := range imgs {
pwlm := getUpperPWLM(imgs, i)
ra, err := getACIFiles(img, ap, allFiles, pwlm)
if err != nil {
return nil, err
}
// Use the manifest from the upper ACI
if first {
ra.FileMap["manifest"] = struct{}{}
first = false
}
renderedACI = append(renderedACI, ra)
}
return renderedACI, nil
}
// getUpperPWLM returns the pwl at the lower level for the branch where
// img[pos] lives.
func getUpperPWLM(imgs Images, pos int) map[string]struct{} {
var pwlm map[string]struct{}
curlevel := imgs[pos].Level
// Start from our position and go back ignoring the other leafs.
for i := pos; i >= 0; i-- {
img := imgs[i]
if img.Level < curlevel && len(img.Im.PathWhitelist) > 0 {
pwlm = pwlToMap(img.Im.PathWhitelist)
}
curlevel = img.Level
}
return pwlm
}
// getACIFiles returns the ACIFiles struct for the given image. All files
// outside rootfs are excluded (at the moment only "manifest").
func getACIFiles(img Image, ap ACIProvider, allFiles map[string]struct{}, pwlm map[string]struct{}) (*ACIFiles, error) {
rs, err := ap.ReadStream(img.Key)
if err != nil {
return nil, err
}
defer rs.Close()
hash := sha512.New()
r := io.TeeReader(rs, hash)
thispwlm := pwlToMap(img.Im.PathWhitelist)
ra := &ACIFiles{FileMap: make(map[string]struct{})}
if err = Walk(tar.NewReader(r), func(hdr *tar.Header) error {
name := hdr.Name
cleanName := filepath.Clean(name)
// Ignore files outside /rootfs/ (at the moment only "manifest")
if !strings.HasPrefix(cleanName, "rootfs/") {
return nil
}
// Is the file in our PathWhiteList?
// If the file is a directory continue also if not in PathWhiteList
if hdr.Typeflag != tar.TypeDir {
if len(img.Im.PathWhitelist) > 0 {
if _, ok := thispwlm[cleanName]; !ok {
return nil
}
}
}
// Is the file in the lower level PathWhiteList of this img branch?
if pwlm != nil {
if _, ok := pwlm[cleanName]; !ok {
return nil
}
}
// Is the file already provided by a previous image?
if _, ok := allFiles[cleanName]; ok {
return nil
}
ra.FileMap[cleanName] = struct{}{}
allFiles[cleanName] = struct{}{}
return nil
}); err != nil {
return nil, err
}
// Tar does not necessarily read the complete file, so ensure we read the entirety into the hash
if _, err := io.Copy(ioutil.Discard, r); err != nil {
return nil, fmt.Errorf("error reading ACI: %v", err)
}
if g := ap.HashToKey(hash); g != img.Key {
return nil, fmt.Errorf("image hash does not match expected (%s != %s)", g, img.Key)
}
ra.Key = img.Key
return ra, nil
}
// pwlToMap converts a pathWhiteList slice to a map for faster search
// It will also prepend "rootfs/" to the provided paths and they will be
// relative to "/" so they can be easily compared with the tar.Header.Name
// If pwl length is 0, a nil map is returned
func pwlToMap(pwl []string) map[string]struct{} {
if len(pwl) == 0 {
return nil
}
m := make(map[string]struct{}, len(pwl))
for _, name := range pwl {
relpath := filepath.Join("rootfs", name)
m[relpath] = struct{}{}
}
return m
}
func Walk(tarReader *tar.Reader, walkFunc func(hdr *tar.Header) error) error {
for {
hdr, err := tarReader.Next()
if err == io.EOF {
// end of tar archive
break
}
if err != nil {
return fmt.Errorf("Error reading tar entry: %v", err)
}
if err := walkFunc(hdr); err != nil {
return err
}
}
return nil
}
package acirenderer
import (
"container/list"
"github.com/appc/spec/schema/types"
)
// CreateDepListFromImageID returns the flat dependency tree of the image with
// the provided imageID
func CreateDepListFromImageID(imageID types.Hash, ap ACIRegistry) (Images, error) {
key, err := ap.ResolveKey(imageID.String())
if err != nil {
return nil, err
}
return createDepList(key, ap)
}
// CreateDepListFromNameLabels returns the flat dependency tree of the image
// with the provided app name and optional labels.
func CreateDepListFromNameLabels(name types.ACName, labels types.Labels, ap ACIRegistry) (Images, error) {
key, err := ap.GetACI(name, labels)
if err != nil {
return nil, err
}
return createDepList(key, ap)
}
// createDepList returns the flat dependency tree as a list of Image type
func createDepList(key string, ap ACIRegistry) (Images, error) {
imgsl := list.New()
im, err := ap.GetImageManifest(key)
if err != nil {
return nil, err
}
img := Image{Im: im, Key: key, Level: 0}
imgsl.PushFront(img)
// Create a flat dependency tree. Use a LinkedList to be able to
// insert elements in the list while working on it.
for el := imgsl.Front(); el != nil; el = el.Next() {
img := el.Value.(Image)
dependencies := img.Im.Dependencies
for _, d := range dependencies {
var depimg Image
var depKey string
if d.ImageID != nil && !d.ImageID.Empty() {
depKey, err = ap.ResolveKey(d.ImageID.String())
if err != nil {
return nil, err
}
} else {
var err error
depKey, err = ap.GetACI(d.App, d.Labels)
if err != nil {
return nil, err
}
}
im, err := ap.GetImageManifest(depKey)
if err != nil {
return nil, err
}
depimg = Image{Im: im, Key: depKey, Level: img.Level + 1}
imgsl.InsertAfter(depimg, el)
}
}
imgs := Images{}
for el := imgsl.Front(); el != nil; el = el.Next() {
imgs = append(imgs, el.Value.(Image))
}
return imgs, nil
}
package acirenderer
import (
"bytes"
"fmt"
"hash"
"io"
"io/ioutil"
"os"
"github.com/appc/spec/aci"
"github.com/appc/spec/schema"
"github.com/appc/spec/schema/types"
)
const (
hashPrefix = "sha512-"
)
type TestStoreAci struct {
data []byte
key string
ImageManifest *schema.ImageManifest
}
type TestStore struct {
acis map[string]*TestStoreAci
}
func NewTestStore() *TestStore {
return &TestStore{acis: make(map[string]*TestStoreAci)}
}
func (ts *TestStore) WriteACI(path string) (string, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
imageID := types.NewHashSHA512(data)
rs, err := os.Open(path)
if err != nil {
return "", err
}
defer rs.Close()
im, err := aci.ManifestFromImage(rs)
if err != nil {
return "", fmt.Errorf("error retrieving ImageManifest: %v", err)
}
key := imageID.String()
ts.acis[key] = &TestStoreAci{data: data, key: key, ImageManifest: im}
return key, nil
}
func (ts *TestStore) GetImageManifest(key string) (*schema.ImageManifest, error) {
aci, ok := ts.acis[key]
if !ok {
return nil, fmt.Errorf("aci with key: %s not found", key)
}
return aci.ImageManifest, nil
}
func (ts *TestStore) GetACI(name types.ACName, labels types.Labels) (string, error) {
for _, aci := range ts.acis {
if aci.ImageManifest.Name.String() == name.String() {
return aci.key, nil
}
}
return "", fmt.Errorf("aci not found")
}
func (ts *TestStore) ReadStream(key string) (io.ReadCloser, error) {
aci, ok := ts.acis[key]
if !ok {
return nil, fmt.Errorf("stream for key: %s not found", key)
}
return ioutil.NopCloser(bytes.NewReader(aci.data)), nil
}
func (ts *TestStore) ResolveKey(key string) (string, error) {
return key, nil
}
// HashToKey takes a hash.Hash (which currently _MUST_ represent a full SHA512),
// calculates its sum, and returns a string which should be used as the key to
// store the data matching the hash.
func (ts *TestStore) HashToKey(h hash.Hash) string {
s := h.Sum(nil)
return fmt.Sprintf("%s%x", hashPrefix, s)
}
// Package tarheader contains a simple abstraction to accurately create
// tar.Headers on different operating systems.
package tarheader
//+build darwin
package tarheader
import (
"archive/tar"
"os"
"syscall"
"time"
)
func init() {
populateHeaderStat = append(populateHeaderStat, populateHeaderCtime)
}
func populateHeaderCtime(h *tar.Header, fi os.FileInfo, _ map[uint64]string) {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return
}
sec, nsec := st.Ctimespec.Unix()
ctime := time.Unix(sec, nsec)
h.ChangeTime = ctime
}
package tarheader
import (
"archive/tar"
"os"
"syscall"
"time"
)
func init() {
populateHeaderStat = append(populateHeaderStat, populateHeaderCtime)
}
func populateHeaderCtime(h *tar.Header, fi os.FileInfo, _ map[uint64]string) {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return
}
sec, nsec := st.Ctim.Unix()
ctime := time.Unix(sec, nsec)
h.ChangeTime = ctime
}
package tarheader
/*
#define _BSD_SOURCE
#define _DEFAULT_SOURCE
#include <sys/types.h>
unsigned int
my_major(dev_t dev)
{
return major(dev);
}
unsigned int
my_minor(dev_t dev)
{
return minor(dev);
}
*/
import "C"
import (
"archive/tar"
"os"
"syscall"
)
func init() {
populateHeaderStat = append(populateHeaderStat, populateHeaderUnix)
}
func populateHeaderUnix(h *tar.Header, fi os.FileInfo, seen map[uint64]string) {
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return
}
h.Uid = int(st.Uid)
h.Gid = int(st.Gid)
if st.Mode&syscall.S_IFMT == syscall.S_IFBLK || st.Mode&syscall.S_IFMT == syscall.S_IFCHR {
h.Devminor = int64(C.my_minor(C.dev_t(st.Rdev)))
h.Devmajor = int64(C.my_major(C.dev_t(st.Rdev)))
}
// If we have already seen this inode, generate a hardlink
p, ok := seen[uint64(st.Ino)]
if ok {
h.Linkname = p
h.Typeflag = tar.TypeLink
} else {
seen[uint64(st.Ino)] = h.Name
}
}
package tarheader
import (
"archive/tar"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"
)
// mknod requires privilege ...
func TestHeaderUnixDev(t *testing.T) {
hExpect := tar.Header{
Name: "./dev/test0",
Size: 0,
Typeflag: tar.TypeBlock,
Devminor: 5,
Devmajor: 233,
}
// make our test block device
var path string
{
var err error
path, err = ioutil.TempDir("", "tarheader-test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(path)
if err := os.Mkdir(filepath.Join(path, "dev"), os.FileMode(0755)); err != nil {
t.Fatal(err)
}
mode := uint32(hExpect.Mode&07777) | syscall.S_IFBLK
dev := uint32(((hExpect.Devminor & 0xfff00) << 12) | ((hExpect.Devmajor & 0xfff) << 8) | (hExpect.Devminor & 0xff))
if err := syscall.Mknod(filepath.Join(path, hExpect.Name), mode, int(dev)); err != nil {
if err == syscall.EPERM {
t.Skip("no permission to CAP_MKNOD")
}
t.Fatal(err)
}
}
fi, err := os.Stat(filepath.Join(path, hExpect.Name))
if err != nil {
t.Fatal(err)
}
hGot := tar.Header{
Name: "./dev/test0",
Size: 0,
Typeflag: tar.TypeBlock,
}
seen := map[uint64]string{}
populateHeaderUnix(&hGot, fi, seen)
if hGot.Devminor != hExpect.Devminor {
t.Errorf("dev minor: got %d, expected %d", hGot.Devminor, hExpect.Devminor)
}
if hGot.Devmajor != hExpect.Devmajor {
t.Errorf("dev major: got %d, expected %d", hGot.Devmajor, hExpect.Devmajor)
}
}
package tarheader
import (
"archive/tar"
"os"
)
var populateHeaderStat []func(h *tar.Header, fi os.FileInfo, seen map[uint64]string)
func Populate(h *tar.Header, fi os.FileInfo, seen map[uint64]string) {
for _, pop := range populateHeaderStat {
pop(h, fi, seen)
}
}
File locking library.
See http://godoc.org/github.com/camlistore/lock
/*
Copyright 2013 The Go Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lock
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sync"
)
// Lock locks the given file, creating the file if necessary. If the
// file already exists, it must have zero size or an error is returned.
// The lock is an exclusive lock (a write lock), but locked files
// should neither be read from nor written to. Such files should have
// zero size and only exist to co-ordinate ownership across processes.
//
// A nil Closer is returned if an error occurred. Otherwise, close that
// Closer to release the lock.
//
// On Linux, FreeBSD and OSX, a lock has the same semantics as fcntl(2)'s
// advisory locks. In particular, closing any other file descriptor for the
// same file will release the lock prematurely.
//
// Attempting to lock a file that is already locked by the current process
// has undefined behavior.
//
// On other operating systems, lock will fallback to using the presence and
// content of a file named name + '.lock' to implement locking behavior.
func Lock(name string) (io.Closer, error) {
return lockFn(name)
}
var lockFn = lockPortable
// Portable version not using fcntl. Doesn't handle crashes as gracefully,
// since it can leave stale lock files.
// TODO: write pid of owner to lock file and on race see if pid is
// still alive?
func lockPortable(name string) (io.Closer, error) {
absName, err := filepath.Abs(name)
if err != nil {
return nil, fmt.Errorf("can't Lock file %q: can't find abs path: %v", name, err)
}
fi, err := os.Stat(absName)
if err == nil && fi.Size() > 0 {
if isStaleLock(absName) {
os.Remove(absName)
} else {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
}
}
f, err := os.OpenFile(absName, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_EXCL, 0666)
if err != nil {
return nil, fmt.Errorf("failed to create lock file %s %v", absName, err)
}
if err := json.NewEncoder(f).Encode(&pidLockMeta{OwnerPID: os.Getpid()}); err != nil {
return nil, err
}
return &lockCloser{f: f, abs: absName}, nil
}
type pidLockMeta struct {
OwnerPID int
}
func isStaleLock(path string) bool {
f, err := os.Open(path)
if err != nil {
return false
}
defer f.Close()
var meta pidLockMeta
if json.NewDecoder(f).Decode(&meta) != nil {
return false
}
if meta.OwnerPID == 0 {
return false
}
p, err := os.FindProcess(meta.OwnerPID)
if err != nil {
// e.g. on Windows
return true
}
// On unix, os.FindProcess always is true, so we have to send
// it a signal to see if it's alive.
if signalZero != nil {
if p.Signal(signalZero) != nil {
return true
}
}
return false
}
var signalZero os.Signal // nil or set by lock_sigzero.go
type lockCloser struct {
f *os.File
abs string
once sync.Once
err error
}
func (lc *lockCloser) Close() error {
lc.once.Do(lc.close)
return lc.err
}
func (lc *lockCloser) close() {
if err := lc.f.Close(); err != nil {
lc.err = err
}
if err := os.Remove(lc.abs); err != nil {
lc.err = err
}
}
var (
lockmu sync.Mutex
locked = map[string]bool{} // abs path -> true
)
// unlocker is used by the darwin and linux implementations with fcntl
// advisory locks.
type unlocker struct {
f *os.File
abs string
}
func (u *unlocker) Close() error {
lockmu.Lock()
// Remove is not necessary but it's nice for us to clean up.
// If we do do this, though, it needs to be before the
// u.f.Close below.
os.Remove(u.abs)
if err := u.f.Close(); err != nil {
return err
}
delete(locked, u.abs)
lockmu.Unlock()
return nil
}
// +build appengine
/*
Copyright 2013 The Go Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lock
import (
"errors"
"io"
)
func init() {
lockFn = lockAppEngine
}
func lockAppEngine(name string) (io.Closer, error) {
return nil, errors.New("Lock not available on App Engine")
}
// +build darwin,amd64
// +build !appengine
/*
Copyright 2013 The Go Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lock
import (
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"unsafe"
)
func init() {
lockFn = lockFcntl
}
func lockFcntl(name string) (io.Closer, error) {
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
}
f, err := os.Create(name)
if err != nil {
return nil, fmt.Errorf("Lock Create of %s (abs: %s) failed: %v", name, abs, err)
}
// This type matches C's "struct flock" defined in /usr/include/sys/fcntl.h.
// TODO: move this into the standard syscall package.
k := struct {
Start uint64 // sizeof(off_t): 8
Len uint64 // sizeof(off_t): 8
Pid uint32 // sizeof(pid_t): 4
Type uint16 // sizeof(short): 2
Whence uint16 // sizeof(short): 2
}{
Type: syscall.F_WRLCK,
Whence: uint16(os.SEEK_SET),
Start: 0,
Len: 0, // 0 means to lock the entire file.
Pid: uint32(os.Getpid()),
}
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k)))
if errno != 0 {
f.Close()
return nil, errno
}
return &unlocker{f, abs}, nil
}
/*
Copyright 2013 The Go Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lock
import (
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"unsafe"
)
func init() {
lockFn = lockFcntl
}
func lockFcntl(name string) (io.Closer, error) {
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
}
f, err := os.Create(name)
if err != nil {
return nil, err
}
// This type matches C's "struct flock" defined in /usr/include/fcntl.h.
// TODO: move this into the standard syscall package.
k := struct {
Start int64 /* off_t starting offset */
Len int64 /* off_t len = 0 means until end of file */
Pid int32 /* pid_t lock owner */
Type int16 /* short lock type: read/write, etc. */
Whence int16 /* short type of l_start */
Sysid int32 /* int remote system id or zero for local */
}{
Start: 0,
Len: 0, // 0 means to lock the entire file.
Pid: int32(os.Getpid()),
Type: syscall.F_WRLCK,
Whence: int16(os.SEEK_SET),
Sysid: 0,
}
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k)))
if errno != 0 {
f.Close()
return nil, errno
}
return &unlocker{f, abs}, nil
}
// +build linux,amd64
// +build !appengine
/*
Copyright 2013 The Go Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lock
import (
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"unsafe"
)
func init() {
lockFn = lockFcntl
}
func lockFcntl(name string) (io.Closer, error) {
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
}
f, err := os.Create(name)
if err != nil {
return nil, err
}
// This type matches C's "struct flock" defined in /usr/include/bits/fcntl.h.
// TODO: move this into the standard syscall package.
k := struct {
Type uint32
Whence uint32
Start uint64
Len uint64
Pid uint32
}{
Type: syscall.F_WRLCK,
Whence: uint32(os.SEEK_SET),
Start: 0,
Len: 0, // 0 means to lock the entire file.
Pid: uint32(os.Getpid()),
}
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(syscall.F_SETLK), uintptr(unsafe.Pointer(&k)))
if errno != 0 {
f.Close()
return nil, errno
}
return &unlocker{f, abs}, nil
}
// +build linux,arm
// +build !appengine
/*
Copyright 2013 The Go Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lock
import (
"fmt"
"io"
"os"
"path/filepath"
"syscall"
"unsafe"
)
func init() {
lockFn = lockFcntl
}
func lockFcntl(name string) (io.Closer, error) {
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
}
f, err := os.Create(name)
if err != nil {
return nil, err
}
// This type matches C's "struct flock" defined in /usr/include/bits/fcntl.h.
// TODO: move this into the standard syscall package.
k := struct {
Type uint16
Whence uint16
Start uint32
Len uint32
Pid uint32
}{
Type: syscall.F_WRLCK,
Whence: uint16(os.SEEK_SET),
Start: 0,
Len: 0, // 0 means to lock the entire file.
Pid: uint32(os.Getpid()),
}
const F_SETLK = 6 // actual value. syscall package is wrong: golang.org/issue/7059
_, _, errno := syscall.Syscall(syscall.SYS_FCNTL, f.Fd(), uintptr(F_SETLK), uintptr(unsafe.Pointer(&k)))
if errno != 0 {
f.Close()
return nil, errno
}
return &unlocker{f, abs}, nil
}
/*
Copyright 2013 The Go Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lock
import (
"fmt"
"io"
"os"
"path/filepath"
)
func init() {
lockFn = lockPlan9
}
func lockPlan9(name string) (io.Closer, error) {
var f *os.File
abs, err := filepath.Abs(name)
if err != nil {
return nil, err
}
lockmu.Lock()
if locked[abs] {
lockmu.Unlock()
return nil, fmt.Errorf("file %q already locked", abs)
}
locked[abs] = true
lockmu.Unlock()
fi, err := os.Stat(name)
if err == nil && fi.Size() > 0 {
return nil, fmt.Errorf("can't Lock file %q: has non-zero size", name)
}
f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE, os.ModeExclusive|0644)
if err != nil {
return nil, fmt.Errorf("Lock Create of %s (abs: %s) failed: %v", name, abs, err)
}
return &unlocker{f, abs}, nil
}
// +build !appengine
// +build linux darwin freebsd openbsd netbsd dragonfly
/*
Copyright 2013 The Go Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lock
import "syscall"
func init() {
signalZero = syscall.Signal(0)
}
/*
Copyright 2013 The Go Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package lock
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"testing"
)
func TestLock(t *testing.T) {
testLock(t, false)
}
func TestLockPortable(t *testing.T) {
testLock(t, true)
}
func TestLockInChild(t *testing.T) {
f := os.Getenv("TEST_LOCK_FILE")
if f == "" {
// not child
return
}
lock := Lock
if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_PORTABLE")); v {
lock = lockPortable
}
lk, err := lock(f)
if err != nil {
log.Fatalf("Lock failed: %v", err)
}
if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_CRASH")); v {
// Simulate a crash, or at least not unlocking the
// lock. We still exit 0 just to simplify the parent
// process exec code.
os.Exit(0)
}
lk.Close()
}
func testLock(t *testing.T, portable bool) {
lock := Lock
if portable {
lock = lockPortable
}
td, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(td)
path := filepath.Join(td, "foo.lock")
childLock := func(crash bool) error {
cmd := exec.Command(os.Args[0], "-test.run=LockInChild$")
cmd.Env = []string{"TEST_LOCK_FILE=" + path}
if portable {
cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1")
}
if crash {
cmd.Env = append(cmd.Env, "TEST_LOCK_CRASH=1")
}
out, err := cmd.CombinedOutput()
t.Logf("Child output: %q (err %v)", out, err)
if err != nil {
return fmt.Errorf("Child Process lock of %s failed: %v %s", path, err, out)
}
return nil
}
t.Logf("Locking in crashing child...")
if err := childLock(true); err != nil {
t.Fatalf("first lock in child process: %v", err)
}
t.Logf("Locking+unlocking in child...")
if err := childLock(false); err != nil {
t.Fatalf("lock in child process after crashing child: %v", err)
}
t.Logf("Locking in parent...")
lk1, err := lock(path)
if err != nil {
t.Fatal(err)
}
t.Logf("Again in parent...")
_, err = lock(path)
if err == nil {
t.Fatal("expected second lock to fail")
}
t.Logf("Locking in child...")
if childLock(false) == nil {
t.Fatalf("expected lock in child process to fail")
}
t.Logf("Unlocking lock in parent")
if err := lk1.Close(); err != nil {
t.Fatal(err)
}
lk3, err := lock(path)
if err != nil {
t.Fatal(err)
}
lk3.Close()
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
Package httptypes defines how etcd's HTTP API entities are serialized to and deserialized from JSON.
*/
package httptypes
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httptypes
import (
"encoding/json"
"log"
"net/http"
)
type HTTPError struct {
Message string `json:"message"`
// HTTP return code
Code int `json:"-"`
}
func (e HTTPError) Error() string {
return e.Message
}
// TODO(xiangli): handle http write errors
func (e HTTPError) WriteTo(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(e.Code)
b, err := json.Marshal(e)
if err != nil {
log.Panicf("marshal HTTPError should never fail: %v", err)
}
w.Write(b)
}
func NewHTTPError(code int, m string) *HTTPError {
return &HTTPError{
Message: m,
Code: code,
}
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httptypes
import (
"net/http"
"net/http/httptest"
"reflect"
"testing"
)
func TestHTTPErrorWriteTo(t *testing.T) {
err := NewHTTPError(http.StatusBadRequest, "what a bad request you made!")
rr := httptest.NewRecorder()
err.WriteTo(rr)
wcode := http.StatusBadRequest
wheader := http.Header(map[string][]string{
"Content-Type": []string{"application/json"},
})
wbody := `{"message":"what a bad request you made!"}`
if wcode != rr.Code {
t.Errorf("HTTP status code %d, want %d", rr.Code, wcode)
}
if !reflect.DeepEqual(wheader, rr.HeaderMap) {
t.Errorf("HTTP headers %v, want %v", rr.HeaderMap, wheader)
}
gbody := rr.Body.String()
if wbody != gbody {
t.Errorf("HTTP body %q, want %q", gbody, wbody)
}
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httptypes
import (
"encoding/json"
"github.com/coreos/etcd/pkg/types"
)
type Member struct {
ID string `json:"id"`
Name string `json:"name"`
PeerURLs []string `json:"peerURLs"`
ClientURLs []string `json:"clientURLs"`
}
type MemberCreateRequest struct {
PeerURLs types.URLs
}
type MemberUpdateRequest struct {
MemberCreateRequest
}
func (m *MemberCreateRequest) UnmarshalJSON(data []byte) error {
s := struct {
PeerURLs []string `json:"peerURLs"`
}{}
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
urls, err := types.NewURLs(s.PeerURLs)
if err != nil {
return err
}
m.PeerURLs = urls
return nil
}
type MemberCollection []Member
func (c *MemberCollection) MarshalJSON() ([]byte, error) {
d := struct {
Members []Member `json:"members"`
}{
Members: []Member(*c),
}
return json.Marshal(d)
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httptypes
import (
"encoding/json"
"net/url"
"reflect"
"testing"
"github.com/coreos/etcd/pkg/types"
)
func TestMemberUnmarshal(t *testing.T) {
tests := []struct {
body []byte
wantMember Member
wantError bool
}{
// no URLs, just check ID & Name
{
body: []byte(`{"id": "c", "name": "dungarees"}`),
wantMember: Member{ID: "c", Name: "dungarees", PeerURLs: nil, ClientURLs: nil},
},
// both client and peer URLs
{
body: []byte(`{"peerURLs": ["http://127.0.0.1:4001"], "clientURLs": ["http://127.0.0.1:4001"]}`),
wantMember: Member{
PeerURLs: []string{
"http://127.0.0.1:4001",
},
ClientURLs: []string{
"http://127.0.0.1:4001",
},
},
},
// multiple peer URLs
{
body: []byte(`{"peerURLs": ["http://127.0.0.1:4001", "https://example.com"]}`),
wantMember: Member{
PeerURLs: []string{
"http://127.0.0.1:4001",
"https://example.com",
},
ClientURLs: nil,
},
},
// multiple client URLs
{
body: []byte(`{"clientURLs": ["http://127.0.0.1:4001", "https://example.com"]}`),
wantMember: Member{
PeerURLs: nil,
ClientURLs: []string{
"http://127.0.0.1:4001",
"https://example.com",
},
},
},
// invalid JSON
{
body: []byte(`{"peerU`),
wantError: true,
},
}
for i, tt := range tests {
got := Member{}
err := json.Unmarshal(tt.body, &got)
if tt.wantError != (err != nil) {
t.Errorf("#%d: want error %t, got %v", i, tt.wantError, err)
continue
}
if !reflect.DeepEqual(tt.wantMember, got) {
t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.wantMember, got)
}
}
}
func TestMemberCreateRequestUnmarshal(t *testing.T) {
body := []byte(`{"peerURLs": ["http://127.0.0.1:8081", "https://127.0.0.1:8080"]}`)
want := MemberCreateRequest{
PeerURLs: types.URLs([]url.URL{
url.URL{Scheme: "http", Host: "127.0.0.1:8081"},
url.URL{Scheme: "https", Host: "127.0.0.1:8080"},
}),
}
var req MemberCreateRequest
if err := json.Unmarshal(body, &req); err != nil {
t.Fatalf("Unmarshal returned unexpected err=%v", err)
}
if !reflect.DeepEqual(want, req) {
t.Fatalf("Failed to unmarshal MemberCreateRequest: want=%#v, got=%#v", want, req)
}
}
func TestMemberCreateRequestUnmarshalFail(t *testing.T) {
tests := [][]byte{
// invalid JSON
[]byte(``),
[]byte(`{`),
// spot-check validation done in types.NewURLs
[]byte(`{"peerURLs": "foo"}`),
[]byte(`{"peerURLs": ["."]}`),
[]byte(`{"peerURLs": []}`),
[]byte(`{"peerURLs": ["http://127.0.0.1:4001/foo"]}`),
[]byte(`{"peerURLs": ["http://127.0.0.1"]}`),
}
for i, tt := range tests {
var req MemberCreateRequest
if err := json.Unmarshal(tt, &req); err == nil {
t.Errorf("#%d: expected err, got nil", i)
}
}
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"strconv"
)
// ID represents a generic identifier which is canonically
// stored as a uint64 but is typically represented as a
// base-16 string for input/output
type ID uint64
func (i ID) String() string {
return strconv.FormatUint(uint64(i), 16)
}
// IDFromString attempts to create an ID from a base-16 string.
func IDFromString(s string) (ID, error) {
i, err := strconv.ParseUint(s, 16, 64)
return ID(i), err
}
// IDSlice implements the sort interface
type IDSlice []ID
func (p IDSlice) Len() int { return len(p) }
func (p IDSlice) Less(i, j int) bool { return uint64(p[i]) < uint64(p[j]) }
func (p IDSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"reflect"
"sort"
"testing"
)
func TestIDString(t *testing.T) {
tests := []struct {
input ID
want string
}{
{
input: 12,
want: "c",
},
{
input: 4918257920282737594,
want: "444129853c343bba",
},
}
for i, tt := range tests {
got := tt.input.String()
if tt.want != got {
t.Errorf("#%d: ID.String failure: want=%v, got=%v", i, tt.want, got)
}
}
}
func TestIDFromString(t *testing.T) {
tests := []struct {
input string
want ID
}{
{
input: "17",
want: 23,
},
{
input: "612840dae127353",
want: 437557308098245459,
},
}
for i, tt := range tests {
got, err := IDFromString(tt.input)
if err != nil {
t.Errorf("#%d: IDFromString failure: err=%v", i, err)
continue
}
if tt.want != got {
t.Errorf("#%d: IDFromString failure: want=%v, got=%v", i, tt.want, got)
}
}
}
func TestIDFromStringFail(t *testing.T) {
tests := []string{
"",
"XXX",
"612840dae127353612840dae127353",
}
for i, tt := range tests {
_, err := IDFromString(tt)
if err == nil {
t.Fatalf("#%d: IDFromString expected error, but err=nil", i)
}
}
}
func TestIDSlice(t *testing.T) {
g := []ID{10, 500, 5, 1, 100, 25}
w := []ID{1, 5, 10, 25, 100, 500}
sort.Sort(IDSlice(g))
if !reflect.DeepEqual(g, w) {
t.Errorf("slice after sort = %#v, want %#v", g, w)
}
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"reflect"
"sort"
"sync"
)
type Set interface {
Add(string)
Remove(string)
Contains(string) bool
Equals(Set) bool
Length() int
Values() []string
Copy() Set
Sub(Set) Set
}
func NewUnsafeSet(values ...string) *unsafeSet {
set := &unsafeSet{make(map[string]struct{})}
for _, v := range values {
set.Add(v)
}
return set
}
func NewThreadsafeSet(values ...string) *tsafeSet {
us := NewUnsafeSet(values...)
return &tsafeSet{us, sync.RWMutex{}}
}
type unsafeSet struct {
d map[string]struct{}
}
// Add adds a new value to the set (no-op if the value is already present)
func (us *unsafeSet) Add(value string) {
us.d[value] = struct{}{}
}
// Remove removes the given value from the set
func (us *unsafeSet) Remove(value string) {
delete(us.d, value)
}
// Contains returns whether the set contains the given value
func (us *unsafeSet) Contains(value string) (exists bool) {
_, exists = us.d[value]
return
}
// ContainsAll returns whether the set contains all given values
func (us *unsafeSet) ContainsAll(values []string) bool {
for _, s := range values {
if !us.Contains(s) {
return false
}
}
return true
}
// Equals returns whether the contents of two sets are identical
func (us *unsafeSet) Equals(other Set) bool {
v1 := sort.StringSlice(us.Values())
v2 := sort.StringSlice(other.Values())
v1.Sort()
v2.Sort()
return reflect.DeepEqual(v1, v2)
}
// Length returns the number of elements in the set
func (us *unsafeSet) Length() int {
return len(us.d)
}
// Values returns the values of the Set in an unspecified order.
func (us *unsafeSet) Values() (values []string) {
values = make([]string, 0)
for val, _ := range us.d {
values = append(values, val)
}
return
}
// Copy creates a new Set containing the values of the first
func (us *unsafeSet) Copy() Set {
cp := NewUnsafeSet()
for val, _ := range us.d {
cp.Add(val)
}
return cp
}
// Sub removes all elements in other from the set
func (us *unsafeSet) Sub(other Set) Set {
oValues := other.Values()
result := us.Copy().(*unsafeSet)
for _, val := range oValues {
if _, ok := result.d[val]; !ok {
continue
}
delete(result.d, val)
}
return result
}
type tsafeSet struct {
us *unsafeSet
m sync.RWMutex
}
func (ts *tsafeSet) Add(value string) {
ts.m.Lock()
defer ts.m.Unlock()
ts.us.Add(value)
}
func (ts *tsafeSet) Remove(value string) {
ts.m.Lock()
defer ts.m.Unlock()
ts.us.Remove(value)
}
func (ts *tsafeSet) Contains(value string) (exists bool) {
ts.m.RLock()
defer ts.m.RUnlock()
return ts.us.Contains(value)
}
func (ts *tsafeSet) Equals(other Set) bool {
ts.m.RLock()
defer ts.m.RUnlock()
return ts.us.Equals(other)
}
func (ts *tsafeSet) Length() int {
ts.m.RLock()
defer ts.m.RUnlock()
return ts.us.Length()
}
func (ts *tsafeSet) Values() (values []string) {
ts.m.RLock()
defer ts.m.RUnlock()
return ts.us.Values()
}
func (ts *tsafeSet) Copy() Set {
ts.m.RLock()
defer ts.m.RUnlock()
usResult := ts.us.Copy().(*unsafeSet)
return &tsafeSet{usResult, sync.RWMutex{}}
}
func (ts *tsafeSet) Sub(other Set) Set {
ts.m.RLock()
defer ts.m.RUnlock()
usResult := ts.us.Sub(other).(*unsafeSet)
return &tsafeSet{usResult, sync.RWMutex{}}
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"reflect"
"sort"
"testing"
)
func TestUnsafeSet(t *testing.T) {
driveSetTests(t, NewUnsafeSet())
}
func TestThreadsafeSet(t *testing.T) {
driveSetTests(t, NewThreadsafeSet())
}
// Check that two slices contents are equal; order is irrelevant
func equal(a, b []string) bool {
as := sort.StringSlice(a)
bs := sort.StringSlice(b)
as.Sort()
bs.Sort()
return reflect.DeepEqual(as, bs)
}
func driveSetTests(t *testing.T, s Set) {
// Verify operations on an empty set
eValues := []string{}
values := s.Values()
if !reflect.DeepEqual(values, eValues) {
t.Fatalf("Expect values=%v got %v", eValues, values)
}
if l := s.Length(); l != 0 {
t.Fatalf("Expected length=0, got %d", l)
}
for _, v := range []string{"foo", "bar", "baz"} {
if s.Contains(v) {
t.Fatalf("Expect s.Contains(%q) to be fale, got true", v)
}
}
// Add three items, ensure they show up
s.Add("foo")
s.Add("bar")
s.Add("baz")
eValues = []string{"foo", "bar", "baz"}
values = s.Values()
if !equal(values, eValues) {
t.Fatalf("Expect values=%v got %v", eValues, values)
}
for _, v := range eValues {
if !s.Contains(v) {
t.Fatalf("Expect s.Contains(%q) to be true, got false", v)
}
}
if l := s.Length(); l != 3 {
t.Fatalf("Expected length=3, got %d", l)
}
// Add the same item a second time, ensuring it is not duplicated
s.Add("foo")
values = s.Values()
if !equal(values, eValues) {
t.Fatalf("Expect values=%v got %v", eValues, values)
}
if l := s.Length(); l != 3 {
t.Fatalf("Expected length=3, got %d", l)
}
// Remove all items, ensure they are gone
s.Remove("foo")
s.Remove("bar")
s.Remove("baz")
eValues = []string{}
values = s.Values()
if !equal(values, eValues) {
t.Fatalf("Expect values=%v got %v", eValues, values)
}
if l := s.Length(); l != 0 {
t.Fatalf("Expected length=0, got %d", l)
}
// Create new copies of the set, and ensure they are unlinked to the
// original Set by making modifications
s.Add("foo")
s.Add("bar")
cp1 := s.Copy()
cp2 := s.Copy()
s.Remove("foo")
cp3 := s.Copy()
cp1.Add("baz")
for i, tt := range []struct {
want []string
got []string
}{
{[]string{"bar"}, s.Values()},
{[]string{"foo", "bar", "baz"}, cp1.Values()},
{[]string{"foo", "bar"}, cp2.Values()},
{[]string{"bar"}, cp3.Values()},
} {
if !equal(tt.want, tt.got) {
t.Fatalf("case %d: expect values=%v got %v", i, tt.want, tt.got)
}
}
for i, tt := range []struct {
want bool
got bool
}{
{true, s.Equals(cp3)},
{true, cp3.Equals(s)},
{false, s.Equals(cp2)},
{false, s.Equals(cp1)},
{false, cp1.Equals(s)},
{false, cp2.Equals(s)},
{false, cp2.Equals(cp1)},
} {
if tt.got != tt.want {
t.Fatalf("case %d: want %t, got %t", i, tt.want, tt.got)
}
}
// Subtract values from a Set, ensuring a new Set is created and
// the original Sets are unmodified
sub1 := cp1.Sub(s)
sub2 := cp2.Sub(cp1)
for i, tt := range []struct {
want []string
got []string
}{
{[]string{"foo", "bar", "baz"}, cp1.Values()},
{[]string{"foo", "bar"}, cp2.Values()},
{[]string{"bar"}, s.Values()},
{[]string{"foo", "baz"}, sub1.Values()},
{[]string{}, sub2.Values()},
} {
if !equal(tt.want, tt.got) {
t.Fatalf("case %d: expect values=%v got %v", i, tt.want, tt.got)
}
}
}
func TestUnsafeSetContainsAll(t *testing.T) {
vals := []string{"foo", "bar", "baz"}
s := NewUnsafeSet(vals...)
tests := []struct {
strs []string
wcontain bool
}{
{[]string{}, true},
{vals[:1], true},
{vals[:2], true},
{vals, true},
{[]string{"cuz"}, false},
{[]string{vals[0], "cuz"}, false},
}
for i, tt := range tests {
if g := s.ContainsAll(tt.strs); g != tt.wcontain {
t.Errorf("#%d: ok = %v, want %v", i, g, tt.wcontain)
}
}
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
// Uint64Slice implements sort interface
type Uint64Slice []uint64
func (p Uint64Slice) Len() int { return len(p) }
func (p Uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
func (p Uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"reflect"
"sort"
"testing"
)
func TestUint64Slice(t *testing.T) {
g := Uint64Slice{10, 500, 5, 1, 100, 25}
w := Uint64Slice{1, 5, 10, 25, 100, 500}
sort.Sort(g)
if !reflect.DeepEqual(g, w) {
t.Errorf("slice after sort = %#v, want %#v", g, w)
}
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"fmt"
"net"
"net/url"
"sort"
"strings"
)
type URLs []url.URL
func NewURLs(strs []string) (URLs, error) {
all := make([]url.URL, len(strs))
if len(all) == 0 {
return nil, errors.New("no valid URLs given")
}
for i, in := range strs {
in = strings.TrimSpace(in)
u, err := url.Parse(in)
if err != nil {
return nil, err
}
if u.Scheme != "http" && u.Scheme != "https" {
return nil, fmt.Errorf("URL scheme must be http or https: %s", in)
}
if _, _, err := net.SplitHostPort(u.Host); err != nil {
return nil, fmt.Errorf(`URL address does not have the form "host:port": %s`, in)
}
if u.Path != "" {
return nil, fmt.Errorf("URL must not contain a path: %s", in)
}
all[i] = *u
}
us := URLs(all)
us.Sort()
return us, nil
}
func (us URLs) String() string {
return strings.Join(us.StringSlice(), ",")
}
func (us *URLs) Sort() {
sort.Sort(us)
}
func (us URLs) Len() int { return len(us) }
func (us URLs) Less(i, j int) bool { return us[i].String() < us[j].String() }
func (us URLs) Swap(i, j int) { us[i], us[j] = us[j], us[i] }
func (us URLs) StringSlice() []string {
out := make([]string, len(us))
for i := range us {
out[i] = us[i].String()
}
return out
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"reflect"
"testing"
"github.com/coreos/etcd/pkg/testutil"
)
func TestNewURLs(t *testing.T) {
tests := []struct {
strs []string
wurls URLs
}{
{
[]string{"http://127.0.0.1:4001"},
testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}),
},
// it can trim space
{
[]string{" http://127.0.0.1:4001 "},
testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}),
},
// it does sort
{
[]string{
"http://127.0.0.2:4001",
"http://127.0.0.1:4001",
},
testutil.MustNewURLs(t, []string{
"http://127.0.0.1:4001",
"http://127.0.0.2:4001",
}),
},
}
for i, tt := range tests {
urls, _ := NewURLs(tt.strs)
if !reflect.DeepEqual(urls, tt.wurls) {
t.Errorf("#%d: urls = %+v, want %+v", i, urls, tt.wurls)
}
}
}
func TestURLsString(t *testing.T) {
tests := []struct {
us URLs
wstr string
}{
{
URLs{},
"",
},
{
testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}),
"http://127.0.0.1:4001",
},
{
testutil.MustNewURLs(t, []string{
"http://127.0.0.1:4001",
"http://127.0.0.2:4001",
}),
"http://127.0.0.1:4001,http://127.0.0.2:4001",
},
{
testutil.MustNewURLs(t, []string{
"http://127.0.0.2:4001",
"http://127.0.0.1:4001",
}),
"http://127.0.0.2:4001,http://127.0.0.1:4001",
},
}
for i, tt := range tests {
g := tt.us.String()
if g != tt.wstr {
t.Errorf("#%d: string = %s, want %s", i, g, tt.wstr)
}
}
}
func TestURLsSort(t *testing.T) {
g := testutil.MustNewURLs(t, []string{
"http://127.0.0.4:4001",
"http://127.0.0.2:4001",
"http://127.0.0.1:4001",
"http://127.0.0.3:4001",
})
w := testutil.MustNewURLs(t, []string{
"http://127.0.0.1:4001",
"http://127.0.0.2:4001",
"http://127.0.0.3:4001",
"http://127.0.0.4:4001",
})
gurls := URLs(g)
gurls.Sort()
if !reflect.DeepEqual(g, w) {
t.Errorf("URLs after sort = %#v, want %#v", g, w)
}
}
func TestURLsStringSlice(t *testing.T) {
tests := []struct {
us URLs
wstr []string
}{
{
URLs{},
[]string{},
},
{
testutil.MustNewURLs(t, []string{"http://127.0.0.1:4001"}),
[]string{"http://127.0.0.1:4001"},
},
{
testutil.MustNewURLs(t, []string{
"http://127.0.0.1:4001",
"http://127.0.0.2:4001",
}),
[]string{"http://127.0.0.1:4001", "http://127.0.0.2:4001"},
},
{
testutil.MustNewURLs(t, []string{
"http://127.0.0.2:4001",
"http://127.0.0.1:4001",
}),
[]string{"http://127.0.0.2:4001", "http://127.0.0.1:4001"},
},
}
for i, tt := range tests {
g := tt.us.StringSlice()
if !reflect.DeepEqual(g, tt.wstr) {
t.Errorf("#%d: string slice = %+v, want %+v", i, g, tt.wstr)
}
}
}
func TestNewURLsFail(t *testing.T) {
tests := [][]string{
// no urls given
{},
// missing protocol scheme
{"://127.0.0.1:4001"},
// unsupported scheme
{"mailto://127.0.0.1:4001"},
// not conform to host:port
{"http://127.0.0.1"},
// contain a path
{"http://127.0.0.1:4001/path"},
}
for i, tt := range tests {
_, err := NewURLs(tt)
if err == nil {
t.Errorf("#%d: err = nil, but error", i)
}
}
}
// Copyright 2014 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package aci implements helper functions for working with ACIs
package aci
import (
"archive/tar"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"time"
"github.com/appc/spec/aci"
"github.com/appc/spec/schema"
"golang.org/x/crypto/openpgp"
)
type ACIEntry struct {
Header *tar.Header
Contents string
}
type imageArchiveWriter struct {
*tar.Writer
am *schema.ImageManifest
}
// NewImageWriter creates a new ArchiveWriter which will generate an App
// Container Image based on the given manifest and write it to the given
// tar.Writer
// TODO(sgotti) this is a copy of appc/spec/aci.imageArchiveWriter with
// addFileNow changed to create the file with the current user. needed for
// testing as non root user.
func NewImageWriter(am schema.ImageManifest, w *tar.Writer) aci.ArchiveWriter {
aw := &imageArchiveWriter{
w,
&am,
}
return aw
}
func (aw *imageArchiveWriter) AddFile(hdr *tar.Header, r io.Reader) error {
err := aw.Writer.WriteHeader(hdr)
if err != nil {
return err
}
if r != nil {
_, err := io.Copy(aw.Writer, r)
if err != nil {
return err
}
}
return nil
}
func (aw *imageArchiveWriter) addFileNow(path string, contents []byte) error {
buf := bytes.NewBuffer(contents)
now := time.Now()
hdr := tar.Header{
Name: path,
Mode: 0644,
Uid: os.Getuid(),
Gid: os.Getgid(),
Size: int64(buf.Len()),
ModTime: now,
Typeflag: tar.TypeReg,
ChangeTime: now,
}
return aw.AddFile(&hdr, buf)
}
func (aw *imageArchiveWriter) addManifest(name string, m json.Marshaler) error {
out, err := m.MarshalJSON()
if err != nil {
return err
}
return aw.addFileNow(name, out)
}
func (aw *imageArchiveWriter) Close() error {
if err := aw.addManifest(aci.ManifestFile, aw.am); err != nil {
return err
}
return aw.Writer.Close()
}
// NewBasicACI creates a new ACI in the given directory with the given name.
// Used for testing.
func NewBasicACI(dir string, name string) (*os.File, error) {
manifest := fmt.Sprintf(`{"acKind":"ImageManifest","acVersion":"0.5.4","name":"%s"}`, name)
return NewACI(dir, manifest, nil)
}
// NewACI creates a new ACI in the given directory with the given image
// manifest and entries.
// Used for testing.
func NewACI(dir string, manifest string, entries []*ACIEntry) (*os.File, error) {
var im schema.ImageManifest
if err := im.UnmarshalJSON([]byte(manifest)); err != nil {
return nil, err
}
tf, err := ioutil.TempFile(dir, "")
if err != nil {
return nil, err
}
defer os.Remove(tf.Name())
tw := tar.NewWriter(tf)
aw := NewImageWriter(im, tw)
for _, entry := range entries {
// Add default mode
if entry.Header.Mode == 0 {
if entry.Header.Typeflag == tar.TypeDir {
entry.Header.Mode = 0755
} else {
entry.Header.Mode = 0644
}
}
// Add calling user uid and gid or tests will fail
entry.Header.Uid = os.Getuid()
entry.Header.Gid = os.Getgid()
sr := strings.NewReader(entry.Contents)
if err := aw.AddFile(entry.Header, sr); err != nil {
return nil, err
}
}
if err := aw.Close(); err != nil {
return nil, err
}
return tf, nil
}
// NewDetachedSignature creates a new openpgp armored detached signature for the given ACI
// signed with armoredPrivateKey.
func NewDetachedSignature(armoredPrivateKey string, aci io.Reader) (io.Reader, error) {
entityList, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(armoredPrivateKey))
if err != nil {
return nil, err
}
if len(entityList) < 1 {
return nil, errors.New("empty entity list")
}
signature := &bytes.Buffer{}
if err := openpgp.ArmoredDetachSign(signature, entityList[0], aci, nil); err != nil {
return nil, err
}
return signature, nil
}
package aci
import (
"archive/tar"
"fmt"
"github.com/appc/spec/pkg/acirenderer"
"github.com/appc/spec/schema/types"
ptar "github.com/coreos/rkt/pkg/tar"
)
// Given an imageID, start with the matching image available in the store,
// build its dependency list and render it inside dir
func RenderACIWithImageID(imageID types.Hash, dir string, ap acirenderer.ACIRegistry) error {
renderedACI, err := acirenderer.GetRenderedACIWithImageID(imageID, ap)
if err != nil {
return err
}
return renderImage(renderedACI, dir, ap)
}
// Given an image app name and optional labels, get the best matching image
// available in the store, build its dependency list and render it inside dir
func RenderACI(name types.ACName, labels types.Labels, dir string, ap acirenderer.ACIRegistry) error {
renderedACI, err := acirenderer.GetRenderedACI(name, labels, ap)
if err != nil {
return err
}
return renderImage(renderedACI, dir, ap)
}
// Given an already populated dependency list, it will extract, under the provided
// directory, the rendered ACI
func RenderACIFromList(imgs acirenderer.Images, dir string, ap acirenderer.ACIProvider) error {
renderedACI, err := acirenderer.GetRenderedACIFromList(imgs, ap)
if err != nil {
return err
}
return renderImage(renderedACI, dir, ap)
}
// Given a RenderedACI, it will extract, under the provided directory, the
// needed files from the right source ACI.
// The manifest will be extracted from the upper ACI.
// No file overwriting is done as it should usually be called
// providing an empty directory.
func renderImage(renderedACI acirenderer.RenderedACI, dir string, ap acirenderer.ACIProvider) error {
for _, ra := range renderedACI {
rs, err := ap.ReadStream(ra.Key)
if err != nil {
return err
}
defer rs.Close()
// Overwrite is not needed. If a file needs to be overwritten then the renderedACI builder has a bug
if err := ptar.ExtractTar(tar.NewReader(rs), dir, false, ra.FileMap); err != nil {
return fmt.Errorf("error extracting ACI: %v", err)
}
}
return nil
}
// Copyright 2014 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package lock implements simple locking primitives on a
// regular file or directory using flock
package lock
import (
"errors"
"syscall"
)
var (
ErrLocked = errors.New("file already locked")
ErrNotExist = errors.New("file does not exist")
ErrPermission = errors.New("permission denied")
ErrNotRegular = errors.New("not a regular file")
)
// FileLock represents a lock on a regular file or a directory
type FileLock struct {
path string
fd int
}
type LockType int
const (
Dir LockType = iota
RegFile
)
// TryExclusiveLock takes an exclusive lock without blocking.
// This is idempotent when the Lock already represents an exclusive lock,
// and tries promote a shared lock to exclusive atomically.
// It will return ErrLocked if any lock is already held.
func (l *FileLock) TryExclusiveLock() error {
err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB)
if err == syscall.EWOULDBLOCK {
err = ErrLocked
}
return err
}
// TryExclusiveLock takes an exclusive lock on a file/directory without blocking.
// It will return ErrLocked if any lock is already held on the file/directory.
func TryExclusiveLock(path string, lockType LockType) (*FileLock, error) {
l, err := NewLock(path, lockType)
if err != nil {
return nil, err
}
err = l.TryExclusiveLock()
if err != nil {
return nil, err
}
return l, err
}
// ExclusiveLock takes an exclusive lock.
// This is idempotent when the Lock already represents an exclusive lock,
// and promotes a shared lock to exclusive atomically.
// It will block if an exclusive lock is already held.
func (l *FileLock) ExclusiveLock() error {
return syscall.Flock(l.fd, syscall.LOCK_EX)
}
// ExclusiveLock takes an exclusive lock on a file/directory.
// It will block if an exclusive lock is already held on the file/directory.
func ExclusiveLock(path string, lockType LockType) (*FileLock, error) {
l, err := NewLock(path, lockType)
if err == nil {
err = l.ExclusiveLock()
}
if err != nil {
return nil, err
}
return l, nil
}
// TrySharedLock takes a co-operative (shared) lock without blocking.
// This is idempotent when the Lock already represents a shared lock,
// and tries demote an exclusive lock to shared atomically.
// It will return ErrLocked if an exclusive lock already exists.
func (l *FileLock) TrySharedLock() error {
err := syscall.Flock(l.fd, syscall.LOCK_SH|syscall.LOCK_NB)
if err == syscall.EWOULDBLOCK {
err = ErrLocked
}
return err
}
// TrySharedLock takes a co-operative (shared) lock on a file/directory without blocking.
// It will return ErrLocked if an exclusive lock already exists on the file/directory.
func TrySharedLock(path string, lockType LockType) (*FileLock, error) {
l, err := NewLock(path, lockType)
if err != nil {
return nil, err
}
err = l.TrySharedLock()
if err != nil {
return nil, err
}
return l, nil
}
// SharedLock takes a co-operative (shared) lock on.
// This is idempotent when the Lock already represents a shared lock,
// and demotes an exclusive lock to shared atomically.
// It will block if an exclusive lock is already held.
func (l *FileLock) SharedLock() error {
return syscall.Flock(l.fd, syscall.LOCK_SH)
}
// SharedLock takes a co-operative (shared) lock on a file/directory.
// It will block if an exclusive lock is already held on the file/directory.
func SharedLock(path string, lockType LockType) (*FileLock, error) {
l, err := NewLock(path, lockType)
if err != nil {
return nil, err
}
err = l.SharedLock()
if err != nil {
return nil, err
}
return l, nil
}
// Unlock unlocks the lock
func (l *FileLock) Unlock() error {
return syscall.Flock(l.fd, syscall.LOCK_UN)
}
// Fd returns the lock's file descriptor, or an error if the lock is closed
func (l *FileLock) Fd() (int, error) {
var err error
if l.fd == -1 {
err = errors.New("lock closed")
}
return l.fd, err
}
// Close closes the lock which implicitly unlocks it as well
func (l *FileLock) Close() error {
fd := l.fd
l.fd = -1
return syscall.Close(fd)
}
// NewLock opens a new lock on a file without acquisition
func NewLock(path string, lockType LockType) (*FileLock, error) {
l := &FileLock{path: path, fd: -1}
mode := syscall.O_RDONLY | syscall.O_CLOEXEC
if lockType == Dir {
mode |= syscall.O_DIRECTORY
}
lfd, err := syscall.Open(l.path, mode, 0)
if err != nil {
if err == syscall.ENOENT {
err = ErrNotExist
} else if err == syscall.EACCES {
err = ErrPermission
}
return nil, err
}
l.fd = lfd
var stat syscall.Stat_t
err = syscall.Fstat(lfd, &stat)
if err != nil {
return nil, err
}
// Check if the file is a regular file
if lockType == RegFile && !(stat.Mode&syscall.S_IFMT == syscall.S_IFREG) {
return nil, ErrNotRegular
}
return l, nil
}
// Copyright 2014 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lock
import (
"io/ioutil"
"os"
"testing"
)
func TestNewLock(t *testing.T) {
f, err := ioutil.TempFile("", "")
if err != nil {
t.Fatalf("error creating tmpfile: %v", err)
}
defer os.Remove(f.Name())
f.Close()
l, err := NewLock(f.Name(), RegFile)
if err != nil {
t.Fatalf("error creating NewFileLock: %v", err)
}
l.Close()
d, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating tmpdir: %v", err)
}
defer os.Remove(d)
l, err = NewLock(d, Dir)
if err != nil {
t.Fatalf("error creating NewLock: %v", err)
}
err = l.Close()
if err != nil {
t.Fatalf("error unlocking lock: %v", err)
}
if err = os.Remove(d); err != nil {
t.Fatalf("error removing tmpdir: %v", err)
}
l, err = NewLock(d, Dir)
if err == nil {
t.Fatalf("expected error creating lock on nonexistent path")
}
}
func TestExclusiveLock(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating tmpdir: %v", err)
}
defer os.Remove(dir)
// Set up the initial exclusive lock
l, err := ExclusiveLock(dir, Dir)
if err != nil {
t.Fatalf("error creating lock: %v", err)
}
// reacquire the exclusive lock using the receiver interface
err = l.TryExclusiveLock()
if err != nil {
t.Fatalf("error reacquiring exclusive lock: %v", err)
}
// Now try another exclusive lock, should fail
_, err = TryExclusiveLock(dir, Dir)
if err == nil {
t.Fatalf("expected err trying exclusive lock")
}
// Unlock the original lock
err = l.Close()
if err != nil {
t.Fatalf("error closing lock: %v", err)
}
// Now another exclusive lock should succeed
_, err = TryExclusiveLock(dir, Dir)
if err != nil {
t.Fatalf("error creating lock: %v", err)
}
}
func TestSharedLock(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating tmpdir: %v", err)
}
defer os.Remove(dir)
// Set up the initial shared lock
l1, err := SharedLock(dir, Dir)
if err != nil {
t.Fatalf("error creating new shared lock: %v", err)
}
err = l1.TrySharedLock()
if err != nil {
t.Fatalf("error reacquiring shared lock: %v", err)
}
// Subsequent shared locks should succeed
l2, err := TrySharedLock(dir, Dir)
if err != nil {
t.Fatalf("error creating shared lock: %v", err)
}
l3, err := TrySharedLock(dir, Dir)
if err != nil {
t.Fatalf("error creating shared lock: %v", err)
}
// But an exclusive lock should fail
_, err = TryExclusiveLock(dir, Dir)
if err == nil {
t.Fatal("expected exclusive lock to fail")
}
// Close the locks
err = l1.Close()
if err != nil {
t.Fatalf("error closing lock: %v", err)
}
err = l2.Close()
if err != nil {
t.Fatalf("error closing lock: %v", err)
}
// Only unlock one of them
err = l3.Unlock()
if err != nil {
t.Fatalf("error unlocking lock: %v", err)
}
// Now try an exclusive lock, should succeed
_, err = TryExclusiveLock(dir, Dir)
if err != nil {
t.Fatalf("error creating lock: %v", err)
}
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lock
import (
"fmt"
"os"
"path/filepath"
"syscall"
)
const (
defaultDirPerm os.FileMode = 0660
defaultFilePerm os.FileMode = 0660
defaultLockRetries = 3
)
type keyLockMode uint
const (
keyLockExclusive keyLockMode = 1 << iota
keyLockShared
keyLockNonBlocking
)
// KeyLock is a lock for a specific key. The lock file is created inside a
// directory using the key name.
// This is useful when multiple processes want to take a lock but cannot use
// FileLock as they don't have a well defined file on the filesystem.
// key value must be a valid file name (as the lock file is named after the key
// value).
type KeyLock struct {
lockDir string
key string
// The lock on the key
keyLock *FileLock
}
// NewKeyLock returns a KeyLock for the specified key without acquisition.
// lockdir is the directory where the lock file will be created. If lockdir
// doesn't exists it will be created.
// key value must be a valid file name (as the lock file is named after the key
// value).
func NewKeyLock(lockDir string, key string) (*KeyLock, error) {
err := os.MkdirAll(lockDir, defaultDirPerm)
if err != nil {
return nil, err
}
keyLockFile := filepath.Join(lockDir, key)
// create the file if it doesn't exists
f, err := os.OpenFile(keyLockFile, os.O_RDONLY|os.O_CREATE, defaultFilePerm)
if err != nil {
return nil, fmt.Errorf("error creating key lock file: %v", err)
}
f.Close()
keyLock, err := NewLock(keyLockFile, RegFile)
if err != nil {
return nil, fmt.Errorf("error opening key lock file: %v", err)
}
return &KeyLock{lockDir: lockDir, key: key, keyLock: keyLock}, nil
}
// Close closes the key lock which implicitly unlocks it as well
func (l *KeyLock) Close() {
l.keyLock.Close()
}
// TryExclusiveLock takes an exclusive lock on a key without blocking.
// This is idempotent when the KeyLock already represents an exclusive lock,
// and tries promote a shared lock to exclusive atomically.
// It will return ErrLocked if any lock is already held on the key.
func (l *KeyLock) TryExclusiveKeyLock() error {
return l.lock(keyLockExclusive|keyLockNonBlocking, defaultLockRetries)
}
// TryExclusiveLock takes an exclusive lock on the key without blocking.
// lockDir is the directory where the lock file will be created.
// It will return ErrLocked if any lock is already held.
func TryExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) {
return createAndLock(lockDir, key, keyLockExclusive|keyLockNonBlocking)
}
// ExclusiveLock takes an exclusive lock on a key.
// This is idempotent when the KeyLock already represents an exclusive lock,
// and promotes a shared lock to exclusive atomically.
// It will block if an exclusive lock is already held on the key.
func (l *KeyLock) ExclusiveKeyLock() error {
return l.lock(keyLockExclusive, defaultLockRetries)
}
// ExclusiveLock takes an exclusive lock on a key.
// lockDir is the directory where the lock file will be created.
// It will block if an exclusive lock is already held on the key.
func ExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) {
return createAndLock(lockDir, key, keyLockExclusive)
}
// TrySharedLock takes a co-operative (shared) lock on the key without blocking.
// This is idempotent when the KeyLock already represents a shared lock,
// and tries demote an exclusive lock to shared atomically.
// It will return ErrLocked if an exclusive lock already exists on the key.
func (l *KeyLock) TrySharedKeyLock() error {
return l.lock(keyLockShared|keyLockNonBlocking, defaultLockRetries)
}
// TrySharedLock takes a co-operative (shared) lock on a key without blocking.
// lockDir is the directory where the lock file will be created.
// It will return ErrLocked if an exclusive lock already exists on the key.
func TrySharedKeyLock(lockDir string, key string) (*KeyLock, error) {
return createAndLock(lockDir, key, keyLockShared|keyLockNonBlocking)
}
// SharedLock takes a co-operative (shared) lock on a key.
// This is idempotent when the KeyLock already represents a shared lock,
// and demotes an exclusive lock to shared atomically.
// It will block if an exclusive lock is already held on the key.
func (l *KeyLock) SharedKeyLock() error {
return l.lock(keyLockShared, defaultLockRetries)
}
// SharedLock takes a co-operative (shared) lock on a key.
// lockDir is the directory where the lock file will be created.
// It will block if an exclusive lock is already held on the key.
func SharedKeyLock(lockDir string, key string) (*KeyLock, error) {
return createAndLock(lockDir, key, keyLockShared)
}
func createAndLock(lockDir string, key string, mode keyLockMode) (*KeyLock, error) {
keyLock, err := NewKeyLock(lockDir, key)
if err != nil {
return nil, err
}
err = keyLock.lock(mode, defaultLockRetries)
if err != nil {
keyLock.Close()
return nil, err
}
return keyLock, nil
}
// lock is the base function to take a lock and handle changed lock files
// As there's the need to remove unused (see CleanKeyLocks) lock files without
// races, a changed file detection is needed.
//
// Without changed file detection this can happen:
//
// Process A takes exclusive lock on file01
// Process B waits for exclusive lock on file01.
// Process A deletes file01 and then releases the lock.
// Process B takes the lock on the removed file01 as it has the fd opened
// Process C comes, creates the file as it doesn't exists, and it also takes an exclusive lock.
// Now B and C thinks to own an exclusive lock.
//
// maxRetries can be passed, useful for testing.
func (l *KeyLock) lock(mode keyLockMode, maxRetries int) error {
retries := 0
for {
var err error
var isExclusive bool
var isNonBlocking bool
if mode&keyLockExclusive != 0 {
isExclusive = true
}
if mode&keyLockNonBlocking != 0 {
isNonBlocking = true
}
switch {
case isExclusive && !isNonBlocking:
err = l.keyLock.ExclusiveLock()
case isExclusive && isNonBlocking:
err = l.keyLock.TryExclusiveLock()
case !isExclusive && !isNonBlocking:
err = l.keyLock.SharedLock()
case !isExclusive && isNonBlocking:
err = l.keyLock.TrySharedLock()
}
if err != nil {
return err
}
// Check that the file referenced by the lock fd is the same as
// the current file on the filesystem
var lockStat, curStat syscall.Stat_t
lfd, err := l.keyLock.Fd()
if err != nil {
return err
}
err = syscall.Fstat(lfd, &lockStat)
if err != nil {
return err
}
keyLockFile := filepath.Join(l.lockDir, l.key)
fd, err := syscall.Open(keyLockFile, syscall.O_RDONLY, 0)
// If there's an error opening the file return an error
if err != nil {
return err
}
if err := syscall.Fstat(fd, &curStat); err != nil {
syscall.Close(fd)
return err
}
syscall.Close(fd)
if lockStat.Ino == curStat.Ino && lockStat.Dev == curStat.Dev {
return nil
}
if retries >= maxRetries {
return fmt.Errorf("cannot acquire lock after %d retries", retries)
}
// If the file has changed discard this lock and try to take another lock.
l.keyLock.Close()
nl, err := NewKeyLock(l.lockDir, l.key)
if err != nil {
return err
}
l.keyLock = nl.keyLock
retries++
}
}
// Unlock unlocks the key lock.
func (l *KeyLock) Unlock() error {
err := l.keyLock.Unlock()
if err != nil {
return err
}
return nil
}
// CleanKeyLocks remove lock files from the lockDir.
// For every key it tries to take an Exclusive lock on it and skip it if it
// fails with ErrLocked
func CleanKeyLocks(lockDir string) error {
f, err := os.Open(lockDir)
if err != nil {
return fmt.Errorf("error opening lockDir: %v", err)
}
defer f.Close()
files, err := f.Readdir(0)
if err != nil {
return fmt.Errorf("error getting lock files list: %v", err)
}
for _, f := range files {
filename := filepath.Join(lockDir, f.Name())
keyLock, err := TryExclusiveKeyLock(lockDir, f.Name())
if err == ErrLocked {
continue
}
if err != nil {
return err
}
err = os.Remove(filename)
if err != nil {
keyLock.Close()
return fmt.Errorf("error removing lock file: %v", err)
}
keyLock.Close()
}
return nil
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lock
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestExclusiveKeyLock(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating tmpdir: %v", err)
}
defer os.RemoveAll(dir)
l1, err := ExclusiveKeyLock(dir, "key01")
if err != nil {
t.Fatalf("error creating key lock: %v", err)
}
_, err = TryExclusiveKeyLock(dir, "key01")
if err == nil {
t.Fatalf("expected err trying exclusive key lock")
}
l1.Close()
}
func TestCleanKeyLocks(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating tmpdir: %v", err)
}
defer os.RemoveAll(dir)
l1, err := ExclusiveKeyLock(dir, "key01")
if err != nil {
t.Fatalf("error creating keyLock: %v", err)
}
err = CleanKeyLocks(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
filesnum, err := countFiles(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if filesnum != 1 {
t.Fatalf("expected 1 file in lock dir. found %d files", filesnum)
}
l2, err := SharedKeyLock(dir, "key02")
if err != nil {
t.Fatalf("error creating keyLock: %v", err)
}
l1.Close()
l2.Close()
err = CleanKeyLocks(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
filesnum, err = countFiles(dir)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if filesnum != 0 {
t.Fatalf("expected empty lock dir. found %d files", filesnum)
}
}
func TestFileChangedLock(t *testing.T) {
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating tmpdir: %v", err)
}
defer os.RemoveAll(dir)
l1, err := ExclusiveKeyLock(dir, "key01")
if err != nil {
t.Fatalf("error creating keyLock: %v", err)
}
l2, err := NewKeyLock(dir, "key01")
if err != nil {
t.Fatalf("error creating keyLock: %v", err)
}
// Simulate that l1 owner removes the actual key1 lock file
err = os.Remove(filepath.Join(dir, "key01"))
if err != nil {
t.Fatalf("error creating NewLock: %v", err)
}
l1.Close()
// Now l2 owner takes a lock, using the fd of the removed file
err = l2.lock(keyLockShared, 0)
if err == nil {
t.Fatalf("expected error")
}
l2.Close()
// Do the same with a new file created after removal
dir, err = ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating tmpdir: %v", err)
}
defer os.RemoveAll(dir)
l1, err = ExclusiveKeyLock(dir, "key01")
if err != nil {
t.Fatalf("error creating keyLock: %v", err)
}
l2, err = NewKeyLock(dir, "key01")
if err != nil {
t.Fatalf("error creating keyLock: %v", err)
}
// Simulate that l1 owner removes the actual key1 lock file
err = os.Remove(filepath.Join(dir, "key01"))
if err != nil {
t.Fatalf("error creating NewLock: %v", err)
}
l1.Close()
// Simulate that another user comes and takes a lock, this will create
// a new lock file as it was removed.
l3, err := ExclusiveKeyLock(dir, "key01")
l3.Close()
// Now l2 owner takes a lock, using the fd of the old file
err = l2.lock(keyLockShared, 0)
if err == nil {
t.Fatalf("expected error")
}
// Do the same but with a retry so if should work.
dir, err = ioutil.TempDir("", "")
if err != nil {
t.Fatalf("error creating tmpdir: %v", err)
}
defer os.RemoveAll(dir)
l1, err = ExclusiveKeyLock(dir, "key01")
if err != nil {
t.Fatalf("error creating keyLock: %v", err)
}
l2, err = NewKeyLock(dir, "key01")
if err != nil {
t.Fatalf("error creating keyLock: %v", err)
}
// Simulate that l1 owner removes the actual key1 lock file
err = os.Remove(filepath.Join(dir, "key01"))
if err != nil {
t.Fatalf("error creating NewLock: %v", err)
}
l1.Close()
// Simulate that another user comes and takes a lock, this will create
// a new lock file as it was removed.
l3, err = ExclusiveKeyLock(dir, "key01")
l3.Close()
// Now l2 owner takes a lock, using the fd of the old file
err = l2.lock(keyLockShared, 1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func countFiles(dir string) (int, error) {
f, err := os.Open(dir)
if err != nil {
return -1, err
}
defer f.Close()
files, err := f.Readdir(0)
if err != nil {
return -1, err
}
return len(files), nil
}
package sys
import (
"syscall"
)
// CloseOnExec sets or clears FD_CLOEXEC flag on a file descriptor
func CloseOnExec(fd int, set bool) error {
flag := uintptr(0)
if set {
flag = syscall.FD_CLOEXEC
}
_, _, err := syscall.RawSyscall(syscall.SYS_FCNTL, uintptr(fd), syscall.F_SETFD, flag)
if err != 0 {
return syscall.Errno(err)
}
return nil
}
package sys
import "syscall"
func Syncfs(fd int) error {
_, _, err := syscall.RawSyscall(SYS_SYNCFS, uintptr(fd), 0, 0)
if err != 0 {
return syscall.Errno(err)
}
return nil
}
// Copyright 2014 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package tar contains helper functions for working with tar files
package tar
import (
"archive/tar"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"time"
)
const DEFAULT_DIR_MODE os.FileMode = 0755
type insecureLinkError error
var ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
// Map of paths that should be whitelisted. The paths should be relative to the
// root of the tar file and should be cleaned (for example using filepath.Clean)
type PathWhitelistMap map[string]struct{}
// ExtractTar extracts a tarball (from a tar.Reader) into the given directory
// if pwl is not nil, only the paths in the map are extracted.
// If overwrite is true, existing files will be overwritten.
func ExtractTar(tr *tar.Reader, dir string, overwrite bool, pwl PathWhitelistMap) error {
um := syscall.Umask(0)
defer syscall.Umask(um)
dirhdrs := []*tar.Header{}
Tar:
for {
hdr, err := tr.Next()
switch err {
case io.EOF:
break Tar
case nil:
if pwl != nil {
relpath := filepath.Clean(hdr.Name)
if _, ok := pwl[relpath]; !ok {
continue
}
}
err = ExtractFile(tr, hdr, dir, overwrite)
if err != nil {
return fmt.Errorf("error extracting tarball: %v", err)
}
if hdr.Typeflag == tar.TypeDir {
dirhdrs = append(dirhdrs, hdr)
}
default:
return fmt.Errorf("error extracting tarball: %v", err)
}
}
// Restore dirs atime and mtime. This has to be done after extracting
// as a file extraction will change its parent directory's times.
for _, hdr := range dirhdrs {
p := filepath.Join(dir, hdr.Name)
if err := syscall.UtimesNano(p, HdrToTimespec(hdr)); err != nil {
return err
}
}
return nil
}
// ExtractFile extracts the file described by hdr from the given tarball into
// the provided directory.
// If overwrite is true, existing files will be overwritten.
func ExtractFile(tr *tar.Reader, hdr *tar.Header, dir string, overwrite bool) error {
p := filepath.Join(dir, hdr.Name)
fi := hdr.FileInfo()
typ := hdr.Typeflag
if overwrite {
info, err := os.Lstat(p)
switch {
case os.IsNotExist(err):
case err == nil:
// If the old and new paths are both dirs do nothing or
// RemoveAll will remove all dir's contents
if !info.IsDir() || typ != tar.TypeDir {
err := os.RemoveAll(p)
if err != nil {
return err
}
}
default:
return err
}
}
// Create parent dir if it doesn't exist
if err := os.MkdirAll(filepath.Dir(p), DEFAULT_DIR_MODE); err != nil {
return err
}
switch {
case typ == tar.TypeReg || typ == tar.TypeRegA:
f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, fi.Mode())
if err != nil {
return err
}
_, err = io.Copy(f, tr)
if err != nil {
f.Close()
return err
}
f.Close()
case typ == tar.TypeDir:
if err := os.MkdirAll(p, fi.Mode()); err != nil {
return err
}
dir, err := os.Open(p)
if err != nil {
return err
}
if err := dir.Chmod(fi.Mode()); err != nil {
dir.Close()
return err
}
dir.Close()
case typ == tar.TypeLink:
dest := filepath.Join(dir, hdr.Linkname)
if !strings.HasPrefix(dest, dir) {
return insecureLinkError(fmt.Errorf("insecure link %q -> %q", p, hdr.Linkname))
}
if err := os.Link(dest, p); err != nil {
return err
}
case typ == tar.TypeSymlink:
dest := filepath.Join(filepath.Dir(p), hdr.Linkname)
if !strings.HasPrefix(dest, dir) {
return insecureLinkError(fmt.Errorf("insecure symlink %q -> %q", p, hdr.Linkname))
}
if err := os.Symlink(hdr.Linkname, p); err != nil {
return err
}
case typ == tar.TypeChar:
dev := makedev(int(hdr.Devmajor), int(hdr.Devminor))
mode := uint32(fi.Mode()) | syscall.S_IFCHR
if err := syscall.Mknod(p, mode, dev); err != nil {
return err
}
case typ == tar.TypeBlock:
dev := makedev(int(hdr.Devmajor), int(hdr.Devminor))
mode := uint32(fi.Mode()) | syscall.S_IFBLK
if err := syscall.Mknod(p, mode, dev); err != nil {
return err
}
case typ == tar.TypeFifo:
if err := syscall.Mkfifo(p, uint32(fi.Mode())); err != nil {
return err
}
// TODO(jonboulle): implement other modes
default:
return fmt.Errorf("unsupported type: %v", typ)
}
if err := os.Lchown(p, hdr.Uid, hdr.Gid); err != nil {
return err
}
// lchown(2) says that, depending on the linux kernel version, it
// can change the file's mode also if executed as root. So call
// os.Chmod after it.
if typ != tar.TypeSymlink {
if err := os.Chmod(p, fi.Mode()); err != nil {
return err
}
}
// Restore entry atime and mtime.
// Use special function LUtimesNano not available on go's syscall package because we
// have to restore symlink's times and not the referenced file times.
ts := HdrToTimespec(hdr)
if hdr.Typeflag != tar.TypeSymlink {
if err := syscall.UtimesNano(p, ts); err != nil {
return err
}
} else {
if err := LUtimesNano(p, ts); err != nil && err != ErrNotSupportedPlatform {
return err
}
}
return nil
}
// ExtractFileFromTar extracts a regular file from the given tar, returning its
// contents as a byte slice
func ExtractFileFromTar(tr *tar.Reader, file string) ([]byte, error) {
for {
hdr, err := tr.Next()
switch err {
case io.EOF:
return nil, fmt.Errorf("file not found")
case nil:
if filepath.Clean(hdr.Name) != filepath.Clean(file) {
continue
}
switch hdr.Typeflag {
case tar.TypeReg:
case tar.TypeRegA:
default:
return nil, fmt.Errorf("requested file not a regular file")
}
buf, err := ioutil.ReadAll(tr)
if err != nil {
return nil, fmt.Errorf("error extracting tarball: %v", err)
}
return buf, nil
default:
return nil, fmt.Errorf("error extracting tarball: %v", err)
}
}
}
// makedev mimics glib's gnu_dev_makedev
func makedev(major, minor int) int {
return (minor & 0xff) | (major & 0xfff << 8) | int((uint64(minor & ^0xff) << 12)) | int(uint64(major & ^0xfff)<<32)
}
func HdrToTimespec(hdr *tar.Header) []syscall.Timespec {
return []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
}
// TODO(sgotti) use UTIMES_OMIT on linux if Time.IsZero ?
func timeToTimespec(time time.Time) (ts syscall.Timespec) {
nsec := int64(0)
if !time.IsZero() {
nsec = time.UnixNano()
}
return syscall.NsecToTimespec(nsec)
}
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// These functions are from github.com/docker/docker/pkg/system
// TODO(sgotti) waiting for a utimensat functions accepting flags and a
// LUtimesNano using it in https://github.com/golang/sys/
package tar
import (
"syscall"
"unsafe"
)
func LUtimesNano(path string, ts []syscall.Timespec) error {
// These are not currently available in syscall
AT_FDCWD := -100
AT_SYMLINK_NOFOLLOW := 0x100
var _path *byte
_path, err := syscall.BytePtrFromString(path)
if err != nil {
return err
}
if _, _, err := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(AT_FDCWD), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), uintptr(AT_SYMLINK_NOFOLLOW), 0, 0); err != 0 && err != syscall.ENOSYS {
return err
}
return nil
}
// +build !linux
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// These functions are from github.com/docker/docker/pkg/system
package tar
import "syscall"
func LUtimesNano(path string, ts []syscall.Timespec) error {
return ErrNotSupportedPlatform
}
package store
import (
"database/sql"
"time"
)
// ACIInfo is used to store information about an ACI.
type ACIInfo struct {
// BlobKey is the key in the blob/imageManifest store of the related
// ACI file and is the db primary key.
BlobKey string
// AppName is the app name provided by the ACI.
AppName string
// ImportTime is the time this ACI was imported in the store.
ImportTime time.Time
// Latest defines if the ACI was imported using the latest pattern (no
// version label was provided on ACI discovery)
Latest bool
}
func NewACIInfo(blobKey string, latest bool, t time.Time) *ACIInfo {
return &ACIInfo{
BlobKey: blobKey,
Latest: latest,
ImportTime: t,
}
}
// GetAciInfosWithKeyPrefix returns all the ACIInfos with a blobkey starting with the given prefix.
func GetACIInfosWithKeyPrefix(tx *sql.Tx, prefix string) ([]*ACIInfo, error) {
aciinfos := []*ACIInfo{}
rows, err := tx.Query("SELECT * from aciinfo WHERE hasPrefix(blobkey, $1)", prefix)
if err != nil {
return nil, err
}
for rows.Next() {
aciinfo := &ACIInfo{}
if err := rows.Scan(&aciinfo.BlobKey, &aciinfo.AppName, &aciinfo.ImportTime, &aciinfo.Latest); err != nil {
return nil, err
}
aciinfos = append(aciinfos, aciinfo)
}
if err := rows.Err(); err != nil {
return nil, err
}
return aciinfos, err
}
// GetAciInfosWithAppName returns all the ACIInfos for a given appname. found will be
// false if no aciinfo exists.
func GetACIInfosWithAppName(tx *sql.Tx, appname string) ([]*ACIInfo, bool, error) {
aciinfos := []*ACIInfo{}
found := false
rows, err := tx.Query("SELECT * from aciinfo WHERE appname == $1", appname)
if err != nil {
return nil, false, err
}
for rows.Next() {
found = true
aciinfo := &ACIInfo{}
if err := rows.Scan(&aciinfo.BlobKey, &aciinfo.AppName, &aciinfo.ImportTime, &aciinfo.Latest); err != nil {
return nil, false, err
}
aciinfos = append(aciinfos, aciinfo)
}
if err := rows.Err(); err != nil {
return nil, false, err
}
return aciinfos, found, err
}
// WriteACIInfo adds or updates the provided aciinfo.
func WriteACIInfo(tx *sql.Tx, aciinfo *ACIInfo) error {
// ql doesn't have an INSERT OR UPDATE function so
// it's faster to remove and reinsert the row
_, err := tx.Exec("DELETE from aciinfo where blobkey == $1", aciinfo.BlobKey)
if err != nil {
return err
}
_, err = tx.Exec("INSERT into aciinfo values ($1, $2, $3, $4)", aciinfo.BlobKey, aciinfo.AppName, aciinfo.ImportTime, aciinfo.Latest)
if err != nil {
return err
}
return nil
}
package store
import (
"database/sql"
"io/ioutil"
"os"
"testing"
)
func TestWriteACIInfo(t *testing.T) {
dir, err := ioutil.TempDir("", tstprefix)
if err != nil {
t.Fatalf("error creating tempdir: %v", err)
}
defer os.RemoveAll(dir)
s, err := NewStore(dir)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if err = s.db.Do(func(tx *sql.Tx) error {
aciinfo := &ACIInfo{
BlobKey: "key01",
AppName: "name01",
}
if err := WriteACIInfo(tx, aciinfo); err != nil {
return err
}
// Insert it another time to check that is should be overwritten
if err := WriteACIInfo(tx, aciinfo); err != nil {
return err
}
return nil
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
aciinfos := []*ACIInfo{}
ok := false
if err = s.db.Do(func(tx *sql.Tx) error {
aciinfos, ok, err = GetACIInfosWithAppName(tx, "name01")
return err
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ok {
t.Fatalf("expected some records but none found")
}
if len(aciinfos) != 1 {
t.Fatalf("wrong number of records returned, wanted: 1, got: %d", len(aciinfos))
}
// Add another ACIInfo for the same app name
if err = s.db.Do(func(tx *sql.Tx) error {
aciinfo := &ACIInfo{
BlobKey: "key02",
AppName: "name01",
}
if err := WriteACIInfo(tx, aciinfo); err != nil {
return err
}
return nil
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if err = s.db.Do(func(tx *sql.Tx) error {
aciinfos, ok, err = GetACIInfosWithAppName(tx, "name01")
return err
}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ok {
t.Fatalf("expected some records but none found")
}
if len(aciinfos) != 2 {
t.Fatalf("wrong number of records returned, wanted: 2, got: %d", len(aciinfos))
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment