update vendor
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
parent
7a437ada25
commit
639756e8c6
4300 changed files with 824810 additions and 9292 deletions
206
Gopkg.lock
generated
Normal file
206
Gopkg.lock
generated
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/Microsoft/go-winio"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
|
||||||
|
version = "v0.4.7"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/containerd/console"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "cb7008ab3d8359b78c5f464cb7cf160107ad5925"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/containerd/continuity"
|
||||||
|
packages = ["pathdriver"]
|
||||||
|
revision = "d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/coreos/go-systemd"
|
||||||
|
packages = [
|
||||||
|
"activation",
|
||||||
|
"dbus",
|
||||||
|
"util"
|
||||||
|
]
|
||||||
|
revision = "54f92f7696b56003327688a2f18514c6c43e98c7"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/coreos/pkg"
|
||||||
|
packages = ["dlopen"]
|
||||||
|
revision = "3ac0863d7acf3bc44daf49afef8919af12f704ef"
|
||||||
|
version = "v3"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/cyphar/filepath-securejoin"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "06bda8370f45268db985f7af15732444d94ed51c"
|
||||||
|
version = "v0.2.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/docker/docker"
|
||||||
|
packages = [
|
||||||
|
"pkg/aaparser",
|
||||||
|
"pkg/archive",
|
||||||
|
"pkg/fileutils",
|
||||||
|
"pkg/idtools",
|
||||||
|
"pkg/ioutils",
|
||||||
|
"pkg/longpath",
|
||||||
|
"pkg/mount",
|
||||||
|
"pkg/pools",
|
||||||
|
"pkg/system",
|
||||||
|
"profiles/apparmor"
|
||||||
|
]
|
||||||
|
revision = "0c1006f1abc1af7aa6b9847754370d054dfa6c68"
|
||||||
|
source = "github.com/moby/moby"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/docker/go-units"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "0dadbb0345b35ec7ef35e228dabb8de89a65bf52"
|
||||||
|
version = "v0.3.2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/godbus/dbus"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "a389bdde4dd695d414e47b755e95e72b7826432c"
|
||||||
|
version = "v4.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/golang/protobuf"
|
||||||
|
packages = ["proto"]
|
||||||
|
revision = "925541529c1fa6821df4e44ce2723319eb2be768"
|
||||||
|
version = "v1.0.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mrunalp/fileutils"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "7d4729fb36185a7c1719923406c9d40e54fb93c7"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/opencontainers/go-digest"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
|
||||||
|
version = "v1.0.0-rc1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/opencontainers/image-spec"
|
||||||
|
packages = [
|
||||||
|
"specs-go",
|
||||||
|
"specs-go/v1"
|
||||||
|
]
|
||||||
|
revision = "d60099175f88c47cd379c4738d158884749ed235"
|
||||||
|
version = "v1.0.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/opencontainers/runc"
|
||||||
|
packages = [
|
||||||
|
"libcontainer",
|
||||||
|
"libcontainer/apparmor",
|
||||||
|
"libcontainer/cgroups",
|
||||||
|
"libcontainer/cgroups/fs",
|
||||||
|
"libcontainer/cgroups/systemd",
|
||||||
|
"libcontainer/configs",
|
||||||
|
"libcontainer/configs/validate",
|
||||||
|
"libcontainer/criurpc",
|
||||||
|
"libcontainer/intelrdt",
|
||||||
|
"libcontainer/keys",
|
||||||
|
"libcontainer/mount",
|
||||||
|
"libcontainer/nsenter",
|
||||||
|
"libcontainer/seccomp",
|
||||||
|
"libcontainer/specconv",
|
||||||
|
"libcontainer/stacktrace",
|
||||||
|
"libcontainer/system",
|
||||||
|
"libcontainer/user",
|
||||||
|
"libcontainer/utils"
|
||||||
|
]
|
||||||
|
revision = "69663f0bd4b60df09991c08812a60108003fa340"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/opencontainers/runtime-spec"
|
||||||
|
packages = ["specs-go"]
|
||||||
|
revision = "4e3b9264a330d094b0386c3703c5f379119711e8"
|
||||||
|
version = "v1.0.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/opencontainers/selinux"
|
||||||
|
packages = [
|
||||||
|
"go-selinux",
|
||||||
|
"go-selinux/label"
|
||||||
|
]
|
||||||
|
revision = "ba1aefe8057f1d0cfb8e88d0ec1dc85925ef987d"
|
||||||
|
version = "v1.0.0-rc1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/pkg/errors"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
|
version = "v0.8.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/seccomp/libseccomp-golang"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "f6ec81daf48e41bf48b475afc7fe06a26bfb72d1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/sirupsen/logrus"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
|
||||||
|
version = "v1.0.5"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/syndtr/gocapability"
|
||||||
|
packages = ["capability"]
|
||||||
|
revision = "33e07d32887e1e06b7c025f27ce52f62c7990bc0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/vishvananda/netlink"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"nl"
|
||||||
|
]
|
||||||
|
revision = "a2ad57a690f3caf3015351d2d6e1c0b95c349752"
|
||||||
|
version = "v1.0.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/vishvananda/netns"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "be1fbeda19366dea804f00efff2dd73a1642fdcc"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/crypto"
|
||||||
|
packages = ["ssh/terminal"]
|
||||||
|
revision = "c3a3ad6d03f7a915c0f7e194b7152974bb73d287"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/net"
|
||||||
|
packages = ["context"]
|
||||||
|
revision = "6078986fec03a1dcc236c34816c71b0e05018fda"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/sys"
|
||||||
|
packages = [
|
||||||
|
"unix",
|
||||||
|
"windows"
|
||||||
|
]
|
||||||
|
revision = "d8e400bc7db4870d786864138af681469693d18c"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "98adca7c9d30cc495c40327558bb9083199eb77eefb7b99aff37bbdf2abd4992"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
50
Gopkg.toml
Normal file
50
Gopkg.toml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "github.com/Sirupsen/logrus"
|
||||||
|
source = "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
[[override]]
|
||||||
|
name = "github.com/docker/docker"
|
||||||
|
source = "github.com/moby/moby"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/opencontainers/runc"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/opencontainers/runtime-spec"
|
||||||
|
version = "v1.0.1"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/containerd/console"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/coreos/go-systemd"
|
||||||
|
branch = "master"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/seccomp/libseccomp-golang"
|
||||||
|
branch = "master"
|
1
vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.exe
|
22
vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
22
vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Microsoft
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
22
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
22
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# go-winio
|
||||||
|
|
||||||
|
This repository contains utilities for efficiently performing Win32 IO operations in
|
||||||
|
Go. Currently, this is focused on accessing named pipes and other file handles, and
|
||||||
|
for using named pipes as a net transport.
|
||||||
|
|
||||||
|
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go
|
||||||
|
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and
|
||||||
|
newer operating systems. This is similar to the implementation of network sockets in Go's net
|
||||||
|
package.
|
||||||
|
|
||||||
|
Please see the LICENSE file for licensing information.
|
||||||
|
|
||||||
|
This project has adopted the [Microsoft Open Source Code of
|
||||||
|
Conduct](https://opensource.microsoft.com/codeofconduct/). For more information
|
||||||
|
see the [Code of Conduct
|
||||||
|
FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact
|
||||||
|
[opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional
|
||||||
|
questions or comments.
|
||||||
|
|
||||||
|
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
|
||||||
|
for another named pipe implementation.
|
27
vendor/github.com/Microsoft/go-winio/archive/tar/LICENSE
generated
vendored
Normal file
27
vendor/github.com/Microsoft/go-winio/archive/tar/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
344
vendor/github.com/Microsoft/go-winio/archive/tar/common.go
generated
vendored
Normal file
344
vendor/github.com/Microsoft/go-winio/archive/tar/common.go
generated
vendored
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package tar implements access to tar archives.
|
||||||
|
// It aims to cover most of the variations, including those produced
|
||||||
|
// by GNU and BSD tars.
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
// http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
|
||||||
|
// http://www.gnu.org/software/tar/manual/html_node/Standard.html
|
||||||
|
// http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
blockSize = 512
|
||||||
|
|
||||||
|
// Types
|
||||||
|
TypeReg = '0' // regular file
|
||||||
|
TypeRegA = '\x00' // regular file
|
||||||
|
TypeLink = '1' // hard link
|
||||||
|
TypeSymlink = '2' // symbolic link
|
||||||
|
TypeChar = '3' // character device node
|
||||||
|
TypeBlock = '4' // block device node
|
||||||
|
TypeDir = '5' // directory
|
||||||
|
TypeFifo = '6' // fifo node
|
||||||
|
TypeCont = '7' // reserved
|
||||||
|
TypeXHeader = 'x' // extended header
|
||||||
|
TypeXGlobalHeader = 'g' // global extended header
|
||||||
|
TypeGNULongName = 'L' // Next file has a long name
|
||||||
|
TypeGNULongLink = 'K' // Next file symlinks to a file w/ a long name
|
||||||
|
TypeGNUSparse = 'S' // sparse file
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Header represents a single header in a tar archive.
|
||||||
|
// Some fields may not be populated.
|
||||||
|
type Header struct {
|
||||||
|
Name string // name of header file entry
|
||||||
|
Mode int64 // permission and mode bits
|
||||||
|
Uid int // user id of owner
|
||||||
|
Gid int // group id of owner
|
||||||
|
Size int64 // length in bytes
|
||||||
|
ModTime time.Time // modified time
|
||||||
|
Typeflag byte // type of header entry
|
||||||
|
Linkname string // target name of link
|
||||||
|
Uname string // user name of owner
|
||||||
|
Gname string // group name of owner
|
||||||
|
Devmajor int64 // major number of character or block device
|
||||||
|
Devminor int64 // minor number of character or block device
|
||||||
|
AccessTime time.Time // access time
|
||||||
|
ChangeTime time.Time // status change time
|
||||||
|
CreationTime time.Time // creation time
|
||||||
|
Xattrs map[string]string
|
||||||
|
Winheaders map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// File name constants from the tar spec.
|
||||||
|
const (
|
||||||
|
fileNameSize = 100 // Maximum number of bytes in a standard tar name.
|
||||||
|
fileNamePrefixSize = 155 // Maximum number of ustar extension bytes.
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileInfo returns an os.FileInfo for the Header.
|
||||||
|
func (h *Header) FileInfo() os.FileInfo {
|
||||||
|
return headerFileInfo{h}
|
||||||
|
}
|
||||||
|
|
||||||
|
// headerFileInfo implements os.FileInfo.
|
||||||
|
type headerFileInfo struct {
|
||||||
|
h *Header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fi headerFileInfo) Size() int64 { return fi.h.Size }
|
||||||
|
func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
||||||
|
func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime }
|
||||||
|
func (fi headerFileInfo) Sys() interface{} { return fi.h }
|
||||||
|
|
||||||
|
// Name returns the base name of the file.
|
||||||
|
func (fi headerFileInfo) Name() string {
|
||||||
|
if fi.IsDir() {
|
||||||
|
return path.Base(path.Clean(fi.h.Name))
|
||||||
|
}
|
||||||
|
return path.Base(fi.h.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns the permission and mode bits for the headerFileInfo.
|
||||||
|
func (fi headerFileInfo) Mode() (mode os.FileMode) {
|
||||||
|
// Set file permission bits.
|
||||||
|
mode = os.FileMode(fi.h.Mode).Perm()
|
||||||
|
|
||||||
|
// Set setuid, setgid and sticky bits.
|
||||||
|
if fi.h.Mode&c_ISUID != 0 {
|
||||||
|
// setuid
|
||||||
|
mode |= os.ModeSetuid
|
||||||
|
}
|
||||||
|
if fi.h.Mode&c_ISGID != 0 {
|
||||||
|
// setgid
|
||||||
|
mode |= os.ModeSetgid
|
||||||
|
}
|
||||||
|
if fi.h.Mode&c_ISVTX != 0 {
|
||||||
|
// sticky
|
||||||
|
mode |= os.ModeSticky
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set file mode bits.
|
||||||
|
// clear perm, setuid, setgid and sticky bits.
|
||||||
|
m := os.FileMode(fi.h.Mode) &^ 07777
|
||||||
|
if m == c_ISDIR {
|
||||||
|
// directory
|
||||||
|
mode |= os.ModeDir
|
||||||
|
}
|
||||||
|
if m == c_ISFIFO {
|
||||||
|
// named pipe (FIFO)
|
||||||
|
mode |= os.ModeNamedPipe
|
||||||
|
}
|
||||||
|
if m == c_ISLNK {
|
||||||
|
// symbolic link
|
||||||
|
mode |= os.ModeSymlink
|
||||||
|
}
|
||||||
|
if m == c_ISBLK {
|
||||||
|
// device file
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
}
|
||||||
|
if m == c_ISCHR {
|
||||||
|
// Unix character device
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
mode |= os.ModeCharDevice
|
||||||
|
}
|
||||||
|
if m == c_ISSOCK {
|
||||||
|
// Unix domain socket
|
||||||
|
mode |= os.ModeSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fi.h.Typeflag {
|
||||||
|
case TypeSymlink:
|
||||||
|
// symbolic link
|
||||||
|
mode |= os.ModeSymlink
|
||||||
|
case TypeChar:
|
||||||
|
// character device node
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
mode |= os.ModeCharDevice
|
||||||
|
case TypeBlock:
|
||||||
|
// block device node
|
||||||
|
mode |= os.ModeDevice
|
||||||
|
case TypeDir:
|
||||||
|
// directory
|
||||||
|
mode |= os.ModeDir
|
||||||
|
case TypeFifo:
|
||||||
|
// fifo node
|
||||||
|
mode |= os.ModeNamedPipe
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
|
||||||
|
// sysStat, if non-nil, populates h from system-dependent fields of fi.
|
||||||
|
var sysStat func(fi os.FileInfo, h *Header) error
|
||||||
|
|
||||||
|
// Mode constants from the tar spec.
|
||||||
|
const (
|
||||||
|
c_ISUID = 04000 // Set uid
|
||||||
|
c_ISGID = 02000 // Set gid
|
||||||
|
c_ISVTX = 01000 // Save text (sticky bit)
|
||||||
|
c_ISDIR = 040000 // Directory
|
||||||
|
c_ISFIFO = 010000 // FIFO
|
||||||
|
c_ISREG = 0100000 // Regular file
|
||||||
|
c_ISLNK = 0120000 // Symbolic link
|
||||||
|
c_ISBLK = 060000 // Block special file
|
||||||
|
c_ISCHR = 020000 // Character special file
|
||||||
|
c_ISSOCK = 0140000 // Socket
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keywords for the PAX Extended Header
|
||||||
|
const (
|
||||||
|
paxAtime = "atime"
|
||||||
|
paxCharset = "charset"
|
||||||
|
paxComment = "comment"
|
||||||
|
paxCtime = "ctime" // please note that ctime is not a valid pax header.
|
||||||
|
paxCreationTime = "LIBARCHIVE.creationtime"
|
||||||
|
paxGid = "gid"
|
||||||
|
paxGname = "gname"
|
||||||
|
paxLinkpath = "linkpath"
|
||||||
|
paxMtime = "mtime"
|
||||||
|
paxPath = "path"
|
||||||
|
paxSize = "size"
|
||||||
|
paxUid = "uid"
|
||||||
|
paxUname = "uname"
|
||||||
|
paxXattr = "SCHILY.xattr."
|
||||||
|
paxWindows = "MSWINDOWS."
|
||||||
|
paxNone = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileInfoHeader creates a partially-populated Header from fi.
|
||||||
|
// If fi describes a symlink, FileInfoHeader records link as the link target.
|
||||||
|
// If fi describes a directory, a slash is appended to the name.
|
||||||
|
// 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.
|
||||||
|
func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) {
|
||||||
|
if fi == nil {
|
||||||
|
return nil, errors.New("tar: FileInfo is nil")
|
||||||
|
}
|
||||||
|
fm := fi.Mode()
|
||||||
|
h := &Header{
|
||||||
|
Name: fi.Name(),
|
||||||
|
ModTime: fi.ModTime(),
|
||||||
|
Mode: int64(fm.Perm()), // or'd with c_IS* constants later
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case fm.IsRegular():
|
||||||
|
h.Mode |= c_ISREG
|
||||||
|
h.Typeflag = TypeReg
|
||||||
|
h.Size = fi.Size()
|
||||||
|
case fi.IsDir():
|
||||||
|
h.Typeflag = TypeDir
|
||||||
|
h.Mode |= c_ISDIR
|
||||||
|
h.Name += "/"
|
||||||
|
case fm&os.ModeSymlink != 0:
|
||||||
|
h.Typeflag = TypeSymlink
|
||||||
|
h.Mode |= c_ISLNK
|
||||||
|
h.Linkname = link
|
||||||
|
case fm&os.ModeDevice != 0:
|
||||||
|
if fm&os.ModeCharDevice != 0 {
|
||||||
|
h.Mode |= c_ISCHR
|
||||||
|
h.Typeflag = TypeChar
|
||||||
|
} else {
|
||||||
|
h.Mode |= c_ISBLK
|
||||||
|
h.Typeflag = TypeBlock
|
||||||
|
}
|
||||||
|
case fm&os.ModeNamedPipe != 0:
|
||||||
|
h.Typeflag = TypeFifo
|
||||||
|
h.Mode |= c_ISFIFO
|
||||||
|
case fm&os.ModeSocket != 0:
|
||||||
|
h.Mode |= c_ISSOCK
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm)
|
||||||
|
}
|
||||||
|
if fm&os.ModeSetuid != 0 {
|
||||||
|
h.Mode |= c_ISUID
|
||||||
|
}
|
||||||
|
if fm&os.ModeSetgid != 0 {
|
||||||
|
h.Mode |= c_ISGID
|
||||||
|
}
|
||||||
|
if fm&os.ModeSticky != 0 {
|
||||||
|
h.Mode |= c_ISVTX
|
||||||
|
}
|
||||||
|
// If possible, populate additional fields from OS-specific
|
||||||
|
// FileInfo fields.
|
||||||
|
if sys, ok := fi.Sys().(*Header); ok {
|
||||||
|
// This FileInfo came from a Header (not the OS). Use the
|
||||||
|
// original Header to populate all remaining fields.
|
||||||
|
h.Uid = sys.Uid
|
||||||
|
h.Gid = sys.Gid
|
||||||
|
h.Uname = sys.Uname
|
||||||
|
h.Gname = sys.Gname
|
||||||
|
h.AccessTime = sys.AccessTime
|
||||||
|
h.ChangeTime = sys.ChangeTime
|
||||||
|
if sys.Xattrs != nil {
|
||||||
|
h.Xattrs = make(map[string]string)
|
||||||
|
for k, v := range sys.Xattrs {
|
||||||
|
h.Xattrs[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sys.Typeflag == TypeLink {
|
||||||
|
// hard link
|
||||||
|
h.Typeflag = TypeLink
|
||||||
|
h.Size = 0
|
||||||
|
h.Linkname = sys.Linkname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sysStat != nil {
|
||||||
|
return h, sysStat(fi, h)
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeroBlock = make([]byte, blockSize)
|
||||||
|
|
||||||
|
// POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values.
|
||||||
|
// We compute and return both.
|
||||||
|
func checksum(header []byte) (unsigned int64, signed int64) {
|
||||||
|
for i := 0; i < len(header); i++ {
|
||||||
|
if i == 148 {
|
||||||
|
// The chksum field (header[148:156]) is special: it should be treated as space bytes.
|
||||||
|
unsigned += ' ' * 8
|
||||||
|
signed += ' ' * 8
|
||||||
|
i += 7
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unsigned += int64(header[i])
|
||||||
|
signed += int64(int8(header[i]))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type slicer []byte
|
||||||
|
|
||||||
|
func (sp *slicer) next(n int) (b []byte) {
|
||||||
|
s := *sp
|
||||||
|
b, *sp = s[0:n], s[n:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func isASCII(s string) bool {
|
||||||
|
for _, c := range s {
|
||||||
|
if c >= 0x80 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func toASCII(s string) string {
|
||||||
|
if isASCII(s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, c := range s {
|
||||||
|
if c < 0x80 {
|
||||||
|
buf.WriteByte(byte(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isHeaderOnlyType checks if the given type flag is of the type that has no
|
||||||
|
// data section even if a size is specified.
|
||||||
|
func isHeaderOnlyType(flag byte) bool {
|
||||||
|
switch flag {
|
||||||
|
case TypeLink, TypeSymlink, TypeChar, TypeBlock, TypeDir, TypeFifo:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
80
vendor/github.com/Microsoft/go-winio/archive/tar/example_test.go
generated
vendored
Normal file
80
vendor/github.com/Microsoft/go-winio/archive/tar/example_test.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tar_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// Create a buffer to write our archive to.
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// Create a new tar archive.
|
||||||
|
tw := tar.NewWriter(buf)
|
||||||
|
|
||||||
|
// Add some files to the archive.
|
||||||
|
var files = []struct {
|
||||||
|
Name, Body string
|
||||||
|
}{
|
||||||
|
{"readme.txt", "This archive contains some text files."},
|
||||||
|
{"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"},
|
||||||
|
{"todo.txt", "Get animal handling license."},
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
hdr := &tar.Header{
|
||||||
|
Name: file.Name,
|
||||||
|
Mode: 0600,
|
||||||
|
Size: int64(len(file.Body)),
|
||||||
|
}
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
if _, err := tw.Write([]byte(file.Body)); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Make sure to check the error on Close.
|
||||||
|
if err := tw.Close(); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open the tar archive for reading.
|
||||||
|
r := bytes.NewReader(buf.Bytes())
|
||||||
|
tr := tar.NewReader(r)
|
||||||
|
|
||||||
|
// Iterate through the files in the archive.
|
||||||
|
for {
|
||||||
|
hdr, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
// end of tar archive
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Contents of %s:\n", hdr.Name)
|
||||||
|
if _, err := io.Copy(os.Stdout, tr); err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Contents of readme.txt:
|
||||||
|
// This archive contains some text files.
|
||||||
|
// Contents of gopher.txt:
|
||||||
|
// Gopher names:
|
||||||
|
// George
|
||||||
|
// Geoffrey
|
||||||
|
// Gonzo
|
||||||
|
// Contents of todo.txt:
|
||||||
|
// Get animal handling license.
|
||||||
|
}
|
1002
vendor/github.com/Microsoft/go-winio/archive/tar/reader.go
generated
vendored
Normal file
1002
vendor/github.com/Microsoft/go-winio/archive/tar/reader.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
1125
vendor/github.com/Microsoft/go-winio/archive/tar/reader_test.go
generated
vendored
Normal file
1125
vendor/github.com/Microsoft/go-winio/archive/tar/reader_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
20
vendor/github.com/Microsoft/go-winio/archive/tar/stat_atim.go
generated
vendored
Normal file
20
vendor/github.com/Microsoft/go-winio/archive/tar/stat_atim.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux dragonfly openbsd solaris
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func statAtime(st *syscall.Stat_t) time.Time {
|
||||||
|
return time.Unix(st.Atim.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func statCtime(st *syscall.Stat_t) time.Time {
|
||||||
|
return time.Unix(st.Ctim.Unix())
|
||||||
|
}
|
20
vendor/github.com/Microsoft/go-winio/archive/tar/stat_atimespec.go
generated
vendored
Normal file
20
vendor/github.com/Microsoft/go-winio/archive/tar/stat_atimespec.go
generated
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build darwin freebsd netbsd
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func statAtime(st *syscall.Stat_t) time.Time {
|
||||||
|
return time.Unix(st.Atimespec.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
func statCtime(st *syscall.Stat_t) time.Time {
|
||||||
|
return time.Unix(st.Ctimespec.Unix())
|
||||||
|
}
|
32
vendor/github.com/Microsoft/go-winio/archive/tar/stat_unix.go
generated
vendored
Normal file
32
vendor/github.com/Microsoft/go-winio/archive/tar/stat_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build linux darwin dragonfly freebsd openbsd netbsd solaris
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sysStat = statUnix
|
||||||
|
}
|
||||||
|
|
||||||
|
func statUnix(fi os.FileInfo, h *Header) error {
|
||||||
|
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
h.Uid = int(sys.Uid)
|
||||||
|
h.Gid = int(sys.Gid)
|
||||||
|
// TODO(bradfitz): populate username & group. os/user
|
||||||
|
// doesn't cache LookupId lookups, and lacks group
|
||||||
|
// lookup functions.
|
||||||
|
h.AccessTime = statAtime(sys)
|
||||||
|
h.ChangeTime = statCtime(sys)
|
||||||
|
// TODO(bradfitz): major/minor device numbers?
|
||||||
|
return nil
|
||||||
|
}
|
325
vendor/github.com/Microsoft/go-winio/archive/tar/tar_test.go
generated
vendored
Normal file
325
vendor/github.com/Microsoft/go-winio/archive/tar/tar_test.go
generated
vendored
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileInfoHeader(t *testing.T) {
|
||||||
|
fi, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h, err := FileInfoHeader(fi, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FileInfoHeader: %v", err)
|
||||||
|
}
|
||||||
|
if g, e := h.Name, "small.txt"; g != e {
|
||||||
|
t.Errorf("Name = %q; want %q", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.Mode, int64(fi.Mode().Perm())|c_ISREG; g != e {
|
||||||
|
t.Errorf("Mode = %#o; want %#o", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.Size, int64(5); g != e {
|
||||||
|
t.Errorf("Size = %v; want %v", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
|
||||||
|
t.Errorf("ModTime = %v; want %v", g, e)
|
||||||
|
}
|
||||||
|
// FileInfoHeader should error when passing nil FileInfo
|
||||||
|
if _, err := FileInfoHeader(nil, ""); err == nil {
|
||||||
|
t.Fatalf("Expected error when passing nil to FileInfoHeader")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileInfoHeaderDir(t *testing.T) {
|
||||||
|
fi, err := os.Stat("testdata")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h, err := FileInfoHeader(fi, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FileInfoHeader: %v", err)
|
||||||
|
}
|
||||||
|
if g, e := h.Name, "testdata/"; g != e {
|
||||||
|
t.Errorf("Name = %q; want %q", g, e)
|
||||||
|
}
|
||||||
|
// Ignoring c_ISGID for golang.org/issue/4867
|
||||||
|
if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm())|c_ISDIR; g != e {
|
||||||
|
t.Errorf("Mode = %#o; want %#o", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.Size, int64(0); g != e {
|
||||||
|
t.Errorf("Size = %v; want %v", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) {
|
||||||
|
t.Errorf("ModTime = %v; want %v", g, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileInfoHeaderSymlink(t *testing.T) {
|
||||||
|
h, err := FileInfoHeader(symlink{}, "some-target")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if g, e := h.Name, "some-symlink"; g != e {
|
||||||
|
t.Errorf("Name = %q; want %q", g, e)
|
||||||
|
}
|
||||||
|
if g, e := h.Linkname, "some-target"; g != e {
|
||||||
|
t.Errorf("Linkname = %q; want %q", g, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type symlink struct{}
|
||||||
|
|
||||||
|
func (symlink) Name() string { return "some-symlink" }
|
||||||
|
func (symlink) Size() int64 { return 0 }
|
||||||
|
func (symlink) Mode() os.FileMode { return os.ModeSymlink }
|
||||||
|
func (symlink) ModTime() time.Time { return time.Time{} }
|
||||||
|
func (symlink) IsDir() bool { return false }
|
||||||
|
func (symlink) Sys() interface{} { return nil }
|
||||||
|
|
||||||
|
func TestRoundTrip(t *testing.T) {
|
||||||
|
data := []byte("some file contents")
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
tw := NewWriter(&b)
|
||||||
|
hdr := &Header{
|
||||||
|
Name: "file.txt",
|
||||||
|
Uid: 1 << 21, // too big for 8 octal digits
|
||||||
|
Size: int64(len(data)),
|
||||||
|
ModTime: time.Now(),
|
||||||
|
}
|
||||||
|
// tar only supports second precision.
|
||||||
|
hdr.ModTime = hdr.ModTime.Add(-time.Duration(hdr.ModTime.Nanosecond()) * time.Nanosecond)
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatalf("tw.WriteHeader: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := tw.Write(data); err != nil {
|
||||||
|
t.Fatalf("tw.Write: %v", err)
|
||||||
|
}
|
||||||
|
if err := tw.Close(); err != nil {
|
||||||
|
t.Fatalf("tw.Close: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read it back.
|
||||||
|
tr := NewReader(&b)
|
||||||
|
rHdr, err := tr.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("tr.Next: %v", err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(rHdr, hdr) {
|
||||||
|
t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr)
|
||||||
|
}
|
||||||
|
rData, err := ioutil.ReadAll(tr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Read: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(rData, data) {
|
||||||
|
t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerRoundTripTest struct {
|
||||||
|
h *Header
|
||||||
|
fm os.FileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeaderRoundTrip(t *testing.T) {
|
||||||
|
golden := []headerRoundTripTest{
|
||||||
|
// regular file.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "test.txt",
|
||||||
|
Mode: 0644 | c_ISREG,
|
||||||
|
Size: 12,
|
||||||
|
ModTime: time.Unix(1360600916, 0),
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0644,
|
||||||
|
},
|
||||||
|
// symbolic link.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "link.txt",
|
||||||
|
Mode: 0777 | c_ISLNK,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360600852, 0),
|
||||||
|
Typeflag: TypeSymlink,
|
||||||
|
},
|
||||||
|
fm: 0777 | os.ModeSymlink,
|
||||||
|
},
|
||||||
|
// character device node.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "dev/null",
|
||||||
|
Mode: 0666 | c_ISCHR,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360578951, 0),
|
||||||
|
Typeflag: TypeChar,
|
||||||
|
},
|
||||||
|
fm: 0666 | os.ModeDevice | os.ModeCharDevice,
|
||||||
|
},
|
||||||
|
// block device node.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "dev/sda",
|
||||||
|
Mode: 0660 | c_ISBLK,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360578954, 0),
|
||||||
|
Typeflag: TypeBlock,
|
||||||
|
},
|
||||||
|
fm: 0660 | os.ModeDevice,
|
||||||
|
},
|
||||||
|
// directory.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "dir/",
|
||||||
|
Mode: 0755 | c_ISDIR,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360601116, 0),
|
||||||
|
Typeflag: TypeDir,
|
||||||
|
},
|
||||||
|
fm: 0755 | os.ModeDir,
|
||||||
|
},
|
||||||
|
// fifo node.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "dev/initctl",
|
||||||
|
Mode: 0600 | c_ISFIFO,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360578949, 0),
|
||||||
|
Typeflag: TypeFifo,
|
||||||
|
},
|
||||||
|
fm: 0600 | os.ModeNamedPipe,
|
||||||
|
},
|
||||||
|
// setuid.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "bin/su",
|
||||||
|
Mode: 0755 | c_ISREG | c_ISUID,
|
||||||
|
Size: 23232,
|
||||||
|
ModTime: time.Unix(1355405093, 0),
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0755 | os.ModeSetuid,
|
||||||
|
},
|
||||||
|
// setguid.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "group.txt",
|
||||||
|
Mode: 0750 | c_ISREG | c_ISGID,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1360602346, 0),
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0750 | os.ModeSetgid,
|
||||||
|
},
|
||||||
|
// sticky.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "sticky.txt",
|
||||||
|
Mode: 0600 | c_ISREG | c_ISVTX,
|
||||||
|
Size: 7,
|
||||||
|
ModTime: time.Unix(1360602540, 0),
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0600 | os.ModeSticky,
|
||||||
|
},
|
||||||
|
// hard link.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "hard.txt",
|
||||||
|
Mode: 0644 | c_ISREG,
|
||||||
|
Size: 0,
|
||||||
|
Linkname: "file.txt",
|
||||||
|
ModTime: time.Unix(1360600916, 0),
|
||||||
|
Typeflag: TypeLink,
|
||||||
|
},
|
||||||
|
fm: 0644,
|
||||||
|
},
|
||||||
|
// More information.
|
||||||
|
{
|
||||||
|
h: &Header{
|
||||||
|
Name: "info.txt",
|
||||||
|
Mode: 0600 | c_ISREG,
|
||||||
|
Size: 0,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 1000,
|
||||||
|
ModTime: time.Unix(1360602540, 0),
|
||||||
|
Uname: "slartibartfast",
|
||||||
|
Gname: "users",
|
||||||
|
Typeflag: TypeReg,
|
||||||
|
},
|
||||||
|
fm: 0600,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, g := range golden {
|
||||||
|
fi := g.h.FileInfo()
|
||||||
|
h2, err := FileInfoHeader(fi, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(fi.Name(), "/") {
|
||||||
|
t.Errorf("FileInfo of %q contains slash: %q", g.h.Name, fi.Name())
|
||||||
|
}
|
||||||
|
name := path.Base(g.h.Name)
|
||||||
|
if fi.IsDir() {
|
||||||
|
name += "/"
|
||||||
|
}
|
||||||
|
if got, want := h2.Name, name; got != want {
|
||||||
|
t.Errorf("i=%d: Name: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Size, g.h.Size; got != want {
|
||||||
|
t.Errorf("i=%d: Size: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Uid, g.h.Uid; got != want {
|
||||||
|
t.Errorf("i=%d: Uid: got %d, want %d", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Gid, g.h.Gid; got != want {
|
||||||
|
t.Errorf("i=%d: Gid: got %d, want %d", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Uname, g.h.Uname; got != want {
|
||||||
|
t.Errorf("i=%d: Uname: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Gname, g.h.Gname; got != want {
|
||||||
|
t.Errorf("i=%d: Gname: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Linkname, g.h.Linkname; got != want {
|
||||||
|
t.Errorf("i=%d: Linkname: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Typeflag, g.h.Typeflag; got != want {
|
||||||
|
t.Logf("%#v %#v", g.h, fi.Sys())
|
||||||
|
t.Errorf("i=%d: Typeflag: got %q, want %q", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.Mode, g.h.Mode; got != want {
|
||||||
|
t.Errorf("i=%d: Mode: got %o, want %o", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := fi.Mode(), g.fm; got != want {
|
||||||
|
t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.AccessTime, g.h.AccessTime; got != want {
|
||||||
|
t.Errorf("i=%d: AccessTime: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.ChangeTime, g.h.ChangeTime; got != want {
|
||||||
|
t.Errorf("i=%d: ChangeTime: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if got, want := h2.ModTime, g.h.ModTime; got != want {
|
||||||
|
t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want)
|
||||||
|
}
|
||||||
|
if sysh, ok := fi.Sys().(*Header); !ok || sysh != g.h {
|
||||||
|
t.Errorf("i=%d: Sys didn't return original *Header", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
vendor/github.com/Microsoft/go-winio/archive/tar/testdata/small.txt
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/archive/tar/testdata/small.txt
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Kilts
|
1
vendor/github.com/Microsoft/go-winio/archive/tar/testdata/small2.txt
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/archive/tar/testdata/small2.txt
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Google.com
|
444
vendor/github.com/Microsoft/go-winio/archive/tar/writer.go
generated
vendored
Normal file
444
vendor/github.com/Microsoft/go-winio/archive/tar/writer.go
generated
vendored
Normal file
|
@ -0,0 +1,444 @@
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
// TODO(dsymonds):
|
||||||
|
// - catch more errors (no first header, etc.)
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"path"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrWriteTooLong = errors.New("archive/tar: write too long")
|
||||||
|
ErrFieldTooLong = errors.New("archive/tar: header field too long")
|
||||||
|
ErrWriteAfterClose = errors.New("archive/tar: write after close")
|
||||||
|
errInvalidHeader = errors.New("archive/tar: header field too long or contains invalid values")
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Writer provides sequential writing of a tar archive in POSIX.1 format.
|
||||||
|
// A tar archive consists of a sequence of files.
|
||||||
|
// Call WriteHeader to begin a new file, and then call Write to supply that file's data,
|
||||||
|
// writing at most hdr.Size bytes in total.
|
||||||
|
type Writer struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
nb int64 // number of unwritten bytes for current file entry
|
||||||
|
pad int64 // amount of padding to write after current file entry
|
||||||
|
closed bool
|
||||||
|
usedBinary bool // whether the binary numeric field extension was used
|
||||||
|
preferPax bool // use pax header instead of binary numeric header
|
||||||
|
hdrBuff [blockSize]byte // buffer to use in writeHeader when writing a regular header
|
||||||
|
paxHdrBuff [blockSize]byte // buffer to use in writeHeader when writing a pax header
|
||||||
|
}
|
||||||
|
|
||||||
|
type formatter struct {
|
||||||
|
err error // Last error seen
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter creates a new Writer writing to w.
|
||||||
|
func NewWriter(w io.Writer) *Writer { return &Writer{w: w, preferPax: true} }
|
||||||
|
|
||||||
|
// Flush finishes writing the current file (optional).
|
||||||
|
func (tw *Writer) Flush() error {
|
||||||
|
if tw.nb > 0 {
|
||||||
|
tw.err = fmt.Errorf("archive/tar: missed writing %d bytes", tw.nb)
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := tw.nb + tw.pad
|
||||||
|
for n > 0 && tw.err == nil {
|
||||||
|
nr := n
|
||||||
|
if nr > blockSize {
|
||||||
|
nr = blockSize
|
||||||
|
}
|
||||||
|
var nw int
|
||||||
|
nw, tw.err = tw.w.Write(zeroBlock[0:nr])
|
||||||
|
n -= int64(nw)
|
||||||
|
}
|
||||||
|
tw.nb = 0
|
||||||
|
tw.pad = 0
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write s into b, terminating it with a NUL if there is room.
|
||||||
|
func (f *formatter) formatString(b []byte, s string) {
|
||||||
|
if len(s) > len(b) {
|
||||||
|
f.err = ErrFieldTooLong
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ascii := toASCII(s)
|
||||||
|
copy(b, ascii)
|
||||||
|
if len(ascii) < len(b) {
|
||||||
|
b[len(ascii)] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode x as an octal ASCII string and write it into b with leading zeros.
|
||||||
|
func (f *formatter) formatOctal(b []byte, x int64) {
|
||||||
|
s := strconv.FormatInt(x, 8)
|
||||||
|
// leading zeros, but leave room for a NUL.
|
||||||
|
for len(s)+1 < len(b) {
|
||||||
|
s = "0" + s
|
||||||
|
}
|
||||||
|
f.formatString(b, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fitsInBase256 reports whether x can be encoded into n bytes using base-256
|
||||||
|
// encoding. Unlike octal encoding, base-256 encoding does not require that the
|
||||||
|
// string ends with a NUL character. Thus, all n bytes are available for output.
|
||||||
|
//
|
||||||
|
// If operating in binary mode, this assumes strict GNU binary mode; which means
|
||||||
|
// that the first byte can only be either 0x80 or 0xff. Thus, the first byte is
|
||||||
|
// equivalent to the sign bit in two's complement form.
|
||||||
|
func fitsInBase256(n int, x int64) bool {
|
||||||
|
var binBits = uint(n-1) * 8
|
||||||
|
return n >= 9 || (x >= -1<<binBits && x < 1<<binBits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write x into b, as binary (GNUtar/star extension).
|
||||||
|
func (f *formatter) formatNumeric(b []byte, x int64) {
|
||||||
|
if fitsInBase256(len(b), x) {
|
||||||
|
for i := len(b) - 1; i >= 0; i-- {
|
||||||
|
b[i] = byte(x)
|
||||||
|
x >>= 8
|
||||||
|
}
|
||||||
|
b[0] |= 0x80 // Highest bit indicates binary format
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.formatOctal(b, 0) // Last resort, just write zero
|
||||||
|
f.err = ErrFieldTooLong
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
minTime = time.Unix(0, 0)
|
||||||
|
// There is room for 11 octal digits (33 bits) of mtime.
|
||||||
|
maxTime = minTime.Add((1<<33 - 1) * time.Second)
|
||||||
|
)
|
||||||
|
|
||||||
|
// WriteHeader writes hdr and prepares to accept the file's contents.
|
||||||
|
// WriteHeader calls Flush if it is not the first header.
|
||||||
|
// Calling after a Close will return ErrWriteAfterClose.
|
||||||
|
func (tw *Writer) WriteHeader(hdr *Header) error {
|
||||||
|
return tw.writeHeader(hdr, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes hdr and prepares to accept the file's contents.
|
||||||
|
// WriteHeader calls Flush if it is not the first header.
|
||||||
|
// Calling after a Close will return ErrWriteAfterClose.
|
||||||
|
// As this method is called internally by writePax header to allow it to
|
||||||
|
// suppress writing the pax header.
|
||||||
|
func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error {
|
||||||
|
if tw.closed {
|
||||||
|
return ErrWriteAfterClose
|
||||||
|
}
|
||||||
|
if tw.err == nil {
|
||||||
|
tw.Flush()
|
||||||
|
}
|
||||||
|
if tw.err != nil {
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// a map to hold pax header records, if any are needed
|
||||||
|
paxHeaders := make(map[string]string)
|
||||||
|
|
||||||
|
// TODO(shanemhansen): we might want to use PAX headers for
|
||||||
|
// subsecond time resolution, but for now let's just capture
|
||||||
|
// too long fields or non ascii characters
|
||||||
|
|
||||||
|
var f formatter
|
||||||
|
var header []byte
|
||||||
|
|
||||||
|
// We need to select which scratch buffer to use carefully,
|
||||||
|
// since this method is called recursively to write PAX headers.
|
||||||
|
// If allowPax is true, this is the non-recursive call, and we will use hdrBuff.
|
||||||
|
// If allowPax is false, we are being called by writePAXHeader, and hdrBuff is
|
||||||
|
// already being used by the non-recursive call, so we must use paxHdrBuff.
|
||||||
|
header = tw.hdrBuff[:]
|
||||||
|
if !allowPax {
|
||||||
|
header = tw.paxHdrBuff[:]
|
||||||
|
}
|
||||||
|
copy(header, zeroBlock)
|
||||||
|
s := slicer(header)
|
||||||
|
|
||||||
|
// Wrappers around formatter that automatically sets paxHeaders if the
|
||||||
|
// argument extends beyond the capacity of the input byte slice.
|
||||||
|
var formatString = func(b []byte, s string, paxKeyword string) {
|
||||||
|
needsPaxHeader := paxKeyword != paxNone && len(s) > len(b) || !isASCII(s)
|
||||||
|
if needsPaxHeader {
|
||||||
|
paxHeaders[paxKeyword] = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.formatString(b, s)
|
||||||
|
}
|
||||||
|
var formatNumeric = func(b []byte, x int64, paxKeyword string) {
|
||||||
|
// Try octal first.
|
||||||
|
s := strconv.FormatInt(x, 8)
|
||||||
|
if len(s) < len(b) {
|
||||||
|
f.formatOctal(b, x)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is too long for octal, and PAX is preferred, use a PAX header.
|
||||||
|
if paxKeyword != paxNone && tw.preferPax {
|
||||||
|
f.formatOctal(b, 0)
|
||||||
|
s := strconv.FormatInt(x, 10)
|
||||||
|
paxHeaders[paxKeyword] = s
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tw.usedBinary = true
|
||||||
|
f.formatNumeric(b, x)
|
||||||
|
}
|
||||||
|
var formatTime = func(b []byte, t time.Time, paxKeyword string) {
|
||||||
|
var unixTime int64
|
||||||
|
if !t.Before(minTime) && !t.After(maxTime) {
|
||||||
|
unixTime = t.Unix()
|
||||||
|
}
|
||||||
|
formatNumeric(b, unixTime, paxNone)
|
||||||
|
|
||||||
|
// Write a PAX header if the time didn't fit precisely.
|
||||||
|
if paxKeyword != "" && tw.preferPax && allowPax && (t.Nanosecond() != 0 || !t.Before(minTime) || !t.After(maxTime)) {
|
||||||
|
paxHeaders[paxKeyword] = formatPAXTime(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep a reference to the filename to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
|
||||||
|
pathHeaderBytes := s.next(fileNameSize)
|
||||||
|
|
||||||
|
formatString(pathHeaderBytes, hdr.Name, paxPath)
|
||||||
|
|
||||||
|
f.formatOctal(s.next(8), hdr.Mode) // 100:108
|
||||||
|
formatNumeric(s.next(8), int64(hdr.Uid), paxUid) // 108:116
|
||||||
|
formatNumeric(s.next(8), int64(hdr.Gid), paxGid) // 116:124
|
||||||
|
formatNumeric(s.next(12), hdr.Size, paxSize) // 124:136
|
||||||
|
formatTime(s.next(12), hdr.ModTime, paxMtime) // 136:148
|
||||||
|
s.next(8) // chksum (148:156)
|
||||||
|
s.next(1)[0] = hdr.Typeflag // 156:157
|
||||||
|
|
||||||
|
formatString(s.next(100), hdr.Linkname, paxLinkpath)
|
||||||
|
|
||||||
|
copy(s.next(8), []byte("ustar\x0000")) // 257:265
|
||||||
|
formatString(s.next(32), hdr.Uname, paxUname) // 265:297
|
||||||
|
formatString(s.next(32), hdr.Gname, paxGname) // 297:329
|
||||||
|
formatNumeric(s.next(8), hdr.Devmajor, paxNone) // 329:337
|
||||||
|
formatNumeric(s.next(8), hdr.Devminor, paxNone) // 337:345
|
||||||
|
|
||||||
|
// keep a reference to the prefix to allow to overwrite it later if we detect that we can use ustar longnames instead of pax
|
||||||
|
prefixHeaderBytes := s.next(155)
|
||||||
|
formatString(prefixHeaderBytes, "", paxNone) // 345:500 prefix
|
||||||
|
|
||||||
|
// Use the GNU magic instead of POSIX magic if we used any GNU extensions.
|
||||||
|
if tw.usedBinary {
|
||||||
|
copy(header[257:265], []byte("ustar \x00"))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, paxPathUsed := paxHeaders[paxPath]
|
||||||
|
// try to use a ustar header when only the name is too long
|
||||||
|
if !tw.preferPax && len(paxHeaders) == 1 && paxPathUsed {
|
||||||
|
prefix, suffix, ok := splitUSTARPath(hdr.Name)
|
||||||
|
if ok {
|
||||||
|
// Since we can encode in USTAR format, disable PAX header.
|
||||||
|
delete(paxHeaders, paxPath)
|
||||||
|
|
||||||
|
// Update the path fields
|
||||||
|
formatString(pathHeaderBytes, suffix, paxNone)
|
||||||
|
formatString(prefixHeaderBytes, prefix, paxNone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The chksum field is terminated by a NUL and a space.
|
||||||
|
// This is different from the other octal fields.
|
||||||
|
chksum, _ := checksum(header)
|
||||||
|
f.formatOctal(header[148:155], chksum) // Never fails
|
||||||
|
header[155] = ' '
|
||||||
|
|
||||||
|
// Check if there were any formatting errors.
|
||||||
|
if f.err != nil {
|
||||||
|
tw.err = f.err
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if allowPax {
|
||||||
|
if !hdr.AccessTime.IsZero() {
|
||||||
|
paxHeaders[paxAtime] = formatPAXTime(hdr.AccessTime)
|
||||||
|
}
|
||||||
|
if !hdr.ChangeTime.IsZero() {
|
||||||
|
paxHeaders[paxCtime] = formatPAXTime(hdr.ChangeTime)
|
||||||
|
}
|
||||||
|
if !hdr.CreationTime.IsZero() {
|
||||||
|
paxHeaders[paxCreationTime] = formatPAXTime(hdr.CreationTime)
|
||||||
|
}
|
||||||
|
for k, v := range hdr.Xattrs {
|
||||||
|
paxHeaders[paxXattr+k] = v
|
||||||
|
}
|
||||||
|
for k, v := range hdr.Winheaders {
|
||||||
|
paxHeaders[paxWindows+k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(paxHeaders) > 0 {
|
||||||
|
if !allowPax {
|
||||||
|
return errInvalidHeader
|
||||||
|
}
|
||||||
|
if err := tw.writePAXHeader(hdr, paxHeaders); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tw.nb = int64(hdr.Size)
|
||||||
|
tw.pad = (blockSize - (tw.nb % blockSize)) % blockSize
|
||||||
|
|
||||||
|
_, tw.err = tw.w.Write(header)
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatPAXTime(t time.Time) string {
|
||||||
|
sec := t.Unix()
|
||||||
|
usec := t.Nanosecond()
|
||||||
|
s := strconv.FormatInt(sec, 10)
|
||||||
|
if usec != 0 {
|
||||||
|
s = fmt.Sprintf("%s.%09d", s, usec)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitUSTARPath splits a path according to USTAR prefix and suffix rules.
|
||||||
|
// If the path is not splittable, then it will return ("", "", false).
|
||||||
|
func splitUSTARPath(name string) (prefix, suffix string, ok bool) {
|
||||||
|
length := len(name)
|
||||||
|
if length <= fileNameSize || !isASCII(name) {
|
||||||
|
return "", "", false
|
||||||
|
} else if length > fileNamePrefixSize+1 {
|
||||||
|
length = fileNamePrefixSize + 1
|
||||||
|
} else if name[length-1] == '/' {
|
||||||
|
length--
|
||||||
|
}
|
||||||
|
|
||||||
|
i := strings.LastIndex(name[:length], "/")
|
||||||
|
nlen := len(name) - i - 1 // nlen is length of suffix
|
||||||
|
plen := i // plen is length of prefix
|
||||||
|
if i <= 0 || nlen > fileNameSize || nlen == 0 || plen > fileNamePrefixSize {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return name[:i], name[i+1:], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// writePaxHeader writes an extended pax header to the
|
||||||
|
// archive.
|
||||||
|
func (tw *Writer) writePAXHeader(hdr *Header, paxHeaders map[string]string) error {
|
||||||
|
// Prepare extended header
|
||||||
|
ext := new(Header)
|
||||||
|
ext.Typeflag = TypeXHeader
|
||||||
|
// Setting ModTime is required for reader parsing to
|
||||||
|
// succeed, and seems harmless enough.
|
||||||
|
ext.ModTime = hdr.ModTime
|
||||||
|
// The spec asks that we namespace our pseudo files
|
||||||
|
// with the current pid. However, this results in differing outputs
|
||||||
|
// for identical inputs. As such, the constant 0 is now used instead.
|
||||||
|
// golang.org/issue/12358
|
||||||
|
dir, file := path.Split(hdr.Name)
|
||||||
|
fullName := path.Join(dir, "PaxHeaders.0", file)
|
||||||
|
|
||||||
|
ascii := toASCII(fullName)
|
||||||
|
if len(ascii) > 100 {
|
||||||
|
ascii = ascii[:100]
|
||||||
|
}
|
||||||
|
ext.Name = ascii
|
||||||
|
// Construct the body
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// Keys are sorted before writing to body to allow deterministic output.
|
||||||
|
var keys []string
|
||||||
|
for k := range paxHeaders {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
fmt.Fprint(&buf, formatPAXRecord(k, paxHeaders[k]))
|
||||||
|
}
|
||||||
|
|
||||||
|
ext.Size = int64(len(buf.Bytes()))
|
||||||
|
if err := tw.writeHeader(ext, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := tw.Write(buf.Bytes()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tw.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPAXRecord formats a single PAX record, prefixing it with the
|
||||||
|
// appropriate length.
|
||||||
|
func formatPAXRecord(k, v string) string {
|
||||||
|
const padding = 3 // Extra padding for ' ', '=', and '\n'
|
||||||
|
size := len(k) + len(v) + padding
|
||||||
|
size += len(strconv.Itoa(size))
|
||||||
|
record := fmt.Sprintf("%d %s=%s\n", size, k, v)
|
||||||
|
|
||||||
|
// Final adjustment if adding size field increased the record size.
|
||||||
|
if len(record) != size {
|
||||||
|
size = len(record)
|
||||||
|
record = fmt.Sprintf("%d %s=%s\n", size, k, v)
|
||||||
|
}
|
||||||
|
return record
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to the current entry in the tar archive.
|
||||||
|
// Write returns the error ErrWriteTooLong if more than
|
||||||
|
// hdr.Size bytes are written after WriteHeader.
|
||||||
|
func (tw *Writer) Write(b []byte) (n int, err error) {
|
||||||
|
if tw.closed {
|
||||||
|
err = ErrWriteAfterClose
|
||||||
|
return
|
||||||
|
}
|
||||||
|
overwrite := false
|
||||||
|
if int64(len(b)) > tw.nb {
|
||||||
|
b = b[0:tw.nb]
|
||||||
|
overwrite = true
|
||||||
|
}
|
||||||
|
n, err = tw.w.Write(b)
|
||||||
|
tw.nb -= int64(n)
|
||||||
|
if err == nil && overwrite {
|
||||||
|
err = ErrWriteTooLong
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tw.err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the tar archive, flushing any unwritten
|
||||||
|
// data to the underlying writer.
|
||||||
|
func (tw *Writer) Close() error {
|
||||||
|
if tw.err != nil || tw.closed {
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
tw.Flush()
|
||||||
|
tw.closed = true
|
||||||
|
if tw.err != nil {
|
||||||
|
return tw.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// trailer: two zero blocks
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
_, tw.err = tw.w.Write(zeroBlock)
|
||||||
|
if tw.err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tw.err
|
||||||
|
}
|
739
vendor/github.com/Microsoft/go-winio/archive/tar/writer_test.go
generated
vendored
Normal file
739
vendor/github.com/Microsoft/go-winio/archive/tar/writer_test.go
generated
vendored
Normal file
|
@ -0,0 +1,739 @@
|
||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package tar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"testing/iotest"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type writerTestEntry struct {
|
||||||
|
header *Header
|
||||||
|
contents string
|
||||||
|
}
|
||||||
|
|
||||||
|
type writerTest struct {
|
||||||
|
file string // filename of expected output
|
||||||
|
entries []*writerTestEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
var writerTests = []*writerTest{
|
||||||
|
// The writer test file was produced with this command:
|
||||||
|
// tar (GNU tar) 1.26
|
||||||
|
// ln -s small.txt link.txt
|
||||||
|
// tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt
|
||||||
|
{
|
||||||
|
file: "testdata/writer.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "small.txt",
|
||||||
|
Mode: 0640,
|
||||||
|
Uid: 73025,
|
||||||
|
Gid: 5000,
|
||||||
|
Size: 5,
|
||||||
|
ModTime: time.Unix(1246508266, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "dsymonds",
|
||||||
|
Gname: "eng",
|
||||||
|
},
|
||||||
|
contents: "Kilts",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "small2.txt",
|
||||||
|
Mode: 0640,
|
||||||
|
Uid: 73025,
|
||||||
|
Gid: 5000,
|
||||||
|
Size: 11,
|
||||||
|
ModTime: time.Unix(1245217492, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "dsymonds",
|
||||||
|
Gname: "eng",
|
||||||
|
},
|
||||||
|
contents: "Google.com\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "link.txt",
|
||||||
|
Mode: 0777,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 1000,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1314603082, 0),
|
||||||
|
Typeflag: '2',
|
||||||
|
Linkname: "small.txt",
|
||||||
|
Uname: "strings",
|
||||||
|
Gname: "strings",
|
||||||
|
},
|
||||||
|
// no contents
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// The truncated test file was produced using these commands:
|
||||||
|
// dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
|
||||||
|
// tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
|
||||||
|
{
|
||||||
|
file: "testdata/writer-big.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "tmp/16gig.txt",
|
||||||
|
Mode: 0640,
|
||||||
|
Uid: 73025,
|
||||||
|
Gid: 5000,
|
||||||
|
Size: 16 << 30,
|
||||||
|
ModTime: time.Unix(1254699560, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "dsymonds",
|
||||||
|
Gname: "eng",
|
||||||
|
},
|
||||||
|
// fake contents
|
||||||
|
contents: strings.Repeat("\x00", 4<<10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// The truncated test file was produced using these commands:
|
||||||
|
// dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt
|
||||||
|
// tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar
|
||||||
|
{
|
||||||
|
file: "testdata/writer-big-long.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: strings.Repeat("longname/", 15) + "16gig.txt",
|
||||||
|
Mode: 0644,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 1000,
|
||||||
|
Size: 16 << 30,
|
||||||
|
ModTime: time.Unix(1399583047, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "guillaume",
|
||||||
|
Gname: "guillaume",
|
||||||
|
},
|
||||||
|
// fake contents
|
||||||
|
contents: strings.Repeat("\x00", 4<<10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// This file was produced using gnu tar 1.17
|
||||||
|
// gnutar -b 4 --format=ustar (longname/)*15 + file.txt
|
||||||
|
{
|
||||||
|
file: "testdata/ustar.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: strings.Repeat("longname/", 15) + "file.txt",
|
||||||
|
Mode: 0644,
|
||||||
|
Uid: 0765,
|
||||||
|
Gid: 024,
|
||||||
|
Size: 06,
|
||||||
|
ModTime: time.Unix(1360135598, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "shane",
|
||||||
|
Gname: "staff",
|
||||||
|
},
|
||||||
|
contents: "hello\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// This file was produced using gnu tar 1.26
|
||||||
|
// echo "Slartibartfast" > file.txt
|
||||||
|
// ln file.txt hard.txt
|
||||||
|
// tar -b 1 --format=ustar -c -f hardlink.tar file.txt hard.txt
|
||||||
|
{
|
||||||
|
file: "testdata/hardlink.tar",
|
||||||
|
entries: []*writerTestEntry{
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "file.txt",
|
||||||
|
Mode: 0644,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 100,
|
||||||
|
Size: 15,
|
||||||
|
ModTime: time.Unix(1425484303, 0),
|
||||||
|
Typeflag: '0',
|
||||||
|
Uname: "vbatts",
|
||||||
|
Gname: "users",
|
||||||
|
},
|
||||||
|
contents: "Slartibartfast\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
header: &Header{
|
||||||
|
Name: "hard.txt",
|
||||||
|
Mode: 0644,
|
||||||
|
Uid: 1000,
|
||||||
|
Gid: 100,
|
||||||
|
Size: 0,
|
||||||
|
ModTime: time.Unix(1425484303, 0),
|
||||||
|
Typeflag: '1',
|
||||||
|
Linkname: "file.txt",
|
||||||
|
Uname: "vbatts",
|
||||||
|
Gname: "users",
|
||||||
|
},
|
||||||
|
// no contents
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
|
||||||
|
func bytestr(offset int, b []byte) string {
|
||||||
|
const rowLen = 32
|
||||||
|
s := fmt.Sprintf("%04x ", offset)
|
||||||
|
for _, ch := range b {
|
||||||
|
switch {
|
||||||
|
case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
|
||||||
|
s += fmt.Sprintf(" %c", ch)
|
||||||
|
default:
|
||||||
|
s += fmt.Sprintf(" %02x", ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a pseudo-diff between two blocks of bytes.
|
||||||
|
func bytediff(a []byte, b []byte) string {
|
||||||
|
const rowLen = 32
|
||||||
|
s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b))
|
||||||
|
for offset := 0; len(a)+len(b) > 0; offset += rowLen {
|
||||||
|
na, nb := rowLen, rowLen
|
||||||
|
if na > len(a) {
|
||||||
|
na = len(a)
|
||||||
|
}
|
||||||
|
if nb > len(b) {
|
||||||
|
nb = len(b)
|
||||||
|
}
|
||||||
|
sa := bytestr(offset, a[0:na])
|
||||||
|
sb := bytestr(offset, b[0:nb])
|
||||||
|
if sa != sb {
|
||||||
|
s += fmt.Sprintf("-%v\n+%v\n", sa, sb)
|
||||||
|
}
|
||||||
|
a = a[na:]
|
||||||
|
b = b[nb:]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriter(t *testing.T) {
|
||||||
|
testLoop:
|
||||||
|
for i, test := range writerTests {
|
||||||
|
expected, err := ioutil.ReadFile(test.file)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("test %d: Unexpected error: %v", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB
|
||||||
|
big := false
|
||||||
|
for j, entry := range test.entries {
|
||||||
|
big = big || entry.header.Size > 1<<10
|
||||||
|
if err := tw.WriteHeader(entry.header); err != nil {
|
||||||
|
t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err)
|
||||||
|
continue testLoop
|
||||||
|
}
|
||||||
|
if _, err := io.WriteString(tw, entry.contents); err != nil {
|
||||||
|
t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err)
|
||||||
|
continue testLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Only interested in Close failures for the small tests.
|
||||||
|
if err := tw.Close(); err != nil && !big {
|
||||||
|
t.Errorf("test %d: Failed closing archive: %v", i, err)
|
||||||
|
continue testLoop
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := buf.Bytes()
|
||||||
|
if !bytes.Equal(expected, actual) {
|
||||||
|
t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
|
||||||
|
i, bytediff(expected, actual))
|
||||||
|
}
|
||||||
|
if testing.Short() { // The second test is expensive.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPax(t *testing.T) {
|
||||||
|
// Create an archive with a large name
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat: %v", err)
|
||||||
|
}
|
||||||
|
// Force a PAX long name to be written
|
||||||
|
longName := strings.Repeat("ab", 100)
|
||||||
|
contents := strings.Repeat(" ", int(hdr.Size))
|
||||||
|
hdr.Name = longName
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Simple test to make sure PAX extensions are in effect
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
|
||||||
|
t.Fatal("Expected at least one PAX header to be written.")
|
||||||
|
}
|
||||||
|
// Test that we can get a long name back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hdr.Name != longName {
|
||||||
|
t.Fatal("Couldn't recover long file name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaxSymlink(t *testing.T) {
|
||||||
|
// Create an archive with a large linkname
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
hdr.Typeflag = TypeSymlink
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat:1 %v", err)
|
||||||
|
}
|
||||||
|
// Force a PAX long linkname to be written
|
||||||
|
longLinkname := strings.Repeat("1234567890/1234567890", 10)
|
||||||
|
hdr.Linkname = longLinkname
|
||||||
|
|
||||||
|
hdr.Size = 0
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Simple test to make sure PAX extensions are in effect
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
|
||||||
|
t.Fatal("Expected at least one PAX header to be written.")
|
||||||
|
}
|
||||||
|
// Test that we can get a long name back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hdr.Linkname != longLinkname {
|
||||||
|
t.Fatal("Couldn't recover long link name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaxNonAscii(t *testing.T) {
|
||||||
|
// Create an archive with non ascii. These should trigger a pax header
|
||||||
|
// because pax headers have a defined utf-8 encoding.
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat:1 %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// some sample data
|
||||||
|
chineseFilename := "文件名"
|
||||||
|
chineseGroupname := "組"
|
||||||
|
chineseUsername := "用戶名"
|
||||||
|
|
||||||
|
hdr.Name = chineseFilename
|
||||||
|
hdr.Gname = chineseGroupname
|
||||||
|
hdr.Uname = chineseUsername
|
||||||
|
|
||||||
|
contents := strings.Repeat(" ", int(hdr.Size))
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Simple test to make sure PAX extensions are in effect
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
|
||||||
|
t.Fatal("Expected at least one PAX header to be written.")
|
||||||
|
}
|
||||||
|
// Test that we can get a long name back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hdr.Name != chineseFilename {
|
||||||
|
t.Fatal("Couldn't recover unicode name")
|
||||||
|
}
|
||||||
|
if hdr.Gname != chineseGroupname {
|
||||||
|
t.Fatal("Couldn't recover unicode group")
|
||||||
|
}
|
||||||
|
if hdr.Uname != chineseUsername {
|
||||||
|
t.Fatal("Couldn't recover unicode user")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaxXattrs(t *testing.T) {
|
||||||
|
xattrs := map[string]string{
|
||||||
|
"user.key": "value",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an archive with an xattr
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat: %v", err)
|
||||||
|
}
|
||||||
|
contents := "Kilts"
|
||||||
|
hdr.Xattrs = xattrs
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Test that we can get the xattrs back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(hdr.Xattrs, xattrs) {
|
||||||
|
t.Fatalf("xattrs did not survive round trip: got %+v, want %+v",
|
||||||
|
hdr.Xattrs, xattrs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPaxHeadersSorted(t *testing.T) {
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat: %v", err)
|
||||||
|
}
|
||||||
|
contents := strings.Repeat(" ", int(hdr.Size))
|
||||||
|
|
||||||
|
hdr.Xattrs = map[string]string{
|
||||||
|
"foo": "foo",
|
||||||
|
"bar": "bar",
|
||||||
|
"baz": "baz",
|
||||||
|
"qux": "qux",
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = writer.Write([]byte(contents)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Simple test to make sure PAX extensions are in effect
|
||||||
|
if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.0")) {
|
||||||
|
t.Fatal("Expected at least one PAX header to be written.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// xattr bar should always appear before others
|
||||||
|
indices := []int{
|
||||||
|
bytes.Index(buf.Bytes(), []byte("bar=bar")),
|
||||||
|
bytes.Index(buf.Bytes(), []byte("baz=baz")),
|
||||||
|
bytes.Index(buf.Bytes(), []byte("foo=foo")),
|
||||||
|
bytes.Index(buf.Bytes(), []byte("qux=qux")),
|
||||||
|
}
|
||||||
|
if !sort.IntsAreSorted(indices) {
|
||||||
|
t.Fatal("PAX headers are not sorted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUSTARLongName(t *testing.T) {
|
||||||
|
// Create an archive with a path that failed to split with USTAR extension in previous versions.
|
||||||
|
fileinfo, err := os.Stat("testdata/small.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
hdr, err := FileInfoHeader(fileinfo, "")
|
||||||
|
hdr.Typeflag = TypeDir
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("os.Stat:1 %v", err)
|
||||||
|
}
|
||||||
|
// Force a PAX long name to be written. The name was taken from a practical example
|
||||||
|
// that fails and replaced ever char through numbers to anonymize the sample.
|
||||||
|
longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/"
|
||||||
|
hdr.Name = longName
|
||||||
|
|
||||||
|
hdr.Size = 0
|
||||||
|
var buf bytes.Buffer
|
||||||
|
writer := NewWriter(&buf)
|
||||||
|
if err := writer.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := writer.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Test that we can get a long name back out of the archive.
|
||||||
|
reader := NewReader(&buf)
|
||||||
|
hdr, err = reader.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if hdr.Name != longName {
|
||||||
|
t.Fatal("Couldn't recover long name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidTypeflagWithPAXHeader(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
tw := NewWriter(&buffer)
|
||||||
|
|
||||||
|
fileName := strings.Repeat("ab", 100)
|
||||||
|
|
||||||
|
hdr := &Header{
|
||||||
|
Name: fileName,
|
||||||
|
Size: 4,
|
||||||
|
Typeflag: 0,
|
||||||
|
}
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatalf("Failed to write header: %s", err)
|
||||||
|
}
|
||||||
|
if _, err := tw.Write([]byte("fooo")); err != nil {
|
||||||
|
t.Fatalf("Failed to write the file's data: %s", err)
|
||||||
|
}
|
||||||
|
tw.Close()
|
||||||
|
|
||||||
|
tr := NewReader(&buffer)
|
||||||
|
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read header: %s", err)
|
||||||
|
}
|
||||||
|
if header.Typeflag != 0 {
|
||||||
|
t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteAfterClose(t *testing.T) {
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
tw := NewWriter(&buffer)
|
||||||
|
|
||||||
|
hdr := &Header{
|
||||||
|
Name: "small.txt",
|
||||||
|
Size: 5,
|
||||||
|
}
|
||||||
|
if err := tw.WriteHeader(hdr); err != nil {
|
||||||
|
t.Fatalf("Failed to write header: %s", err)
|
||||||
|
}
|
||||||
|
tw.Close()
|
||||||
|
if _, err := tw.Write([]byte("Kilts")); err != ErrWriteAfterClose {
|
||||||
|
t.Fatalf("Write: got %v; want ErrWriteAfterClose", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitUSTARPath(t *testing.T) {
|
||||||
|
var sr = strings.Repeat
|
||||||
|
|
||||||
|
var vectors = []struct {
|
||||||
|
input string // Input path
|
||||||
|
prefix string // Expected output prefix
|
||||||
|
suffix string // Expected output suffix
|
||||||
|
ok bool // Split success?
|
||||||
|
}{
|
||||||
|
{"", "", "", false},
|
||||||
|
{"abc", "", "", false},
|
||||||
|
{"用戶名", "", "", false},
|
||||||
|
{sr("a", fileNameSize), "", "", false},
|
||||||
|
{sr("a", fileNameSize) + "/", "", "", false},
|
||||||
|
{sr("a", fileNameSize) + "/a", sr("a", fileNameSize), "a", true},
|
||||||
|
{sr("a", fileNamePrefixSize) + "/", "", "", false},
|
||||||
|
{sr("a", fileNamePrefixSize) + "/a", sr("a", fileNamePrefixSize), "a", true},
|
||||||
|
{sr("a", fileNameSize+1), "", "", false},
|
||||||
|
{sr("/", fileNameSize+1), sr("/", fileNameSize-1), "/", true},
|
||||||
|
{sr("a", fileNamePrefixSize) + "/" + sr("b", fileNameSize),
|
||||||
|
sr("a", fileNamePrefixSize), sr("b", fileNameSize), true},
|
||||||
|
{sr("a", fileNamePrefixSize) + "//" + sr("b", fileNameSize), "", "", false},
|
||||||
|
{sr("a/", fileNameSize), sr("a/", 77) + "a", sr("a/", 22), true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vectors {
|
||||||
|
prefix, suffix, ok := splitUSTARPath(v.input)
|
||||||
|
if prefix != v.prefix || suffix != v.suffix || ok != v.ok {
|
||||||
|
t.Errorf("splitUSTARPath(%q):\ngot (%q, %q, %v)\nwant (%q, %q, %v)",
|
||||||
|
v.input, prefix, suffix, ok, v.prefix, v.suffix, v.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatPAXRecord(t *testing.T) {
|
||||||
|
var medName = strings.Repeat("CD", 50)
|
||||||
|
var longName = strings.Repeat("AB", 100)
|
||||||
|
|
||||||
|
var vectors = []struct {
|
||||||
|
inputKey string
|
||||||
|
inputVal string
|
||||||
|
output string
|
||||||
|
}{
|
||||||
|
{"k", "v", "6 k=v\n"},
|
||||||
|
{"path", "/etc/hosts", "19 path=/etc/hosts\n"},
|
||||||
|
{"path", longName, "210 path=" + longName + "\n"},
|
||||||
|
{"path", medName, "110 path=" + medName + "\n"},
|
||||||
|
{"foo", "ba", "9 foo=ba\n"},
|
||||||
|
{"foo", "bar", "11 foo=bar\n"},
|
||||||
|
{"foo", "b=\nar=\n==\x00", "18 foo=b=\nar=\n==\x00\n"},
|
||||||
|
{"foo", "hello9 foo=ba\nworld", "27 foo=hello9 foo=ba\nworld\n"},
|
||||||
|
{"☺☻☹", "日a本b語ç", "27 ☺☻☹=日a本b語ç\n"},
|
||||||
|
{"\x00hello", "\x00world", "17 \x00hello=\x00world\n"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vectors {
|
||||||
|
output := formatPAXRecord(v.inputKey, v.inputVal)
|
||||||
|
if output != v.output {
|
||||||
|
t.Errorf("formatPAXRecord(%q, %q): got %q, want %q",
|
||||||
|
v.inputKey, v.inputVal, output, v.output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFitsInBase256(t *testing.T) {
|
||||||
|
var vectors = []struct {
|
||||||
|
input int64
|
||||||
|
width int
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{+1, 8, true},
|
||||||
|
{0, 8, true},
|
||||||
|
{-1, 8, true},
|
||||||
|
{1 << 56, 8, false},
|
||||||
|
{(1 << 56) - 1, 8, true},
|
||||||
|
{-1 << 56, 8, true},
|
||||||
|
{(-1 << 56) - 1, 8, false},
|
||||||
|
{121654, 8, true},
|
||||||
|
{-9849849, 8, true},
|
||||||
|
{math.MaxInt64, 9, true},
|
||||||
|
{0, 9, true},
|
||||||
|
{math.MinInt64, 9, true},
|
||||||
|
{math.MaxInt64, 12, true},
|
||||||
|
{0, 12, true},
|
||||||
|
{math.MinInt64, 12, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vectors {
|
||||||
|
ok := fitsInBase256(v.width, v.input)
|
||||||
|
if ok != v.ok {
|
||||||
|
t.Errorf("checkNumeric(%d, %d): got %v, want %v", v.input, v.width, ok, v.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatNumeric(t *testing.T) {
|
||||||
|
var vectors = []struct {
|
||||||
|
input int64
|
||||||
|
output string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
// Test base-256 (binary) encoded values.
|
||||||
|
{-1, "\xff", true},
|
||||||
|
{-1, "\xff\xff", true},
|
||||||
|
{-1, "\xff\xff\xff", true},
|
||||||
|
{(1 << 0), "0", false},
|
||||||
|
{(1 << 8) - 1, "\x80\xff", true},
|
||||||
|
{(1 << 8), "0\x00", false},
|
||||||
|
{(1 << 16) - 1, "\x80\xff\xff", true},
|
||||||
|
{(1 << 16), "00\x00", false},
|
||||||
|
{-1 * (1 << 0), "\xff", true},
|
||||||
|
{-1*(1<<0) - 1, "0", false},
|
||||||
|
{-1 * (1 << 8), "\xff\x00", true},
|
||||||
|
{-1*(1<<8) - 1, "0\x00", false},
|
||||||
|
{-1 * (1 << 16), "\xff\x00\x00", true},
|
||||||
|
{-1*(1<<16) - 1, "00\x00", false},
|
||||||
|
{537795476381659745, "0000000\x00", false},
|
||||||
|
{537795476381659745, "\x80\x00\x00\x00\x07\x76\xa2\x22\xeb\x8a\x72\x61", true},
|
||||||
|
{-615126028225187231, "0000000\x00", false},
|
||||||
|
{-615126028225187231, "\xff\xff\xff\xff\xf7\x76\xa2\x22\xeb\x8a\x72\x61", true},
|
||||||
|
{math.MaxInt64, "0000000\x00", false},
|
||||||
|
{math.MaxInt64, "\x80\x00\x00\x00\x7f\xff\xff\xff\xff\xff\xff\xff", true},
|
||||||
|
{math.MinInt64, "0000000\x00", false},
|
||||||
|
{math.MinInt64, "\xff\xff\xff\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
|
||||||
|
{math.MaxInt64, "\x80\x7f\xff\xff\xff\xff\xff\xff\xff", true},
|
||||||
|
{math.MinInt64, "\xff\x80\x00\x00\x00\x00\x00\x00\x00", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range vectors {
|
||||||
|
var f formatter
|
||||||
|
output := make([]byte, len(v.output))
|
||||||
|
f.formatNumeric(output, v.input)
|
||||||
|
ok := (f.err == nil)
|
||||||
|
if ok != v.ok {
|
||||||
|
if v.ok {
|
||||||
|
t.Errorf("formatNumeric(%d): got formatting failure, want success", v.input)
|
||||||
|
} else {
|
||||||
|
t.Errorf("formatNumeric(%d): got formatting success, want failure", v.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if string(output) != v.output {
|
||||||
|
t.Errorf("formatNumeric(%d): got %q, want %q", v.input, output, v.output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFormatPAXTime(t *testing.T) {
|
||||||
|
t1 := time.Date(2000, 1, 1, 11, 0, 0, 0, time.UTC)
|
||||||
|
t2 := time.Date(2000, 1, 1, 11, 0, 0, 100, time.UTC)
|
||||||
|
t3 := time.Date(1960, 1, 1, 11, 0, 0, 0, time.UTC)
|
||||||
|
t4 := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
verify := func(time time.Time, s string) {
|
||||||
|
p := formatPAXTime(time)
|
||||||
|
if p != s {
|
||||||
|
t.Errorf("for %v, expected %s, got %s", time, s, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verify(t1, "946724400")
|
||||||
|
verify(t2, "946724400.000000100")
|
||||||
|
verify(t3, "-315579600")
|
||||||
|
verify(t4, "0")
|
||||||
|
}
|
280
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
280
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
||||||
|
//sys backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
|
||||||
|
|
||||||
|
const (
|
||||||
|
BackupData = uint32(iota + 1)
|
||||||
|
BackupEaData
|
||||||
|
BackupSecurity
|
||||||
|
BackupAlternateData
|
||||||
|
BackupLink
|
||||||
|
BackupPropertyData
|
||||||
|
BackupObjectId
|
||||||
|
BackupReparseData
|
||||||
|
BackupSparseBlock
|
||||||
|
BackupTxfsData
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StreamSparseAttributes = uint32(8)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
WRITE_DAC = 0x40000
|
||||||
|
WRITE_OWNER = 0x80000
|
||||||
|
ACCESS_SYSTEM_SECURITY = 0x1000000
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackupHeader represents a backup stream of a file.
|
||||||
|
type BackupHeader struct {
|
||||||
|
Id uint32 // The backup stream ID
|
||||||
|
Attributes uint32 // Stream attributes
|
||||||
|
Size int64 // The size of the stream in bytes
|
||||||
|
Name string // The name of the stream (for BackupAlternateData only).
|
||||||
|
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32StreamId struct {
|
||||||
|
StreamId uint32
|
||||||
|
Attributes uint32
|
||||||
|
Size uint64
|
||||||
|
NameSize uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
|
||||||
|
// of BackupHeader values.
|
||||||
|
type BackupStreamReader struct {
|
||||||
|
r io.Reader
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
|
||||||
|
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
|
||||||
|
return &BackupStreamReader{r, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
|
||||||
|
// it was not completely read.
|
||||||
|
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
||||||
|
if r.bytesLeft > 0 {
|
||||||
|
if s, ok := r.r.(io.Seeker); ok {
|
||||||
|
// Make sure Seek on io.SeekCurrent sometimes succeeds
|
||||||
|
// before trying the actual seek.
|
||||||
|
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
|
||||||
|
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.bytesLeft = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(ioutil.Discard, r); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var wsi win32StreamId
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr := &BackupHeader{
|
||||||
|
Id: wsi.StreamId,
|
||||||
|
Attributes: wsi.Attributes,
|
||||||
|
Size: int64(wsi.Size),
|
||||||
|
}
|
||||||
|
if wsi.NameSize != 0 {
|
||||||
|
name := make([]uint16, int(wsi.NameSize/2))
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Name = syscall.UTF16ToString(name)
|
||||||
|
}
|
||||||
|
if wsi.StreamId == BackupSparseBlock {
|
||||||
|
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hdr.Size -= 8
|
||||||
|
}
|
||||||
|
r.bytesLeft = hdr.Size
|
||||||
|
return hdr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from the current backup stream.
|
||||||
|
func (r *BackupStreamReader) Read(b []byte) (int, error) {
|
||||||
|
if r.bytesLeft == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if int64(len(b)) > r.bytesLeft {
|
||||||
|
b = b[:r.bytesLeft]
|
||||||
|
}
|
||||||
|
n, err := r.r.Read(b)
|
||||||
|
r.bytesLeft -= int64(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
} else if r.bytesLeft == 0 && err == nil {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
|
||||||
|
type BackupStreamWriter struct {
|
||||||
|
w io.Writer
|
||||||
|
bytesLeft int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
|
||||||
|
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
|
||||||
|
return &BackupStreamWriter{w, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHeader writes the next backup stream header and prepares for calls to Write().
|
||||||
|
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
|
||||||
|
if w.bytesLeft != 0 {
|
||||||
|
return fmt.Errorf("missing %d bytes", w.bytesLeft)
|
||||||
|
}
|
||||||
|
name := utf16.Encode([]rune(hdr.Name))
|
||||||
|
wsi := win32StreamId{
|
||||||
|
StreamId: hdr.Id,
|
||||||
|
Attributes: hdr.Attributes,
|
||||||
|
Size: uint64(hdr.Size),
|
||||||
|
NameSize: uint32(len(name) * 2),
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
// Include space for the int64 block offset
|
||||||
|
wsi.Size += 8
|
||||||
|
}
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(name) != 0 {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Id == BackupSparseBlock {
|
||||||
|
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.bytesLeft = hdr.Size
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to the current backup stream.
|
||||||
|
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
|
||||||
|
if w.bytesLeft < int64(len(b)) {
|
||||||
|
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
|
||||||
|
}
|
||||||
|
n, err := w.w.Write(b)
|
||||||
|
w.bytesLeft -= int64(n)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
|
||||||
|
type BackupFileReader struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
|
||||||
|
// Read will attempt to read the security descriptor of the file.
|
||||||
|
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
|
||||||
|
r := &BackupFileReader{f, includeSecurity, 0}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
|
||||||
|
func (r *BackupFileReader) Read(b []byte) (int, error) {
|
||||||
|
var bytesRead uint32
|
||||||
|
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{"BackupRead", r.f.Name(), err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(r.f)
|
||||||
|
if bytesRead == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return int(bytesRead), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileReader. It does not close
|
||||||
|
// the underlying file.
|
||||||
|
func (r *BackupFileReader) Close() error {
|
||||||
|
if r.ctx != 0 {
|
||||||
|
backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
|
||||||
|
runtime.KeepAlive(r.f)
|
||||||
|
r.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
|
||||||
|
type BackupFileWriter struct {
|
||||||
|
f *os.File
|
||||||
|
includeSecurity bool
|
||||||
|
ctx uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
|
||||||
|
// Write() will attempt to restore the security descriptor from the stream.
|
||||||
|
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
||||||
|
w := &BackupFileWriter{f, includeSecurity, 0}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write restores a portion of the file using the provided backup stream.
|
||||||
|
func (w *BackupFileWriter) Write(b []byte) (int, error) {
|
||||||
|
var bytesWritten uint32
|
||||||
|
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{"BackupWrite", w.f.Name(), err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(w.f)
|
||||||
|
if int(bytesWritten) != len(b) {
|
||||||
|
return int(bytesWritten), errors.New("not all bytes could be written")
|
||||||
|
}
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close frees Win32 resources associated with the BackupFileWriter. It does not
|
||||||
|
// close the underlying file.
|
||||||
|
func (w *BackupFileWriter) Close() error {
|
||||||
|
if w.ctx != 0 {
|
||||||
|
backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
|
||||||
|
runtime.KeepAlive(w.f)
|
||||||
|
w.ctx = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenForBackup opens a file or directory, potentially skipping access checks if the backup
|
||||||
|
// or restore privileges have been acquired.
|
||||||
|
//
|
||||||
|
// If the file opened was a directory, it cannot be used with Readdir().
|
||||||
|
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
|
||||||
|
winPath, err := syscall.UTF16FromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0)
|
||||||
|
if err != nil {
|
||||||
|
err = &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return os.NewFile(uintptr(h), path), nil
|
||||||
|
}
|
255
vendor/github.com/Microsoft/go-winio/backup_test.go
generated
vendored
Normal file
255
vendor/github.com/Microsoft/go-winio/backup_test.go
generated
vendored
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testFileName string
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
f, err := ioutil.TempFile("", "tmp")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
testFileName = f.Name()
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove(testFileName)
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTestFile(makeADS bool) error {
|
||||||
|
os.Remove(testFileName)
|
||||||
|
f, err := os.Create(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = f.Write([]byte("testing 1 2 3\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if makeADS {
|
||||||
|
a, err := os.Create(testFileName + ":ads.txt")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer a.Close()
|
||||||
|
_, err = a.Write([]byte("alternate data stream\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupRead(t *testing.T) {
|
||||||
|
err := makeTestFile(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := NewBackupFileReader(f, false)
|
||||||
|
defer r.Close()
|
||||||
|
b, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
t.Fatal("no data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupStreamRead(t *testing.T) {
|
||||||
|
err := makeTestFile(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := NewBackupFileReader(f, false)
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
br := NewBackupStreamReader(r)
|
||||||
|
gotData := false
|
||||||
|
gotAltData := false
|
||||||
|
for {
|
||||||
|
hdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch hdr.Id {
|
||||||
|
case BackupData:
|
||||||
|
if gotData {
|
||||||
|
t.Fatal("duplicate data")
|
||||||
|
}
|
||||||
|
if hdr.Name != "" {
|
||||||
|
t.Fatalf("unexpected name %s", hdr.Name)
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(br)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != "testing 1 2 3\n" {
|
||||||
|
t.Fatalf("incorrect data %v", b)
|
||||||
|
}
|
||||||
|
gotData = true
|
||||||
|
case BackupAlternateData:
|
||||||
|
if gotAltData {
|
||||||
|
t.Fatal("duplicate alt data")
|
||||||
|
}
|
||||||
|
if hdr.Name != ":ads.txt:$DATA" {
|
||||||
|
t.Fatalf("incorrect name %s", hdr.Name)
|
||||||
|
}
|
||||||
|
b, err := ioutil.ReadAll(br)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != "alternate data stream\n" {
|
||||||
|
t.Fatalf("incorrect data %v", b)
|
||||||
|
}
|
||||||
|
gotAltData = true
|
||||||
|
default:
|
||||||
|
t.Fatalf("unknown stream ID %d", hdr.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !gotData || !gotAltData {
|
||||||
|
t.Fatal("missing stream")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupStreamWrite(t *testing.T) {
|
||||||
|
f, err := os.Create(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
w := NewBackupFileWriter(f, false)
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
data := "testing 1 2 3\n"
|
||||||
|
altData := "alternate stream\n"
|
||||||
|
|
||||||
|
br := NewBackupStreamWriter(w)
|
||||||
|
err = br.WriteHeader(&BackupHeader{Id: BackupData, Size: int64(len(data))})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
n, err := br.Write([]byte(data))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != len(data) {
|
||||||
|
t.Fatal("short write")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = br.WriteHeader(&BackupHeader{Id: BackupAlternateData, Size: int64(len(altData)), Name: ":ads.txt:$DATA"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
n, err = br.Write([]byte(altData))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != len(altData) {
|
||||||
|
t.Fatal("short write")
|
||||||
|
}
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadFile(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != data {
|
||||||
|
t.Fatalf("wrong data %v", b)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = ioutil.ReadFile(testFileName + ":ads.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != altData {
|
||||||
|
t.Fatalf("wrong data %v", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSparseFile() error {
|
||||||
|
os.Remove(testFileName)
|
||||||
|
f, err := os.Create(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
const (
|
||||||
|
FSCTL_SET_SPARSE = 0x000900c4
|
||||||
|
FSCTL_SET_ZERO_DATA = 0x000980c8
|
||||||
|
)
|
||||||
|
|
||||||
|
err = syscall.DeviceIoControl(syscall.Handle(f.Fd()), FSCTL_SET_SPARSE, nil, 0, nil, 0, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Write([]byte("testing 1 2 3\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Seek(1000000, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Write([]byte("more data later\n"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupSparseFile(t *testing.T) {
|
||||||
|
err := makeSparseFile()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(testFileName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
r := NewBackupFileReader(f, false)
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
br := NewBackupStreamReader(r)
|
||||||
|
for {
|
||||||
|
hdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(hdr)
|
||||||
|
}
|
||||||
|
}
|
4
vendor/github.com/Microsoft/go-winio/backuptar/noop.go
generated
vendored
Normal file
4
vendor/github.com/Microsoft/go-winio/backuptar/noop.go
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// +build !windows
|
||||||
|
// This file only exists to allow go get on non-Windows platforms.
|
||||||
|
|
||||||
|
package backuptar
|
439
vendor/github.com/Microsoft/go-winio/backuptar/tar.go
generated
vendored
Normal file
439
vendor/github.com/Microsoft/go-winio/backuptar/tar.go
generated
vendored
Normal file
|
@ -0,0 +1,439 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package backuptar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio"
|
||||||
|
"github.com/Microsoft/go-winio/archive/tar" // until archive/tar supports pax extensions in its interface
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
c_ISUID = 04000 // Set uid
|
||||||
|
c_ISGID = 02000 // Set gid
|
||||||
|
c_ISVTX = 01000 // Save text (sticky bit)
|
||||||
|
c_ISDIR = 040000 // Directory
|
||||||
|
c_ISFIFO = 010000 // FIFO
|
||||||
|
c_ISREG = 0100000 // Regular file
|
||||||
|
c_ISLNK = 0120000 // Symbolic link
|
||||||
|
c_ISBLK = 060000 // Block special file
|
||||||
|
c_ISCHR = 020000 // Character special file
|
||||||
|
c_ISSOCK = 0140000 // Socket
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hdrFileAttributes = "fileattr"
|
||||||
|
hdrSecurityDescriptor = "sd"
|
||||||
|
hdrRawSecurityDescriptor = "rawsd"
|
||||||
|
hdrMountPoint = "mountpoint"
|
||||||
|
hdrEaPrefix = "xattr."
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeZeroes(w io.Writer, count int64) error {
|
||||||
|
buf := make([]byte, 8192)
|
||||||
|
c := len(buf)
|
||||||
|
for i := int64(0); i < count; i += int64(c) {
|
||||||
|
if int64(c) > count-i {
|
||||||
|
c = int(count - i)
|
||||||
|
}
|
||||||
|
_, err := w.Write(buf[:c])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
|
||||||
|
curOffset := int64(0)
|
||||||
|
for {
|
||||||
|
bhdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bhdr.Id != winio.BackupSparseBlock {
|
||||||
|
return fmt.Errorf("unexpected stream %d", bhdr.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// archive/tar does not support writing sparse files
|
||||||
|
// so just write zeroes to catch up to the current offset.
|
||||||
|
err = writeZeroes(t, bhdr.Offset-curOffset)
|
||||||
|
if bhdr.Size == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n, err := io.Copy(t, br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
curOffset = bhdr.Offset + n
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicInfoHeader creates a tar header from basic file information.
|
||||||
|
func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
|
||||||
|
hdr := &tar.Header{
|
||||||
|
Name: filepath.ToSlash(name),
|
||||||
|
Size: size,
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
|
||||||
|
ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
|
||||||
|
AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
|
||||||
|
CreationTime: time.Unix(0, fileInfo.CreationTime.Nanoseconds()),
|
||||||
|
Winheaders: make(map[string]string),
|
||||||
|
}
|
||||||
|
hdr.Winheaders[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
|
||||||
|
|
||||||
|
if (fileInfo.FileAttributes & syscall.FILE_ATTRIBUTE_DIRECTORY) != 0 {
|
||||||
|
hdr.Mode |= c_ISDIR
|
||||||
|
hdr.Size = 0
|
||||||
|
hdr.Typeflag = tar.TypeDir
|
||||||
|
}
|
||||||
|
return hdr
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
|
||||||
|
//
|
||||||
|
// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
|
||||||
|
//
|
||||||
|
// The additional Win32 metadata is:
|
||||||
|
//
|
||||||
|
// MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
|
||||||
|
//
|
||||||
|
// MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
|
||||||
|
//
|
||||||
|
// MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
|
||||||
|
func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
|
||||||
|
name = filepath.ToSlash(name)
|
||||||
|
hdr := BasicInfoHeader(name, size, fileInfo)
|
||||||
|
|
||||||
|
// If r can be seeked, then this function is two-pass: pass 1 collects the
|
||||||
|
// tar header data, and pass 2 copies the data stream. If r cannot be
|
||||||
|
// seeked, then some header data (in particular EAs) will be silently lost.
|
||||||
|
var (
|
||||||
|
restartPos int64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
sr, readTwice := r.(io.Seeker)
|
||||||
|
if readTwice {
|
||||||
|
if restartPos, err = sr.Seek(0, io.SeekCurrent); err != nil {
|
||||||
|
readTwice = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
br := winio.NewBackupStreamReader(r)
|
||||||
|
var dataHdr *winio.BackupHeader
|
||||||
|
for dataHdr == nil {
|
||||||
|
bhdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch bhdr.Id {
|
||||||
|
case winio.BackupData:
|
||||||
|
hdr.Mode |= c_ISREG
|
||||||
|
if !readTwice {
|
||||||
|
dataHdr = bhdr
|
||||||
|
}
|
||||||
|
case winio.BackupSecurity:
|
||||||
|
sd, err := ioutil.ReadAll(br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hdr.Winheaders[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
|
||||||
|
|
||||||
|
case winio.BackupReparseData:
|
||||||
|
hdr.Mode |= c_ISLNK
|
||||||
|
hdr.Typeflag = tar.TypeSymlink
|
||||||
|
reparseBuffer, err := ioutil.ReadAll(br)
|
||||||
|
rp, err := winio.DecodeReparsePoint(reparseBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if rp.IsMountPoint {
|
||||||
|
hdr.Winheaders[hdrMountPoint] = "1"
|
||||||
|
}
|
||||||
|
hdr.Linkname = rp.Target
|
||||||
|
|
||||||
|
case winio.BackupEaData:
|
||||||
|
eab, err := ioutil.ReadAll(br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
eas, err := winio.DecodeExtendedAttributes(eab)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ea := range eas {
|
||||||
|
// Use base64 encoding for the binary value. Note that there
|
||||||
|
// is no way to encode the EA's flags, since their use doesn't
|
||||||
|
// make any sense for persisted EAs.
|
||||||
|
hdr.Winheaders[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case winio.BackupAlternateData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
||||||
|
// ignore these streams
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = t.WriteHeader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if readTwice {
|
||||||
|
// Get back to the data stream.
|
||||||
|
if _, err = sr.Seek(restartPos, io.SeekStart); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for dataHdr == nil {
|
||||||
|
bhdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bhdr.Id == winio.BackupData {
|
||||||
|
dataHdr = bhdr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dataHdr != nil {
|
||||||
|
// A data stream was found. Copy the data.
|
||||||
|
if (dataHdr.Attributes & winio.StreamSparseAttributes) == 0 {
|
||||||
|
if size != dataHdr.Size {
|
||||||
|
return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
|
||||||
|
}
|
||||||
|
_, err = io.Copy(t, br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = copySparse(t, br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for streams after the data stream. The only ones we handle are alternate data streams.
|
||||||
|
// Other streams may have metadata that could be serialized, but the tar header has already
|
||||||
|
// been written. In practice, this means that we don't get EA or TXF metadata.
|
||||||
|
for {
|
||||||
|
bhdr, err := br.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch bhdr.Id {
|
||||||
|
case winio.BackupAlternateData:
|
||||||
|
altName := bhdr.Name
|
||||||
|
if strings.HasSuffix(altName, ":$DATA") {
|
||||||
|
altName = altName[:len(altName)-len(":$DATA")]
|
||||||
|
}
|
||||||
|
if (bhdr.Attributes & winio.StreamSparseAttributes) == 0 {
|
||||||
|
hdr = &tar.Header{
|
||||||
|
Name: name + altName,
|
||||||
|
Mode: hdr.Mode,
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
Size: bhdr.Size,
|
||||||
|
ModTime: hdr.ModTime,
|
||||||
|
AccessTime: hdr.AccessTime,
|
||||||
|
ChangeTime: hdr.ChangeTime,
|
||||||
|
}
|
||||||
|
err = t.WriteHeader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(t, br)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Unsupported for now, since the size of the alternate stream is not present
|
||||||
|
// in the backup stream until after the data has been read.
|
||||||
|
return errors.New("tar of sparse alternate data streams is unsupported")
|
||||||
|
}
|
||||||
|
case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
|
||||||
|
// ignore these streams
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
|
||||||
|
// WriteTarFileFromBackupStream.
|
||||||
|
func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
|
||||||
|
name = hdr.Name
|
||||||
|
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
|
||||||
|
size = hdr.Size
|
||||||
|
}
|
||||||
|
fileInfo = &winio.FileBasicInfo{
|
||||||
|
LastAccessTime: syscall.NsecToFiletime(hdr.AccessTime.UnixNano()),
|
||||||
|
LastWriteTime: syscall.NsecToFiletime(hdr.ModTime.UnixNano()),
|
||||||
|
ChangeTime: syscall.NsecToFiletime(hdr.ChangeTime.UnixNano()),
|
||||||
|
CreationTime: syscall.NsecToFiletime(hdr.CreationTime.UnixNano()),
|
||||||
|
}
|
||||||
|
if attrStr, ok := hdr.Winheaders[hdrFileAttributes]; ok {
|
||||||
|
attr, err := strconv.ParseUint(attrStr, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, nil, err
|
||||||
|
}
|
||||||
|
fileInfo.FileAttributes = uintptr(attr)
|
||||||
|
} else {
|
||||||
|
if hdr.Typeflag == tar.TypeDir {
|
||||||
|
fileInfo.FileAttributes |= syscall.FILE_ATTRIBUTE_DIRECTORY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
|
||||||
|
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
|
||||||
|
// tar file that was not processed, or io.EOF is there are no more.
|
||||||
|
func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
|
||||||
|
bw := winio.NewBackupStreamWriter(w)
|
||||||
|
var sd []byte
|
||||||
|
var err error
|
||||||
|
// Maintaining old SDDL-based behavior for backward compatibility. All new tar headers written
|
||||||
|
// by this library will have raw binary for the security descriptor.
|
||||||
|
if sddl, ok := hdr.Winheaders[hdrSecurityDescriptor]; ok {
|
||||||
|
sd, err = winio.SddlToSecurityDescriptor(sddl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sdraw, ok := hdr.Winheaders[hdrRawSecurityDescriptor]; ok {
|
||||||
|
sd, err = base64.StdEncoding.DecodeString(sdraw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(sd) != 0 {
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupSecurity,
|
||||||
|
Size: int64(len(sd)),
|
||||||
|
}
|
||||||
|
err := bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = bw.Write(sd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var eas []winio.ExtendedAttribute
|
||||||
|
for k, v := range hdr.Winheaders {
|
||||||
|
if !strings.HasPrefix(k, hdrEaPrefix) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
data, err := base64.StdEncoding.DecodeString(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
eas = append(eas, winio.ExtendedAttribute{
|
||||||
|
Name: k[len(hdrEaPrefix):],
|
||||||
|
Value: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if len(eas) != 0 {
|
||||||
|
eadata, err := winio.EncodeExtendedAttributes(eas)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupEaData,
|
||||||
|
Size: int64(len(eadata)),
|
||||||
|
}
|
||||||
|
err = bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = bw.Write(eadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeSymlink {
|
||||||
|
_, isMountPoint := hdr.Winheaders[hdrMountPoint]
|
||||||
|
rp := winio.ReparsePoint{
|
||||||
|
Target: filepath.FromSlash(hdr.Linkname),
|
||||||
|
IsMountPoint: isMountPoint,
|
||||||
|
}
|
||||||
|
reparse := winio.EncodeReparsePoint(&rp)
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupReparseData,
|
||||||
|
Size: int64(len(reparse)),
|
||||||
|
}
|
||||||
|
err := bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = bw.Write(reparse)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hdr.Typeflag == tar.TypeReg || hdr.Typeflag == tar.TypeRegA {
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupData,
|
||||||
|
Size: hdr.Size,
|
||||||
|
}
|
||||||
|
err := bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(bw, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Copy all the alternate data streams and return the next non-ADS header.
|
||||||
|
for {
|
||||||
|
ahdr, err := t.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
|
||||||
|
return ahdr, nil
|
||||||
|
}
|
||||||
|
bhdr := winio.BackupHeader{
|
||||||
|
Id: winio.BackupAlternateData,
|
||||||
|
Size: ahdr.Size,
|
||||||
|
Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
|
||||||
|
}
|
||||||
|
err = bw.WriteHeader(&bhdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(bw, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
vendor/github.com/Microsoft/go-winio/backuptar/tar_test.go
generated
vendored
Normal file
84
vendor/github.com/Microsoft/go-winio/backuptar/tar_test.go
generated
vendored
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package backuptar
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio"
|
||||||
|
"github.com/Microsoft/go-winio/archive/tar"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ensurePresent(t *testing.T, m map[string]string, keys ...string) {
|
||||||
|
for _, k := range keys {
|
||||||
|
if _, ok := m[k]; !ok {
|
||||||
|
t.Error(k, "not present in tar header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRoundTrip(t *testing.T) {
|
||||||
|
f, err := ioutil.TempFile("", "tst")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
if _, err = f.Write([]byte("testing 1 2 3\n")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = f.Seek(0, 0); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bi, err := winio.GetFileBasicInfo(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
br := winio.NewBackupFileReader(f, true)
|
||||||
|
defer br.Close()
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
tw := tar.NewWriter(&buf)
|
||||||
|
|
||||||
|
err = WriteTarFileFromBackupStream(tw, br, f.Name(), fi.Size(), bi)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := tar.NewReader(&buf)
|
||||||
|
hdr, err := tr.Next()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
name, size, bi2, err := FileInfoFromHeader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name != filepath.ToSlash(f.Name()) {
|
||||||
|
t.Errorf("got name %s, expected %s", name, filepath.ToSlash(f.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if size != fi.Size() {
|
||||||
|
t.Errorf("got size %d, expected %d", size, fi.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(*bi, *bi2) {
|
||||||
|
t.Errorf("got %#v, expected %#v", *bi, *bi2)
|
||||||
|
}
|
||||||
|
|
||||||
|
ensurePresent(t, hdr.Winheaders, "fileattr", "rawsd")
|
||||||
|
}
|
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fileFullEaInformation struct {
|
||||||
|
NextEntryOffset uint32
|
||||||
|
Flags uint8
|
||||||
|
NameLength uint8
|
||||||
|
ValueLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
|
||||||
|
|
||||||
|
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
|
||||||
|
errEaNameTooLarge = errors.New("extended attribute name too large")
|
||||||
|
errEaValueTooLarge = errors.New("extended attribute value too large")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExtendedAttribute represents a single Windows EA.
|
||||||
|
type ExtendedAttribute struct {
|
||||||
|
Name string
|
||||||
|
Value []byte
|
||||||
|
Flags uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
||||||
|
var info fileFullEaInformation
|
||||||
|
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
||||||
|
if err != nil {
|
||||||
|
err = errInvalidEaBuffer
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nameOffset := fileFullEaInformationSize
|
||||||
|
nameLen := int(info.NameLength)
|
||||||
|
valueOffset := nameOffset + int(info.NameLength) + 1
|
||||||
|
valueLen := int(info.ValueLength)
|
||||||
|
nextOffset := int(info.NextEntryOffset)
|
||||||
|
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
|
||||||
|
err = errInvalidEaBuffer
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ea.Name = string(b[nameOffset : nameOffset+nameLen])
|
||||||
|
ea.Value = b[valueOffset : valueOffset+valueLen]
|
||||||
|
ea.Flags = info.Flags
|
||||||
|
if info.NextEntryOffset != 0 {
|
||||||
|
nb = b[info.NextEntryOffset:]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
|
||||||
|
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
|
||||||
|
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
||||||
|
for len(b) != 0 {
|
||||||
|
ea, nb, err := parseEa(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
eas = append(eas, ea)
|
||||||
|
b = nb
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
||||||
|
if int(uint8(len(ea.Name))) != len(ea.Name) {
|
||||||
|
return errEaNameTooLarge
|
||||||
|
}
|
||||||
|
if int(uint16(len(ea.Value))) != len(ea.Value) {
|
||||||
|
return errEaValueTooLarge
|
||||||
|
}
|
||||||
|
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
|
||||||
|
withPadding := (entrySize + 3) &^ 3
|
||||||
|
nextOffset := uint32(0)
|
||||||
|
if !last {
|
||||||
|
nextOffset = withPadding
|
||||||
|
}
|
||||||
|
info := fileFullEaInformation{
|
||||||
|
NextEntryOffset: nextOffset,
|
||||||
|
Flags: ea.Flags,
|
||||||
|
NameLength: uint8(len(ea.Name)),
|
||||||
|
ValueLength: uint16(len(ea.Value)),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := binary.Write(buf, binary.LittleEndian, &info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write([]byte(ea.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buf.WriteByte(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write(ea.Value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
|
||||||
|
// buffer for use with BackupWrite, ZwSetEaFile, etc.
|
||||||
|
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for i := range eas {
|
||||||
|
last := false
|
||||||
|
if i == len(eas)-1 {
|
||||||
|
last = true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := writeEa(&buf, &eas[i], last)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
89
vendor/github.com/Microsoft/go-winio/ea_test.go
generated
vendored
Normal file
89
vendor/github.com/Microsoft/go-winio/ea_test.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testEas = []ExtendedAttribute{
|
||||||
|
{Name: "foo", Value: []byte("bar")},
|
||||||
|
{Name: "fizz", Value: []byte("buzz")},
|
||||||
|
}
|
||||||
|
|
||||||
|
testEasEncoded = []byte{16, 0, 0, 0, 0, 3, 3, 0, 102, 111, 111, 0, 98, 97, 114, 0, 0, 0, 0, 0, 0, 4, 4, 0, 102, 105, 122, 122, 0, 98, 117, 122, 122, 0, 0, 0}
|
||||||
|
testEasNotPadded = testEasEncoded[0 : len(testEasEncoded)-3]
|
||||||
|
testEasTruncated = testEasEncoded[0:20]
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_RoundTripEas(t *testing.T) {
|
||||||
|
b, err := EncodeExtendedAttributes(testEas)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(testEasEncoded, b) {
|
||||||
|
t.Fatalf("encoded mismatch %v %v", testEasEncoded, b)
|
||||||
|
}
|
||||||
|
eas, err := DecodeExtendedAttributes(b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(testEas, eas) {
|
||||||
|
t.Fatalf("mismatch %+v %+v", testEas, eas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_EasDontNeedPaddingAtEnd(t *testing.T) {
|
||||||
|
eas, err := DecodeExtendedAttributes(testEasNotPadded)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(testEas, eas) {
|
||||||
|
t.Fatalf("mismatch %+v %+v", testEas, eas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_TruncatedEasFailCorrectly(t *testing.T) {
|
||||||
|
_, err := DecodeExtendedAttributes(testEasTruncated)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_NilEasEncodeAndDecodeAsNil(t *testing.T) {
|
||||||
|
b, err := EncodeExtendedAttributes(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(b) != 0 {
|
||||||
|
t.Fatal("expected empty")
|
||||||
|
}
|
||||||
|
eas, err := DecodeExtendedAttributes(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(eas) != 0 {
|
||||||
|
t.Fatal("expected empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test_SetFileEa makes sure that the test buffer is actually parsable by NtSetEaFile.
|
||||||
|
func Test_SetFileEa(t *testing.T) {
|
||||||
|
f, err := ioutil.TempFile("", "winio")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
defer f.Close()
|
||||||
|
ntdll := syscall.MustLoadDLL("ntdll.dll")
|
||||||
|
ntSetEaFile := ntdll.MustFindProc("NtSetEaFile")
|
||||||
|
var iosb [2]uintptr
|
||||||
|
r, _, _ := ntSetEaFile.Call(f.Fd(), uintptr(unsafe.Pointer(&iosb[0])), uintptr(unsafe.Pointer(&testEasEncoded[0])), uintptr(len(testEasEncoded)))
|
||||||
|
if r != 0 {
|
||||||
|
t.Fatalf("NtSetEaFile failed with %08x", r)
|
||||||
|
}
|
||||||
|
}
|
307
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
307
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
|
@ -0,0 +1,307 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
|
||||||
|
//sys createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) = CreateIoCompletionPort
|
||||||
|
//sys getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||||
|
//sys setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||||
|
|
||||||
|
type atomicBool int32
|
||||||
|
|
||||||
|
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
|
||||||
|
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
|
||||||
|
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
|
||||||
|
func (b *atomicBool) swap(new bool) bool {
|
||||||
|
var newInt int32
|
||||||
|
if new {
|
||||||
|
newInt = 1
|
||||||
|
}
|
||||||
|
return atomic.SwapInt32((*int32)(b), newInt) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
|
||||||
|
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrFileClosed = errors.New("file has already been closed")
|
||||||
|
ErrTimeout = &timeoutError{}
|
||||||
|
)
|
||||||
|
|
||||||
|
type timeoutError struct{}
|
||||||
|
|
||||||
|
func (e *timeoutError) Error() string { return "i/o timeout" }
|
||||||
|
func (e *timeoutError) Timeout() bool { return true }
|
||||||
|
func (e *timeoutError) Temporary() bool { return true }
|
||||||
|
|
||||||
|
type timeoutChan chan struct{}
|
||||||
|
|
||||||
|
var ioInitOnce sync.Once
|
||||||
|
var ioCompletionPort syscall.Handle
|
||||||
|
|
||||||
|
// ioResult contains the result of an asynchronous IO operation
|
||||||
|
type ioResult struct {
|
||||||
|
bytes uint32
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioOperation represents an outstanding asynchronous Win32 IO
|
||||||
|
type ioOperation struct {
|
||||||
|
o syscall.Overlapped
|
||||||
|
ch chan ioResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func initIo() {
|
||||||
|
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
ioCompletionPort = h
|
||||||
|
go ioCompletionProcessor(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
|
||||||
|
// It takes ownership of this handle and will close it if it is garbage collected.
|
||||||
|
type win32File struct {
|
||||||
|
handle syscall.Handle
|
||||||
|
wg sync.WaitGroup
|
||||||
|
wgLock sync.RWMutex
|
||||||
|
closing atomicBool
|
||||||
|
readDeadline deadlineHandler
|
||||||
|
writeDeadline deadlineHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
type deadlineHandler struct {
|
||||||
|
setLock sync.Mutex
|
||||||
|
channel timeoutChan
|
||||||
|
channelLock sync.RWMutex
|
||||||
|
timer *time.Timer
|
||||||
|
timedout atomicBool
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeWin32File makes a new win32File from an existing file handle
|
||||||
|
func makeWin32File(h syscall.Handle) (*win32File, error) {
|
||||||
|
f := &win32File{handle: h}
|
||||||
|
ioInitOnce.Do(initIo)
|
||||||
|
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.readDeadline.channel = make(timeoutChan)
|
||||||
|
f.writeDeadline.channel = make(timeoutChan)
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||||
|
return makeWin32File(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeHandle closes the resources associated with a Win32 handle
|
||||||
|
func (f *win32File) closeHandle() {
|
||||||
|
f.wgLock.Lock()
|
||||||
|
// Atomically set that we are closing, releasing the resources only once.
|
||||||
|
if !f.closing.swap(true) {
|
||||||
|
f.wgLock.Unlock()
|
||||||
|
// cancel all IO and wait for it to complete
|
||||||
|
cancelIoEx(f.handle, nil)
|
||||||
|
f.wg.Wait()
|
||||||
|
// at this point, no new IO can start
|
||||||
|
syscall.Close(f.handle)
|
||||||
|
f.handle = 0
|
||||||
|
} else {
|
||||||
|
f.wgLock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes a win32File.
|
||||||
|
func (f *win32File) Close() error {
|
||||||
|
f.closeHandle()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareIo prepares for a new IO operation.
|
||||||
|
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
|
||||||
|
func (f *win32File) prepareIo() (*ioOperation, error) {
|
||||||
|
f.wgLock.RLock()
|
||||||
|
if f.closing.isSet() {
|
||||||
|
f.wgLock.RUnlock()
|
||||||
|
return nil, ErrFileClosed
|
||||||
|
}
|
||||||
|
f.wg.Add(1)
|
||||||
|
f.wgLock.RUnlock()
|
||||||
|
c := &ioOperation{}
|
||||||
|
c.ch = make(chan ioResult)
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ioCompletionProcessor processes completed async IOs forever
|
||||||
|
func ioCompletionProcessor(h syscall.Handle) {
|
||||||
|
for {
|
||||||
|
var bytes uint32
|
||||||
|
var key uintptr
|
||||||
|
var op *ioOperation
|
||||||
|
err := getQueuedCompletionStatus(h, &bytes, &key, &op, syscall.INFINITE)
|
||||||
|
if op == nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
op.ch <- ioResult{bytes, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// asyncIo processes the return value from ReadFile or WriteFile, blocking until
|
||||||
|
// the operation has actually completed.
|
||||||
|
func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
|
||||||
|
if err != syscall.ERROR_IO_PENDING {
|
||||||
|
return int(bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.closing.isSet() {
|
||||||
|
cancelIoEx(f.handle, &c.o)
|
||||||
|
}
|
||||||
|
|
||||||
|
var timeout timeoutChan
|
||||||
|
if d != nil {
|
||||||
|
d.channelLock.Lock()
|
||||||
|
timeout = d.channel
|
||||||
|
d.channelLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
var r ioResult
|
||||||
|
select {
|
||||||
|
case r = <-c.ch:
|
||||||
|
err = r.err
|
||||||
|
if err == syscall.ERROR_OPERATION_ABORTED {
|
||||||
|
if f.closing.isSet() {
|
||||||
|
err = ErrFileClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case <-timeout:
|
||||||
|
cancelIoEx(f.handle, &c.o)
|
||||||
|
r = <-c.ch
|
||||||
|
err = r.err
|
||||||
|
if err == syscall.ERROR_OPERATION_ABORTED {
|
||||||
|
err = ErrTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runtime.KeepAlive is needed, as c is passed via native
|
||||||
|
// code to ioCompletionProcessor, c must remain alive
|
||||||
|
// until the channel read is complete.
|
||||||
|
runtime.KeepAlive(c)
|
||||||
|
return int(r.bytes), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads from a file handle.
|
||||||
|
func (f *win32File) Read(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.wg.Done()
|
||||||
|
|
||||||
|
if f.readDeadline.timedout.isSet() {
|
||||||
|
return 0, ErrTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIo(c, &f.readDeadline, bytes, err)
|
||||||
|
runtime.KeepAlive(b)
|
||||||
|
|
||||||
|
// Handle EOF conditions.
|
||||||
|
if err == nil && n == 0 && len(b) != 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else if err == syscall.ERROR_BROKEN_PIPE {
|
||||||
|
return 0, io.EOF
|
||||||
|
} else {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes to a file handle.
|
||||||
|
func (f *win32File) Write(b []byte) (int, error) {
|
||||||
|
c, err := f.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer f.wg.Done()
|
||||||
|
|
||||||
|
if f.writeDeadline.timedout.isSet() {
|
||||||
|
return 0, ErrTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
|
||||||
|
n, err := f.asyncIo(c, &f.writeDeadline, bytes, err)
|
||||||
|
runtime.KeepAlive(b)
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetReadDeadline(deadline time.Time) error {
|
||||||
|
return f.readDeadline.set(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) SetWriteDeadline(deadline time.Time) error {
|
||||||
|
return f.writeDeadline.set(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32File) Flush() error {
|
||||||
|
return syscall.FlushFileBuffers(f.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *deadlineHandler) set(deadline time.Time) error {
|
||||||
|
d.setLock.Lock()
|
||||||
|
defer d.setLock.Unlock()
|
||||||
|
|
||||||
|
if d.timer != nil {
|
||||||
|
if !d.timer.Stop() {
|
||||||
|
<-d.channel
|
||||||
|
}
|
||||||
|
d.timer = nil
|
||||||
|
}
|
||||||
|
d.timedout.setFalse()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-d.channel:
|
||||||
|
d.channelLock.Lock()
|
||||||
|
d.channel = make(chan struct{})
|
||||||
|
d.channelLock.Unlock()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
if deadline.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutIO := func() {
|
||||||
|
d.timedout.setTrue()
|
||||||
|
close(d.channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
duration := deadline.Sub(now)
|
||||||
|
if deadline.After(now) {
|
||||||
|
// Deadline is in the future, set a timer to wait
|
||||||
|
d.timer = time.AfterFunc(duration, timeoutIO)
|
||||||
|
} else {
|
||||||
|
// Deadline is in the past. Cancel all pending IO now.
|
||||||
|
timeoutIO()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
60
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
60
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = GetFileInformationByHandleEx
|
||||||
|
//sys setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) = SetFileInformationByHandle
|
||||||
|
|
||||||
|
const (
|
||||||
|
fileBasicInfo = 0
|
||||||
|
fileIDInfo = 0x12
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileBasicInfo contains file access time and file attributes information.
|
||||||
|
type FileBasicInfo struct {
|
||||||
|
CreationTime, LastAccessTime, LastWriteTime, ChangeTime syscall.Filetime
|
||||||
|
FileAttributes uintptr // includes padding
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileBasicInfo retrieves times and attributes for a file.
|
||||||
|
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||||
|
bi := &FileBasicInfo{}
|
||||||
|
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
return bi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFileBasicInfo sets times and attributes for a file.
|
||||||
|
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
||||||
|
if err := setFileInformationByHandle(syscall.Handle(f.Fd()), fileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil {
|
||||||
|
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
|
||||||
|
// unique on a system.
|
||||||
|
type FileIDInfo struct {
|
||||||
|
VolumeSerialNumber uint64
|
||||||
|
FileID [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileID retrieves the unique (volume, file ID) pair for a file.
|
||||||
|
func GetFileID(f *os.File) (*FileIDInfo, error) {
|
||||||
|
fileID := &FileIDInfo{}
|
||||||
|
if err := getFileInformationByHandleEx(syscall.Handle(f.Fd()), fileIDInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil {
|
||||||
|
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||||
|
}
|
||||||
|
runtime.KeepAlive(f)
|
||||||
|
return fileID, nil
|
||||||
|
}
|
424
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
424
vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
|
@ -0,0 +1,424 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
|
||||||
|
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
|
||||||
|
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
|
||||||
|
//sys waitNamedPipe(name string, timeout uint32) (err error) = WaitNamedPipeW
|
||||||
|
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
||||||
|
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||||
|
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
|
||||||
|
|
||||||
|
const (
|
||||||
|
cERROR_PIPE_BUSY = syscall.Errno(231)
|
||||||
|
cERROR_NO_DATA = syscall.Errno(232)
|
||||||
|
cERROR_PIPE_CONNECTED = syscall.Errno(535)
|
||||||
|
cERROR_SEM_TIMEOUT = syscall.Errno(121)
|
||||||
|
|
||||||
|
cPIPE_ACCESS_DUPLEX = 0x3
|
||||||
|
cFILE_FLAG_FIRST_PIPE_INSTANCE = 0x80000
|
||||||
|
cSECURITY_SQOS_PRESENT = 0x100000
|
||||||
|
cSECURITY_ANONYMOUS = 0
|
||||||
|
|
||||||
|
cPIPE_REJECT_REMOTE_CLIENTS = 0x8
|
||||||
|
|
||||||
|
cPIPE_UNLIMITED_INSTANCES = 255
|
||||||
|
|
||||||
|
cNMPWAIT_USE_DEFAULT_WAIT = 0
|
||||||
|
cNMPWAIT_NOWAIT = 1
|
||||||
|
|
||||||
|
cPIPE_TYPE_MESSAGE = 4
|
||||||
|
|
||||||
|
cPIPE_READMODE_MESSAGE = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||||
|
// This error should match net.errClosing since docker takes a dependency on its text.
|
||||||
|
ErrPipeListenerClosed = errors.New("use of closed network connection")
|
||||||
|
|
||||||
|
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
||||||
|
)
|
||||||
|
|
||||||
|
type win32Pipe struct {
|
||||||
|
*win32File
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32MessageBytePipe struct {
|
||||||
|
win32Pipe
|
||||||
|
writeClosed bool
|
||||||
|
readEOF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeAddress string
|
||||||
|
|
||||||
|
func (f *win32Pipe) LocalAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) RemoteAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) SetDeadline(t time.Time) error {
|
||||||
|
f.SetReadDeadline(t)
|
||||||
|
f.SetWriteDeadline(t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite closes the write side of a message pipe in byte mode.
|
||||||
|
func (f *win32MessageBytePipe) CloseWrite() error {
|
||||||
|
if f.writeClosed {
|
||||||
|
return errPipeWriteClosed
|
||||||
|
}
|
||||||
|
err := f.win32File.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.win32File.Write(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writeClosed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
||||||
|
// they are used to implement CloseWrite().
|
||||||
|
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
||||||
|
if f.writeClosed {
|
||||||
|
return 0, errPipeWriteClosed
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return f.win32File.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
|
||||||
|
// mode pipe will return io.EOF, as will all subsequent reads.
|
||||||
|
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
|
||||||
|
if f.readEOF {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n, err := f.win32File.Read(b)
|
||||||
|
if err == io.EOF {
|
||||||
|
// If this was the result of a zero-byte read, then
|
||||||
|
// it is possible that the read was due to a zero-size
|
||||||
|
// message. Since we are simulating CloseWrite with a
|
||||||
|
// zero-byte message, ensure that all future Read() calls
|
||||||
|
// also return EOF.
|
||||||
|
f.readEOF = true
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) Network() string {
|
||||||
|
return "pipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipe connects to a named pipe by path, timing out if the connection
|
||||||
|
// takes longer than the specified duration. If timeout is nil, then the timeout
|
||||||
|
// is the default timeout established by the pipe server.
|
||||||
|
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||||
|
var absTimeout time.Time
|
||||||
|
if timeout != nil {
|
||||||
|
absTimeout = time.Now().Add(*timeout)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var h syscall.Handle
|
||||||
|
for {
|
||||||
|
h, err = createFile(path, syscall.GENERIC_READ|syscall.GENERIC_WRITE, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||||
|
if err != cERROR_PIPE_BUSY {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
var ms uint32
|
||||||
|
if absTimeout.IsZero() {
|
||||||
|
ms = cNMPWAIT_USE_DEFAULT_WAIT
|
||||||
|
} else if now.After(absTimeout) {
|
||||||
|
ms = cNMPWAIT_NOWAIT
|
||||||
|
} else {
|
||||||
|
ms = uint32(absTimeout.Sub(now).Nanoseconds() / 1000 / 1000)
|
||||||
|
}
|
||||||
|
err = waitNamedPipe(path, ms)
|
||||||
|
if err != nil {
|
||||||
|
if err == cERROR_SEM_TIMEOUT {
|
||||||
|
return nil, ErrTimeout
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32
|
||||||
|
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var state uint32
|
||||||
|
err = getNamedPipeHandleState(h, &state, nil, nil, nil, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if state&cPIPE_READMODE_MESSAGE != 0 {
|
||||||
|
return nil, &os.PathError{Op: "open", Path: path, Err: errors.New("message readmode pipes not supported")}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pipe is in message mode, return a message byte pipe, which
|
||||||
|
// supports CloseWrite().
|
||||||
|
if flags&cPIPE_TYPE_MESSAGE != 0 {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: f, path: path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: f, path: path}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type acceptResponse struct {
|
||||||
|
f *win32File
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32PipeListener struct {
|
||||||
|
firstHandle syscall.Handle
|
||||||
|
path string
|
||||||
|
securityDescriptor []byte
|
||||||
|
config PipeConfig
|
||||||
|
acceptCh chan (chan acceptResponse)
|
||||||
|
closeCh chan int
|
||||||
|
doneCh chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeServerPipeHandle(path string, securityDescriptor []byte, c *PipeConfig, first bool) (syscall.Handle, error) {
|
||||||
|
var flags uint32 = cPIPE_ACCESS_DUPLEX | syscall.FILE_FLAG_OVERLAPPED
|
||||||
|
if first {
|
||||||
|
flags |= cFILE_FLAG_FIRST_PIPE_INSTANCE
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode uint32 = cPIPE_REJECT_REMOTE_CLIENTS
|
||||||
|
if c.MessageMode {
|
||||||
|
mode |= cPIPE_TYPE_MESSAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
sa := &syscall.SecurityAttributes{}
|
||||||
|
sa.Length = uint32(unsafe.Sizeof(*sa))
|
||||||
|
if securityDescriptor != nil {
|
||||||
|
len := uint32(len(securityDescriptor))
|
||||||
|
sa.SecurityDescriptor = localAlloc(0, len)
|
||||||
|
defer localFree(sa.SecurityDescriptor)
|
||||||
|
copy((*[0xffff]byte)(unsafe.Pointer(sa.SecurityDescriptor))[:], securityDescriptor)
|
||||||
|
}
|
||||||
|
h, err := createNamedPipe(path, flags, mode, cPIPE_UNLIMITED_INSTANCES, uint32(c.OutputBufferSize), uint32(c.InputBufferSize), 0, sa)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
||||||
|
h, err := makeServerPipeHandle(l.path, l.securityDescriptor, &l.config, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
|
||||||
|
p, err := l.makeServerPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the client to connect.
|
||||||
|
ch := make(chan error)
|
||||||
|
go func(p *win32File) {
|
||||||
|
ch <- connectPipe(p)
|
||||||
|
}(p)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-ch:
|
||||||
|
if err != nil {
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
case <-l.closeCh:
|
||||||
|
// Abort the connect request by closing the handle.
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
err = <-ch
|
||||||
|
if err == nil || err == ErrFileClosed {
|
||||||
|
err = ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) listenerRoutine() {
|
||||||
|
closed := false
|
||||||
|
for !closed {
|
||||||
|
select {
|
||||||
|
case <-l.closeCh:
|
||||||
|
closed = true
|
||||||
|
case responseCh := <-l.acceptCh:
|
||||||
|
var (
|
||||||
|
p *win32File
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
p, err = l.makeConnectedServerPipe()
|
||||||
|
// If the connection was immediately closed by the client, try
|
||||||
|
// again.
|
||||||
|
if err != cERROR_NO_DATA {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseCh <- acceptResponse{p, err}
|
||||||
|
closed = err == ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syscall.Close(l.firstHandle)
|
||||||
|
l.firstHandle = 0
|
||||||
|
// Notify Close() and Accept() callers that the handle has been closed.
|
||||||
|
close(l.doneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeConfig contain configuration for the pipe listener.
|
||||||
|
type PipeConfig struct {
|
||||||
|
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
|
||||||
|
SecurityDescriptor string
|
||||||
|
|
||||||
|
// MessageMode determines whether the pipe is in byte or message mode. In either
|
||||||
|
// case the pipe is read in byte mode by default. The only practical difference in
|
||||||
|
// this implementation is that CloseWrite() is only supported for message mode pipes;
|
||||||
|
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
|
||||||
|
// transferred to the reader (and returned as io.EOF in this implementation)
|
||||||
|
// when the pipe is in message mode.
|
||||||
|
MessageMode bool
|
||||||
|
|
||||||
|
// InputBufferSize specifies the size the input buffer, in bytes.
|
||||||
|
InputBufferSize int32
|
||||||
|
|
||||||
|
// OutputBufferSize specifies the size the input buffer, in bytes.
|
||||||
|
OutputBufferSize int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
|
||||||
|
// The pipe must not already exist.
|
||||||
|
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
||||||
|
var (
|
||||||
|
sd []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if c == nil {
|
||||||
|
c = &PipeConfig{}
|
||||||
|
}
|
||||||
|
if c.SecurityDescriptor != "" {
|
||||||
|
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h, err := makeServerPipeHandle(path, sd, c, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Immediately open and then close a client handle so that the named pipe is
|
||||||
|
// created but not currently accepting connections.
|
||||||
|
h2, err := createFile(path, 0, 0, nil, syscall.OPEN_EXISTING, cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0)
|
||||||
|
if err != nil {
|
||||||
|
syscall.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
syscall.Close(h2)
|
||||||
|
l := &win32PipeListener{
|
||||||
|
firstHandle: h,
|
||||||
|
path: path,
|
||||||
|
securityDescriptor: sd,
|
||||||
|
config: *c,
|
||||||
|
acceptCh: make(chan (chan acceptResponse)),
|
||||||
|
closeCh: make(chan int),
|
||||||
|
doneCh: make(chan int),
|
||||||
|
}
|
||||||
|
go l.listenerRoutine()
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectPipe(p *win32File) error {
|
||||||
|
c, err := p.prepareIo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer p.wg.Done()
|
||||||
|
|
||||||
|
err = connectNamedPipe(p.handle, &c.o)
|
||||||
|
_, err = p.asyncIo(c, nil, 0, err)
|
||||||
|
if err != nil && err != cERROR_PIPE_CONNECTED {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Accept() (net.Conn, error) {
|
||||||
|
ch := make(chan acceptResponse)
|
||||||
|
select {
|
||||||
|
case l.acceptCh <- ch:
|
||||||
|
response := <-ch
|
||||||
|
err := response.err
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if l.config.MessageMode {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
||||||
|
case <-l.doneCh:
|
||||||
|
return nil, ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Close() error {
|
||||||
|
select {
|
||||||
|
case l.closeCh <- 1:
|
||||||
|
<-l.doneCh
|
||||||
|
case <-l.doneCh:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Addr() net.Addr {
|
||||||
|
return pipeAddress(l.path)
|
||||||
|
}
|
453
vendor/github.com/Microsoft/go-winio/pipe_test.go
generated
vendored
Normal file
453
vendor/github.com/Microsoft/go-winio/pipe_test.go
generated
vendored
Normal file
|
@ -0,0 +1,453 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testPipeName = `\\.\pipe\winiotestpipe`
|
||||||
|
|
||||||
|
var aLongTimeAgo = time.Unix(1, 0)
|
||||||
|
|
||||||
|
func TestDialUnknownFailsImmediately(t *testing.T) {
|
||||||
|
_, err := DialPipe(testPipeName, nil)
|
||||||
|
if err.(*os.PathError).Err != syscall.ENOENT {
|
||||||
|
t.Fatalf("expected ENOENT got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialListenerTimesOut(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
var d = time.Duration(10 * time.Millisecond)
|
||||||
|
_, err = DialPipe(testPipeName, &d)
|
||||||
|
if err != ErrTimeout {
|
||||||
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialAccessDeniedWithRestrictedSD(t *testing.T) {
|
||||||
|
c := PipeConfig{
|
||||||
|
SecurityDescriptor: "D:P(A;;0x1200FF;;;WD)",
|
||||||
|
}
|
||||||
|
l, err := ListenPipe(testPipeName, &c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
_, err = DialPipe(testPipeName, nil)
|
||||||
|
if err.(*os.PathError).Err != syscall.ERROR_ACCESS_DENIED {
|
||||||
|
t.Fatalf("expected ERROR_ACCESS_DENIED, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConnection(cfg *PipeConfig) (client net.Conn, server net.Conn, err error) {
|
||||||
|
l, err := ListenPipe(testPipeName, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
c net.Conn
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
ch := make(chan response)
|
||||||
|
go func() {
|
||||||
|
c, err := l.Accept()
|
||||||
|
ch <- response{c, err}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c, err := DialPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r := <-ch
|
||||||
|
if err = r.err; err != nil {
|
||||||
|
c.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client = c
|
||||||
|
server = r.c
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadTimeout(t *testing.T) {
|
||||||
|
c, s, err := getConnection(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
c.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
|
||||||
|
|
||||||
|
buf := make([]byte, 10)
|
||||||
|
_, err = c.Read(buf)
|
||||||
|
if err != ErrTimeout {
|
||||||
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func server(l net.Listener, ch chan int) {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c))
|
||||||
|
s, err := rw.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
_, err = rw.WriteString("got " + s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = rw.Flush()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
c.Close()
|
||||||
|
ch <- 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFullListenDialReadWrite(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
ch := make(chan int)
|
||||||
|
go server(l, ch)
|
||||||
|
|
||||||
|
c, err := DialPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
rw := bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c))
|
||||||
|
_, err = rw.WriteString("hello world\n")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = rw.Flush()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := rw.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ms := "got hello world\n"
|
||||||
|
if s != ms {
|
||||||
|
t.Errorf("expected '%s', got '%s'", ms, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseAbortsListen(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan error)
|
||||||
|
go func() {
|
||||||
|
_, err := l.Accept()
|
||||||
|
ch <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(30 * time.Millisecond)
|
||||||
|
l.Close()
|
||||||
|
|
||||||
|
err = <-ch
|
||||||
|
if err != ErrPipeListenerClosed {
|
||||||
|
t.Fatalf("expected ErrPipeListenerClosed, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureEOFOnClose(t *testing.T, r io.Reader, w io.Closer) {
|
||||||
|
b := make([]byte, 10)
|
||||||
|
w.Close()
|
||||||
|
n, err := r.Read(b)
|
||||||
|
if n > 0 {
|
||||||
|
t.Errorf("unexpected byte count %d", n)
|
||||||
|
}
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Errorf("expected EOF: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseClientEOFServer(t *testing.T) {
|
||||||
|
c, s, err := getConnection(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
defer s.Close()
|
||||||
|
ensureEOFOnClose(t, c, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseServerEOFClient(t *testing.T) {
|
||||||
|
c, s, err := getConnection(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
defer s.Close()
|
||||||
|
ensureEOFOnClose(t, s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCloseWriteEOF(t *testing.T) {
|
||||||
|
cfg := &PipeConfig{
|
||||||
|
MessageMode: true,
|
||||||
|
}
|
||||||
|
c, s, err := getConnection(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
type closeWriter interface {
|
||||||
|
CloseWrite() error
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.(closeWriter).CloseWrite()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 10)
|
||||||
|
_, err = s.Read(b)
|
||||||
|
if err != io.EOF {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAcceptAfterCloseFails(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
l.Close()
|
||||||
|
_, err = l.Accept()
|
||||||
|
if err != ErrPipeListenerClosed {
|
||||||
|
t.Fatalf("expected ErrPipeListenerClosed, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDialTimesOutByDefault(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
_, err = DialPipe(testPipeName, nil)
|
||||||
|
if err != ErrTimeout {
|
||||||
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeoutPendingRead(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
s.Close()
|
||||||
|
close(serverDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
client, err := DialPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
clientErr := make(chan error)
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 10)
|
||||||
|
_, err = client.Read(buf)
|
||||||
|
clientErr <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond) // make *sure* the pipe is reading before we set the deadline
|
||||||
|
client.SetReadDeadline(aLongTimeAgo)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-clientErr:
|
||||||
|
if err != ErrTimeout {
|
||||||
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
t.Fatalf("timed out while waiting for read to cancel")
|
||||||
|
<-clientErr
|
||||||
|
}
|
||||||
|
<-serverDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimeoutPendingWrite(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
serverDone := make(chan struct{})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
s, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
s.Close()
|
||||||
|
close(serverDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
client, err := DialPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
clientErr := make(chan error)
|
||||||
|
go func() {
|
||||||
|
_, err = client.Write([]byte("this should timeout"))
|
||||||
|
clientErr <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(100 * time.Millisecond) // make *sure* the pipe is writing before we set the deadline
|
||||||
|
client.SetWriteDeadline(aLongTimeAgo)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-clientErr:
|
||||||
|
if err != ErrTimeout {
|
||||||
|
t.Fatalf("expected ErrTimeout, got %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
t.Fatalf("timed out while waiting for write to cancel")
|
||||||
|
<-clientErr
|
||||||
|
}
|
||||||
|
<-serverDone
|
||||||
|
}
|
||||||
|
|
||||||
|
type CloseWriter interface {
|
||||||
|
CloseWrite() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEchoWithMessaging(t *testing.T) {
|
||||||
|
c := PipeConfig{
|
||||||
|
MessageMode: true, // Use message mode so that CloseWrite() is supported
|
||||||
|
InputBufferSize: 65536, // Use 64KB buffers to improve performance
|
||||||
|
OutputBufferSize: 65536,
|
||||||
|
}
|
||||||
|
l, err := ListenPipe(testPipeName, &c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
listenerDone := make(chan bool)
|
||||||
|
clientDone := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
// server echo
|
||||||
|
conn, e := l.Accept()
|
||||||
|
if e != nil {
|
||||||
|
t.Fatal(e)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Millisecond) // make *sure* we don't begin to read before eof signal is sent
|
||||||
|
io.Copy(conn, conn)
|
||||||
|
conn.(CloseWriter).CloseWrite()
|
||||||
|
close(listenerDone)
|
||||||
|
}()
|
||||||
|
timeout := 1 * time.Second
|
||||||
|
client, err := DialPipe(testPipeName, &timeout)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// client read back
|
||||||
|
bytes := make([]byte, 2)
|
||||||
|
n, e := client.Read(bytes)
|
||||||
|
if e != nil {
|
||||||
|
t.Fatal(e)
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
t.Fatalf("expected 2 bytes, got %v", n)
|
||||||
|
}
|
||||||
|
close(clientDone)
|
||||||
|
}()
|
||||||
|
|
||||||
|
payload := make([]byte, 2)
|
||||||
|
payload[0] = 0
|
||||||
|
payload[1] = 1
|
||||||
|
|
||||||
|
n, err := client.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != 2 {
|
||||||
|
t.Fatalf("expected 2 bytes, got %v", n)
|
||||||
|
}
|
||||||
|
client.(CloseWriter).CloseWrite()
|
||||||
|
<-listenerDone
|
||||||
|
<-clientDone
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConnectRace(t *testing.T) {
|
||||||
|
l, err := ListenPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
s, err := l.Accept()
|
||||||
|
if err == ErrPipeListenerClosed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for i := 0; i < 1000; i++ {
|
||||||
|
c, err := DialPipe(testPipeName, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
c.Close()
|
||||||
|
}
|
||||||
|
}
|
202
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
202
vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unicode/utf16"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
||||||
|
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
|
||||||
|
//sys revertToSelf() (err error) = advapi32.RevertToSelf
|
||||||
|
//sys openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
|
||||||
|
//sys getCurrentThread() (h syscall.Handle) = GetCurrentThread
|
||||||
|
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
|
||||||
|
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
|
||||||
|
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
|
||||||
|
|
||||||
|
const (
|
||||||
|
SE_PRIVILEGE_ENABLED = 2
|
||||||
|
|
||||||
|
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300
|
||||||
|
|
||||||
|
SeBackupPrivilege = "SeBackupPrivilege"
|
||||||
|
SeRestorePrivilege = "SeRestorePrivilege"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
securityAnonymous = iota
|
||||||
|
securityIdentification
|
||||||
|
securityImpersonation
|
||||||
|
securityDelegation
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
privNames = make(map[string]uint64)
|
||||||
|
privNameMutex sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivilegeError represents an error enabling privileges.
|
||||||
|
type PrivilegeError struct {
|
||||||
|
privileges []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PrivilegeError) Error() string {
|
||||||
|
s := ""
|
||||||
|
if len(e.privileges) > 1 {
|
||||||
|
s = "Could not enable privileges "
|
||||||
|
} else {
|
||||||
|
s = "Could not enable privilege "
|
||||||
|
}
|
||||||
|
for i, p := range e.privileges {
|
||||||
|
if i != 0 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
s += `"`
|
||||||
|
s += getPrivilegeName(p)
|
||||||
|
s += `"`
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivilege enables a single privilege for a function call.
|
||||||
|
func RunWithPrivilege(name string, fn func() error) error {
|
||||||
|
return RunWithPrivileges([]string{name}, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivileges enables privileges for a function call.
|
||||||
|
func RunWithPrivileges(names []string, fn func() error) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
token, err := newThreadToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer releaseThreadToken(token)
|
||||||
|
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapPrivileges(names []string) ([]uint64, error) {
|
||||||
|
var privileges []uint64
|
||||||
|
privNameMutex.Lock()
|
||||||
|
defer privNameMutex.Unlock()
|
||||||
|
for _, name := range names {
|
||||||
|
p, ok := privNames[name]
|
||||||
|
if !ok {
|
||||||
|
err := lookupPrivilegeValue("", name, &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privNames[name] = p
|
||||||
|
}
|
||||||
|
privileges = append(privileges, p)
|
||||||
|
}
|
||||||
|
return privileges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableProcessPrivileges enables privileges globally for the process.
|
||||||
|
func EnableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableProcessPrivileges disables privileges globally for the process.
|
||||||
|
func DisableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableDisableProcessPrivilege(names []string, action uint32) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, _ := windows.GetCurrentProcess()
|
||||||
|
var token windows.Token
|
||||||
|
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer token.Close()
|
||||||
|
return adjustPrivileges(token, privileges, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
|
||||||
|
var b bytes.Buffer
|
||||||
|
binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
|
||||||
|
for _, p := range privileges {
|
||||||
|
binary.Write(&b, binary.LittleEndian, p)
|
||||||
|
binary.Write(&b, binary.LittleEndian, action)
|
||||||
|
}
|
||||||
|
prevState := make([]byte, b.Len())
|
||||||
|
reqSize := uint32(0)
|
||||||
|
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
|
||||||
|
if !success {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == ERROR_NOT_ALL_ASSIGNED {
|
||||||
|
return &PrivilegeError{privileges}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrivilegeName(luid uint64) string {
|
||||||
|
var nameBuffer [256]uint16
|
||||||
|
bufSize := uint32(len(nameBuffer))
|
||||||
|
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %d>", luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayNameBuffer [256]uint16
|
||||||
|
displayBufSize := uint32(len(displayNameBuffer))
|
||||||
|
var langID uint32
|
||||||
|
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newThreadToken() (windows.Token, error) {
|
||||||
|
err := impersonateSelf(securityImpersonation)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var token windows.Token
|
||||||
|
err = openThreadToken(getCurrentThread(), syscall.TOKEN_ADJUST_PRIVILEGES|syscall.TOKEN_QUERY, false, &token)
|
||||||
|
if err != nil {
|
||||||
|
rerr := revertToSelf()
|
||||||
|
if rerr != nil {
|
||||||
|
panic(rerr)
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseThreadToken(h windows.Token) {
|
||||||
|
err := revertToSelf()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
h.Close()
|
||||||
|
}
|
17
vendor/github.com/Microsoft/go-winio/privileges_test.go
generated
vendored
Normal file
17
vendor/github.com/Microsoft/go-winio/privileges_test.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestRunWithUnavailablePrivilege(t *testing.T) {
|
||||||
|
err := RunWithPrivilege("SeCreateTokenPrivilege", func() error { return nil })
|
||||||
|
if _, ok := err.(*PrivilegeError); err == nil || !ok {
|
||||||
|
t.Fatal("expected PrivilegeError")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunWithPrivileges(t *testing.T) {
|
||||||
|
err := RunWithPrivilege("SeShutdownPrivilege", func() error { return nil })
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
128
vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
128
vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
reparseTagMountPoint = 0xA0000003
|
||||||
|
reparseTagSymlink = 0xA000000C
|
||||||
|
)
|
||||||
|
|
||||||
|
type reparseDataBuffer struct {
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseDataLength uint16
|
||||||
|
Reserved uint16
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReparsePoint describes a Win32 symlink or mount point.
|
||||||
|
type ReparsePoint struct {
|
||||||
|
Target string
|
||||||
|
IsMountPoint bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
|
||||||
|
// mount point reparse point.
|
||||||
|
type UnsupportedReparsePointError struct {
|
||||||
|
Tag uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnsupportedReparsePointError) Error() string {
|
||||||
|
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
|
||||||
|
// or a mount point.
|
||||||
|
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
|
||||||
|
tag := binary.LittleEndian.Uint32(b[0:4])
|
||||||
|
return DecodeReparsePointData(tag, b[8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
||||||
|
isMountPoint := false
|
||||||
|
switch tag {
|
||||||
|
case reparseTagMountPoint:
|
||||||
|
isMountPoint = true
|
||||||
|
case reparseTagSymlink:
|
||||||
|
default:
|
||||||
|
return nil, &UnsupportedReparsePointError{tag}
|
||||||
|
}
|
||||||
|
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
|
||||||
|
if !isMountPoint {
|
||||||
|
nameOffset += 4
|
||||||
|
}
|
||||||
|
nameLength := binary.LittleEndian.Uint16(b[6:8])
|
||||||
|
name := make([]uint16, nameLength/2)
|
||||||
|
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDriveLetter(c byte) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
|
||||||
|
// mount point.
|
||||||
|
func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
||||||
|
// Generate an NT path and determine if this is a relative path.
|
||||||
|
var ntTarget string
|
||||||
|
relative := false
|
||||||
|
if strings.HasPrefix(rp.Target, `\\?\`) {
|
||||||
|
ntTarget = `\??\` + rp.Target[4:]
|
||||||
|
} else if strings.HasPrefix(rp.Target, `\\`) {
|
||||||
|
ntTarget = `\??\UNC\` + rp.Target[2:]
|
||||||
|
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
|
||||||
|
ntTarget = `\??\` + rp.Target
|
||||||
|
} else {
|
||||||
|
ntTarget = rp.Target
|
||||||
|
relative = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The paths must be NUL-terminated even though they are counted strings.
|
||||||
|
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
|
||||||
|
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
|
||||||
|
|
||||||
|
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
|
||||||
|
size += len(ntTarget16)*2 + len(target16)*2
|
||||||
|
|
||||||
|
tag := uint32(reparseTagMountPoint)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
tag = reparseTagSymlink
|
||||||
|
size += 4 // Add room for symlink flags
|
||||||
|
}
|
||||||
|
|
||||||
|
data := reparseDataBuffer{
|
||||||
|
ReparseTag: tag,
|
||||||
|
ReparseDataLength: uint16(size),
|
||||||
|
SubstituteNameOffset: 0,
|
||||||
|
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
|
||||||
|
PrintNameOffset: uint16(len(ntTarget16) * 2),
|
||||||
|
PrintNameLength: uint16((len(target16) - 1) * 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
binary.Write(&b, binary.LittleEndian, &data)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
flags := uint32(0)
|
||||||
|
if relative {
|
||||||
|
flags |= 1
|
||||||
|
}
|
||||||
|
binary.Write(&b, binary.LittleEndian, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
binary.Write(&b, binary.LittleEndian, ntTarget16)
|
||||||
|
binary.Write(&b, binary.LittleEndian, target16)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
98
vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
98
vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
|
||||||
|
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
|
||||||
|
//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
|
||||||
|
//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW
|
||||||
|
//sys localFree(mem uintptr) = LocalFree
|
||||||
|
//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength
|
||||||
|
|
||||||
|
const (
|
||||||
|
cERROR_NONE_MAPPED = syscall.Errno(1332)
|
||||||
|
)
|
||||||
|
|
||||||
|
type AccountLookupError struct {
|
||||||
|
Name string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AccountLookupError) Error() string {
|
||||||
|
if e.Name == "" {
|
||||||
|
return "lookup account: empty account name specified"
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
switch e.Err {
|
||||||
|
case cERROR_NONE_MAPPED:
|
||||||
|
s = "not found"
|
||||||
|
default:
|
||||||
|
s = e.Err.Error()
|
||||||
|
}
|
||||||
|
return "lookup account " + e.Name + ": " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
type SddlConversionError struct {
|
||||||
|
Sddl string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SddlConversionError) Error() string {
|
||||||
|
return "convert " + e.Sddl + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupSidByName looks up the SID of an account by name
|
||||||
|
func LookupSidByName(name string) (sid string, err error) {
|
||||||
|
if name == "" {
|
||||||
|
return "", &AccountLookupError{name, cERROR_NONE_MAPPED}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidSize, sidNameUse, refDomainSize uint32
|
||||||
|
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sidBuffer := make([]byte, sidSize)
|
||||||
|
refDomainBuffer := make([]uint16, refDomainSize)
|
||||||
|
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
var strBuffer *uint16
|
||||||
|
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sid = syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
|
||||||
|
localFree(uintptr(unsafe.Pointer(strBuffer)))
|
||||||
|
return sid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
|
||||||
|
var sdBuffer uintptr
|
||||||
|
err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &SddlConversionError{sddl, err}
|
||||||
|
}
|
||||||
|
defer localFree(sdBuffer)
|
||||||
|
sd := make([]byte, getSecurityDescriptorLength(sdBuffer))
|
||||||
|
copy(sd, (*[0xffff]byte)(unsafe.Pointer(sdBuffer))[:len(sd)])
|
||||||
|
return sd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SecurityDescriptorToSddl(sd []byte) (string, error) {
|
||||||
|
var sddl *uint16
|
||||||
|
// The returned string length seems to including an aribtrary number of terminating NULs.
|
||||||
|
// Don't use it.
|
||||||
|
err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer localFree(uintptr(unsafe.Pointer(sddl)))
|
||||||
|
return syscall.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(sddl))[:]), nil
|
||||||
|
}
|
26
vendor/github.com/Microsoft/go-winio/sd_test.go
generated
vendored
Normal file
26
vendor/github.com/Microsoft/go-winio/sd_test.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestLookupInvalidSid(t *testing.T) {
|
||||||
|
_, err := LookupSidByName(".\\weoifjdsklfj")
|
||||||
|
aerr, ok := err.(*AccountLookupError)
|
||||||
|
if !ok || aerr.Err != cERROR_NONE_MAPPED {
|
||||||
|
t.Fatalf("expected AccountLookupError with ERROR_NONE_MAPPED, got %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupValidSid(t *testing.T) {
|
||||||
|
sid, err := LookupSidByName("Everyone")
|
||||||
|
if err != nil || sid != "S-1-1-0" {
|
||||||
|
t.Fatal("expected S-1-1-0, got %s, %s", sid, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLookupEmptyNameFails(t *testing.T) {
|
||||||
|
_, err := LookupSidByName(".\\weoifjdsklfj")
|
||||||
|
aerr, ok := err.(*AccountLookupError)
|
||||||
|
if !ok || aerr.Err != cERROR_NONE_MAPPED {
|
||||||
|
t.Fatalf("expected AccountLookupError with ERROR_NONE_MAPPED, got %s", err)
|
||||||
|
}
|
||||||
|
}
|
3
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
3
vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package winio
|
||||||
|
|
||||||
|
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go
|
901
vendor/github.com/Microsoft/go-winio/vhd/mksyscall_windows.go
generated
vendored
Normal file
901
vendor/github.com/Microsoft/go-winio/vhd/mksyscall_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,901 @@
|
||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Hard-coding unicode mode for VHD library.
|
||||||
|
|
||||||
|
// +build ignore
|
||||||
|
|
||||||
|
/*
|
||||||
|
mksyscall_windows generates windows system call bodies
|
||||||
|
|
||||||
|
It parses all files specified on command line containing function
|
||||||
|
prototypes (like syscall_windows.go) and prints system call bodies
|
||||||
|
to standard output.
|
||||||
|
|
||||||
|
The prototypes are marked by lines beginning with "//sys" and read
|
||||||
|
like func declarations if //sys is replaced by func, but:
|
||||||
|
|
||||||
|
* The parameter lists must give a name for each argument. This
|
||||||
|
includes return parameters.
|
||||||
|
|
||||||
|
* The parameter lists must give a type for each argument:
|
||||||
|
the (x, y, z int) shorthand is not allowed.
|
||||||
|
|
||||||
|
* If the return parameter is an error number, it must be named err.
|
||||||
|
|
||||||
|
* If go func name needs to be different from it's winapi dll name,
|
||||||
|
the winapi name could be specified at the end, after "=" sign, like
|
||||||
|
//sys LoadLibrary(libname string) (handle uint32, err error) = LoadLibraryA
|
||||||
|
|
||||||
|
* Each function that returns err needs to supply a condition, that
|
||||||
|
return value of winapi will be tested against to detect failure.
|
||||||
|
This would set err to windows "last-error", otherwise it will be nil.
|
||||||
|
The value can be provided at end of //sys declaration, like
|
||||||
|
//sys LoadLibrary(libname string) (handle uint32, err error) [failretval==-1] = LoadLibraryA
|
||||||
|
and is [failretval==0] by default.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
mksyscall_windows [flags] [path ...]
|
||||||
|
|
||||||
|
The flags are:
|
||||||
|
-output
|
||||||
|
Specify output file name (outputs to console if blank).
|
||||||
|
-trace
|
||||||
|
Generate print statement after every syscall.
|
||||||
|
*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"go/parser"
|
||||||
|
"go/token"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
filename = flag.String("output", "", "output file name (standard output if omitted)")
|
||||||
|
printTraceFlag = flag.Bool("trace", false, "generate print statement after every syscall")
|
||||||
|
systemDLL = flag.Bool("systemdll", true, "whether all DLLs should be loaded from the Windows system directory")
|
||||||
|
)
|
||||||
|
|
||||||
|
func trim(s string) string {
|
||||||
|
return strings.Trim(s, " \t")
|
||||||
|
}
|
||||||
|
|
||||||
|
var packageName string
|
||||||
|
|
||||||
|
func packagename() string {
|
||||||
|
return packageName
|
||||||
|
}
|
||||||
|
|
||||||
|
func syscalldot() string {
|
||||||
|
if packageName == "syscall" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "syscall."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Param is function parameter
|
||||||
|
type Param struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
fn *Fn
|
||||||
|
tmpVarIdx int
|
||||||
|
}
|
||||||
|
|
||||||
|
// tmpVar returns temp variable name that will be used to represent p during syscall.
|
||||||
|
func (p *Param) tmpVar() string {
|
||||||
|
if p.tmpVarIdx < 0 {
|
||||||
|
p.tmpVarIdx = p.fn.curTmpVarIdx
|
||||||
|
p.fn.curTmpVarIdx++
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("_p%d", p.tmpVarIdx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolTmpVarCode returns source code for bool temp variable.
|
||||||
|
func (p *Param) BoolTmpVarCode() string {
|
||||||
|
const code = `var %s uint32
|
||||||
|
if %s {
|
||||||
|
%s = 1
|
||||||
|
} else {
|
||||||
|
%s = 0
|
||||||
|
}`
|
||||||
|
tmp := p.tmpVar()
|
||||||
|
return fmt.Sprintf(code, tmp, p.Name, tmp, tmp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceTmpVarCode returns source code for slice temp variable.
|
||||||
|
func (p *Param) SliceTmpVarCode() string {
|
||||||
|
const code = `var %s *%s
|
||||||
|
if len(%s) > 0 {
|
||||||
|
%s = &%s[0]
|
||||||
|
}`
|
||||||
|
tmp := p.tmpVar()
|
||||||
|
return fmt.Sprintf(code, tmp, p.Type[2:], p.Name, tmp, p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringTmpVarCode returns source code for string temp variable.
|
||||||
|
func (p *Param) StringTmpVarCode() string {
|
||||||
|
errvar := p.fn.Rets.ErrorVarName()
|
||||||
|
if errvar == "" {
|
||||||
|
errvar = "_"
|
||||||
|
}
|
||||||
|
tmp := p.tmpVar()
|
||||||
|
const code = `var %s %s
|
||||||
|
%s, %s = %s(%s)`
|
||||||
|
s := fmt.Sprintf(code, tmp, p.fn.StrconvType(), tmp, errvar, p.fn.StrconvFunc(), p.Name)
|
||||||
|
if errvar == "-" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
const morecode = `
|
||||||
|
if %s != nil {
|
||||||
|
return
|
||||||
|
}`
|
||||||
|
return s + fmt.Sprintf(morecode, errvar)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TmpVarCode returns source code for temp variable.
|
||||||
|
func (p *Param) TmpVarCode() string {
|
||||||
|
switch {
|
||||||
|
case p.Type == "bool":
|
||||||
|
return p.BoolTmpVarCode()
|
||||||
|
case strings.HasPrefix(p.Type, "[]"):
|
||||||
|
return p.SliceTmpVarCode()
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TmpVarHelperCode returns source code for helper's temp variable.
|
||||||
|
func (p *Param) TmpVarHelperCode() string {
|
||||||
|
if p.Type != "string" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return p.StringTmpVarCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyscallArgList returns source code fragments representing p parameter
|
||||||
|
// in syscall. Slices are translated into 2 syscall parameters: pointer to
|
||||||
|
// the first element and length.
|
||||||
|
func (p *Param) SyscallArgList() []string {
|
||||||
|
t := p.HelperType()
|
||||||
|
var s string
|
||||||
|
switch {
|
||||||
|
case t[0] == '*':
|
||||||
|
s = fmt.Sprintf("unsafe.Pointer(%s)", p.Name)
|
||||||
|
case t == "bool":
|
||||||
|
s = p.tmpVar()
|
||||||
|
case strings.HasPrefix(t, "[]"):
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("uintptr(unsafe.Pointer(%s))", p.tmpVar()),
|
||||||
|
fmt.Sprintf("uintptr(len(%s))", p.Name),
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
s = p.Name
|
||||||
|
}
|
||||||
|
return []string{fmt.Sprintf("uintptr(%s)", s)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsError determines if p parameter is used to return error.
|
||||||
|
func (p *Param) IsError() bool {
|
||||||
|
return p.Name == "err" && p.Type == "error"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelperType returns type of parameter p used in helper function.
|
||||||
|
func (p *Param) HelperType() string {
|
||||||
|
if p.Type == "string" {
|
||||||
|
return p.fn.StrconvType()
|
||||||
|
}
|
||||||
|
return p.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// join concatenates parameters ps into a string with sep separator.
|
||||||
|
// Each parameter is converted into string by applying fn to it
|
||||||
|
// before conversion.
|
||||||
|
func join(ps []*Param, fn func(*Param) string, sep string) string {
|
||||||
|
if len(ps) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
a := make([]string, 0)
|
||||||
|
for _, p := range ps {
|
||||||
|
a = append(a, fn(p))
|
||||||
|
}
|
||||||
|
return strings.Join(a, sep)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rets describes function return parameters.
|
||||||
|
type Rets struct {
|
||||||
|
Name string
|
||||||
|
Type string
|
||||||
|
ReturnsError bool
|
||||||
|
FailCond string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorVarName returns error variable name for r.
|
||||||
|
func (r *Rets) ErrorVarName() string {
|
||||||
|
if r.ReturnsError {
|
||||||
|
return "err"
|
||||||
|
}
|
||||||
|
if r.Type == "error" {
|
||||||
|
return r.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToParams converts r into slice of *Param.
|
||||||
|
func (r *Rets) ToParams() []*Param {
|
||||||
|
ps := make([]*Param, 0)
|
||||||
|
if len(r.Name) > 0 {
|
||||||
|
ps = append(ps, &Param{Name: r.Name, Type: r.Type})
|
||||||
|
}
|
||||||
|
if r.ReturnsError {
|
||||||
|
ps = append(ps, &Param{Name: "err", Type: "error"})
|
||||||
|
}
|
||||||
|
return ps
|
||||||
|
}
|
||||||
|
|
||||||
|
// List returns source code of syscall return parameters.
|
||||||
|
func (r *Rets) List() string {
|
||||||
|
s := join(r.ToParams(), func(p *Param) string { return p.Name + " " + p.Type }, ", ")
|
||||||
|
if len(s) > 0 {
|
||||||
|
s = "(" + s + ")"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintList returns source code of trace printing part correspondent
|
||||||
|
// to syscall return values.
|
||||||
|
func (r *Rets) PrintList() string {
|
||||||
|
return join(r.ToParams(), func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReturnValuesCode returns source code that accepts syscall return values.
|
||||||
|
func (r *Rets) SetReturnValuesCode() string {
|
||||||
|
if r.Name == "" && !r.ReturnsError {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
retvar := "r0"
|
||||||
|
if r.Name == "" {
|
||||||
|
retvar = "r1"
|
||||||
|
}
|
||||||
|
errvar := "_"
|
||||||
|
if r.ReturnsError {
|
||||||
|
errvar = "e1"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s, _, %s := ", retvar, errvar)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rets) useLongHandleErrorCode(retvar string) string {
|
||||||
|
const code = `if %s {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = %sEINVAL
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
cond := retvar + " == 0"
|
||||||
|
if r.FailCond != "" {
|
||||||
|
cond = strings.Replace(r.FailCond, "failretval", retvar, 1)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(code, cond, syscalldot())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetErrorCode returns source code that sets return parameters.
|
||||||
|
func (r *Rets) SetErrorCode() string {
|
||||||
|
const code = `if r0 != 0 {
|
||||||
|
%s = %sErrno(r0)
|
||||||
|
}`
|
||||||
|
if r.Name == "" && !r.ReturnsError {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if r.Name == "" {
|
||||||
|
return r.useLongHandleErrorCode("r1")
|
||||||
|
}
|
||||||
|
if r.Type == "error" {
|
||||||
|
return fmt.Sprintf(code, r.Name, syscalldot())
|
||||||
|
}
|
||||||
|
s := ""
|
||||||
|
switch {
|
||||||
|
case r.Type[0] == '*':
|
||||||
|
s = fmt.Sprintf("%s = (%s)(unsafe.Pointer(r0))", r.Name, r.Type)
|
||||||
|
case r.Type == "bool":
|
||||||
|
s = fmt.Sprintf("%s = r0 != 0", r.Name)
|
||||||
|
default:
|
||||||
|
s = fmt.Sprintf("%s = %s(r0)", r.Name, r.Type)
|
||||||
|
}
|
||||||
|
if !r.ReturnsError {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s + "\n\t" + r.useLongHandleErrorCode(r.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fn describes syscall function.
|
||||||
|
type Fn struct {
|
||||||
|
Name string
|
||||||
|
Params []*Param
|
||||||
|
Rets *Rets
|
||||||
|
PrintTrace bool
|
||||||
|
dllname string
|
||||||
|
dllfuncname string
|
||||||
|
src string
|
||||||
|
// TODO: get rid of this field and just use parameter index instead
|
||||||
|
curTmpVarIdx int // insure tmp variables have uniq names
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractParams parses s to extract function parameters.
|
||||||
|
func extractParams(s string, f *Fn) ([]*Param, error) {
|
||||||
|
s = trim(s)
|
||||||
|
if s == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
a := strings.Split(s, ",")
|
||||||
|
ps := make([]*Param, len(a))
|
||||||
|
for i := range ps {
|
||||||
|
s2 := trim(a[i])
|
||||||
|
b := strings.Split(s2, " ")
|
||||||
|
if len(b) != 2 {
|
||||||
|
b = strings.Split(s2, "\t")
|
||||||
|
if len(b) != 2 {
|
||||||
|
return nil, errors.New("Could not extract function parameter from \"" + s2 + "\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ps[i] = &Param{
|
||||||
|
Name: trim(b[0]),
|
||||||
|
Type: trim(b[1]),
|
||||||
|
fn: f,
|
||||||
|
tmpVarIdx: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractSection extracts text out of string s starting after start
|
||||||
|
// and ending just before end. found return value will indicate success,
|
||||||
|
// and prefix, body and suffix will contain correspondent parts of string s.
|
||||||
|
func extractSection(s string, start, end rune) (prefix, body, suffix string, found bool) {
|
||||||
|
s = trim(s)
|
||||||
|
if strings.HasPrefix(s, string(start)) {
|
||||||
|
// no prefix
|
||||||
|
body = s[1:]
|
||||||
|
} else {
|
||||||
|
a := strings.SplitN(s, string(start), 2)
|
||||||
|
if len(a) != 2 {
|
||||||
|
return "", "", s, false
|
||||||
|
}
|
||||||
|
prefix = a[0]
|
||||||
|
body = a[1]
|
||||||
|
}
|
||||||
|
a := strings.SplitN(body, string(end), 2)
|
||||||
|
if len(a) != 2 {
|
||||||
|
return "", "", "", false
|
||||||
|
}
|
||||||
|
return prefix, a[0], a[1], true
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFn parses string s and return created function Fn.
|
||||||
|
func newFn(s string) (*Fn, error) {
|
||||||
|
s = trim(s)
|
||||||
|
f := &Fn{
|
||||||
|
Rets: &Rets{},
|
||||||
|
src: s,
|
||||||
|
PrintTrace: *printTraceFlag,
|
||||||
|
}
|
||||||
|
// function name and args
|
||||||
|
prefix, body, s, found := extractSection(s, '(', ')')
|
||||||
|
if !found || prefix == "" {
|
||||||
|
return nil, errors.New("Could not extract function name and parameters from \"" + f.src + "\"")
|
||||||
|
}
|
||||||
|
f.Name = prefix
|
||||||
|
var err error
|
||||||
|
f.Params, err = extractParams(body, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// return values
|
||||||
|
_, body, s, found = extractSection(s, '(', ')')
|
||||||
|
if found {
|
||||||
|
r, err := extractParams(body, f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch len(r) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
if r[0].IsError() {
|
||||||
|
f.Rets.ReturnsError = true
|
||||||
|
} else {
|
||||||
|
f.Rets.Name = r[0].Name
|
||||||
|
f.Rets.Type = r[0].Type
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
if !r[1].IsError() {
|
||||||
|
return nil, errors.New("Only last windows error is allowed as second return value in \"" + f.src + "\"")
|
||||||
|
}
|
||||||
|
f.Rets.ReturnsError = true
|
||||||
|
f.Rets.Name = r[0].Name
|
||||||
|
f.Rets.Type = r[0].Type
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Too many return values in \"" + f.src + "\"")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fail condition
|
||||||
|
_, body, s, found = extractSection(s, '[', ']')
|
||||||
|
if found {
|
||||||
|
f.Rets.FailCond = body
|
||||||
|
}
|
||||||
|
// dll and dll function names
|
||||||
|
s = trim(s)
|
||||||
|
if s == "" {
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(s, "=") {
|
||||||
|
return nil, errors.New("Could not extract dll name from \"" + f.src + "\"")
|
||||||
|
}
|
||||||
|
s = trim(s[1:])
|
||||||
|
a := strings.Split(s, ".")
|
||||||
|
switch len(a) {
|
||||||
|
case 1:
|
||||||
|
f.dllfuncname = a[0]
|
||||||
|
case 2:
|
||||||
|
f.dllname = a[0]
|
||||||
|
f.dllfuncname = a[1]
|
||||||
|
default:
|
||||||
|
return nil, errors.New("Could not extract dll name from \"" + f.src + "\"")
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DLLName returns DLL name for function f.
|
||||||
|
func (f *Fn) DLLName() string {
|
||||||
|
if f.dllname == "" {
|
||||||
|
return "kernel32"
|
||||||
|
}
|
||||||
|
return f.dllname
|
||||||
|
}
|
||||||
|
|
||||||
|
// DLLName returns DLL function name for function f.
|
||||||
|
func (f *Fn) DLLFuncName() string {
|
||||||
|
if f.dllfuncname == "" {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
return f.dllfuncname
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamList returns source code for function f parameters.
|
||||||
|
func (f *Fn) ParamList() string {
|
||||||
|
return join(f.Params, func(p *Param) string { return p.Name + " " + p.Type }, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelperParamList returns source code for helper function f parameters.
|
||||||
|
func (f *Fn) HelperParamList() string {
|
||||||
|
return join(f.Params, func(p *Param) string { return p.Name + " " + p.HelperType() }, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamPrintList returns source code of trace printing part correspondent
|
||||||
|
// to syscall input parameters.
|
||||||
|
func (f *Fn) ParamPrintList() string {
|
||||||
|
return join(f.Params, func(p *Param) string { return fmt.Sprintf(`"%s=", %s, `, p.Name, p.Name) }, `", ", `)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamCount return number of syscall parameters for function f.
|
||||||
|
func (f *Fn) ParamCount() int {
|
||||||
|
n := 0
|
||||||
|
for _, p := range f.Params {
|
||||||
|
n += len(p.SyscallArgList())
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyscallParamCount determines which version of Syscall/Syscall6/Syscall9/...
|
||||||
|
// to use. It returns parameter count for correspondent SyscallX function.
|
||||||
|
func (f *Fn) SyscallParamCount() int {
|
||||||
|
n := f.ParamCount()
|
||||||
|
switch {
|
||||||
|
case n <= 3:
|
||||||
|
return 3
|
||||||
|
case n <= 6:
|
||||||
|
return 6
|
||||||
|
case n <= 9:
|
||||||
|
return 9
|
||||||
|
case n <= 12:
|
||||||
|
return 12
|
||||||
|
case n <= 15:
|
||||||
|
return 15
|
||||||
|
default:
|
||||||
|
panic("too many arguments to system call")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syscall determines which SyscallX function to use for function f.
|
||||||
|
func (f *Fn) Syscall() string {
|
||||||
|
c := f.SyscallParamCount()
|
||||||
|
if c == 3 {
|
||||||
|
return syscalldot() + "Syscall"
|
||||||
|
}
|
||||||
|
return syscalldot() + "Syscall" + strconv.Itoa(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyscallParamList returns source code for SyscallX parameters for function f.
|
||||||
|
func (f *Fn) SyscallParamList() string {
|
||||||
|
a := make([]string, 0)
|
||||||
|
for _, p := range f.Params {
|
||||||
|
a = append(a, p.SyscallArgList()...)
|
||||||
|
}
|
||||||
|
for len(a) < f.SyscallParamCount() {
|
||||||
|
a = append(a, "0")
|
||||||
|
}
|
||||||
|
return strings.Join(a, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelperCallParamList returns source code of call into function f helper.
|
||||||
|
func (f *Fn) HelperCallParamList() string {
|
||||||
|
a := make([]string, 0, len(f.Params))
|
||||||
|
for _, p := range f.Params {
|
||||||
|
s := p.Name
|
||||||
|
if p.Type == "string" {
|
||||||
|
s = p.tmpVar()
|
||||||
|
}
|
||||||
|
a = append(a, s)
|
||||||
|
}
|
||||||
|
return strings.Join(a, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUTF16 is true, if f is W (utf16) function. It is false
|
||||||
|
// for all A (ascii) functions.
|
||||||
|
func (f *Fn) IsUTF16() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrconvFunc returns name of Go string to OS string function for f.
|
||||||
|
func (f *Fn) StrconvFunc() string {
|
||||||
|
if f.IsUTF16() {
|
||||||
|
return syscalldot() + "UTF16PtrFromString"
|
||||||
|
}
|
||||||
|
return syscalldot() + "BytePtrFromString"
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrconvType returns Go type name used for OS string for f.
|
||||||
|
func (f *Fn) StrconvType() string {
|
||||||
|
if f.IsUTF16() {
|
||||||
|
return "*uint16"
|
||||||
|
}
|
||||||
|
return "*byte"
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasStringParam is true, if f has at least one string parameter.
|
||||||
|
// Otherwise it is false.
|
||||||
|
func (f *Fn) HasStringParam() bool {
|
||||||
|
for _, p := range f.Params {
|
||||||
|
if p.Type == "string" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelperName returns name of function f helper.
|
||||||
|
func (f *Fn) HelperName() string {
|
||||||
|
if !f.HasStringParam() {
|
||||||
|
return f.Name
|
||||||
|
}
|
||||||
|
return "_" + f.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source files and functions.
|
||||||
|
type Source struct {
|
||||||
|
Funcs []*Fn
|
||||||
|
Files []string
|
||||||
|
StdLibImports []string
|
||||||
|
ExternalImports []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *Source) Import(pkg string) {
|
||||||
|
src.StdLibImports = append(src.StdLibImports, pkg)
|
||||||
|
sort.Strings(src.StdLibImports)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *Source) ExternalImport(pkg string) {
|
||||||
|
src.ExternalImports = append(src.ExternalImports, pkg)
|
||||||
|
sort.Strings(src.ExternalImports)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFiles parses files listed in fs and extracts all syscall
|
||||||
|
// functions listed in sys comments. It returns source files
|
||||||
|
// and functions collection *Source if successful.
|
||||||
|
func ParseFiles(fs []string) (*Source, error) {
|
||||||
|
src := &Source{
|
||||||
|
Funcs: make([]*Fn, 0),
|
||||||
|
Files: make([]string, 0),
|
||||||
|
StdLibImports: []string{
|
||||||
|
"unsafe",
|
||||||
|
},
|
||||||
|
ExternalImports: make([]string, 0),
|
||||||
|
}
|
||||||
|
for _, file := range fs {
|
||||||
|
if err := src.ParseFile(file); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return src, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DLLs return dll names for a source set src.
|
||||||
|
func (src *Source) DLLs() []string {
|
||||||
|
uniq := make(map[string]bool)
|
||||||
|
r := make([]string, 0)
|
||||||
|
for _, f := range src.Funcs {
|
||||||
|
name := f.DLLName()
|
||||||
|
if _, found := uniq[name]; !found {
|
||||||
|
uniq[name] = true
|
||||||
|
r = append(r, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFile adds additional file path to a source set src.
|
||||||
|
func (src *Source) ParseFile(path string) error {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
s := bufio.NewScanner(file)
|
||||||
|
for s.Scan() {
|
||||||
|
t := trim(s.Text())
|
||||||
|
if len(t) < 7 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(t, "//sys") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t = t[5:]
|
||||||
|
if !(t[0] == ' ' || t[0] == '\t') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f, err := newFn(t[1:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
src.Funcs = append(src.Funcs, f)
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
src.Files = append(src.Files, path)
|
||||||
|
|
||||||
|
// get package name
|
||||||
|
fset := token.NewFileSet()
|
||||||
|
_, err = file.Seek(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pkg, err := parser.ParseFile(fset, "", file, parser.PackageClauseOnly)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
packageName = pkg.Name.Name
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsStdRepo returns true if src is part of standard library.
|
||||||
|
func (src *Source) IsStdRepo() (bool, error) {
|
||||||
|
if len(src.Files) == 0 {
|
||||||
|
return false, errors.New("no input files provided")
|
||||||
|
}
|
||||||
|
abspath, err := filepath.Abs(src.Files[0])
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
goroot := runtime.GOROOT()
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
abspath = strings.ToLower(abspath)
|
||||||
|
goroot = strings.ToLower(goroot)
|
||||||
|
}
|
||||||
|
sep := string(os.PathSeparator)
|
||||||
|
if !strings.HasSuffix(goroot, sep) {
|
||||||
|
goroot += sep
|
||||||
|
}
|
||||||
|
return strings.HasPrefix(abspath, goroot), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate output source file from a source set src.
|
||||||
|
func (src *Source) Generate(w io.Writer) error {
|
||||||
|
const (
|
||||||
|
pkgStd = iota // any package in std library
|
||||||
|
pkgXSysWindows // x/sys/windows package
|
||||||
|
pkgOther
|
||||||
|
)
|
||||||
|
isStdRepo, err := src.IsStdRepo()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var pkgtype int
|
||||||
|
switch {
|
||||||
|
case isStdRepo:
|
||||||
|
pkgtype = pkgStd
|
||||||
|
case packageName == "windows":
|
||||||
|
// TODO: this needs better logic than just using package name
|
||||||
|
pkgtype = pkgXSysWindows
|
||||||
|
default:
|
||||||
|
pkgtype = pkgOther
|
||||||
|
}
|
||||||
|
if *systemDLL {
|
||||||
|
switch pkgtype {
|
||||||
|
case pkgStd:
|
||||||
|
src.Import("internal/syscall/windows/sysdll")
|
||||||
|
case pkgXSysWindows:
|
||||||
|
default:
|
||||||
|
src.ExternalImport("golang.org/x/sys/windows")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if packageName != "syscall" {
|
||||||
|
src.Import("syscall")
|
||||||
|
}
|
||||||
|
funcMap := template.FuncMap{
|
||||||
|
"packagename": packagename,
|
||||||
|
"syscalldot": syscalldot,
|
||||||
|
"newlazydll": func(dll string) string {
|
||||||
|
arg := "\"" + dll + ".dll\""
|
||||||
|
if !*systemDLL {
|
||||||
|
return syscalldot() + "NewLazyDLL(" + arg + ")"
|
||||||
|
}
|
||||||
|
switch pkgtype {
|
||||||
|
case pkgStd:
|
||||||
|
return syscalldot() + "NewLazyDLL(sysdll.Add(" + arg + "))"
|
||||||
|
case pkgXSysWindows:
|
||||||
|
return "NewLazySystemDLL(" + arg + ")"
|
||||||
|
default:
|
||||||
|
return "windows.NewLazySystemDLL(" + arg + ")"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t := template.Must(template.New("main").Funcs(funcMap).Parse(srcTemplate))
|
||||||
|
err = t.Execute(w, src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("Failed to execute template: " + err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func usage() {
|
||||||
|
fmt.Fprintf(os.Stderr, "usage: mksyscall_windows [flags] [path ...]\n")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Usage = usage
|
||||||
|
flag.Parse()
|
||||||
|
if len(flag.Args()) <= 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "no files to parse provided\n")
|
||||||
|
usage()
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := ParseFiles(flag.Args())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := src.Generate(&buf); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := format.Source(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if *filename == "" {
|
||||||
|
_, err = os.Stdout.Write(data)
|
||||||
|
} else {
|
||||||
|
err = ioutil.WriteFile(*filename, data, 0644)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use println instead to print in the following template
|
||||||
|
const srcTemplate = `
|
||||||
|
|
||||||
|
{{define "main"}}// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||||
|
|
||||||
|
package {{packagename}}
|
||||||
|
|
||||||
|
import (
|
||||||
|
{{range .StdLibImports}}"{{.}}"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{range .ExternalImports}}"{{.}}"
|
||||||
|
{{end}}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = {{syscalldot}}Errno(errnoERROR_IO_PENDING)
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e {{syscalldot}}Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
// TODO: add more here, after collecting data on the common
|
||||||
|
// error values see on Windows. (perhaps when running
|
||||||
|
// all.bat?)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
{{template "dlls" .}}
|
||||||
|
{{template "funcnames" .}})
|
||||||
|
{{range .Funcs}}{{if .HasStringParam}}{{template "helperbody" .}}{{end}}{{template "funcbody" .}}{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{/* help functions */}}
|
||||||
|
|
||||||
|
{{define "dlls"}}{{range .DLLs}} mod{{.}} = {{newlazydll .}}
|
||||||
|
{{end}}{{end}}
|
||||||
|
|
||||||
|
{{define "funcnames"}}{{range .Funcs}} proc{{.DLLFuncName}} = mod{{.DLLName}}.NewProc("{{.DLLFuncName}}")
|
||||||
|
{{end}}{{end}}
|
||||||
|
|
||||||
|
{{define "helperbody"}}
|
||||||
|
func {{.Name}}({{.ParamList}}) {{template "results" .}}{
|
||||||
|
{{template "helpertmpvars" .}} return {{.HelperName}}({{.HelperCallParamList}})
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "funcbody"}}
|
||||||
|
func {{.HelperName}}({{.HelperParamList}}) {{template "results" .}}{
|
||||||
|
{{template "tmpvars" .}} {{template "syscall" .}}
|
||||||
|
{{template "seterror" .}}{{template "printtrace" .}} return
|
||||||
|
}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "helpertmpvars"}}{{range .Params}}{{if .TmpVarHelperCode}} {{.TmpVarHelperCode}}
|
||||||
|
{{end}}{{end}}{{end}}
|
||||||
|
|
||||||
|
{{define "tmpvars"}}{{range .Params}}{{if .TmpVarCode}} {{.TmpVarCode}}
|
||||||
|
{{end}}{{end}}{{end}}
|
||||||
|
|
||||||
|
{{define "results"}}{{if .Rets.List}}{{.Rets.List}} {{end}}{{end}}
|
||||||
|
|
||||||
|
{{define "syscall"}}{{.Rets.SetReturnValuesCode}}{{.Syscall}}(proc{{.DLLFuncName}}.Addr(), {{.ParamCount}}, {{.SyscallParamList}}){{end}}
|
||||||
|
|
||||||
|
{{define "seterror"}}{{if .Rets.SetErrorCode}} {{.Rets.SetErrorCode}}
|
||||||
|
{{end}}{{end}}
|
||||||
|
|
||||||
|
{{define "printtrace"}}{{if .PrintTrace}} print("SYSCALL: {{.Name}}(", {{.ParamPrintList}}") (", {{.Rets.PrintList}}")\n")
|
||||||
|
{{end}}{{end}}
|
||||||
|
|
||||||
|
`
|
82
vendor/github.com/Microsoft/go-winio/vhd/vhd.go
generated
vendored
Normal file
82
vendor/github.com/Microsoft/go-winio/vhd/vhd.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package vhd
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
//go:generate go run mksyscall_windows.go -output zvhd.go vhd.go
|
||||||
|
|
||||||
|
//sys createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) [failretval != 0] = VirtDisk.CreateVirtualDisk
|
||||||
|
|
||||||
|
type virtualStorageType struct {
|
||||||
|
DeviceID uint32
|
||||||
|
VendorID [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const virtualDiskAccessNONE uint32 = 0
|
||||||
|
const virtualDiskAccessATTACHRO uint32 = 65536
|
||||||
|
const virtualDiskAccessATTACHRW uint32 = 131072
|
||||||
|
const virtualDiskAccessDETACH uint32 = 262144
|
||||||
|
const virtualDiskAccessGETINFO uint32 = 524288
|
||||||
|
const virtualDiskAccessCREATE uint32 = 1048576
|
||||||
|
const virtualDiskAccessMETAOPS uint32 = 2097152
|
||||||
|
const virtualDiskAccessREAD uint32 = 851968
|
||||||
|
const virtualDiskAccessALL uint32 = 4128768
|
||||||
|
const virtualDiskAccessWRITABLE uint32 = 3276800
|
||||||
|
|
||||||
|
const createVirtualDiskFlagNone uint32 = 0
|
||||||
|
const createVirtualDiskFlagFullPhysicalAllocation uint32 = 1
|
||||||
|
const createVirtualDiskFlagPreventWritesToSourceDisk uint32 = 2
|
||||||
|
const createVirtualDiskFlagDoNotCopyMetadataFromParent uint32 = 4
|
||||||
|
|
||||||
|
type version2 struct {
|
||||||
|
UniqueID [16]byte // GUID
|
||||||
|
MaximumSize uint64
|
||||||
|
BlockSizeInBytes uint32
|
||||||
|
SectorSizeInBytes uint32
|
||||||
|
ParentPath *uint16 // string
|
||||||
|
SourcePath *uint16 // string
|
||||||
|
OpenFlags uint32
|
||||||
|
ParentVirtualStorageType virtualStorageType
|
||||||
|
SourceVirtualStorageType virtualStorageType
|
||||||
|
ResiliencyGUID [16]byte // GUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type createVirtualDiskParameters struct {
|
||||||
|
Version uint32 // Must always be set to 2
|
||||||
|
Version2 version2
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateVhdx will create a simple vhdx file at the given path using default values.
|
||||||
|
func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
|
||||||
|
var defaultType virtualStorageType
|
||||||
|
|
||||||
|
parameters := createVirtualDiskParameters{
|
||||||
|
Version: 2,
|
||||||
|
Version2: version2{
|
||||||
|
MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
|
||||||
|
BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle syscall.Handle
|
||||||
|
|
||||||
|
if err := createVirtualDisk(
|
||||||
|
&defaultType,
|
||||||
|
path,
|
||||||
|
virtualDiskAccessNONE,
|
||||||
|
nil,
|
||||||
|
createVirtualDiskFlagNone,
|
||||||
|
0,
|
||||||
|
¶meters,
|
||||||
|
nil,
|
||||||
|
&handle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := syscall.CloseHandle(handle); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
64
vendor/github.com/Microsoft/go-winio/vhd/zvhd.go
generated
vendored
Normal file
64
vendor/github.com/Microsoft/go-winio/vhd/zvhd.go
generated
vendored
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||||
|
|
||||||
|
package vhd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
// TODO: add more here, after collecting data on the common
|
||||||
|
// error values see on Windows. (perhaps when running
|
||||||
|
// all.bat?)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modVirtDisk = windows.NewLazySystemDLL("VirtDisk.dll")
|
||||||
|
|
||||||
|
procCreateVirtualDisk = modVirtDisk.NewProc("CreateVirtualDisk")
|
||||||
|
)
|
||||||
|
|
||||||
|
func createVirtualDisk(virtualStorageType *virtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, flags, providerSpecificFlags, parameters, o, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createVirtualDisk(virtualStorageType *virtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, flags uint32, providerSpecificFlags uint32, parameters *createVirtualDiskParameters, o *syscall.Overlapped, handle *syscall.Handle) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall9(procCreateVirtualDisk.Addr(), 9, uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(flags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(handle)))
|
||||||
|
if r1 != 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
138
vendor/github.com/Microsoft/go-winio/wim/decompress.go
generated
vendored
Normal file
138
vendor/github.com/Microsoft/go-winio/wim/decompress.go
generated
vendored
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
package wim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/wim/lzx"
|
||||||
|
)
|
||||||
|
|
||||||
|
const chunkSize = 32768 // Compressed resource chunk size
|
||||||
|
|
||||||
|
type compressedReader struct {
|
||||||
|
r *io.SectionReader
|
||||||
|
d io.ReadCloser
|
||||||
|
chunks []int64
|
||||||
|
curChunk int
|
||||||
|
originalSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCompressedReader(r *io.SectionReader, originalSize int64, offset int64) (*compressedReader, error) {
|
||||||
|
nchunks := (originalSize + chunkSize - 1) / chunkSize
|
||||||
|
var base int64
|
||||||
|
chunks := make([]int64, nchunks)
|
||||||
|
if originalSize <= 0xffffffff {
|
||||||
|
// 32-bit chunk offsets
|
||||||
|
base = (nchunks - 1) * 4
|
||||||
|
chunks32 := make([]uint32, nchunks-1)
|
||||||
|
err := binary.Read(r, binary.LittleEndian, chunks32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for i, n := range chunks32 {
|
||||||
|
chunks[i+1] = int64(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 64-bit chunk offsets
|
||||||
|
base = (nchunks - 1) * 8
|
||||||
|
err := binary.Read(r, binary.LittleEndian, chunks[1:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range chunks {
|
||||||
|
chunks[i] = c + base
|
||||||
|
}
|
||||||
|
|
||||||
|
cr := &compressedReader{
|
||||||
|
r: r,
|
||||||
|
chunks: chunks,
|
||||||
|
originalSize: originalSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := cr.reset(int(offset / chunkSize))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
suboff := offset % chunkSize
|
||||||
|
if suboff != 0 {
|
||||||
|
_, err := io.CopyN(ioutil.Discard, cr.d, suboff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) chunkOffset(n int) int64 {
|
||||||
|
if n == len(r.chunks) {
|
||||||
|
return r.r.Size()
|
||||||
|
}
|
||||||
|
return r.chunks[n]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) chunkSize(n int) int {
|
||||||
|
return int(r.chunkOffset(n+1) - r.chunkOffset(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) uncompressedSize(n int) int {
|
||||||
|
if n < len(r.chunks)-1 {
|
||||||
|
return chunkSize
|
||||||
|
}
|
||||||
|
size := int(r.originalSize % chunkSize)
|
||||||
|
if size == 0 {
|
||||||
|
size = chunkSize
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) reset(n int) error {
|
||||||
|
if n >= len(r.chunks) {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
if r.d != nil {
|
||||||
|
r.d.Close()
|
||||||
|
}
|
||||||
|
r.curChunk = n
|
||||||
|
size := r.chunkSize(n)
|
||||||
|
uncompressedSize := r.uncompressedSize(n)
|
||||||
|
section := io.NewSectionReader(r.r, r.chunkOffset(n), int64(size))
|
||||||
|
if size != uncompressedSize {
|
||||||
|
d, err := lzx.NewReader(section, uncompressedSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.d = d
|
||||||
|
} else {
|
||||||
|
r.d = ioutil.NopCloser(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) Read(b []byte) (int, error) {
|
||||||
|
for {
|
||||||
|
n, err := r.d.Read(b)
|
||||||
|
if err != io.EOF {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.reset(r.curChunk + 1)
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *compressedReader) Close() error {
|
||||||
|
var err error
|
||||||
|
if r.d != nil {
|
||||||
|
err = r.d.Close()
|
||||||
|
r.d = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
606
vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go
generated
vendored
Normal file
606
vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go
generated
vendored
Normal file
|
@ -0,0 +1,606 @@
|
||||||
|
// Package lzx implements a decompressor for the the WIM variant of the
|
||||||
|
// LZX compression algorithm.
|
||||||
|
//
|
||||||
|
// The LZX algorithm is an earlier variant of LZX DELTA, which is documented
|
||||||
|
// at https://msdn.microsoft.com/en-us/library/cc483133(v=exchg.80).aspx.
|
||||||
|
package lzx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maincodecount = 496
|
||||||
|
maincodesplit = 256
|
||||||
|
lencodecount = 249
|
||||||
|
lenshift = 9
|
||||||
|
codemask = 0x1ff
|
||||||
|
tablebits = 9
|
||||||
|
tablesize = 1 << tablebits
|
||||||
|
|
||||||
|
maxBlockSize = 32768
|
||||||
|
windowSize = 32768
|
||||||
|
|
||||||
|
maxTreePathLen = 16
|
||||||
|
|
||||||
|
e8filesize = 12000000
|
||||||
|
maxe8offset = 0x3fffffff
|
||||||
|
|
||||||
|
verbatimBlock = 1
|
||||||
|
alignedOffsetBlock = 2
|
||||||
|
uncompressedBlock = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
var footerBits = [...]byte{
|
||||||
|
0, 0, 0, 0, 1, 1, 2, 2,
|
||||||
|
3, 3, 4, 4, 5, 5, 6, 6,
|
||||||
|
7, 7, 8, 8, 9, 9, 10, 10,
|
||||||
|
11, 11, 12, 12, 13, 13, 14,
|
||||||
|
}
|
||||||
|
|
||||||
|
var basePosition = [...]uint16{
|
||||||
|
0, 1, 2, 3, 4, 6, 8, 12,
|
||||||
|
16, 24, 32, 48, 64, 96, 128, 192,
|
||||||
|
256, 384, 512, 768, 1024, 1536, 2048, 3072,
|
||||||
|
4096, 6144, 8192, 12288, 16384, 24576, 32768,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errCorrupt = errors.New("LZX data corrupt")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader is an interface used by the decompressor to access
|
||||||
|
// the input stream. If the provided io.Reader does not implement
|
||||||
|
// Reader, then a bufio.Reader is used.
|
||||||
|
type Reader interface {
|
||||||
|
io.Reader
|
||||||
|
io.ByteReader
|
||||||
|
}
|
||||||
|
|
||||||
|
type decompressor struct {
|
||||||
|
r io.Reader
|
||||||
|
err error
|
||||||
|
unaligned bool
|
||||||
|
nbits byte
|
||||||
|
c uint32
|
||||||
|
lru [3]uint16
|
||||||
|
uncompressed int
|
||||||
|
windowReader *bytes.Reader
|
||||||
|
mainlens [maincodecount]byte
|
||||||
|
lenlens [lencodecount]byte
|
||||||
|
window [windowSize]byte
|
||||||
|
b []byte
|
||||||
|
bv int
|
||||||
|
bo int
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:noinline
|
||||||
|
func (f *decompressor) fail(err error) {
|
||||||
|
if f.err == nil {
|
||||||
|
f.err = err
|
||||||
|
}
|
||||||
|
f.bo = 0
|
||||||
|
f.bv = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *decompressor) ensureAtLeast(n int) error {
|
||||||
|
if f.bv-f.bo >= n {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.err != nil {
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.bv != f.bo {
|
||||||
|
copy(f.b[:f.bv-f.bo], f.b[f.bo:f.bv])
|
||||||
|
}
|
||||||
|
n, err := io.ReadAtLeast(f.r, f.b[f.bv-f.bo:], n)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
} else {
|
||||||
|
f.fail(err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.bv = f.bv - f.bo + n
|
||||||
|
f.bo = 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// feed retrieves another 16-bit word from the stream and consumes
|
||||||
|
// it into f.c. It returns false if there are no more bytes available.
|
||||||
|
// Otherwise, on error, it sets f.err.
|
||||||
|
func (f *decompressor) feed() bool {
|
||||||
|
err := f.ensureAtLeast(2)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.ErrUnexpectedEOF {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.c |= (uint32(f.b[f.bo+1])<<8 | uint32(f.b[f.bo])) << (16 - f.nbits)
|
||||||
|
f.nbits += 16
|
||||||
|
f.bo += 2
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBits retrieves the next n bits from the byte stream. n
|
||||||
|
// must be <= 16. It sets f.err on error.
|
||||||
|
func (f *decompressor) getBits(n byte) uint16 {
|
||||||
|
if f.nbits < n {
|
||||||
|
if !f.feed() {
|
||||||
|
f.fail(io.ErrUnexpectedEOF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c := uint16(f.c >> (32 - n))
|
||||||
|
f.c <<= n
|
||||||
|
f.nbits -= n
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type huffman struct {
|
||||||
|
extra [][]uint16
|
||||||
|
maxbits byte
|
||||||
|
table [tablesize]uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildTable builds a huffman decoding table from a slice of code lengths,
|
||||||
|
// one per code, in order. Each code length must be <= maxTreePathLen.
|
||||||
|
// See https://en.wikipedia.org/wiki/Canonical_Huffman_code.
|
||||||
|
func buildTable(codelens []byte) *huffman {
|
||||||
|
// Determine the number of codes of each length, and the
|
||||||
|
// maximum length.
|
||||||
|
var count [maxTreePathLen + 1]uint
|
||||||
|
var max byte
|
||||||
|
for _, cl := range codelens {
|
||||||
|
count[cl]++
|
||||||
|
if max < cl {
|
||||||
|
max = cl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if max == 0 {
|
||||||
|
return &huffman{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the first code of each length.
|
||||||
|
var first [maxTreePathLen + 1]uint
|
||||||
|
code := uint(0)
|
||||||
|
for i := byte(1); i <= max; i++ {
|
||||||
|
code <<= 1
|
||||||
|
first[i] = code
|
||||||
|
code += count[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if code != 1<<max {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a table for code lookup. For code sizes < max,
|
||||||
|
// put all possible suffixes for the code into the table, too.
|
||||||
|
// For max > tablebits, split long codes into additional tables
|
||||||
|
// of suffixes of max-tablebits length.
|
||||||
|
h := &huffman{maxbits: max}
|
||||||
|
if max > tablebits {
|
||||||
|
core := first[tablebits+1] / 2 // Number of codes that fit without extra tables
|
||||||
|
nextra := 1<<tablebits - core // Number of extra entries
|
||||||
|
h.extra = make([][]uint16, nextra)
|
||||||
|
for code := core; code < 1<<tablebits; code++ {
|
||||||
|
h.table[code] = uint16(code - core)
|
||||||
|
h.extra[code-core] = make([]uint16, 1<<(max-tablebits))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, cl := range codelens {
|
||||||
|
if cl != 0 {
|
||||||
|
code := first[cl]
|
||||||
|
first[cl]++
|
||||||
|
v := uint16(cl)<<lenshift | uint16(i)
|
||||||
|
if cl <= tablebits {
|
||||||
|
extendedCode := code << (tablebits - cl)
|
||||||
|
for j := uint(0); j < 1<<(tablebits-cl); j++ {
|
||||||
|
h.table[extendedCode+j] = v
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prefix := code >> (cl - tablebits)
|
||||||
|
suffix := code & (1<<(cl-tablebits) - 1)
|
||||||
|
extendedCode := suffix << (max - cl)
|
||||||
|
for j := uint(0); j < 1<<(max-cl); j++ {
|
||||||
|
h.extra[h.table[prefix]][extendedCode+j] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCode retrieves the next code using the provided
|
||||||
|
// huffman tree. It sets f.err on error.
|
||||||
|
func (f *decompressor) getCode(h *huffman) uint16 {
|
||||||
|
if h.maxbits > 0 {
|
||||||
|
if f.nbits < maxTreePathLen {
|
||||||
|
f.feed()
|
||||||
|
}
|
||||||
|
|
||||||
|
// For codes with length < tablebits, it doesn't matter
|
||||||
|
// what the remainder of the bits used for table lookup
|
||||||
|
// are, since entries with all possible suffixes were
|
||||||
|
// added to the table.
|
||||||
|
c := h.table[f.c>>(32-tablebits)]
|
||||||
|
if c >= 1<<lenshift {
|
||||||
|
// The code is already in c.
|
||||||
|
} else {
|
||||||
|
c = h.extra[c][f.c<<tablebits>>(32-(h.maxbits-tablebits))]
|
||||||
|
}
|
||||||
|
|
||||||
|
n := byte(c >> lenshift)
|
||||||
|
if f.nbits >= n {
|
||||||
|
// Only consume the length of the code, not the maximum
|
||||||
|
// code length.
|
||||||
|
f.c <<= n
|
||||||
|
f.nbits -= n
|
||||||
|
return c & codemask
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fail(io.ErrUnexpectedEOF)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is an empty tree. It should not be used.
|
||||||
|
f.fail(errCorrupt)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTree updates the huffman tree path lengths in lens by
|
||||||
|
// reading and decoding lengths from the byte stream. lens
|
||||||
|
// should be prepopulated with the previous block's tree's path
|
||||||
|
// lengths. For the first block, lens should be zero.
|
||||||
|
func (f *decompressor) readTree(lens []byte) error {
|
||||||
|
// Get the pre-tree for the main tree.
|
||||||
|
var pretreeLen [20]byte
|
||||||
|
for i := range pretreeLen {
|
||||||
|
pretreeLen[i] = byte(f.getBits(4))
|
||||||
|
}
|
||||||
|
if f.err != nil {
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
h := buildTable(pretreeLen[:])
|
||||||
|
|
||||||
|
// The lengths are encoded as a series of huffman codes
|
||||||
|
// encoded by the pre-tree.
|
||||||
|
for i := 0; i < len(lens); {
|
||||||
|
c := byte(f.getCode(h))
|
||||||
|
if f.err != nil {
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case c <= 16: // length is delta from previous length
|
||||||
|
lens[i] = (lens[i] + 17 - c) % 17
|
||||||
|
i++
|
||||||
|
case c == 17: // next n + 4 lengths are zero
|
||||||
|
zeroes := int(f.getBits(4)) + 4
|
||||||
|
if i+zeroes > len(lens) {
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
for j := 0; j < zeroes; j++ {
|
||||||
|
lens[i+j] = 0
|
||||||
|
}
|
||||||
|
i += zeroes
|
||||||
|
case c == 18: // next n + 20 lengths are zero
|
||||||
|
zeroes := int(f.getBits(5)) + 20
|
||||||
|
if i+zeroes > len(lens) {
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
for j := 0; j < zeroes; j++ {
|
||||||
|
lens[i+j] = 0
|
||||||
|
}
|
||||||
|
i += zeroes
|
||||||
|
case c == 19: // next n + 4 lengths all have the same value
|
||||||
|
same := int(f.getBits(1)) + 4
|
||||||
|
if i+same > len(lens) {
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
c = byte(f.getCode(h))
|
||||||
|
if c > 16 {
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
l := (lens[i] + 17 - c) % 17
|
||||||
|
for j := 0; j < same; j++ {
|
||||||
|
lens[i+j] = l
|
||||||
|
}
|
||||||
|
i += same
|
||||||
|
default:
|
||||||
|
return errCorrupt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.err != nil {
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *decompressor) readBlockHeader() (byte, uint16, error) {
|
||||||
|
// If the previous block was an unaligned uncompressed block, restore
|
||||||
|
// 2-byte alignment.
|
||||||
|
if f.unaligned {
|
||||||
|
err := f.ensureAtLeast(1)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
f.bo++
|
||||||
|
f.unaligned = false
|
||||||
|
}
|
||||||
|
|
||||||
|
blockType := f.getBits(3)
|
||||||
|
full := f.getBits(1)
|
||||||
|
var blockSize uint16
|
||||||
|
if full != 0 {
|
||||||
|
blockSize = maxBlockSize
|
||||||
|
} else {
|
||||||
|
blockSize = f.getBits(16)
|
||||||
|
if blockSize > maxBlockSize {
|
||||||
|
return 0, 0, errCorrupt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.err != nil {
|
||||||
|
return 0, 0, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch blockType {
|
||||||
|
case verbatimBlock, alignedOffsetBlock:
|
||||||
|
// The caller will read the huffman trees.
|
||||||
|
case uncompressedBlock:
|
||||||
|
if f.nbits > 16 {
|
||||||
|
panic("impossible: more than one 16-bit word remains")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the remaining bits in the current 16-bit word
|
||||||
|
// If there are no bits left, discard a full 16-bit word.
|
||||||
|
n := f.nbits
|
||||||
|
if n == 0 {
|
||||||
|
n = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
f.getBits(n)
|
||||||
|
|
||||||
|
// Read the LRU values for the next block.
|
||||||
|
err := f.ensureAtLeast(12)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f.lru[0] = uint16(binary.LittleEndian.Uint32(f.b[f.bo : f.bo+4]))
|
||||||
|
f.lru[1] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+4 : f.bo+8]))
|
||||||
|
f.lru[2] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+8 : f.bo+12]))
|
||||||
|
f.bo += 12
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0, 0, errCorrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
return byte(blockType), blockSize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readTrees reads the two or three huffman trees for the current block.
|
||||||
|
// readAligned specifies whether to read the aligned offset tree.
|
||||||
|
func (f *decompressor) readTrees(readAligned bool) (main *huffman, length *huffman, aligned *huffman, err error) {
|
||||||
|
// Aligned offset blocks start with a small aligned offset tree.
|
||||||
|
if readAligned {
|
||||||
|
var alignedLen [8]byte
|
||||||
|
for i := range alignedLen {
|
||||||
|
alignedLen[i] = byte(f.getBits(3))
|
||||||
|
}
|
||||||
|
aligned = buildTable(alignedLen[:])
|
||||||
|
if aligned == nil {
|
||||||
|
err = errors.New("corrupt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The main tree is encoded in two parts.
|
||||||
|
err = f.readTree(f.mainlens[:maincodesplit])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = f.readTree(f.mainlens[maincodesplit:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
main = buildTable(f.mainlens[:])
|
||||||
|
if main == nil {
|
||||||
|
err = errors.New("corrupt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The length tree is encoding in a single part.
|
||||||
|
err = f.readTree(f.lenlens[:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
length = buildTable(f.lenlens[:])
|
||||||
|
if length == nil {
|
||||||
|
err = errors.New("corrupt")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// readCompressedBlock decodes a compressed block, writing into the window
|
||||||
|
// starting at start and ending at end, and using the provided huffman trees.
|
||||||
|
func (f *decompressor) readCompressedBlock(start, end uint16, hmain, hlength, haligned *huffman) (int, error) {
|
||||||
|
i := start
|
||||||
|
for i < end {
|
||||||
|
main := f.getCode(hmain)
|
||||||
|
if f.err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if main < 256 {
|
||||||
|
// Literal byte.
|
||||||
|
f.window[i] = byte(main)
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a match backward in the window. Determine
|
||||||
|
// the offset and dlength.
|
||||||
|
matchlen := (main - 256) % 8
|
||||||
|
slot := (main - 256) / 8
|
||||||
|
|
||||||
|
// The length is either the low bits of the code,
|
||||||
|
// or if this is 7, is encoded with the length tree.
|
||||||
|
if matchlen == 7 {
|
||||||
|
matchlen += f.getCode(hlength)
|
||||||
|
}
|
||||||
|
matchlen += 2
|
||||||
|
|
||||||
|
var matchoffset uint16
|
||||||
|
if slot < 3 {
|
||||||
|
// The offset is one of the LRU values.
|
||||||
|
matchoffset = f.lru[slot]
|
||||||
|
f.lru[slot] = f.lru[0]
|
||||||
|
f.lru[0] = matchoffset
|
||||||
|
} else {
|
||||||
|
// The offset is encoded as a combination of the
|
||||||
|
// slot and more bits from the bit stream.
|
||||||
|
offsetbits := footerBits[slot]
|
||||||
|
var verbatimbits, alignedbits uint16
|
||||||
|
if offsetbits > 0 {
|
||||||
|
if haligned != nil && offsetbits >= 3 {
|
||||||
|
// This is an aligned offset block. Combine
|
||||||
|
// the bits written verbatim with the aligned
|
||||||
|
// offset tree code.
|
||||||
|
verbatimbits = f.getBits(offsetbits-3) * 8
|
||||||
|
alignedbits = f.getCode(haligned)
|
||||||
|
} else {
|
||||||
|
// There are no aligned offset bits to read,
|
||||||
|
// only verbatim bits.
|
||||||
|
verbatimbits = f.getBits(offsetbits)
|
||||||
|
alignedbits = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matchoffset = basePosition[slot] + verbatimbits + alignedbits - 2
|
||||||
|
// Update the LRU cache.
|
||||||
|
f.lru[2] = f.lru[1]
|
||||||
|
f.lru[1] = f.lru[0]
|
||||||
|
f.lru[0] = matchoffset
|
||||||
|
}
|
||||||
|
|
||||||
|
if matchoffset <= i && matchlen <= end-i {
|
||||||
|
copyend := i + matchlen
|
||||||
|
for ; i < copyend; i++ {
|
||||||
|
f.window[i] = f.window[i-matchoffset]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.fail(errCorrupt)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return int(i - start), f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBlock decodes the current block and returns the number of uncompressed bytes.
|
||||||
|
func (f *decompressor) readBlock(start uint16) (int, error) {
|
||||||
|
blockType, size, err := f.readBlockHeader()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockType == uncompressedBlock {
|
||||||
|
if size%2 == 1 {
|
||||||
|
// Remember to realign the byte stream at the next block.
|
||||||
|
f.unaligned = true
|
||||||
|
}
|
||||||
|
copied := 0
|
||||||
|
if f.bo < f.bv {
|
||||||
|
copied = int(size)
|
||||||
|
s := int(start)
|
||||||
|
if copied > f.bv-f.bo {
|
||||||
|
copied = f.bv - f.bo
|
||||||
|
}
|
||||||
|
copy(f.window[s:s+copied], f.b[f.bo:f.bo+copied])
|
||||||
|
f.bo += copied
|
||||||
|
}
|
||||||
|
n, err := io.ReadFull(f.r, f.window[start+uint16(copied):start+size])
|
||||||
|
return copied + n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hmain, hlength, haligned, err := f.readTrees(blockType == alignedOffsetBlock)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.readCompressedBlock(start, start+size, hmain, hlength, haligned)
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeE8 reverses the 0xe8 x86 instruction encoding that was performed
|
||||||
|
// to the uncompressed data before it was compressed.
|
||||||
|
func decodeE8(b []byte, off int64) {
|
||||||
|
if off > maxe8offset || len(b) < 10 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < len(b)-10; i++ {
|
||||||
|
if b[i] == 0xe8 {
|
||||||
|
currentPtr := int32(off) + int32(i)
|
||||||
|
abs := int32(binary.LittleEndian.Uint32(b[i+1 : i+5]))
|
||||||
|
if abs >= -currentPtr && abs < e8filesize {
|
||||||
|
var rel int32
|
||||||
|
if abs >= 0 {
|
||||||
|
rel = abs - currentPtr
|
||||||
|
} else {
|
||||||
|
rel = abs + e8filesize
|
||||||
|
}
|
||||||
|
binary.LittleEndian.PutUint32(b[i+1:i+5], uint32(rel))
|
||||||
|
}
|
||||||
|
i += 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *decompressor) Read(b []byte) (int, error) {
|
||||||
|
// Read and uncompress everything.
|
||||||
|
if f.windowReader == nil {
|
||||||
|
n := 0
|
||||||
|
for n < f.uncompressed {
|
||||||
|
k, err := f.readBlock(uint16(n))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
n += k
|
||||||
|
}
|
||||||
|
decodeE8(f.window[:f.uncompressed], 0)
|
||||||
|
f.windowReader = bytes.NewReader(f.window[:f.uncompressed])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just read directly from the window.
|
||||||
|
return f.windowReader.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *decompressor) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a new io.ReadCloser that decompresses a
|
||||||
|
// WIM LZX stream until uncompressedSize bytes have been returned.
|
||||||
|
func NewReader(r io.Reader, uncompressedSize int) (io.ReadCloser, error) {
|
||||||
|
if uncompressedSize > windowSize {
|
||||||
|
return nil, errors.New("uncompressed size is limited to 32KB")
|
||||||
|
}
|
||||||
|
f := &decompressor{
|
||||||
|
lru: [3]uint16{1, 1, 1},
|
||||||
|
uncompressed: uncompressedSize,
|
||||||
|
b: make([]byte, 4096),
|
||||||
|
r: r,
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
51
vendor/github.com/Microsoft/go-winio/wim/validate/validate.go
generated
vendored
Normal file
51
vendor/github.com/Microsoft/go-winio/wim/validate/validate.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/wim"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
f, err := os.Open(flag.Arg(0))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := wim.NewReader(f)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%#v\n%#v\n", w.Image[0], w.Image[0].Windows)
|
||||||
|
|
||||||
|
dir, err := w.Image[0].Open()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = recur(dir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recur(d *wim.File) error {
|
||||||
|
files, err := d.Readdir()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", d.Name, err)
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
if f.IsDir() {
|
||||||
|
err = recur(f)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s: %s", f.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
866
vendor/github.com/Microsoft/go-winio/wim/wim.go
generated
vendored
Normal file
866
vendor/github.com/Microsoft/go-winio/wim/wim.go
generated
vendored
Normal file
|
@ -0,0 +1,866 @@
|
||||||
|
// Package wim implements a WIM file parser.
|
||||||
|
//
|
||||||
|
// WIM files are used to distribute Windows file system and container images.
|
||||||
|
// They are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/dd861280.aspx.
|
||||||
|
package wim
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File attribute constants from Windows.
|
||||||
|
const (
|
||||||
|
FILE_ATTRIBUTE_READONLY = 0x00000001
|
||||||
|
FILE_ATTRIBUTE_HIDDEN = 0x00000002
|
||||||
|
FILE_ATTRIBUTE_SYSTEM = 0x00000004
|
||||||
|
FILE_ATTRIBUTE_DIRECTORY = 0x00000010
|
||||||
|
FILE_ATTRIBUTE_ARCHIVE = 0x00000020
|
||||||
|
FILE_ATTRIBUTE_DEVICE = 0x00000040
|
||||||
|
FILE_ATTRIBUTE_NORMAL = 0x00000080
|
||||||
|
FILE_ATTRIBUTE_TEMPORARY = 0x00000100
|
||||||
|
FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200
|
||||||
|
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
|
||||||
|
FILE_ATTRIBUTE_COMPRESSED = 0x00000800
|
||||||
|
FILE_ATTRIBUTE_OFFLINE = 0x00001000
|
||||||
|
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000
|
||||||
|
FILE_ATTRIBUTE_ENCRYPTED = 0x00004000
|
||||||
|
FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000
|
||||||
|
FILE_ATTRIBUTE_VIRTUAL = 0x00010000
|
||||||
|
FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000
|
||||||
|
FILE_ATTRIBUTE_EA = 0x00040000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows processor architectures.
|
||||||
|
const (
|
||||||
|
PROCESSOR_ARCHITECTURE_INTEL = 0
|
||||||
|
PROCESSOR_ARCHITECTURE_MIPS = 1
|
||||||
|
PROCESSOR_ARCHITECTURE_ALPHA = 2
|
||||||
|
PROCESSOR_ARCHITECTURE_PPC = 3
|
||||||
|
PROCESSOR_ARCHITECTURE_SHX = 4
|
||||||
|
PROCESSOR_ARCHITECTURE_ARM = 5
|
||||||
|
PROCESSOR_ARCHITECTURE_IA64 = 6
|
||||||
|
PROCESSOR_ARCHITECTURE_ALPHA64 = 7
|
||||||
|
PROCESSOR_ARCHITECTURE_MSIL = 8
|
||||||
|
PROCESSOR_ARCHITECTURE_AMD64 = 9
|
||||||
|
PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10
|
||||||
|
PROCESSOR_ARCHITECTURE_NEUTRAL = 11
|
||||||
|
PROCESSOR_ARCHITECTURE_ARM64 = 12
|
||||||
|
)
|
||||||
|
|
||||||
|
var wimImageTag = [...]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0}
|
||||||
|
|
||||||
|
type guid struct {
|
||||||
|
Data1 uint32
|
||||||
|
Data2 uint16
|
||||||
|
Data3 uint16
|
||||||
|
Data4 [8]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g guid) String() string {
|
||||||
|
return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", g.Data1, g.Data2, g.Data3, g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7])
|
||||||
|
}
|
||||||
|
|
||||||
|
type resourceDescriptor struct {
|
||||||
|
FlagsAndCompressedSize uint64
|
||||||
|
Offset int64
|
||||||
|
OriginalSize int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type resFlag byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
resFlagFree resFlag = 1 << iota
|
||||||
|
resFlagMetadata
|
||||||
|
resFlagCompressed
|
||||||
|
resFlagSpanned
|
||||||
|
)
|
||||||
|
|
||||||
|
const validate = false
|
||||||
|
|
||||||
|
const supportedResFlags = resFlagMetadata | resFlagCompressed
|
||||||
|
|
||||||
|
func (r *resourceDescriptor) Flags() resFlag {
|
||||||
|
return resFlag(r.FlagsAndCompressedSize >> 56)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resourceDescriptor) CompressedSize() int64 {
|
||||||
|
return int64(r.FlagsAndCompressedSize & 0xffffffffffffff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resourceDescriptor) String() string {
|
||||||
|
s := fmt.Sprintf("%d bytes at %d", r.CompressedSize(), r.Offset)
|
||||||
|
if r.Flags()&4 != 0 {
|
||||||
|
s += fmt.Sprintf(" (uncompresses to %d)", r.OriginalSize)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHA1Hash contains the SHA1 hash of a file or stream.
|
||||||
|
type SHA1Hash [20]byte
|
||||||
|
|
||||||
|
type streamDescriptor struct {
|
||||||
|
resourceDescriptor
|
||||||
|
PartNumber uint16
|
||||||
|
RefCount uint32
|
||||||
|
Hash SHA1Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
type hdrFlag uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
hdrFlagReserved hdrFlag = 1 << iota
|
||||||
|
hdrFlagCompressed
|
||||||
|
hdrFlagReadOnly
|
||||||
|
hdrFlagSpanned
|
||||||
|
hdrFlagResourceOnly
|
||||||
|
hdrFlagMetadataOnly
|
||||||
|
hdrFlagWriteInProgress
|
||||||
|
hdrFlagRpFix
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hdrFlagCompressReserved hdrFlag = 1 << (iota + 16)
|
||||||
|
hdrFlagCompressXpress
|
||||||
|
hdrFlagCompressLzx
|
||||||
|
)
|
||||||
|
|
||||||
|
const supportedHdrFlags = hdrFlagRpFix | hdrFlagReadOnly | hdrFlagCompressed | hdrFlagCompressLzx
|
||||||
|
|
||||||
|
type wimHeader struct {
|
||||||
|
ImageTag [8]byte
|
||||||
|
Size uint32
|
||||||
|
Version uint32
|
||||||
|
Flags hdrFlag
|
||||||
|
CompressionSize uint32
|
||||||
|
WIMGuid guid
|
||||||
|
PartNumber uint16
|
||||||
|
TotalParts uint16
|
||||||
|
ImageCount uint32
|
||||||
|
OffsetTable resourceDescriptor
|
||||||
|
XMLData resourceDescriptor
|
||||||
|
BootMetadata resourceDescriptor
|
||||||
|
BootIndex uint32
|
||||||
|
Padding uint32
|
||||||
|
Integrity resourceDescriptor
|
||||||
|
Unused [60]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type securityblockDisk struct {
|
||||||
|
TotalLength uint32
|
||||||
|
NumEntries uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const securityblockDiskSize = 8
|
||||||
|
|
||||||
|
type direntry struct {
|
||||||
|
Attributes uint32
|
||||||
|
SecurityID uint32
|
||||||
|
SubdirOffset int64
|
||||||
|
Unused1, Unused2 int64
|
||||||
|
CreationTime Filetime
|
||||||
|
LastAccessTime Filetime
|
||||||
|
LastWriteTime Filetime
|
||||||
|
Hash SHA1Hash
|
||||||
|
Padding uint32
|
||||||
|
ReparseHardLink int64
|
||||||
|
StreamCount uint16
|
||||||
|
ShortNameLength uint16
|
||||||
|
FileNameLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var direntrySize = int64(binary.Size(direntry{}) + 8) // includes an 8-byte length prefix
|
||||||
|
|
||||||
|
type streamentry struct {
|
||||||
|
Unused int64
|
||||||
|
Hash SHA1Hash
|
||||||
|
NameLength int16
|
||||||
|
}
|
||||||
|
|
||||||
|
var streamentrySize = int64(binary.Size(streamentry{}) + 8) // includes an 8-byte length prefix
|
||||||
|
|
||||||
|
// Filetime represents a Windows time.
|
||||||
|
type Filetime struct {
|
||||||
|
LowDateTime uint32
|
||||||
|
HighDateTime uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns the time as time.Time.
|
||||||
|
func (ft *Filetime) Time() time.Time {
|
||||||
|
// 100-nanosecond intervals since January 1, 1601
|
||||||
|
nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime)
|
||||||
|
// change starting time to the Epoch (00:00:00 UTC, January 1, 1970)
|
||||||
|
nsec -= 116444736000000000
|
||||||
|
// convert into nanoseconds
|
||||||
|
nsec *= 100
|
||||||
|
return time.Unix(0, nsec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML unmarshals the time from a WIM XML blob.
|
||||||
|
func (ft *Filetime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
type time struct {
|
||||||
|
Low string `xml:"LOWPART"`
|
||||||
|
High string `xml:"HIGHPART"`
|
||||||
|
}
|
||||||
|
var t time
|
||||||
|
err := d.DecodeElement(&t, &start)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
low, err := strconv.ParseUint(t.Low, 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
high, err := strconv.ParseUint(t.High, 0, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ft.LowDateTime = uint32(low)
|
||||||
|
ft.HighDateTime = uint32(high)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type info struct {
|
||||||
|
Image []ImageInfo `xml:"IMAGE"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageInfo contains information about the image.
|
||||||
|
type ImageInfo struct {
|
||||||
|
Name string `xml:"NAME"`
|
||||||
|
Index int `xml:"INDEX,attr"`
|
||||||
|
CreationTime Filetime `xml:"CREATIONTIME"`
|
||||||
|
ModTime Filetime `xml:"LASTMODIFICATIONTIME"`
|
||||||
|
Windows *WindowsInfo `xml:"WINDOWS"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowsInfo contains information about the Windows installation in the image.
|
||||||
|
type WindowsInfo struct {
|
||||||
|
Arch byte `xml:"ARCH"`
|
||||||
|
ProductName string `xml:"PRODUCTNAME"`
|
||||||
|
EditionID string `xml:"EDITIONID"`
|
||||||
|
InstallationType string `xml:"INSTALLATIONTYPE"`
|
||||||
|
ProductType string `xml:"PRODUCTTYPE"`
|
||||||
|
Languages []string `xml:"LANGUAGES>LANGUAGE"`
|
||||||
|
DefaultLanguage string `xml:"LANGUAGES>DEFAULT"`
|
||||||
|
Version Version `xml:"VERSION"`
|
||||||
|
SystemRoot string `xml:"SYSTEMROOT"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version represents a Windows build version.
|
||||||
|
type Version struct {
|
||||||
|
Major int `xml:"MAJOR"`
|
||||||
|
Minor int `xml:"MINOR"`
|
||||||
|
Build int `xml:"BUILD"`
|
||||||
|
SPBuild int `xml:"SPBUILD"`
|
||||||
|
SPLevel int `xml:"SPLEVEL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseError is returned when the WIM cannot be parsed.
|
||||||
|
type ParseError struct {
|
||||||
|
Oper string
|
||||||
|
Path string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ParseError) Error() string {
|
||||||
|
if e.Path == "" {
|
||||||
|
return "WIM parse error at " + e.Oper + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("WIM parse error: %s %s: %s", e.Oper, e.Path, e.Err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reader provides functions to read a WIM file.
|
||||||
|
type Reader struct {
|
||||||
|
hdr wimHeader
|
||||||
|
r io.ReaderAt
|
||||||
|
fileData map[SHA1Hash]resourceDescriptor
|
||||||
|
|
||||||
|
XMLInfo string // The XML information about the WIM.
|
||||||
|
Image []*Image // The WIM's images.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image represents an image within a WIM file.
|
||||||
|
type Image struct {
|
||||||
|
wim *Reader
|
||||||
|
offset resourceDescriptor
|
||||||
|
sds [][]byte
|
||||||
|
rootOffset int64
|
||||||
|
r io.ReadCloser
|
||||||
|
curOffset int64
|
||||||
|
m sync.Mutex
|
||||||
|
|
||||||
|
ImageInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamHeader contains alternate data stream metadata.
|
||||||
|
type StreamHeader struct {
|
||||||
|
Name string
|
||||||
|
Hash SHA1Hash
|
||||||
|
Size int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream represents an alternate data stream or reparse point data stream.
|
||||||
|
type Stream struct {
|
||||||
|
StreamHeader
|
||||||
|
wim *Reader
|
||||||
|
offset resourceDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileHeader contains file metadata.
|
||||||
|
type FileHeader struct {
|
||||||
|
Name string
|
||||||
|
ShortName string
|
||||||
|
Attributes uint32
|
||||||
|
SecurityDescriptor []byte
|
||||||
|
CreationTime Filetime
|
||||||
|
LastAccessTime Filetime
|
||||||
|
LastWriteTime Filetime
|
||||||
|
Hash SHA1Hash
|
||||||
|
Size int64
|
||||||
|
LinkID int64
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseReserved uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// File represents a file or directory in a WIM image.
|
||||||
|
type File struct {
|
||||||
|
FileHeader
|
||||||
|
Streams []*Stream
|
||||||
|
offset resourceDescriptor
|
||||||
|
img *Image
|
||||||
|
subdirOffset int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewReader returns a Reader that can be used to read WIM file data.
|
||||||
|
func NewReader(f io.ReaderAt) (*Reader, error) {
|
||||||
|
r := &Reader{r: f}
|
||||||
|
section := io.NewSectionReader(f, 0, 0xffff)
|
||||||
|
err := binary.Read(section, binary.LittleEndian, &r.hdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.hdr.ImageTag != wimImageTag {
|
||||||
|
return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.hdr.Flags&^supportedHdrFlags != 0 {
|
||||||
|
return nil, fmt.Errorf("unsupported WIM flags %x", r.hdr.Flags&^supportedHdrFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.hdr.CompressionSize != 0x8000 {
|
||||||
|
return nil, fmt.Errorf("unsupported compression size %d", r.hdr.CompressionSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.hdr.TotalParts != 1 {
|
||||||
|
return nil, errors.New("multi-part WIM not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
fileData, images, err := r.readOffsetTable(&r.hdr.OffsetTable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlinfo, err := r.readXML()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var info info
|
||||||
|
err = xml.Unmarshal([]byte(xmlinfo), &info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &ParseError{Oper: "XML info", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, img := range images {
|
||||||
|
for _, imgInfo := range info.Image {
|
||||||
|
if imgInfo.Index == i+1 {
|
||||||
|
img.ImageInfo = imgInfo
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.fileData = fileData
|
||||||
|
r.Image = images
|
||||||
|
r.XMLInfo = xmlinfo
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close releases resources associated with the Reader.
|
||||||
|
func (r *Reader) Close() error {
|
||||||
|
for _, img := range r.Image {
|
||||||
|
img.reset()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) resourceReader(hdr *resourceDescriptor) (io.ReadCloser, error) {
|
||||||
|
return r.resourceReaderWithOffset(hdr, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) resourceReaderWithOffset(hdr *resourceDescriptor, offset int64) (io.ReadCloser, error) {
|
||||||
|
var sr io.ReadCloser
|
||||||
|
section := io.NewSectionReader(r.r, hdr.Offset, hdr.CompressedSize())
|
||||||
|
if hdr.Flags()&resFlagCompressed == 0 {
|
||||||
|
section.Seek(offset, 0)
|
||||||
|
sr = ioutil.NopCloser(section)
|
||||||
|
} else {
|
||||||
|
cr, err := newCompressedReader(section, hdr.OriginalSize, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sr = cr
|
||||||
|
}
|
||||||
|
|
||||||
|
return sr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) {
|
||||||
|
rsrc, err := r.resourceReader(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rsrc.Close()
|
||||||
|
return ioutil.ReadAll(rsrc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readXML() (string, error) {
|
||||||
|
if r.hdr.XMLData.CompressedSize() == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
rsrc, err := r.resourceReader(&r.hdr.XMLData)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer rsrc.Close()
|
||||||
|
|
||||||
|
XMLData := make([]uint16, r.hdr.XMLData.OriginalSize/2)
|
||||||
|
err = binary.Read(rsrc, binary.LittleEndian, XMLData)
|
||||||
|
if err != nil {
|
||||||
|
return "", &ParseError{Oper: "XML data", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The BOM will always indicate little-endian UTF-16.
|
||||||
|
if XMLData[0] != 0xfeff {
|
||||||
|
return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")}
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(XMLData[1:])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resourceDescriptor, []*Image, error) {
|
||||||
|
fileData := make(map[SHA1Hash]resourceDescriptor)
|
||||||
|
var images []*Image
|
||||||
|
|
||||||
|
offsetTable, err := r.readResource(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, &ParseError{Oper: "offset table", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
br := bytes.NewReader(offsetTable)
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
var res streamDescriptor
|
||||||
|
err := binary.Read(br, binary.LittleEndian, &res)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, &ParseError{Oper: "offset table", Err: err}
|
||||||
|
}
|
||||||
|
if res.Flags()&^supportedResFlags != 0 {
|
||||||
|
return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation for ad-hoc testing
|
||||||
|
if validate {
|
||||||
|
sec, err := r.resourceReader(&res.resourceDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprint(i, err))
|
||||||
|
}
|
||||||
|
hash := sha1.New()
|
||||||
|
_, err = io.Copy(hash, sec)
|
||||||
|
sec.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprint(i, err))
|
||||||
|
}
|
||||||
|
var cmphash SHA1Hash
|
||||||
|
copy(cmphash[:], hash.Sum(nil))
|
||||||
|
if cmphash != res.Hash {
|
||||||
|
panic(fmt.Sprint(i, "hash mismatch"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Flags()&resFlagMetadata != 0 {
|
||||||
|
image := &Image{
|
||||||
|
wim: r,
|
||||||
|
offset: res.resourceDescriptor,
|
||||||
|
}
|
||||||
|
images = append(images, image)
|
||||||
|
} else {
|
||||||
|
fileData[res.Hash] = res.resourceDescriptor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(images) != int(r.hdr.ImageCount) {
|
||||||
|
return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileData, images, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, err error) {
|
||||||
|
var secBlock securityblockDisk
|
||||||
|
err = binary.Read(rsrc, binary.LittleEndian, &secBlock)
|
||||||
|
if err != nil {
|
||||||
|
err = &ParseError{Oper: "security table", Err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n += securityblockDiskSize
|
||||||
|
|
||||||
|
secSizes := make([]int64, secBlock.NumEntries)
|
||||||
|
err = binary.Read(rsrc, binary.LittleEndian, &secSizes)
|
||||||
|
if err != nil {
|
||||||
|
err = &ParseError{Oper: "security table sizes", Err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n += int64(secBlock.NumEntries * 8)
|
||||||
|
|
||||||
|
sds = make([][]byte, secBlock.NumEntries)
|
||||||
|
for i, size := range secSizes {
|
||||||
|
sd := make([]byte, size&0xffffffff)
|
||||||
|
_, err = io.ReadFull(rsrc, sd)
|
||||||
|
if err != nil {
|
||||||
|
err = &ParseError{Oper: "security descriptor", Err: err}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += int64(len(sd))
|
||||||
|
sds[i] = sd
|
||||||
|
}
|
||||||
|
|
||||||
|
secsize := int64((secBlock.TotalLength + 7) &^ 7)
|
||||||
|
if n > secsize {
|
||||||
|
err = &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyN(ioutil.Discard, rsrc, secsize-n)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n = secsize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open parses the image and returns the root directory.
|
||||||
|
func (img *Image) Open() (*File, error) {
|
||||||
|
if img.sds == nil {
|
||||||
|
rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, img.rootOffset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sds, n, err := img.wim.readSecurityDescriptors(rsrc)
|
||||||
|
if err != nil {
|
||||||
|
rsrc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
img.sds = sds
|
||||||
|
img.r = rsrc
|
||||||
|
img.rootOffset = n
|
||||||
|
img.curOffset = n
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := img.readdir(img.rootOffset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(f) != 1 {
|
||||||
|
return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")}
|
||||||
|
}
|
||||||
|
return f[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) reset() {
|
||||||
|
if img.r != nil {
|
||||||
|
img.r.Close()
|
||||||
|
img.r = nil
|
||||||
|
}
|
||||||
|
img.curOffset = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) readdir(offset int64) ([]*File, error) {
|
||||||
|
img.m.Lock()
|
||||||
|
defer img.m.Unlock()
|
||||||
|
|
||||||
|
if offset < img.curOffset || offset > img.curOffset+chunkSize {
|
||||||
|
// Reset to seek backward or to seek forward very far.
|
||||||
|
img.reset()
|
||||||
|
}
|
||||||
|
if img.r == nil {
|
||||||
|
rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, offset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
img.r = rsrc
|
||||||
|
img.curOffset = offset
|
||||||
|
}
|
||||||
|
if offset > img.curOffset {
|
||||||
|
_, err := io.CopyN(ioutil.Discard, img.r, offset-img.curOffset)
|
||||||
|
if err != nil {
|
||||||
|
img.reset()
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries []*File
|
||||||
|
for {
|
||||||
|
e, n, err := img.readNextEntry(img.r)
|
||||||
|
img.curOffset += n
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
img.reset()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
entries = append(entries, e)
|
||||||
|
}
|
||||||
|
return entries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) {
|
||||||
|
var length int64
|
||||||
|
err := binary.Read(r, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory length check", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if length == 0 {
|
||||||
|
return nil, 8, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
left := length
|
||||||
|
if left < direntrySize {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short")}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dentry direntry
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &dentry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left -= direntrySize
|
||||||
|
|
||||||
|
namesLen := int64(dentry.FileNameLength + 2 + dentry.ShortNameLength)
|
||||||
|
if left < namesLen {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short for names")}
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make([]uint16, namesLen/2)
|
||||||
|
err = binary.Read(r, binary.LittleEndian, names)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "file name", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left -= namesLen
|
||||||
|
|
||||||
|
var name, shortName string
|
||||||
|
if dentry.FileNameLength > 0 {
|
||||||
|
name = string(utf16.Decode(names[:dentry.FileNameLength/2]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if dentry.ShortNameLength > 0 {
|
||||||
|
shortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:]))
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset resourceDescriptor
|
||||||
|
zerohash := SHA1Hash{}
|
||||||
|
if dentry.Hash != zerohash {
|
||||||
|
var ok bool
|
||||||
|
offset, ok = img.wim.fileData[dentry.Hash]
|
||||||
|
if !ok {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %#v", dentry)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f := &File{
|
||||||
|
FileHeader: FileHeader{
|
||||||
|
Attributes: dentry.Attributes,
|
||||||
|
CreationTime: dentry.CreationTime,
|
||||||
|
LastAccessTime: dentry.LastAccessTime,
|
||||||
|
LastWriteTime: dentry.LastWriteTime,
|
||||||
|
Hash: dentry.Hash,
|
||||||
|
Size: offset.OriginalSize,
|
||||||
|
Name: name,
|
||||||
|
ShortName: shortName,
|
||||||
|
},
|
||||||
|
|
||||||
|
offset: offset,
|
||||||
|
img: img,
|
||||||
|
subdirOffset: dentry.SubdirOffset,
|
||||||
|
}
|
||||||
|
|
||||||
|
isDir := false
|
||||||
|
|
||||||
|
if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT == 0 {
|
||||||
|
f.LinkID = dentry.ReparseHardLink
|
||||||
|
if dentry.Attributes&FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
isDir = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f.ReparseTag = uint32(dentry.ReparseHardLink)
|
||||||
|
f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDir && f.subdirOffset == 0 {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")}
|
||||||
|
} else if !isDir && f.subdirOffset != 0 {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dentry.SecurityID != 0xffffffff {
|
||||||
|
f.SecurityDescriptor = img.sds[dentry.SecurityID]
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyN(ioutil.Discard, r, left)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dentry.StreamCount > 0 {
|
||||||
|
var streams []*Stream
|
||||||
|
for i := uint16(0); i < dentry.StreamCount; i++ {
|
||||||
|
s, n, err := img.readNextStream(r)
|
||||||
|
length += n
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
// The first unnamed stream should be treated as the file stream.
|
||||||
|
if i == 0 && s.Name == "" {
|
||||||
|
f.Hash = s.Hash
|
||||||
|
f.Size = s.Size
|
||||||
|
f.offset = s.offset
|
||||||
|
} else if s.Name != "" {
|
||||||
|
streams = append(streams, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.Streams = streams
|
||||||
|
}
|
||||||
|
|
||||||
|
if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 {
|
||||||
|
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("reparse point is missing reparse stream")}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) {
|
||||||
|
var length int64
|
||||||
|
err := binary.Read(r, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, 0, &ParseError{Oper: "stream length check", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left := length
|
||||||
|
if left < streamentrySize {
|
||||||
|
return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short")}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sentry streamentry
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &sentry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "stream entry", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left -= streamentrySize
|
||||||
|
|
||||||
|
if left < int64(sentry.NameLength) {
|
||||||
|
return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short for name")}
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make([]uint16, sentry.NameLength/2)
|
||||||
|
err = binary.Read(r, binary.LittleEndian, names)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, &ParseError{Oper: "file name", Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
left -= int64(sentry.NameLength)
|
||||||
|
name := string(utf16.Decode(names))
|
||||||
|
|
||||||
|
var offset resourceDescriptor
|
||||||
|
if sentry.Hash != (SHA1Hash{}) {
|
||||||
|
var ok bool
|
||||||
|
offset, ok = img.wim.fileData[sentry.Hash]
|
||||||
|
if !ok {
|
||||||
|
return nil, 0, &ParseError{Oper: "stream entry", Path: name, Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Stream{
|
||||||
|
StreamHeader: StreamHeader{
|
||||||
|
Hash: sentry.Hash,
|
||||||
|
Size: offset.OriginalSize,
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
wim: img.wim,
|
||||||
|
offset: offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.CopyN(ioutil.Discard, r, left)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, length, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns an io.ReadCloser that can be used to read the stream's contents.
|
||||||
|
func (s *Stream) Open() (io.ReadCloser, error) {
|
||||||
|
return s.wim.resourceReader(&s.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns an io.ReadCloser that can be used to read the file's contents.
|
||||||
|
func (f *File) Open() (io.ReadCloser, error) {
|
||||||
|
return f.img.wim.resourceReader(&f.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readdir reads the directory entries.
|
||||||
|
func (f *File) Readdir() ([]*File, error) {
|
||||||
|
if !f.IsDir() {
|
||||||
|
return nil, errors.New("not a directory")
|
||||||
|
}
|
||||||
|
return f.img.readdir(f.subdirOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir returns whether the given file is a directory. It returns false when it
|
||||||
|
// is a directory reparse point.
|
||||||
|
func (f *FileHeader) IsDir() bool {
|
||||||
|
return f.Attributes&(FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_DIRECTORY
|
||||||
|
}
|
520
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
520
vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,520 @@
|
||||||
|
// MACHINE GENERATED BY 'go generate' COMMAND; DO NOT EDIT
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
// TODO: add more here, after collecting data on the common
|
||||||
|
// error values see on Windows. (perhaps when running
|
||||||
|
// all.bat?)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
|
||||||
|
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||||
|
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||||
|
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
||||||
|
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
||||||
|
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||||
|
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||||
|
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||||
|
procWaitNamedPipeW = modkernel32.NewProc("WaitNamedPipeW")
|
||||||
|
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
||||||
|
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
||||||
|
procLocalAlloc = modkernel32.NewProc("LocalAlloc")
|
||||||
|
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
||||||
|
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
||||||
|
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
|
||||||
|
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
|
||||||
|
procLocalFree = modkernel32.NewProc("LocalFree")
|
||||||
|
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
|
||||||
|
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
|
||||||
|
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
|
||||||
|
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||||
|
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
||||||
|
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||||
|
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
||||||
|
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||||
|
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
||||||
|
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
|
||||||
|
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||||
|
procBackupRead = modkernel32.NewProc("BackupRead")
|
||||||
|
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||||
|
)
|
||||||
|
|
||||||
|
func cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procCancelIoEx.Addr(), 2, uintptr(file), uintptr(unsafe.Pointer(o)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
|
||||||
|
newport = syscall.Handle(r0)
|
||||||
|
if newport == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueuedCompletionStatus(port syscall.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetQueuedCompletionStatus.Addr(), 5, uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procSetFileCompletionNotificationModes.Addr(), 2, uintptr(h), uintptr(flags), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(o)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall9(procCreateNamedPipeW.Addr(), 8, uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)), 0)
|
||||||
|
handle = syscall.Handle(r0)
|
||||||
|
if handle == syscall.InvalidHandle {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
|
||||||
|
handle = syscall.Handle(r0)
|
||||||
|
if handle == syscall.InvalidHandle {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitNamedPipe(name string, timeout uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _waitNamedPipe(_p0, timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _waitNamedPipe(name *uint16, timeout uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procWaitNamedPipeW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(timeout), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetNamedPipeInfo.Addr(), 5, uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func localAlloc(uFlags uint32, length uint32) (ptr uintptr) {
|
||||||
|
r0, _, _ := syscall.Syscall(procLocalAlloc.Addr(), 2, uintptr(uFlags), uintptr(length), 0)
|
||||||
|
ptr = uintptr(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(accountName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall9(procLookupAccountNameW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procConvertSidToStringSidW.Addr(), 2, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(str)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _convertStringSecurityDescriptorToSecurityDescriptor(_p0, revision, sd, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision uint32, sd *uintptr, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procConvertStringSecurityDescriptorToSecurityDescriptorW.Addr(), 4, uintptr(unsafe.Pointer(str)), uintptr(revision), uintptr(unsafe.Pointer(sd)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procConvertSecurityDescriptorToStringSecurityDescriptorW.Addr(), 5, uintptr(unsafe.Pointer(sd)), uintptr(revision), uintptr(secInfo), uintptr(unsafe.Pointer(sddl)), uintptr(unsafe.Pointer(sddlSize)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func localFree(mem uintptr) {
|
||||||
|
syscall.Syscall(procLocalFree.Addr(), 1, uintptr(mem), 0, 0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSecurityDescriptorLength(sd uintptr) (len uint32) {
|
||||||
|
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
|
||||||
|
len = uint32(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFileInformationByHandleEx(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileInformationByHandle(h syscall.Handle, class uint32, buffer *byte, size uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procSetFileInformationByHandle.Addr(), 4, uintptr(h), uintptr(class), uintptr(unsafe.Pointer(buffer)), uintptr(size), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if releaseAll {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r0, _, e1 := syscall.Syscall6(procAdjustTokenPrivileges.Addr(), 6, uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
|
||||||
|
success = r0 != 0
|
||||||
|
if true {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func impersonateSelf(level uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procImpersonateSelf.Addr(), 1, uintptr(level), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func revertToSelf() (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procRevertToSelf.Addr(), 0, 0, 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func openThreadToken(thread syscall.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if openAsSelf {
|
||||||
|
_p0 = 1
|
||||||
|
} else {
|
||||||
|
_p0 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall6(procOpenThreadToken.Addr(), 4, uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentThread() (h syscall.Handle) {
|
||||||
|
r0, _, _ := syscall.Syscall(procGetCurrentThread.Addr(), 0, 0, 0, 0)
|
||||||
|
h = syscall.Handle(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *uint16
|
||||||
|
_p1, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeValue(_p0, _p1, luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall(procLookupPrivilegeValueW.Addr(), 3, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeName(_p0, luid, buffer, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeNameW.Addr(), 4, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.Syscall6(procLookupPrivilegeDisplayNameW.Addr(), 5, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)), 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
} else {
|
||||||
|
_p1 = 0
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
} else {
|
||||||
|
_p2 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall9(procBackupRead.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupWrite(h syscall.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
} else {
|
||||||
|
_p1 = 0
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
} else {
|
||||||
|
_p2 = 0
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.Syscall9(procBackupWrite.Addr(), 7, uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)), 0, 0)
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
41
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
41
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
|
@ -1,41 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type JSONFormatter struct {
|
|
||||||
// TimestampFormat sets the format used for marshaling timestamps.
|
|
||||||
TimestampFormat string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
|
||||||
data := make(Fields, len(entry.Data)+3)
|
|
||||||
for k, v := range entry.Data {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case error:
|
|
||||||
// Otherwise errors are ignored by `encoding/json`
|
|
||||||
// https://github.com/Sirupsen/logrus/issues/137
|
|
||||||
data[k] = v.Error()
|
|
||||||
default:
|
|
||||||
data[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prefixFieldClashes(data)
|
|
||||||
|
|
||||||
timestampFormat := f.TimestampFormat
|
|
||||||
if timestampFormat == "" {
|
|
||||||
timestampFormat = DefaultTimestampFormat
|
|
||||||
}
|
|
||||||
|
|
||||||
data["time"] = entry.Time.Format(timestampFormat)
|
|
||||||
data["msg"] = entry.Message
|
|
||||||
data["level"] = entry.Level.String()
|
|
||||||
|
|
||||||
serialized, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
|
||||||
}
|
|
||||||
return append(serialized, '\n'), nil
|
|
||||||
}
|
|
212
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
212
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
|
@ -1,212 +0,0 @@
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Logger struct {
|
|
||||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
|
||||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
|
||||||
// something more adventorous, such as logging to Kafka.
|
|
||||||
Out io.Writer
|
|
||||||
// Hooks for the logger instance. These allow firing events based on logging
|
|
||||||
// levels and log entries. For example, to send errors to an error tracking
|
|
||||||
// service, log to StatsD or dump the core on fatal errors.
|
|
||||||
Hooks LevelHooks
|
|
||||||
// All log entries pass through the formatter before logged to Out. The
|
|
||||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
|
||||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
|
||||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
|
||||||
// own that implements the `Formatter` interface, see the `README` or included
|
|
||||||
// formatters for examples.
|
|
||||||
Formatter Formatter
|
|
||||||
// The logging level the logger should log at. This is typically (and defaults
|
|
||||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
|
||||||
// logged. `logrus.Debug` is useful in
|
|
||||||
Level Level
|
|
||||||
// Used to sync writing to the log.
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
|
||||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
|
||||||
// instantiate your own:
|
|
||||||
//
|
|
||||||
// var log = &Logger{
|
|
||||||
// Out: os.Stderr,
|
|
||||||
// Formatter: new(JSONFormatter),
|
|
||||||
// Hooks: make(LevelHooks),
|
|
||||||
// Level: logrus.DebugLevel,
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// It's recommended to make this a global instance called `log`.
|
|
||||||
func New() *Logger {
|
|
||||||
return &Logger{
|
|
||||||
Out: os.Stderr,
|
|
||||||
Formatter: new(TextFormatter),
|
|
||||||
Hooks: make(LevelHooks),
|
|
||||||
Level: InfoLevel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a field to the log entry, note that you it doesn't log until you call
|
|
||||||
// Debug, Print, Info, Warn, Fatal or Panic. It only creates a log entry.
|
|
||||||
// If you want multiple fields, use `WithFields`.
|
|
||||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
|
||||||
return NewEntry(logger).WithField(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
|
||||||
// each `Field`.
|
|
||||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
|
||||||
return NewEntry(logger).WithFields(fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an error as single field to the log entry. All it does is call
|
|
||||||
// `WithError` for the given `error`.
|
|
||||||
func (logger *Logger) WithError(err error) *Entry {
|
|
||||||
return NewEntry(logger).WithError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= DebugLevel {
|
|
||||||
NewEntry(logger).Debugf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= InfoLevel {
|
|
||||||
NewEntry(logger).Infof(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
|
||||||
NewEntry(logger).Printf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
NewEntry(logger).Warnf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
NewEntry(logger).Warnf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= ErrorLevel {
|
|
||||||
NewEntry(logger).Errorf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= FatalLevel {
|
|
||||||
NewEntry(logger).Fatalf(format, args...)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
|
||||||
if logger.Level >= PanicLevel {
|
|
||||||
NewEntry(logger).Panicf(format, args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debug(args ...interface{}) {
|
|
||||||
if logger.Level >= DebugLevel {
|
|
||||||
NewEntry(logger).Debug(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Info(args ...interface{}) {
|
|
||||||
if logger.Level >= InfoLevel {
|
|
||||||
NewEntry(logger).Info(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Print(args ...interface{}) {
|
|
||||||
NewEntry(logger).Info(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warn(args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
NewEntry(logger).Warn(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warning(args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
NewEntry(logger).Warn(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Error(args ...interface{}) {
|
|
||||||
if logger.Level >= ErrorLevel {
|
|
||||||
NewEntry(logger).Error(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatal(args ...interface{}) {
|
|
||||||
if logger.Level >= FatalLevel {
|
|
||||||
NewEntry(logger).Fatal(args...)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panic(args ...interface{}) {
|
|
||||||
if logger.Level >= PanicLevel {
|
|
||||||
NewEntry(logger).Panic(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Debugln(args ...interface{}) {
|
|
||||||
if logger.Level >= DebugLevel {
|
|
||||||
NewEntry(logger).Debugln(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Infoln(args ...interface{}) {
|
|
||||||
if logger.Level >= InfoLevel {
|
|
||||||
NewEntry(logger).Infoln(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Println(args ...interface{}) {
|
|
||||||
NewEntry(logger).Println(args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warnln(args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
NewEntry(logger).Warnln(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Warningln(args ...interface{}) {
|
|
||||||
if logger.Level >= WarnLevel {
|
|
||||||
NewEntry(logger).Warnln(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Errorln(args ...interface{}) {
|
|
||||||
if logger.Level >= ErrorLevel {
|
|
||||||
NewEntry(logger).Errorln(args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
|
||||||
if logger.Level >= FatalLevel {
|
|
||||||
NewEntry(logger).Fatalln(args...)
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (logger *Logger) Panicln(args ...interface{}) {
|
|
||||||
if logger.Level >= PanicLevel {
|
|
||||||
NewEntry(logger).Panicln(args...)
|
|
||||||
}
|
|
||||||
}
|
|
9
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
9
vendor/github.com/Sirupsen/logrus/terminal_bsd.go
generated
vendored
|
@ -1,9 +0,0 @@
|
||||||
// +build darwin freebsd openbsd netbsd dragonfly
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
const ioctlReadTermios = syscall.TIOCGETA
|
|
||||||
|
|
||||||
type Termios syscall.Termios
|
|
21
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
21
vendor/github.com/Sirupsen/logrus/terminal_notwindows.go
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build linux darwin freebsd openbsd netbsd dragonfly
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stderr
|
|
||||||
var termios Termios
|
|
||||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
|
||||||
return err == 0
|
|
||||||
}
|
|
15
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
15
vendor/github.com/Sirupsen/logrus/terminal_solaris.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// +build solaris
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
_, err := unix.IoctlGetTermios(int(os.Stdout.Fd()), unix.TCGETA)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
27
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
27
vendor/github.com/Sirupsen/logrus/terminal_windows.go
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
// Based on ssh/terminal:
|
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package logrus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
|
|
||||||
var (
|
|
||||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsTerminal returns true if stderr's file descriptor is a terminal.
|
|
||||||
func IsTerminal() bool {
|
|
||||||
fd := syscall.Stderr
|
|
||||||
var st uint32
|
|
||||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0)
|
|
||||||
return r != 0 && e == 0
|
|
||||||
}
|
|
26
vendor/github.com/containerd/console/.travis.yml
generated
vendored
Normal file
26
vendor/github.com/containerd/console/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.9.3
|
||||||
|
- 1.9.4
|
||||||
|
- tip
|
||||||
|
|
||||||
|
go_import_path: github.com/containerd/console
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get -d
|
||||||
|
- GOOS=openbsd go get -d
|
||||||
|
- GOOS=solaris go get -d
|
||||||
|
- GOOS=windows go get -d
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -race
|
||||||
|
- GOOS=openbsd go build
|
||||||
|
- GOOS=openbsd go test -c
|
||||||
|
- GOOS=solaris go build
|
||||||
|
- GOOS=solaris go test -c
|
||||||
|
- GOOS=windows go test
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: 1.9.4
|
||||||
|
|
201
vendor/github.com/containerd/console/LICENSE
generated
vendored
Normal file
201
vendor/github.com/containerd/console/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
17
vendor/github.com/containerd/console/README.md
generated
vendored
Normal file
17
vendor/github.com/containerd/console/README.md
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# console
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/containerd/console.svg?branch=master)](https://travis-ci.org/containerd/console)
|
||||||
|
|
||||||
|
Golang package for dealing with consoles. Light on deps and a simple API.
|
||||||
|
|
||||||
|
## Modifying the current process
|
||||||
|
|
||||||
|
```go
|
||||||
|
current := console.Current()
|
||||||
|
defer current.Reset()
|
||||||
|
|
||||||
|
if err := current.SetRaw(); err != nil {
|
||||||
|
}
|
||||||
|
ws, err := current.Size()
|
||||||
|
current.Resize(ws)
|
||||||
|
```
|
78
vendor/github.com/containerd/console/console.go
generated
vendored
Normal file
78
vendor/github.com/containerd/console/console.go
generated
vendored
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotAConsole = errors.New("provided file is not a console")
|
||||||
|
|
||||||
|
type Console interface {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
io.Closer
|
||||||
|
|
||||||
|
// Resize resizes the console to the provided window size
|
||||||
|
Resize(WinSize) error
|
||||||
|
// ResizeFrom resizes the calling console to the size of the
|
||||||
|
// provided console
|
||||||
|
ResizeFrom(Console) error
|
||||||
|
// SetRaw sets the console in raw mode
|
||||||
|
SetRaw() error
|
||||||
|
// DisableEcho disables echo on the console
|
||||||
|
DisableEcho() error
|
||||||
|
// Reset restores the console to its orignal state
|
||||||
|
Reset() error
|
||||||
|
// Size returns the window size of the console
|
||||||
|
Size() (WinSize, error)
|
||||||
|
// Fd returns the console's file descriptor
|
||||||
|
Fd() uintptr
|
||||||
|
// Name returns the console's file name
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WinSize specifies the window size of the console
|
||||||
|
type WinSize struct {
|
||||||
|
// Height of the console
|
||||||
|
Height uint16
|
||||||
|
// Width of the console
|
||||||
|
Width uint16
|
||||||
|
x uint16
|
||||||
|
y uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current returns the current processes console
|
||||||
|
func Current() Console {
|
||||||
|
c, err := ConsoleFromFile(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
// stdin should always be a console for the design
|
||||||
|
// of this function
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConsoleFromFile returns a console using the provided file
|
||||||
|
func ConsoleFromFile(f *os.File) (Console, error) {
|
||||||
|
if err := checkConsole(f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newMaster(f)
|
||||||
|
}
|
271
vendor/github.com/containerd/console/console_linux.go
generated
vendored
Normal file
271
vendor/github.com/containerd/console/console_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxEvents = 128
|
||||||
|
)
|
||||||
|
|
||||||
|
// Epoller manages multiple epoll consoles using edge-triggered epoll api so we
|
||||||
|
// dont have to deal with repeated wake-up of EPOLLER or EPOLLHUP.
|
||||||
|
// For more details, see:
|
||||||
|
// - https://github.com/systemd/systemd/pull/4262
|
||||||
|
// - https://github.com/moby/moby/issues/27202
|
||||||
|
//
|
||||||
|
// Example usage of Epoller and EpollConsole can be as follow:
|
||||||
|
//
|
||||||
|
// epoller, _ := NewEpoller()
|
||||||
|
// epollConsole, _ := epoller.Add(console)
|
||||||
|
// go epoller.Wait()
|
||||||
|
// var (
|
||||||
|
// b bytes.Buffer
|
||||||
|
// wg sync.WaitGroup
|
||||||
|
// )
|
||||||
|
// wg.Add(1)
|
||||||
|
// go func() {
|
||||||
|
// io.Copy(&b, epollConsole)
|
||||||
|
// wg.Done()
|
||||||
|
// }()
|
||||||
|
// // perform I/O on the console
|
||||||
|
// epollConsole.Shutdown(epoller.CloseConsole)
|
||||||
|
// wg.Wait()
|
||||||
|
// epollConsole.Close()
|
||||||
|
type Epoller struct {
|
||||||
|
efd int
|
||||||
|
mu sync.Mutex
|
||||||
|
fdMapping map[int]*EpollConsole
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEpoller returns an instance of epoller with a valid epoll fd.
|
||||||
|
func NewEpoller() (*Epoller, error) {
|
||||||
|
efd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Epoller{
|
||||||
|
efd: efd,
|
||||||
|
fdMapping: make(map[int]*EpollConsole),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add creates a epoll console based on the provided console. The console will
|
||||||
|
// be registered with EPOLLET (i.e. using edge-triggered notification) and its
|
||||||
|
// file descriptor will be set to non-blocking mode. After this, user should use
|
||||||
|
// the return console to perform I/O.
|
||||||
|
func (e *Epoller) Add(console Console) (*EpollConsole, error) {
|
||||||
|
sysfd := int(console.Fd())
|
||||||
|
// Set sysfd to non-blocking mode
|
||||||
|
if err := unix.SetNonblock(sysfd, true); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ev := unix.EpollEvent{
|
||||||
|
Events: unix.EPOLLIN | unix.EPOLLOUT | unix.EPOLLRDHUP | unix.EPOLLET,
|
||||||
|
Fd: int32(sysfd),
|
||||||
|
}
|
||||||
|
if err := unix.EpollCtl(e.efd, unix.EPOLL_CTL_ADD, sysfd, &ev); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ef := &EpollConsole{
|
||||||
|
Console: console,
|
||||||
|
sysfd: sysfd,
|
||||||
|
readc: sync.NewCond(&sync.Mutex{}),
|
||||||
|
writec: sync.NewCond(&sync.Mutex{}),
|
||||||
|
}
|
||||||
|
e.mu.Lock()
|
||||||
|
e.fdMapping[sysfd] = ef
|
||||||
|
e.mu.Unlock()
|
||||||
|
return ef, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait starts the loop to wait for its consoles' notifications and signal
|
||||||
|
// appropriate console that it can perform I/O.
|
||||||
|
func (e *Epoller) Wait() error {
|
||||||
|
events := make([]unix.EpollEvent, maxEvents)
|
||||||
|
for {
|
||||||
|
n, err := unix.EpollWait(e.efd, events, -1)
|
||||||
|
if err != nil {
|
||||||
|
// EINTR: The call was interrupted by a signal handler before either
|
||||||
|
// any of the requested events occurred or the timeout expired
|
||||||
|
if err == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
ev := &events[i]
|
||||||
|
// the console is ready to be read from
|
||||||
|
if ev.Events&(unix.EPOLLIN|unix.EPOLLHUP|unix.EPOLLERR) != 0 {
|
||||||
|
if epfile := e.getConsole(int(ev.Fd)); epfile != nil {
|
||||||
|
epfile.signalRead()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the console is ready to be written to
|
||||||
|
if ev.Events&(unix.EPOLLOUT|unix.EPOLLHUP|unix.EPOLLERR) != 0 {
|
||||||
|
if epfile := e.getConsole(int(ev.Fd)); epfile != nil {
|
||||||
|
epfile.signalWrite()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close unregister the console's file descriptor from epoll interface
|
||||||
|
func (e *Epoller) CloseConsole(fd int) error {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
delete(e.fdMapping, fd)
|
||||||
|
return unix.EpollCtl(e.efd, unix.EPOLL_CTL_DEL, fd, &unix.EpollEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Epoller) getConsole(sysfd int) *EpollConsole {
|
||||||
|
e.mu.Lock()
|
||||||
|
f := e.fdMapping[sysfd]
|
||||||
|
e.mu.Unlock()
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the epoll fd
|
||||||
|
func (e *Epoller) Close() error {
|
||||||
|
return unix.Close(e.efd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EpollConsole acts like a console but register its file descriptor with a
|
||||||
|
// epoll fd and uses epoll API to perform I/O.
|
||||||
|
type EpollConsole struct {
|
||||||
|
Console
|
||||||
|
readc *sync.Cond
|
||||||
|
writec *sync.Cond
|
||||||
|
sysfd int
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads up to len(p) bytes into p. It returns the number of bytes read
|
||||||
|
// (0 <= n <= len(p)) and any error encountered.
|
||||||
|
//
|
||||||
|
// If the console's read returns EAGAIN or EIO, we assumes that its a
|
||||||
|
// temporary error because the other side went away and wait for the signal
|
||||||
|
// generated by epoll event to continue.
|
||||||
|
func (ec *EpollConsole) Read(p []byte) (n int, err error) {
|
||||||
|
var read int
|
||||||
|
ec.readc.L.Lock()
|
||||||
|
defer ec.readc.L.Unlock()
|
||||||
|
for {
|
||||||
|
read, err = ec.Console.Read(p[n:])
|
||||||
|
n += read
|
||||||
|
if err != nil {
|
||||||
|
var hangup bool
|
||||||
|
if perr, ok := err.(*os.PathError); ok {
|
||||||
|
hangup = (perr.Err == unix.EAGAIN || perr.Err == unix.EIO)
|
||||||
|
} else {
|
||||||
|
hangup = (err == unix.EAGAIN || err == unix.EIO)
|
||||||
|
}
|
||||||
|
// if the other end disappear, assume this is temporary and wait for the
|
||||||
|
// signal to continue again. Unless we didnt read anything and the
|
||||||
|
// console is already marked as closed then we should exit
|
||||||
|
if hangup && !(n == 0 && len(p) > 0 && ec.closed) {
|
||||||
|
ec.readc.Wait()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// if we didnt read anything then return io.EOF to end gracefully
|
||||||
|
if n == 0 && len(p) > 0 && err == nil {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
// signal for others that we finished the read
|
||||||
|
ec.readc.Signal()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writes len(p) bytes from p to the console. It returns the number of bytes
|
||||||
|
// written from p (0 <= n <= len(p)) and any error encountered that caused
|
||||||
|
// the write to stop early.
|
||||||
|
//
|
||||||
|
// If writes to the console returns EAGAIN or EIO, we assumes that its a
|
||||||
|
// temporary error because the other side went away and wait for the signal
|
||||||
|
// generated by epoll event to continue.
|
||||||
|
func (ec *EpollConsole) Write(p []byte) (n int, err error) {
|
||||||
|
var written int
|
||||||
|
ec.writec.L.Lock()
|
||||||
|
defer ec.writec.L.Unlock()
|
||||||
|
for {
|
||||||
|
written, err = ec.Console.Write(p[n:])
|
||||||
|
n += written
|
||||||
|
if err != nil {
|
||||||
|
var hangup bool
|
||||||
|
if perr, ok := err.(*os.PathError); ok {
|
||||||
|
hangup = (perr.Err == unix.EAGAIN || perr.Err == unix.EIO)
|
||||||
|
} else {
|
||||||
|
hangup = (err == unix.EAGAIN || err == unix.EIO)
|
||||||
|
}
|
||||||
|
// if the other end disappear, assume this is temporary and wait for the
|
||||||
|
// signal to continue again.
|
||||||
|
if hangup {
|
||||||
|
ec.writec.Wait()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unrecoverable error, break the loop and return the error
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if n < len(p) && err == nil {
|
||||||
|
err = io.ErrShortWrite
|
||||||
|
}
|
||||||
|
// signal for others that we finished the write
|
||||||
|
ec.writec.Signal()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closed the file descriptor and signal call waiters for this fd.
|
||||||
|
// It accepts a callback which will be called with the console's fd. The
|
||||||
|
// callback typically will be used to do further cleanup such as unregister the
|
||||||
|
// console's fd from the epoll interface.
|
||||||
|
// User should call Shutdown and wait for all I/O operation to be finished
|
||||||
|
// before closing the console.
|
||||||
|
func (ec *EpollConsole) Shutdown(close func(int) error) error {
|
||||||
|
ec.readc.L.Lock()
|
||||||
|
defer ec.readc.L.Unlock()
|
||||||
|
ec.writec.L.Lock()
|
||||||
|
defer ec.writec.L.Unlock()
|
||||||
|
|
||||||
|
ec.readc.Broadcast()
|
||||||
|
ec.writec.Broadcast()
|
||||||
|
ec.closed = true
|
||||||
|
return close(ec.sysfd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// signalRead signals that the console is readable.
|
||||||
|
func (ec *EpollConsole) signalRead() {
|
||||||
|
ec.readc.Signal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// signalWrite signals that the console is writable.
|
||||||
|
func (ec *EpollConsole) signalWrite() {
|
||||||
|
ec.writec.Signal()
|
||||||
|
}
|
88
vendor/github.com/containerd/console/console_linux_test.go
generated
vendored
Normal file
88
vendor/github.com/containerd/console/console_linux_test.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEpollConsole(t *testing.T) {
|
||||||
|
console, slavePath, err := NewPty()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer console.Close()
|
||||||
|
|
||||||
|
slave, err := os.OpenFile(slavePath, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer slave.Close()
|
||||||
|
|
||||||
|
iteration := 10
|
||||||
|
|
||||||
|
cmd := exec.Command("sh", "-c", fmt.Sprintf("for x in `seq 1 %d`; do echo -n test; done", iteration))
|
||||||
|
cmd.Stdin = slave
|
||||||
|
cmd.Stdout = slave
|
||||||
|
cmd.Stderr = slave
|
||||||
|
|
||||||
|
epoller, err := NewEpoller()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
epollConsole, err := epoller.Add(console)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
go epoller.Wait()
|
||||||
|
|
||||||
|
var (
|
||||||
|
b bytes.Buffer
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
io.Copy(&b, epollConsole)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
slave.Close()
|
||||||
|
if err := epollConsole.Shutdown(epoller.CloseConsole); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
if err := epollConsole.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedOutput := ""
|
||||||
|
for i := 0; i < iteration; i++ {
|
||||||
|
expectedOutput += "test"
|
||||||
|
}
|
||||||
|
if out := b.String(); out != expectedOutput {
|
||||||
|
t.Errorf("unexpected output %q", out)
|
||||||
|
}
|
||||||
|
}
|
100
vendor/github.com/containerd/console/console_test.go
generated
vendored
Normal file
100
vendor/github.com/containerd/console/console_test.go
generated
vendored
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// +build linux solaris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWinSize(t *testing.T) {
|
||||||
|
c, _, err := NewPty()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
if err := c.Resize(WinSize{
|
||||||
|
Width: 11,
|
||||||
|
Height: 10,
|
||||||
|
}); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
size, err := c.Size()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if size.Width != 11 {
|
||||||
|
t.Errorf("width should be 11 but received %d", size.Width)
|
||||||
|
}
|
||||||
|
if size.Height != 10 {
|
||||||
|
t.Errorf("height should be 10 but received %d", size.Height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConsolePty(t *testing.T) {
|
||||||
|
console, slavePath, err := NewPty()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer console.Close()
|
||||||
|
|
||||||
|
slave, err := os.OpenFile(slavePath, os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer slave.Close()
|
||||||
|
|
||||||
|
iteration := 10
|
||||||
|
|
||||||
|
cmd := exec.Command("sh", "-c", fmt.Sprintf("for x in `seq 1 %d`; do echo -n test; done", iteration))
|
||||||
|
cmd.Stdin = slave
|
||||||
|
cmd.Stdout = slave
|
||||||
|
cmd.Stderr = slave
|
||||||
|
|
||||||
|
var (
|
||||||
|
b bytes.Buffer
|
||||||
|
wg sync.WaitGroup
|
||||||
|
)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
io.Copy(&b, console)
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
slave.Close()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
expectedOutput := ""
|
||||||
|
for i := 0; i < iteration; i++ {
|
||||||
|
expectedOutput += "test"
|
||||||
|
}
|
||||||
|
if out := b.String(); out != expectedOutput {
|
||||||
|
t.Errorf("unexpected output %q", out)
|
||||||
|
}
|
||||||
|
}
|
158
vendor/github.com/containerd/console/console_unix.go
generated
vendored
Normal file
158
vendor/github.com/containerd/console/console_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
// +build darwin freebsd linux openbsd solaris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPty creates a new pty pair
|
||||||
|
// The master is returned as the first console and a string
|
||||||
|
// with the path to the pty slave is returned as the second
|
||||||
|
func NewPty() (Console, string, error) {
|
||||||
|
f, err := os.OpenFile("/dev/ptmx", unix.O_RDWR|unix.O_NOCTTY|unix.O_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
slave, err := ptsname(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
if err := unlockpt(f); err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
m, err := newMaster(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return m, slave, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type master struct {
|
||||||
|
f *os.File
|
||||||
|
original *unix.Termios
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Read(b []byte) (int, error) {
|
||||||
|
return m.f.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Write(b []byte) (int, error) {
|
||||||
|
return m.f.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Close() error {
|
||||||
|
return m.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Resize(ws WinSize) error {
|
||||||
|
return tcswinsz(m.f.Fd(), ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) ResizeFrom(c Console) error {
|
||||||
|
ws, err := c.Size()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return m.Resize(ws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Reset() error {
|
||||||
|
if m.original == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return tcset(m.f.Fd(), m.original)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) getCurrent() (unix.Termios, error) {
|
||||||
|
var termios unix.Termios
|
||||||
|
if err := tcget(m.f.Fd(), &termios); err != nil {
|
||||||
|
return unix.Termios{}, err
|
||||||
|
}
|
||||||
|
return termios, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) SetRaw() error {
|
||||||
|
rawState, err := m.getCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawState = cfmakeraw(rawState)
|
||||||
|
rawState.Oflag = rawState.Oflag | unix.OPOST
|
||||||
|
return tcset(m.f.Fd(), &rawState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) DisableEcho() error {
|
||||||
|
rawState, err := m.getCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawState.Lflag = rawState.Lflag &^ unix.ECHO
|
||||||
|
return tcset(m.f.Fd(), &rawState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Size() (WinSize, error) {
|
||||||
|
return tcgwinsz(m.f.Fd())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Fd() uintptr {
|
||||||
|
return m.f.Fd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Name() string {
|
||||||
|
return m.f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkConsole checks if the provided file is a console
|
||||||
|
func checkConsole(f *os.File) error {
|
||||||
|
var termios unix.Termios
|
||||||
|
if tcget(f.Fd(), &termios) != nil {
|
||||||
|
return ErrNotAConsole
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMaster(f *os.File) (Console, error) {
|
||||||
|
m := &master{
|
||||||
|
f: f,
|
||||||
|
}
|
||||||
|
t, err := m.getCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.original = &t
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
|
||||||
|
// created by us acts normally. In particular, a not-very-well-known default of
|
||||||
|
// Linux unix98 ptys is that they have +onlcr by default. While this isn't a
|
||||||
|
// problem for terminal emulators, because we relay data from the terminal we
|
||||||
|
// also relay that funky line discipline.
|
||||||
|
func ClearONLCR(fd uintptr) error {
|
||||||
|
return setONLCR(fd, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetONLCR sets the necessary tty_ioctl(4)s to ensure that a pty pair
|
||||||
|
// created by us acts as intended for a terminal emulator.
|
||||||
|
func SetONLCR(fd uintptr) error {
|
||||||
|
return setONLCR(fd, true)
|
||||||
|
}
|
216
vendor/github.com/containerd/console/console_windows.go
generated
vendored
Normal file
216
vendor/github.com/containerd/console/console_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,216 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
vtInputSupported bool
|
||||||
|
ErrNotImplemented = errors.New("not implemented")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *master) initStdios() {
|
||||||
|
m.in = windows.Handle(os.Stdin.Fd())
|
||||||
|
if err := windows.GetConsoleMode(m.in, &m.inMode); err == nil {
|
||||||
|
// Validate that windows.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it.
|
||||||
|
if err = windows.SetConsoleMode(m.in, m.inMode|windows.ENABLE_VIRTUAL_TERMINAL_INPUT); err == nil {
|
||||||
|
vtInputSupported = true
|
||||||
|
}
|
||||||
|
// Unconditionally set the console mode back even on failure because SetConsoleMode
|
||||||
|
// remembers invalid bits on input handles.
|
||||||
|
windows.SetConsoleMode(m.in, m.inMode)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("failed to get console mode for stdin: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.out = windows.Handle(os.Stdout.Fd())
|
||||||
|
if err := windows.GetConsoleMode(m.out, &m.outMode); err == nil {
|
||||||
|
if err := windows.SetConsoleMode(m.out, m.outMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
|
||||||
|
m.outMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
} else {
|
||||||
|
windows.SetConsoleMode(m.out, m.outMode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("failed to get console mode for stdout: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.err = windows.Handle(os.Stderr.Fd())
|
||||||
|
if err := windows.GetConsoleMode(m.err, &m.errMode); err == nil {
|
||||||
|
if err := windows.SetConsoleMode(m.err, m.errMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err == nil {
|
||||||
|
m.errMode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
} else {
|
||||||
|
windows.SetConsoleMode(m.err, m.errMode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("failed to get console mode for stderr: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type master struct {
|
||||||
|
in windows.Handle
|
||||||
|
inMode uint32
|
||||||
|
|
||||||
|
out windows.Handle
|
||||||
|
outMode uint32
|
||||||
|
|
||||||
|
err windows.Handle
|
||||||
|
errMode uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) SetRaw() error {
|
||||||
|
if err := makeInputRaw(m.in, m.inMode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set StdOut and StdErr to raw mode, we ignore failures since
|
||||||
|
// windows.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this version of
|
||||||
|
// Windows.
|
||||||
|
|
||||||
|
windows.SetConsoleMode(m.out, m.outMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
|
|
||||||
|
windows.SetConsoleMode(m.err, m.errMode|windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Reset() error {
|
||||||
|
for _, s := range []struct {
|
||||||
|
fd windows.Handle
|
||||||
|
mode uint32
|
||||||
|
}{
|
||||||
|
{m.in, m.inMode},
|
||||||
|
{m.out, m.outMode},
|
||||||
|
{m.err, m.errMode},
|
||||||
|
} {
|
||||||
|
if err := windows.SetConsoleMode(s.fd, s.mode); err != nil {
|
||||||
|
return errors.Wrap(err, "unable to restore console mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Size() (WinSize, error) {
|
||||||
|
var info windows.ConsoleScreenBufferInfo
|
||||||
|
err := windows.GetConsoleScreenBufferInfo(m.out, &info)
|
||||||
|
if err != nil {
|
||||||
|
return WinSize{}, errors.Wrap(err, "unable to get console info")
|
||||||
|
}
|
||||||
|
|
||||||
|
winsize := WinSize{
|
||||||
|
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
||||||
|
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
return winsize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Resize(ws WinSize) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) ResizeFrom(c Console) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) DisableEcho() error {
|
||||||
|
mode := m.inMode &^ windows.ENABLE_ECHO_INPUT
|
||||||
|
mode |= windows.ENABLE_PROCESSED_INPUT
|
||||||
|
mode |= windows.ENABLE_LINE_INPUT
|
||||||
|
|
||||||
|
if err := windows.SetConsoleMode(m.in, mode); err != nil {
|
||||||
|
return errors.Wrap(err, "unable to set console to disable echo")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Read(b []byte) (int, error) {
|
||||||
|
panic("not implemented on windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Write(b []byte) (int, error) {
|
||||||
|
panic("not implemented on windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *master) Fd() uintptr {
|
||||||
|
return uintptr(m.in)
|
||||||
|
}
|
||||||
|
|
||||||
|
// on windows, console can only be made from os.Std{in,out,err}, hence there
|
||||||
|
// isnt a single name here we can use. Return a dummy "console" value in this
|
||||||
|
// case should be sufficient.
|
||||||
|
func (m *master) Name() string {
|
||||||
|
return "console"
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeInputRaw puts the terminal (Windows Console) connected to the given
|
||||||
|
// file descriptor into raw mode
|
||||||
|
func makeInputRaw(fd windows.Handle, mode uint32) error {
|
||||||
|
// See
|
||||||
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||||
|
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||||
|
|
||||||
|
// Disable these modes
|
||||||
|
mode &^= windows.ENABLE_ECHO_INPUT
|
||||||
|
mode &^= windows.ENABLE_LINE_INPUT
|
||||||
|
mode &^= windows.ENABLE_MOUSE_INPUT
|
||||||
|
mode &^= windows.ENABLE_WINDOW_INPUT
|
||||||
|
mode &^= windows.ENABLE_PROCESSED_INPUT
|
||||||
|
|
||||||
|
// Enable these modes
|
||||||
|
mode |= windows.ENABLE_EXTENDED_FLAGS
|
||||||
|
mode |= windows.ENABLE_INSERT_MODE
|
||||||
|
mode |= windows.ENABLE_QUICK_EDIT_MODE
|
||||||
|
|
||||||
|
if vtInputSupported {
|
||||||
|
mode |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := windows.SetConsoleMode(fd, mode); err != nil {
|
||||||
|
return errors.Wrap(err, "unable to set console to raw mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkConsole(f *os.File) error {
|
||||||
|
var mode uint32
|
||||||
|
if err := windows.GetConsoleMode(windows.Handle(f.Fd()), &mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMaster(f *os.File) (Console, error) {
|
||||||
|
if f != os.Stdin && f != os.Stdout && f != os.Stderr {
|
||||||
|
return nil, errors.New("creating a console from a file is not supported on windows")
|
||||||
|
}
|
||||||
|
m := &master{}
|
||||||
|
m.initStdios()
|
||||||
|
return m, nil
|
||||||
|
}
|
53
vendor/github.com/containerd/console/tc_darwin.go
generated
vendored
Normal file
53
vendor/github.com/containerd/console/tc_darwin.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TIOCGETA
|
||||||
|
cmdTcSet = unix.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
func ioctl(fd, flag, data uintptr) error {
|
||||||
|
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, flag, data); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
var u int32
|
||||||
|
return ioctl(f.Fd(), unix.TIOCPTYUNLK, uintptr(unsafe.Pointer(&u)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCPTYGNAME)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("/dev/pts/%d", n), nil
|
||||||
|
}
|
45
vendor/github.com/containerd/console/tc_freebsd.go
generated
vendored
Normal file
45
vendor/github.com/containerd/console/tc_freebsd.go
generated
vendored
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TIOCGETA
|
||||||
|
cmdTcSet = unix.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
// This does not exist on FreeBSD, it does not allocate controlling terminals on open
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
n, err := unix.IoctlGetInt(int(f.Fd()), unix.TIOCGPTN)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("/dev/pts/%d", n), nil
|
||||||
|
}
|
49
vendor/github.com/containerd/console/tc_linux.go
generated
vendored
Normal file
49
vendor/github.com/containerd/console/tc_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TCGETS
|
||||||
|
cmdTcSet = unix.TCSETS
|
||||||
|
)
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
var u int32
|
||||||
|
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCSPTLCK, uintptr(unsafe.Pointer(&u))); err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
var u uint32
|
||||||
|
if _, _, err := unix.Syscall(unix.SYS_IOCTL, f.Fd(), unix.TIOCGPTN, uintptr(unsafe.Pointer(&u))); err != 0 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("/dev/pts/%d", u), nil
|
||||||
|
}
|
51
vendor/github.com/containerd/console/tc_openbsd_cgo.go
generated
vendored
Normal file
51
vendor/github.com/containerd/console/tc_openbsd_cgo.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// +build openbsd,cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
//#include <stdlib.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TIOCGETA
|
||||||
|
cmdTcSet = unix.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
ptspath, err := C.ptsname(C.int(f.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return C.GoString(ptspath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
if _, err := C.grantpt(C.int(f.Fd())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
47
vendor/github.com/containerd/console/tc_openbsd_nocgo.go
generated
vendored
Normal file
47
vendor/github.com/containerd/console/tc_openbsd_nocgo.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// +build openbsd,!cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Implementing the functions below requires cgo support. Non-cgo stubs
|
||||||
|
// versions are defined below to enable cross-compilation of source code
|
||||||
|
// that depends on these functions, but the resultant cross-compiled
|
||||||
|
// binaries cannot actually be used. If the stub function(s) below are
|
||||||
|
// actually invoked they will display an error message and cause the
|
||||||
|
// calling process to exit.
|
||||||
|
//
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TIOCGETA
|
||||||
|
cmdTcSet = unix.TIOCSETA
|
||||||
|
)
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
panic("ptsname() support requires cgo.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
panic("unlockpt() support requires cgo.")
|
||||||
|
}
|
51
vendor/github.com/containerd/console/tc_solaris_cgo.go
generated
vendored
Normal file
51
vendor/github.com/containerd/console/tc_solaris_cgo.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// +build solaris,cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
//#include <stdlib.h>
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TCGETS
|
||||||
|
cmdTcSet = unix.TCSETS
|
||||||
|
)
|
||||||
|
|
||||||
|
// ptsname retrieves the name of the first available pts for the given master.
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
ptspath, err := C.ptsname(C.int(f.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return C.GoString(ptspath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockpt unlocks the slave pseudoterminal device corresponding to the master pseudoterminal referred to by f.
|
||||||
|
// unlockpt should be called before opening the slave side of a pty.
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
if _, err := C.grantpt(C.int(f.Fd())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
47
vendor/github.com/containerd/console/tc_solaris_nocgo.go
generated
vendored
Normal file
47
vendor/github.com/containerd/console/tc_solaris_nocgo.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// +build solaris,!cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
//
|
||||||
|
// Implementing the functions below requires cgo support. Non-cgo stubs
|
||||||
|
// versions are defined below to enable cross-compilation of source code
|
||||||
|
// that depends on these functions, but the resultant cross-compiled
|
||||||
|
// binaries cannot actually be used. If the stub function(s) below are
|
||||||
|
// actually invoked they will display an error message and cause the
|
||||||
|
// calling process to exit.
|
||||||
|
//
|
||||||
|
|
||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cmdTcGet = unix.TCGETS
|
||||||
|
cmdTcSet = unix.TCSETS
|
||||||
|
)
|
||||||
|
|
||||||
|
func ptsname(f *os.File) (string, error) {
|
||||||
|
panic("ptsname() support requires cgo.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func unlockpt(f *os.File) error {
|
||||||
|
panic("unlockpt() support requires cgo.")
|
||||||
|
}
|
91
vendor/github.com/containerd/console/tc_unix.go
generated
vendored
Normal file
91
vendor/github.com/containerd/console/tc_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
// +build darwin freebsd linux openbsd solaris
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright The containerd 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 console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func tcget(fd uintptr, p *unix.Termios) error {
|
||||||
|
termios, err := unix.IoctlGetTermios(int(fd), cmdTcGet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = *termios
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcset(fd uintptr, p *unix.Termios) error {
|
||||||
|
return unix.IoctlSetTermios(int(fd), cmdTcSet, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcgwinsz(fd uintptr) (WinSize, error) {
|
||||||
|
var ws WinSize
|
||||||
|
|
||||||
|
uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
|
||||||
|
if err != nil {
|
||||||
|
return ws, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate from unix.Winsize to console.WinSize
|
||||||
|
ws.Height = uws.Row
|
||||||
|
ws.Width = uws.Col
|
||||||
|
ws.x = uws.Xpixel
|
||||||
|
ws.y = uws.Ypixel
|
||||||
|
return ws, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcswinsz(fd uintptr, ws WinSize) error {
|
||||||
|
// Translate from console.WinSize to unix.Winsize
|
||||||
|
|
||||||
|
var uws unix.Winsize
|
||||||
|
uws.Row = ws.Height
|
||||||
|
uws.Col = ws.Width
|
||||||
|
uws.Xpixel = ws.x
|
||||||
|
uws.Ypixel = ws.y
|
||||||
|
|
||||||
|
return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, &uws)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setONLCR(fd uintptr, enable bool) error {
|
||||||
|
var termios unix.Termios
|
||||||
|
if err := tcget(fd, &termios); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if enable {
|
||||||
|
// Set +onlcr so we can act like a real terminal
|
||||||
|
termios.Oflag |= unix.ONLCR
|
||||||
|
} else {
|
||||||
|
// Set -onlcr so we don't have to deal with \r.
|
||||||
|
termios.Oflag &^= unix.ONLCR
|
||||||
|
}
|
||||||
|
return tcset(fd, &termios)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cfmakeraw(t unix.Termios) unix.Termios {
|
||||||
|
t.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON)
|
||||||
|
t.Oflag &^= unix.OPOST
|
||||||
|
t.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN)
|
||||||
|
t.Cflag &^= (unix.CSIZE | unix.PARENB)
|
||||||
|
t.Cflag &^= unix.CS8
|
||||||
|
t.Cc[unix.VMIN] = 1
|
||||||
|
t.Cc[unix.VTIME] = 0
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
25
vendor/github.com/containerd/continuity/.gitignore
generated
vendored
Normal file
25
vendor/github.com/containerd/continuity/.gitignore
generated
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
bin
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
*.prof
|
1
vendor/github.com/containerd/continuity/.mailmap
generated
vendored
Normal file
1
vendor/github.com/containerd/continuity/.mailmap
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.github.com>
|
21
vendor/github.com/containerd/continuity/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/containerd/continuity/.travis.yml
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
language: go
|
||||||
|
sudo: required
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- tip
|
||||||
|
|
||||||
|
go_import_path: github.com/containerd/continuity
|
||||||
|
|
||||||
|
env:
|
||||||
|
# NOTE: we cannot set GOOS directly (because gimme overrides the value)
|
||||||
|
- TRAVIS_GOOS=windows
|
||||||
|
- TRAVIS_GOOS=linux
|
||||||
|
- TRAVIS_GOOS=darwin
|
||||||
|
|
||||||
|
script:
|
||||||
|
- export GOOS=${TRAVIS_GOOS}
|
||||||
|
- make build binaries
|
||||||
|
- if [ "$GOOS" = "linux" ]; then make vet test; fi
|
||||||
|
- if [ "$GOOS" != "linux" ]; then make test-compile; fi
|
16
vendor/github.com/containerd/continuity/AUTHORS
generated
vendored
Normal file
16
vendor/github.com/containerd/continuity/AUTHORS
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
Aaron Lehmann <aaron.lehmann@docker.com>
|
||||||
|
Akash Gupta <akagup@microsoft.com>
|
||||||
|
Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
|
||||||
|
Andrew Pennebaker <apennebaker@datapipe.com>
|
||||||
|
Brandon Philips <brandon.philips@coreos.com>
|
||||||
|
Christopher Jones <tophj@linux.vnet.ibm.com>
|
||||||
|
Daniel, Dao Quang Minh <dqminh89@gmail.com>
|
||||||
|
Derek McGowan <derek@mcgstyle.net>
|
||||||
|
Edward Pilatowicz <edward.pilatowicz@oracle.com>
|
||||||
|
Ian Campbell <ijc@docker.com>
|
||||||
|
Justin Cormack <justin.cormack@docker.com>
|
||||||
|
Justin Cummins <sul3n3t@gmail.com>
|
||||||
|
Phil Estes <estesp@gmail.com>
|
||||||
|
Stephen J Day <stephen.day@docker.com>
|
||||||
|
Tobias Klauser <tklauser@distanz.ch>
|
||||||
|
Tonis Tiigi <tonistiigi@gmail.com>
|
202
vendor/github.com/containerd/continuity/LICENSE
generated
vendored
Normal file
202
vendor/github.com/containerd/continuity/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
68
vendor/github.com/containerd/continuity/Makefile
generated
vendored
Normal file
68
vendor/github.com/containerd/continuity/Makefile
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
# Set an output prefix, which is the local directory if not specified
|
||||||
|
PREFIX?=$(shell pwd)
|
||||||
|
|
||||||
|
# Used to populate version variable in main package.
|
||||||
|
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
|
||||||
|
|
||||||
|
GO_LDFLAGS=-ldflags "-X `go list ./version`.Version=$(VERSION)"
|
||||||
|
|
||||||
|
PACKAGES=$(shell go list ./... | grep -v /vendor/)
|
||||||
|
|
||||||
|
.PHONY: clean all fmt vet lint build test binaries setup
|
||||||
|
.DEFAULT: default
|
||||||
|
# skip lint at the moment
|
||||||
|
all: AUTHORS clean fmt vet fmt build test binaries
|
||||||
|
|
||||||
|
AUTHORS: .mailmap .git/HEAD
|
||||||
|
git log --format='%aN <%aE>' | sort -fu > $@
|
||||||
|
|
||||||
|
# This only needs to be generated by hand when cutting full releases.
|
||||||
|
version/version.go:
|
||||||
|
./version/version.sh > $@
|
||||||
|
|
||||||
|
${PREFIX}/bin/continuity: version/version.go $(shell find . -type f -name '*.go')
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -o $@ ${GO_LDFLAGS} ${GO_GCFLAGS} ./cmd/continuity
|
||||||
|
|
||||||
|
setup:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go get -u github.com/golang/lint/golint
|
||||||
|
|
||||||
|
generate:
|
||||||
|
go generate $(PACKAGES)
|
||||||
|
|
||||||
|
# Depends on binaries because vet will silently fail if it can't load compiled
|
||||||
|
# imports
|
||||||
|
vet: binaries
|
||||||
|
@echo "+ $@"
|
||||||
|
@go vet $(PACKAGES)
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@echo "+ $@"
|
||||||
|
@test -z "$$(gofmt -s -l . | grep -v Godeps/_workspace/src/ | grep -v vendor/ | tee /dev/stderr)" || \
|
||||||
|
echo "+ please format Go code with 'gofmt -s'"
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@echo "+ $@"
|
||||||
|
@test -z "$$(golint $(PACKAGES) | grep -v Godeps/_workspace/src/ | grep -v vendor/ |tee /dev/stderr)"
|
||||||
|
|
||||||
|
build:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go build -v ${GO_LDFLAGS} $(PACKAGES)
|
||||||
|
|
||||||
|
test:
|
||||||
|
@echo "+ $@"
|
||||||
|
@go test $(PACKAGES)
|
||||||
|
|
||||||
|
test-compile:
|
||||||
|
@echo "+ $@"
|
||||||
|
@for pkg in $(PACKAGES); do go test -c $$pkg; done
|
||||||
|
|
||||||
|
binaries: ${PREFIX}/bin/continuity
|
||||||
|
@echo "+ $@"
|
||||||
|
@if [ x$$GOOS = xwindows ]; then echo "+ continuity -> continuity.exe"; mv ${PREFIX}/bin/continuity ${PREFIX}/bin/continuity.exe; fi
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo "+ $@"
|
||||||
|
@rm -rf "${PREFIX}/bin/continuity" "${PREFIX}/bin/continuity.exe"
|
||||||
|
|
74
vendor/github.com/containerd/continuity/README.md
generated
vendored
Normal file
74
vendor/github.com/containerd/continuity/README.md
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
# continuity
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/containerd/continuity?status.svg)](https://godoc.org/github.com/containerd/continuity)
|
||||||
|
[![Build Status](https://travis-ci.org/containerd/continuity.svg?branch=master)](https://travis-ci.org/containerd/continuity)
|
||||||
|
|
||||||
|
A transport-agnostic, filesystem metadata manifest system
|
||||||
|
|
||||||
|
This project is a staging area for experiments in providing transport agnostic
|
||||||
|
metadata storage.
|
||||||
|
|
||||||
|
Please see https://github.com/opencontainers/specs/issues/11 for more details.
|
||||||
|
|
||||||
|
## Manifest Format
|
||||||
|
|
||||||
|
A continuity manifest encodes filesystem metadata in Protocol Buffers.
|
||||||
|
Please refer to [proto/manifest.proto](proto/manifest.proto).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Build:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ make
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a manifest (of this repo itself):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ ./bin/continuity build . > /tmp/a.pb
|
||||||
|
```
|
||||||
|
|
||||||
|
Dump a manifest:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ ./bin/continuity ls /tmp/a.pb
|
||||||
|
...
|
||||||
|
-rw-rw-r-- 270 B /.gitignore
|
||||||
|
-rw-rw-r-- 88 B /.mailmap
|
||||||
|
-rw-rw-r-- 187 B /.travis.yml
|
||||||
|
-rw-rw-r-- 359 B /AUTHORS
|
||||||
|
-rw-rw-r-- 11 kB /LICENSE
|
||||||
|
-rw-rw-r-- 1.5 kB /Makefile
|
||||||
|
...
|
||||||
|
-rw-rw-r-- 986 B /testutil_test.go
|
||||||
|
drwxrwxr-x 0 B /version
|
||||||
|
-rw-rw-r-- 478 B /version/version.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify a manifest:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ ./bin/continuity verify . /tmp/a.pb
|
||||||
|
```
|
||||||
|
|
||||||
|
Break the directory and restore using the manifest:
|
||||||
|
```console
|
||||||
|
$ chmod 777 Makefile
|
||||||
|
$ ./bin/continuity verify . /tmp/a.pb
|
||||||
|
2017/06/23 08:00:34 error verifying manifest: resource "/Makefile" has incorrect mode: -rwxrwxrwx != -rw-rw-r--
|
||||||
|
$ ./bin/continuity apply . /tmp/a.pb
|
||||||
|
$ stat -c %a Makefile
|
||||||
|
664
|
||||||
|
$ ./bin/continuity verify . /tmp/a.pb
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Contribution Guide
|
||||||
|
### Building Proto Package
|
||||||
|
|
||||||
|
If you change the proto file you will need to rebuild the generated Go with `go generate`.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ go generate ./proto
|
||||||
|
```
|
11
vendor/github.com/containerd/continuity/cmd/continuity/main.go
generated
vendored
Normal file
11
vendor/github.com/containerd/continuity/cmd/continuity/main.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "crypto/sha256"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/commands"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
commands.MainCmd.Execute()
|
||||||
|
}
|
36
vendor/github.com/containerd/continuity/commands/apply.go
generated
vendored
Normal file
36
vendor/github.com/containerd/continuity/commands/apply.go
generated
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ApplyCmd = &cobra.Command{
|
||||||
|
Use: "apply <root> [<manifest>]",
|
||||||
|
Short: "Apply the manifest to the provided root",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
root, path := args[0], args[1]
|
||||||
|
|
||||||
|
p, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := continuity.Unmarshal(p)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error unmarshaling manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, err := continuity.NewContext(root)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error getting context: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := continuity.ApplyManifest(ctx, m); err != nil {
|
||||||
|
log.Fatalf("error applying manifest: %v", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
48
vendor/github.com/containerd/continuity/commands/build.go
generated
vendored
Normal file
48
vendor/github.com/containerd/continuity/commands/build.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
buildCmdConfig struct {
|
||||||
|
format string
|
||||||
|
}
|
||||||
|
|
||||||
|
BuildCmd = &cobra.Command{
|
||||||
|
Use: "build <root>",
|
||||||
|
Short: "Build a manifest for the provided root",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
log.Fatalln("please specify a root")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, err := continuity.NewContext(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error creating path context: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := continuity.BuildManifest(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error generating manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := continuity.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error marshaling manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stdout.Write(p); err != nil {
|
||||||
|
log.Fatalf("error writing to stdout: %v", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
BuildCmd.Flags().StringVar(&buildCmdConfig.format, "format", "pb", "specify the output format of the manifest")
|
||||||
|
}
|
44
vendor/github.com/containerd/continuity/commands/dump.go
generated
vendored
Normal file
44
vendor/github.com/containerd/continuity/commands/dump.go
generated
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
pb "github.com/containerd/continuity/proto"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DumpCmd = &cobra.Command{
|
||||||
|
Use: "dump <manifest>",
|
||||||
|
Short: "Dump the contents of the manifest in protobuf text format",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
var p []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if len(args) < 1 {
|
||||||
|
p, err = ioutil.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading manifest: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p, err = ioutil.ReadFile(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading manifest: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bm pb.Manifest
|
||||||
|
|
||||||
|
if err := proto.Unmarshal(p, &bm); err != nil {
|
||||||
|
log.Fatalf("error unmarshaling manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): For now, just dump the text format. Turn this into
|
||||||
|
// nice text output later.
|
||||||
|
if err := proto.MarshalText(os.Stdout, &bm); err != nil {
|
||||||
|
log.Fatalf("error dumping manifest: %v", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
41
vendor/github.com/containerd/continuity/commands/ls.go
generated
vendored
Normal file
41
vendor/github.com/containerd/continuity/commands/ls.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var LSCmd = &cobra.Command{
|
||||||
|
Use: "ls <manifest>",
|
||||||
|
Short: "List the contents of the manifest.",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
log.Fatalln("please specify a manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
bm, err := readManifestFile(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
|
||||||
|
|
||||||
|
for _, entry := range bm.Resource {
|
||||||
|
for _, path := range entry.Path {
|
||||||
|
if os.FileMode(entry.Mode)&os.ModeSymlink != 0 {
|
||||||
|
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v -> %v\n", os.FileMode(entry.Mode), entry.User, entry.Group, humanize.Bytes(uint64(entry.Size)), path, entry.Target)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n", os.FileMode(entry.Mode), entry.User, entry.Group, humanize.Bytes(uint64(entry.Size)), path)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Flush()
|
||||||
|
},
|
||||||
|
}
|
82
vendor/github.com/containerd/continuity/commands/main.go
generated
vendored
Normal file
82
vendor/github.com/containerd/continuity/commands/main.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
pb "github.com/containerd/continuity/proto"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
MainCmd = &cobra.Command{
|
||||||
|
Use: "continuity <command>",
|
||||||
|
Short: "A transport-agnostic filesytem metadata tool.",
|
||||||
|
}
|
||||||
|
|
||||||
|
// usageTemplate is nearly identical to the default template without the
|
||||||
|
// automatic addition of flags. Instead, Command.Use is used unmodified.
|
||||||
|
usageTemplate = `Usage:{{if .Runnable}}
|
||||||
|
{{.UseLine}}{{end}}{{if .HasSubCommands}}
|
||||||
|
{{ .CommandPath}} [command]{{end}}{{if gt .Aliases 0}}
|
||||||
|
|
||||||
|
Aliases:
|
||||||
|
{{.NameAndAliases}}
|
||||||
|
{{end}}{{if .HasExample}}
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
{{ .Example }}{{end}}{{ if .HasAvailableSubCommands}}
|
||||||
|
|
||||||
|
Available Commands:{{range .Commands}}{{if .IsAvailableCommand}}
|
||||||
|
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasLocalFlags}}
|
||||||
|
|
||||||
|
Flags:
|
||||||
|
{{.LocalFlags.FlagUsages | trimRightSpace}}{{end}}{{ if .HasInheritedFlags}}
|
||||||
|
|
||||||
|
Global Flags:
|
||||||
|
{{.InheritedFlags.FlagUsages | trimRightSpace}}{{end}}{{if .HasHelpSubCommands}}
|
||||||
|
|
||||||
|
Additional help topics:{{range .Commands}}{{if .IsHelpCommand}}
|
||||||
|
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{ if .HasSubCommands }}
|
||||||
|
|
||||||
|
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
MainCmd.AddCommand(BuildCmd)
|
||||||
|
MainCmd.AddCommand(VerifyCmd)
|
||||||
|
MainCmd.AddCommand(ApplyCmd)
|
||||||
|
MainCmd.AddCommand(LSCmd)
|
||||||
|
MainCmd.AddCommand(StatsCmd)
|
||||||
|
MainCmd.AddCommand(DumpCmd)
|
||||||
|
if MountCmd != nil {
|
||||||
|
MainCmd.AddCommand(MountCmd)
|
||||||
|
}
|
||||||
|
MainCmd.SetUsageTemplate(usageTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readManifestFile reads the manifest from the given path. This should
|
||||||
|
// probably be provided by the continuity library.
|
||||||
|
func readManifestFile(path string) (*pb.Manifest, error) {
|
||||||
|
p, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bm pb.Manifest
|
||||||
|
|
||||||
|
if err := proto.Unmarshal(p, &bm); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTabwriter provides a common tabwriter with defaults.
|
||||||
|
func newTabwriter(w io.Writer) *tabwriter.Writer {
|
||||||
|
return tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
|
||||||
|
}
|
115
vendor/github.com/containerd/continuity/commands/mount.go
generated
vendored
Normal file
115
vendor/github.com/containerd/continuity/commands/mount.go
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
// +build linux darwin freebsd
|
||||||
|
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
"bazil.org/fuse/fs"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity"
|
||||||
|
"github.com/containerd/continuity/continuityfs"
|
||||||
|
"github.com/containerd/continuity/driver"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MountCmd = &cobra.Command{
|
||||||
|
Use: "mount <mountpoint> [<manifest>] [<source directory>]",
|
||||||
|
Short: "Mount the manifest to the provided mountpoint using content from a source directory",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 3 {
|
||||||
|
log.Fatal("Must specify mountpoint, manifest, and source directory")
|
||||||
|
}
|
||||||
|
mountpoint := args[0]
|
||||||
|
manifest, source := args[1], args[2]
|
||||||
|
|
||||||
|
manifestName := filepath.Base(manifest)
|
||||||
|
|
||||||
|
p, err := ioutil.ReadFile(manifest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := continuity.Unmarshal(p)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error unmarshaling manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
driver, err := driver.NewSystemDriver()
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := continuityfs.NewFSFileContentProvider(source, driver)
|
||||||
|
|
||||||
|
contfs, err := continuityfs.NewFSFromManifest(m, mountpoint, provider)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := fuse.Mount(
|
||||||
|
mountpoint,
|
||||||
|
fuse.ReadOnly(),
|
||||||
|
fuse.FSName(manifestName),
|
||||||
|
fuse.Subtype("continuity"),
|
||||||
|
// OSX Only options
|
||||||
|
fuse.LocalVolume(),
|
||||||
|
fuse.VolumeName("Continuity FileSystem"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
<-c.Ready
|
||||||
|
if err := c.MountError; err != nil {
|
||||||
|
c.Close()
|
||||||
|
logrus.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
// TODO: Create server directory to use context
|
||||||
|
err = fs.Serve(c, contfs)
|
||||||
|
if err != nil {
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, os.Interrupt)
|
||||||
|
signal.Notify(sigChan, syscall.SIGTERM)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-sigChan:
|
||||||
|
logrus.Infof("Shutting down")
|
||||||
|
case err = <-errChan:
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := c.Close(); err != nil {
|
||||||
|
logrus.Errorf("Unable to close connection %s", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for any inprogress requests to be handled
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
|
logrus.Infof("Attempting unmount")
|
||||||
|
if err := fuse.Unmount(mountpoint); err != nil {
|
||||||
|
logrus.Errorf("Error unmounting %s: %v", mountpoint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle server error
|
||||||
|
if err != nil {
|
||||||
|
logrus.Fatalf("Error serving fuse server: %v", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
9
vendor/github.com/containerd/continuity/commands/mount_unsupported.go
generated
vendored
Normal file
9
vendor/github.com/containerd/continuity/commands/mount_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// +build windows solaris
|
||||||
|
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var MountCmd *cobra.Command = nil
|
58
vendor/github.com/containerd/continuity/commands/stats.go
generated
vendored
Normal file
58
vendor/github.com/containerd/continuity/commands/stats.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/dustin/go-humanize"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
StatsCmd = &cobra.Command{
|
||||||
|
Use: "stats <manifest>",
|
||||||
|
Short: "display statistics about the specified manifest",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 1 {
|
||||||
|
log.Fatalln("please specify a manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
bm, err := readManifestFile(args[0])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stats struct {
|
||||||
|
resources int
|
||||||
|
files int
|
||||||
|
directories int
|
||||||
|
totalSize int64
|
||||||
|
symlinks int
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range bm.Resource {
|
||||||
|
stats.resources++
|
||||||
|
stats.totalSize += int64(entry.Size)
|
||||||
|
|
||||||
|
mode := os.FileMode(entry.Mode)
|
||||||
|
if mode.IsRegular() {
|
||||||
|
stats.files += len(entry.Path) // count hardlinks!
|
||||||
|
} else if mode.IsDir() {
|
||||||
|
stats.directories++
|
||||||
|
} else if mode&os.ModeSymlink != 0 {
|
||||||
|
stats.symlinks++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w := newTabwriter(os.Stdout)
|
||||||
|
defer w.Flush()
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "resources\t%v\n", stats.resources)
|
||||||
|
fmt.Fprintf(w, "directories\t%v\n", stats.directories)
|
||||||
|
fmt.Fprintf(w, "files\t%v\n", stats.files)
|
||||||
|
fmt.Fprintf(w, "symlinks\t%v\n", stats.symlinks)
|
||||||
|
fmt.Fprintf(w, "size\t%v\n", humanize.Bytes(uint64(stats.totalSize)))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
41
vendor/github.com/containerd/continuity/commands/verify.go
generated
vendored
Normal file
41
vendor/github.com/containerd/continuity/commands/verify.go
generated
vendored
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var VerifyCmd = &cobra.Command{
|
||||||
|
Use: "verify <root> [<manifest>]",
|
||||||
|
Short: "Verify the root against the provided manifest",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
log.Fatalln("please specify a root and manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
root, path := args[0], args[1]
|
||||||
|
|
||||||
|
p, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error reading manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := continuity.Unmarshal(p)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error unmarshaling manifest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, err := continuity.NewContext(root)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error getting context: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := continuity.VerifyManifest(ctx, m); err != nil {
|
||||||
|
// TODO(stevvooe): Support more interesting error reporting.
|
||||||
|
log.Fatalf("error verifying manifest: %v", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
641
vendor/github.com/containerd/continuity/context.go
generated
vendored
Normal file
641
vendor/github.com/containerd/continuity/context.go
generated
vendored
Normal file
|
@ -0,0 +1,641 @@
|
||||||
|
package continuity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/devices"
|
||||||
|
driverpkg "github.com/containerd/continuity/driver"
|
||||||
|
"github.com/containerd/continuity/pathdriver"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = fmt.Errorf("not found")
|
||||||
|
ErrNotSupported = fmt.Errorf("not supported")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context represents a file system context for accessing resources. The
|
||||||
|
// responsibility of the context is to convert system specific resources to
|
||||||
|
// generic Resource objects. Most of this is safe path manipulation, as well
|
||||||
|
// as extraction of resource details.
|
||||||
|
type Context interface {
|
||||||
|
Apply(Resource) error
|
||||||
|
Verify(Resource) error
|
||||||
|
Resource(string, os.FileInfo) (Resource, error)
|
||||||
|
Walk(filepath.WalkFunc) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SymlinkPath is intended to give the symlink target value
|
||||||
|
// in a root context. Target and linkname are absolute paths
|
||||||
|
// not under the given root.
|
||||||
|
type SymlinkPath func(root, linkname, target string) (string, error)
|
||||||
|
|
||||||
|
type ContextOptions struct {
|
||||||
|
Digester Digester
|
||||||
|
Driver driverpkg.Driver
|
||||||
|
PathDriver pathdriver.PathDriver
|
||||||
|
Provider ContentProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// context represents a file system context for accessing resources.
|
||||||
|
// Generally, all path qualified access and system considerations should land
|
||||||
|
// here.
|
||||||
|
type context struct {
|
||||||
|
driver driverpkg.Driver
|
||||||
|
pathDriver pathdriver.PathDriver
|
||||||
|
root string
|
||||||
|
digester Digester
|
||||||
|
provider ContentProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext returns a Context associated with root. The default driver will
|
||||||
|
// be used, as returned by NewDriver.
|
||||||
|
func NewContext(root string) (Context, error) {
|
||||||
|
return NewContextWithOptions(root, ContextOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContextWithOptions returns a Context associate with the root.
|
||||||
|
func NewContextWithOptions(root string, options ContextOptions) (Context, error) {
|
||||||
|
// normalize to absolute path
|
||||||
|
pathDriver := options.PathDriver
|
||||||
|
if pathDriver == nil {
|
||||||
|
pathDriver = pathdriver.LocalPathDriver
|
||||||
|
}
|
||||||
|
|
||||||
|
root = pathDriver.FromSlash(root)
|
||||||
|
root, err := pathDriver.Abs(pathDriver.Clean(root))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
driver := options.Driver
|
||||||
|
if driver == nil {
|
||||||
|
driver, err = driverpkg.NewSystemDriver()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
digester := options.Digester
|
||||||
|
if digester == nil {
|
||||||
|
digester = simpleDigester{digest.Canonical}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the root directory. Need to be a little careful here. We are
|
||||||
|
// allowing a link for now, but this may have odd behavior when
|
||||||
|
// canonicalizing paths. As long as all files are opened through the link
|
||||||
|
// path, this should be okay.
|
||||||
|
fi, err := driver.Stat(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !fi.IsDir() {
|
||||||
|
return nil, &os.PathError{Op: "NewContext", Path: root, Err: os.ErrInvalid}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &context{
|
||||||
|
root: root,
|
||||||
|
driver: driver,
|
||||||
|
pathDriver: pathDriver,
|
||||||
|
digester: digester,
|
||||||
|
provider: options.Provider,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resource returns the resource as path p, populating the entry with info
|
||||||
|
// from fi. The path p should be the path of the resource in the context,
|
||||||
|
// typically obtained through Walk or from the value of Resource.Path(). If fi
|
||||||
|
// is nil, it will be resolved.
|
||||||
|
func (c *context) Resource(p string, fi os.FileInfo) (Resource, error) {
|
||||||
|
fp, err := c.fullpath(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi == nil {
|
||||||
|
fi, err = c.driver.Lstat(fp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base, err := newBaseResource(p, fi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
base.xattrs, err = c.resolveXAttrs(fp, fi, base)
|
||||||
|
if err == ErrNotSupported {
|
||||||
|
log.Printf("resolving xattrs on %s not supported", fp)
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): Handle windows alternate data streams.
|
||||||
|
|
||||||
|
if fi.Mode().IsRegular() {
|
||||||
|
dgst, err := c.digest(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRegularFile(*base, base.paths, fi.Size(), dgst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode().IsDir() {
|
||||||
|
return newDirectory(*base)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
// We handle relative links vs absolute links by including a
|
||||||
|
// beginning slash for absolute links. Effectively, the bundle's
|
||||||
|
// root is treated as the absolute link anchor.
|
||||||
|
target, err := c.driver.Readlink(fp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSymLink(*base, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeNamedPipe != 0 {
|
||||||
|
return newNamedPipe(*base, base.paths)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeDevice != 0 {
|
||||||
|
deviceDriver, ok := c.driver.(driverpkg.DeviceInfoDriver)
|
||||||
|
if !ok {
|
||||||
|
log.Printf("device extraction not supported %s", fp)
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// character and block devices merely need to recover the
|
||||||
|
// major/minor device number.
|
||||||
|
major, minor, err := deviceDriver.DeviceInfo(fi)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDevice(*base, base.paths, major, minor)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%q (%v) is not supported", fp, fi.Mode())
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) verifyMetadata(resource, target Resource) error {
|
||||||
|
if target.Mode() != resource.Mode() {
|
||||||
|
return fmt.Errorf("resource %q has incorrect mode: %v != %v", target.Path(), target.Mode(), resource.Mode())
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.UID() != resource.UID() {
|
||||||
|
return fmt.Errorf("unexpected uid for %q: %v != %v", target.Path(), target.UID(), resource.GID())
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.GID() != resource.GID() {
|
||||||
|
return fmt.Errorf("unexpected gid for %q: %v != %v", target.Path(), target.GID(), target.GID())
|
||||||
|
}
|
||||||
|
|
||||||
|
if xattrer, ok := resource.(XAttrer); ok {
|
||||||
|
txattrer, tok := target.(XAttrer)
|
||||||
|
if !tok {
|
||||||
|
return fmt.Errorf("resource %q has xattrs but target does not support them", resource.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// For xattrs, only ensure that we have those defined in the resource
|
||||||
|
// and their values match. We can ignore other xattrs. In other words,
|
||||||
|
// we only verify that target has the subset defined by resource.
|
||||||
|
txattrs := txattrer.XAttrs()
|
||||||
|
for attr, value := range xattrer.XAttrs() {
|
||||||
|
tvalue, ok := txattrs[attr]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target missing xattr %q", resource.Path(), attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(value, tvalue) {
|
||||||
|
return fmt.Errorf("xattr %q value differs for resource %q", attr, resource.Path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r := resource.(type) {
|
||||||
|
case RegularFile:
|
||||||
|
// TODO(stevvooe): Another reason to use a record-based approach. We
|
||||||
|
// have to do another type switch to get this to work. This could be
|
||||||
|
// fixed with an Equal function, but let's study this a little more to
|
||||||
|
// be sure.
|
||||||
|
t, ok := target.(RegularFile)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target not a regular file", r.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Size() != r.Size() {
|
||||||
|
return fmt.Errorf("resource %q target has incorrect size: %v != %v", t.Path(), t.Size(), r.Size())
|
||||||
|
}
|
||||||
|
case Directory:
|
||||||
|
t, ok := target.(Directory)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target not a directory", t.Path())
|
||||||
|
}
|
||||||
|
case SymLink:
|
||||||
|
t, ok := target.(SymLink)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target not a symlink", t.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Target() != r.Target() {
|
||||||
|
return fmt.Errorf("resource %q target has mismatched target: %q != %q", t.Path(), t.Target(), r.Target())
|
||||||
|
}
|
||||||
|
case Device:
|
||||||
|
t, ok := target.(Device)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q is not a device", t.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Major() != r.Major() || t.Minor() != r.Minor() {
|
||||||
|
return fmt.Errorf("resource %q has mismatched major/minor numbers: %d,%d != %d,%d", t.Path(), t.Major(), t.Minor(), r.Major(), r.Minor())
|
||||||
|
}
|
||||||
|
case NamedPipe:
|
||||||
|
t, ok := target.(NamedPipe)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q is not a named pipe", t.Path())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("cannot verify resource: %v", resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the resource in the context. An error will be returned a discrepancy
|
||||||
|
// is found.
|
||||||
|
func (c *context) Verify(resource Resource) error {
|
||||||
|
fp, err := c.fullpath(resource.Path())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := c.driver.Lstat(fp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := c.Resource(resource.Path(), fi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.Path() != resource.Path() {
|
||||||
|
return fmt.Errorf("resource paths do not match: %q != %q", target.Path(), resource.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.verifyMetadata(resource, target); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
|
||||||
|
hardlinkKey, err := newHardlinkKey(fi)
|
||||||
|
if err == errNotAHardLink {
|
||||||
|
if len(h.Paths()) > 1 {
|
||||||
|
return fmt.Errorf("%q is not a hardlink to %q", h.Paths()[1], resource.Path())
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range h.Paths()[1:] {
|
||||||
|
fpLink, err := c.fullpath(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fiLink, err := c.driver.Lstat(fpLink)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetLink, err := c.Resource(path, fiLink)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hardlinkKeyLink, err := newHardlinkKey(fiLink)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if hardlinkKeyLink != hardlinkKey {
|
||||||
|
return fmt.Errorf("%q is not a hardlink to %q", path, resource.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.verifyMetadata(resource, targetLink); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r := resource.(type) {
|
||||||
|
case RegularFile:
|
||||||
|
t, ok := target.(RegularFile)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("resource %q target not a regular file", r.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(stevvooe): This may need to get a little more sophisticated
|
||||||
|
// for digest comparison. We may want to actually calculate the
|
||||||
|
// provided digests, rather than the implementations having an
|
||||||
|
// overlap.
|
||||||
|
if !digestsMatch(t.Digests(), r.Digests()) {
|
||||||
|
return fmt.Errorf("digests for resource %q do not match: %v != %v", t.Path(), t.Digests(), r.Digests())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) checkoutFile(fp string, rf RegularFile) error {
|
||||||
|
if c.provider == nil {
|
||||||
|
return fmt.Errorf("no file provider")
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
r io.ReadCloser
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, dgst := range rf.Digests() {
|
||||||
|
r, err = c.provider.Reader(dgst)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("file content could not be provided: %v", err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
return atomicWriteFile(fp, r, rf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the resource to the contexts. An error will be returned if the
|
||||||
|
// operation fails. Depending on the resource type, the resource may be
|
||||||
|
// created. For resource that cannot be resolved, an error will be returned.
|
||||||
|
func (c *context) Apply(resource Resource) error {
|
||||||
|
fp, err := c.fullpath(resource.Path())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(fp, c.root) {
|
||||||
|
return fmt.Errorf("resource %v escapes root", resource)
|
||||||
|
}
|
||||||
|
|
||||||
|
var chmod = true
|
||||||
|
fi, err := c.driver.Lstat(fp)
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r := resource.(type) {
|
||||||
|
case RegularFile:
|
||||||
|
if fi == nil {
|
||||||
|
if err := c.checkoutFile(fp, r); err != nil {
|
||||||
|
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
chmod = false
|
||||||
|
} else {
|
||||||
|
if !fi.Mode().IsRegular() {
|
||||||
|
return fmt.Errorf("file %q should be a regular file, but is not", resource.Path())
|
||||||
|
}
|
||||||
|
if fi.Size() != r.Size() {
|
||||||
|
if err := c.checkoutFile(fp, r); err != nil {
|
||||||
|
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, dgst := range r.Digests() {
|
||||||
|
f, err := os.Open(fp)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failure opening file for read %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
compared, err := dgst.Algorithm().FromReader(f)
|
||||||
|
if err == nil && dgst != compared {
|
||||||
|
if err := c.checkoutFile(fp, r); err != nil {
|
||||||
|
return fmt.Errorf("error checking out file %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err1 := f.Close(); err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error checking digest for %q: %v", resource.Path(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Directory:
|
||||||
|
if fi == nil {
|
||||||
|
if err := c.driver.Mkdir(fp, resource.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if !fi.Mode().IsDir() {
|
||||||
|
return fmt.Errorf("%q should be a directory, but is not", resource.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
case SymLink:
|
||||||
|
var target string // only possibly set if target resource is a symlink
|
||||||
|
|
||||||
|
if fi != nil {
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
target, err = c.driver.Readlink(fp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target != r.Target() {
|
||||||
|
if fi != nil {
|
||||||
|
if err := c.driver.Remove(fp); err != nil { // RemoveAll in case of directory?
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.driver.Symlink(r.Target(), fp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(stevvooe): Chmod on symlink is not supported on linux. We
|
||||||
|
// may want to maintain support for other platforms that have it.
|
||||||
|
chmod = false
|
||||||
|
|
||||||
|
case Device:
|
||||||
|
if fi == nil {
|
||||||
|
if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if (fi.Mode() & os.ModeDevice) == 0 {
|
||||||
|
return fmt.Errorf("%q should be a device, but is not", resource.Path())
|
||||||
|
} else {
|
||||||
|
major, minor, err := devices.DeviceInfo(fi)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if major != r.Major() || minor != r.Minor() {
|
||||||
|
if err := c.driver.Remove(fp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.driver.Mknod(fp, resource.Mode(), int(r.Major()), int(r.Minor())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case NamedPipe:
|
||||||
|
if fi == nil {
|
||||||
|
if err := c.driver.Mkfifo(fp, resource.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if (fi.Mode() & os.ModeNamedPipe) == 0 {
|
||||||
|
return fmt.Errorf("%q should be a named pipe, but is not", resource.Path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if h, isHardlinkable := resource.(Hardlinkable); isHardlinkable {
|
||||||
|
for _, path := range h.Paths() {
|
||||||
|
if path == resource.Path() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
lp, err := c.fullpath(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, fi := c.driver.Lstat(lp); fi == nil {
|
||||||
|
c.driver.Remove(lp)
|
||||||
|
}
|
||||||
|
if err := c.driver.Link(fp, lp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update filemode if file was not created
|
||||||
|
if chmod {
|
||||||
|
if err := c.driver.Lchmod(fp, resource.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.driver.Lchown(fp, resource.UID(), resource.GID()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if xattrer, ok := resource.(XAttrer); ok {
|
||||||
|
// For xattrs, only ensure that we have those defined in the resource
|
||||||
|
// and their values are set. We can ignore other xattrs. In other words,
|
||||||
|
// we only set xattres defined by resource but never remove.
|
||||||
|
|
||||||
|
if _, ok := resource.(SymLink); ok {
|
||||||
|
lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unsupported symlink xattr for resource %q", resource.Path())
|
||||||
|
}
|
||||||
|
if err := lxattrDriver.LSetxattr(fp, xattrer.XAttrs()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unsupported xattr for resource %q", resource.Path())
|
||||||
|
}
|
||||||
|
if err := xattrDriver.Setxattr(fp, xattrer.XAttrs()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk provides a convenience function to call filepath.Walk correctly for
|
||||||
|
// the context. Otherwise identical to filepath.Walk, the path argument is
|
||||||
|
// corrected to be contained within the context.
|
||||||
|
func (c *context) Walk(fn filepath.WalkFunc) error {
|
||||||
|
return c.pathDriver.Walk(c.root, func(p string, fi os.FileInfo, err error) error {
|
||||||
|
contained, err := c.contain(p)
|
||||||
|
return fn(contained, fi, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// fullpath returns the system path for the resource, joined with the context
|
||||||
|
// root. The path p must be a part of the context.
|
||||||
|
func (c *context) fullpath(p string) (string, error) {
|
||||||
|
p = c.pathDriver.Join(c.root, p)
|
||||||
|
if !strings.HasPrefix(p, c.root) {
|
||||||
|
return "", fmt.Errorf("invalid context path")
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contain cleans and santizes the filesystem path p to be an absolute path,
|
||||||
|
// effectively relative to the context root.
|
||||||
|
func (c *context) contain(p string) (string, error) {
|
||||||
|
sanitized, err := c.pathDriver.Rel(c.root, p)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZOMBIES(stevvooe): In certain cases, we may want to remap these to a
|
||||||
|
// "containment error", so the caller can decide what to do.
|
||||||
|
return c.pathDriver.Join("/", c.pathDriver.Clean(sanitized)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// digest returns the digest of the file at path p, relative to the root.
|
||||||
|
func (c *context) digest(p string) (digest.Digest, error) {
|
||||||
|
f, err := c.driver.Open(c.pathDriver.Join(c.root, p))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
return c.digester.Digest(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveXAttrs attempts to resolve the extended attributes for the resource
|
||||||
|
// at the path fp, which is the full path to the resource. If the resource
|
||||||
|
// cannot have xattrs, nil will be returned.
|
||||||
|
func (c *context) resolveXAttrs(fp string, fi os.FileInfo, base *resource) (map[string][]byte, error) {
|
||||||
|
if fi.Mode().IsRegular() || fi.Mode().IsDir() {
|
||||||
|
xattrDriver, ok := c.driver.(driverpkg.XAttrDriver)
|
||||||
|
if !ok {
|
||||||
|
log.Println("xattr extraction not supported")
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
return xattrDriver.Getxattr(fp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
lxattrDriver, ok := c.driver.(driverpkg.LXAttrDriver)
|
||||||
|
if !ok {
|
||||||
|
log.Println("xattr extraction for symlinks not supported")
|
||||||
|
return nil, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
return lxattrDriver.LGetxattr(fp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
306
vendor/github.com/containerd/continuity/continuityfs/fuse.go
generated
vendored
Normal file
306
vendor/github.com/containerd/continuity/continuityfs/fuse.go
generated
vendored
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
// +build linux darwin freebsd
|
||||||
|
|
||||||
|
package continuityfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
"bazil.org/fuse/fs"
|
||||||
|
"github.com/containerd/continuity"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File represents any file type (non directory) in the filesystem
|
||||||
|
type File struct {
|
||||||
|
inode uint64
|
||||||
|
uid uint32
|
||||||
|
gid uint32
|
||||||
|
provider FileContentProvider
|
||||||
|
resource continuity.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFile creates a new file with the given inode and content provider
|
||||||
|
func NewFile(inode uint64, provider FileContentProvider) *File {
|
||||||
|
return &File{
|
||||||
|
inode: inode,
|
||||||
|
provider: provider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) setResource(r continuity.Resource) (err error) {
|
||||||
|
// TODO: error out if uid excesses uint32?
|
||||||
|
f.uid = uint32(r.UID())
|
||||||
|
f.gid = uint32(r.GID())
|
||||||
|
f.resource = r
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attr sets the fuse attribute for the file
|
||||||
|
func (f *File) Attr(ctx context.Context, attr *fuse.Attr) (err error) {
|
||||||
|
// Set attributes from resource metadata
|
||||||
|
attr.Mode = f.resource.Mode()
|
||||||
|
attr.Uid = f.uid
|
||||||
|
attr.Gid = f.gid
|
||||||
|
|
||||||
|
if rf, ok := f.resource.(continuity.RegularFile); ok {
|
||||||
|
attr.Nlink = uint32(len(rf.Paths()))
|
||||||
|
attr.Size = uint64(rf.Size())
|
||||||
|
} else {
|
||||||
|
attr.Nlink = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
attr.Inode = f.inode
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens the file for read
|
||||||
|
// currently only regular files can be opened
|
||||||
|
func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenResponse) (fs.Handle, error) {
|
||||||
|
var dgst digest.Digest
|
||||||
|
if rf, ok := f.resource.(continuity.RegularFile); ok {
|
||||||
|
digests := rf.Digests()
|
||||||
|
if len(digests) > 0 {
|
||||||
|
dgst = digests[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO(dmcgowan): else check if device can be opened for read
|
||||||
|
r, err := f.provider.Open(f.resource.Path(), dgst)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Error opening handle: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &fileHandler{
|
||||||
|
reader: r,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) getDirent(name string) (fuse.Dirent, error) {
|
||||||
|
var t fuse.DirentType
|
||||||
|
switch f.resource.(type) {
|
||||||
|
case continuity.RegularFile:
|
||||||
|
t = fuse.DT_File
|
||||||
|
case continuity.SymLink:
|
||||||
|
t = fuse.DT_Link
|
||||||
|
case continuity.Device:
|
||||||
|
t = fuse.DT_Block
|
||||||
|
case continuity.NamedPipe:
|
||||||
|
t = fuse.DT_FIFO
|
||||||
|
default:
|
||||||
|
t = fuse.DT_Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
return fuse.Dirent{
|
||||||
|
Inode: f.inode,
|
||||||
|
Type: t,
|
||||||
|
Name: name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileHandler struct {
|
||||||
|
offset int64
|
||||||
|
reader io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *fileHandler) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
||||||
|
if h.offset != req.Offset {
|
||||||
|
if seeker, ok := h.reader.(io.Seeker); ok {
|
||||||
|
if _, err := seeker.Seek(req.Offset, os.SEEK_SET); err != nil {
|
||||||
|
logrus.Debugf("Error seeking: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.offset = req.Offset
|
||||||
|
} else {
|
||||||
|
return errors.New("unable to seek to offset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := h.reader.Read(resp.Data[:req.Size])
|
||||||
|
if err != nil {
|
||||||
|
logrus.Debugf("Read error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.offset = h.offset + int64(n)
|
||||||
|
|
||||||
|
resp.Data = resp.Data[:n]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *fileHandler) Release(ctx context.Context, req *fuse.ReleaseRequest) error {
|
||||||
|
return h.reader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dir represents a file system directory
|
||||||
|
type Dir struct {
|
||||||
|
inode uint64
|
||||||
|
uid uint32
|
||||||
|
gid uint32
|
||||||
|
nodes map[string]fs.Node
|
||||||
|
provider FileContentProvider
|
||||||
|
resource continuity.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attr sets the fuse attributes for the directory
|
||||||
|
func (d *Dir) Attr(ctx context.Context, attr *fuse.Attr) (err error) {
|
||||||
|
if d.resource == nil {
|
||||||
|
attr.Mode = os.ModeDir | 0555
|
||||||
|
} else {
|
||||||
|
attr.Mode = d.resource.Mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
attr.Uid = d.uid
|
||||||
|
attr.Gid = d.gid
|
||||||
|
attr.Inode = d.inode
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dir) getDirent(name string) (fuse.Dirent, error) {
|
||||||
|
return fuse.Dirent{
|
||||||
|
Inode: d.inode,
|
||||||
|
Type: fuse.DT_Dir,
|
||||||
|
Name: name,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type direnter interface {
|
||||||
|
getDirent(name string) (fuse.Dirent, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup looks up the filesystem node for the name within the directory
|
||||||
|
func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||||
|
node, ok := d.nodes[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, fuse.ENOENT
|
||||||
|
}
|
||||||
|
return node, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDirAll reads all the directory entries
|
||||||
|
func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||||
|
ents := make([]fuse.Dirent, 0, len(d.nodes))
|
||||||
|
for name, node := range d.nodes {
|
||||||
|
if nd, ok := node.(direnter); ok {
|
||||||
|
de, err := nd.getDirent(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ents = append(ents, de)
|
||||||
|
} else {
|
||||||
|
logrus.Errorf("%s does not have a directory entry", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dir) setResource(r continuity.Resource) (err error) {
|
||||||
|
d.uid = uint32(r.UID())
|
||||||
|
d.gid = uint32(r.GID())
|
||||||
|
d.resource = r
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDir creates a new directory object
|
||||||
|
func NewDir(inode uint64, provider FileContentProvider) *Dir {
|
||||||
|
return &Dir{
|
||||||
|
inode: inode,
|
||||||
|
nodes: map[string]fs.Node{},
|
||||||
|
provider: provider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
rootPath = fmt.Sprintf("%c", filepath.Separator)
|
||||||
|
)
|
||||||
|
|
||||||
|
func addNode(path string, node fs.Node, cache map[string]*Dir, provider FileContentProvider) {
|
||||||
|
dirPath, file := filepath.Split(path)
|
||||||
|
d, ok := cache[dirPath]
|
||||||
|
if !ok {
|
||||||
|
d = NewDir(0, provider)
|
||||||
|
cache[dirPath] = d
|
||||||
|
addNode(filepath.Clean(dirPath), d, cache, provider)
|
||||||
|
}
|
||||||
|
d.nodes[file] = node
|
||||||
|
logrus.Debugf("%s (%#v) added to %s", file, node, dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
type treeRoot struct {
|
||||||
|
root *Dir
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t treeRoot) Root() (fs.Node, error) {
|
||||||
|
logrus.Debugf("Returning root with %#v", t.root.nodes)
|
||||||
|
return t.root, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFSFromManifest creates a fuse filesystem using the given manifest
|
||||||
|
// to create the node tree and the content provider to serve up
|
||||||
|
// content for regular files.
|
||||||
|
func NewFSFromManifest(manifest *continuity.Manifest, mountRoot string, provider FileContentProvider) (fs.FS, error) {
|
||||||
|
tree := treeRoot{
|
||||||
|
root: NewDir(0, provider),
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(mountRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("could not access directory")
|
||||||
|
}
|
||||||
|
tree.root.uid = st.Uid
|
||||||
|
tree.root.gid = st.Gid
|
||||||
|
|
||||||
|
dirCache := map[string]*Dir{
|
||||||
|
rootPath: tree.root,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, resource := range manifest.Resources {
|
||||||
|
inode := uint64(i) + 1
|
||||||
|
if _, ok := resource.(continuity.Directory); ok {
|
||||||
|
cleanPath := filepath.Clean(resource.Path())
|
||||||
|
keyPath := fmt.Sprintf("%s%c", cleanPath, filepath.Separator)
|
||||||
|
d, ok := dirCache[keyPath]
|
||||||
|
if !ok {
|
||||||
|
d = NewDir(inode, provider)
|
||||||
|
dirCache[keyPath] = d
|
||||||
|
addNode(cleanPath, d, dirCache, provider)
|
||||||
|
} else {
|
||||||
|
d.inode = inode
|
||||||
|
}
|
||||||
|
if err := d.setResource(resource); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f := NewFile(inode, provider)
|
||||||
|
if err := f.setResource(resource); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if rf, ok := resource.(continuity.RegularFile); ok {
|
||||||
|
|
||||||
|
for _, p := range rf.Paths() {
|
||||||
|
addNode(p, f, dirCache, provider)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addNode(resource.Path(), f, dirCache, provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tree, nil
|
||||||
|
}
|
43
vendor/github.com/containerd/continuity/continuityfs/provider.go
generated
vendored
Normal file
43
vendor/github.com/containerd/continuity/continuityfs/provider.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// +build linux darwin freebsd
|
||||||
|
|
||||||
|
package continuityfs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/driver"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileContentProvider is an object which is used to fetch
|
||||||
|
// data and inode information about a path or digest.
|
||||||
|
// TODO(dmcgowan): Update GetContentPath to provide a
|
||||||
|
// filehandle or ReadWriteCloser.
|
||||||
|
type FileContentProvider interface {
|
||||||
|
Path(string, digest.Digest) (string, error)
|
||||||
|
Open(string, digest.Digest) (io.ReadCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type fsContentProvider struct {
|
||||||
|
root string
|
||||||
|
driver driver.Driver
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFSFileContentProvider creates a new content provider which
|
||||||
|
// gets content from a directory on an existing filesystem based
|
||||||
|
// on the resource path.
|
||||||
|
func NewFSFileContentProvider(root string, driver driver.Driver) FileContentProvider {
|
||||||
|
return &fsContentProvider{
|
||||||
|
root: root,
|
||||||
|
driver: driver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fsContentProvider) Path(path string, dgst digest.Digest) (string, error) {
|
||||||
|
return filepath.Join(p.root, path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *fsContentProvider) Open(path string, dgst digest.Digest) (io.ReadCloser, error) {
|
||||||
|
return p.driver.Open(filepath.Join(p.root, path))
|
||||||
|
}
|
5
vendor/github.com/containerd/continuity/devices/devices.go
generated
vendored
Normal file
5
vendor/github.com/containerd/continuity/devices/devices.go
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package devices
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var ErrNotSupported = fmt.Errorf("not supported")
|
58
vendor/github.com/containerd/continuity/devices/devices_unix.go
generated
vendored
Normal file
58
vendor/github.com/containerd/continuity/devices/devices_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// +build linux darwin freebsd solaris
|
||||||
|
|
||||||
|
package devices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
|
||||||
|
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return 0, 0, fmt.Errorf("cannot extract device from os.FileInfo")
|
||||||
|
}
|
||||||
|
|
||||||
|
dev := uint64(sys.Rdev)
|
||||||
|
return uint64(unix.Major(dev)), uint64(unix.Minor(dev)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mknod provides a shortcut for syscall.Mknod
|
||||||
|
func Mknod(p string, mode os.FileMode, maj, min int) error {
|
||||||
|
var (
|
||||||
|
m = syscallMode(mode.Perm())
|
||||||
|
dev uint64
|
||||||
|
)
|
||||||
|
|
||||||
|
if mode&os.ModeDevice != 0 {
|
||||||
|
dev = unix.Mkdev(uint32(maj), uint32(min))
|
||||||
|
|
||||||
|
if mode&os.ModeCharDevice != 0 {
|
||||||
|
m |= unix.S_IFCHR
|
||||||
|
} else {
|
||||||
|
m |= unix.S_IFBLK
|
||||||
|
}
|
||||||
|
} else if mode&os.ModeNamedPipe != 0 {
|
||||||
|
m |= unix.S_IFIFO
|
||||||
|
}
|
||||||
|
|
||||||
|
return unix.Mknod(p, m, int(dev))
|
||||||
|
}
|
||||||
|
|
||||||
|
// syscallMode returns the syscall-specific mode bits from Go's portable mode bits.
|
||||||
|
func syscallMode(i os.FileMode) (o uint32) {
|
||||||
|
o |= uint32(i.Perm())
|
||||||
|
if i&os.ModeSetuid != 0 {
|
||||||
|
o |= unix.S_ISUID
|
||||||
|
}
|
||||||
|
if i&os.ModeSetgid != 0 {
|
||||||
|
o |= unix.S_ISGID
|
||||||
|
}
|
||||||
|
if i&os.ModeSticky != 0 {
|
||||||
|
o |= unix.S_ISVTX
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
11
vendor/github.com/containerd/continuity/devices/devices_windows.go
generated
vendored
Normal file
11
vendor/github.com/containerd/continuity/devices/devices_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package devices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeviceInfo(fi os.FileInfo) (uint64, uint64, error) {
|
||||||
|
return 0, 0, errors.Wrap(ErrNotSupported, "cannot get device info on windows")
|
||||||
|
}
|
88
vendor/github.com/containerd/continuity/digests.go
generated
vendored
Normal file
88
vendor/github.com/containerd/continuity/digests.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package continuity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Digester produces a digest for a given read stream
|
||||||
|
type Digester interface {
|
||||||
|
Digest(io.Reader) (digest.Digest, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentProvider produces a read stream for a given digest
|
||||||
|
type ContentProvider interface {
|
||||||
|
Reader(digest.Digest) (io.ReadCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type simpleDigester struct {
|
||||||
|
algorithm digest.Algorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sd simpleDigester) Digest(r io.Reader) (digest.Digest, error) {
|
||||||
|
digester := sd.algorithm.Digester()
|
||||||
|
|
||||||
|
if _, err := io.Copy(digester.Hash(), r); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return digester.Digest(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// uniqifyDigests sorts and uniqifies the provided digest, ensuring that the
|
||||||
|
// digests are not repeated and no two digests with the same algorithm have
|
||||||
|
// different values. Because a stable sort is used, this has the effect of
|
||||||
|
// "zipping" digest collections from multiple resources.
|
||||||
|
func uniqifyDigests(digests ...digest.Digest) ([]digest.Digest, error) {
|
||||||
|
sort.Stable(digestSlice(digests)) // stable sort is important for the behavior here.
|
||||||
|
seen := map[digest.Digest]struct{}{}
|
||||||
|
algs := map[digest.Algorithm][]digest.Digest{} // detect different digests.
|
||||||
|
|
||||||
|
var out []digest.Digest
|
||||||
|
// uniqify the digests
|
||||||
|
for _, d := range digests {
|
||||||
|
if _, ok := seen[d]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
seen[d] = struct{}{}
|
||||||
|
algs[d.Algorithm()] = append(algs[d.Algorithm()], d)
|
||||||
|
|
||||||
|
if len(algs[d.Algorithm()]) > 1 {
|
||||||
|
return nil, fmt.Errorf("conflicting digests for %v found", d.Algorithm())
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// digestsMatch compares the two sets of digests to see if they match.
|
||||||
|
func digestsMatch(as, bs []digest.Digest) bool {
|
||||||
|
all := append(as, bs...)
|
||||||
|
|
||||||
|
uniqified, err := uniqifyDigests(all...)
|
||||||
|
if err != nil {
|
||||||
|
// the only error uniqifyDigests returns is when the digests disagree.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
disjoint := len(as) + len(bs)
|
||||||
|
if len(uniqified) == disjoint {
|
||||||
|
// if these two sets have the same cardinality, we know both sides
|
||||||
|
// didn't share any digests.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type digestSlice []digest.Digest
|
||||||
|
|
||||||
|
func (p digestSlice) Len() int { return len(p) }
|
||||||
|
func (p digestSlice) Less(i, j int) bool { return p[i] < p[j] }
|
||||||
|
func (p digestSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
72
vendor/github.com/containerd/continuity/digests_test.go
generated
vendored
Normal file
72
vendor/github.com/containerd/continuity/digests_test.go
generated
vendored
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package continuity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestUniqifyDigests ensures that we deterministically sort digest entries
|
||||||
|
// for a resource.
|
||||||
|
func TestUniqifyDigests(t *testing.T) {
|
||||||
|
for _, testcase := range []struct {
|
||||||
|
description string
|
||||||
|
input [][]digest.Digest // input is a series of digest collections from separate resources.
|
||||||
|
expected []digest.Digest
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "simple merge",
|
||||||
|
input: [][]digest.Digest{
|
||||||
|
{"sha1:abc", "sha256:def"},
|
||||||
|
{"sha1:abc", "sha256:def"},
|
||||||
|
},
|
||||||
|
expected: []digest.Digest{"sha1:abc", "sha256:def"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "simple reversed order",
|
||||||
|
input: [][]digest.Digest{
|
||||||
|
{"sha1:abc", "sha256:def"},
|
||||||
|
{"sha256:def", "sha1:abc"},
|
||||||
|
},
|
||||||
|
expected: []digest.Digest{"sha1:abc", "sha256:def"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "conflicting values for sha1",
|
||||||
|
input: [][]digest.Digest{
|
||||||
|
{"sha1:abc", "sha256:def"},
|
||||||
|
{"sha256:def", "sha1:def"},
|
||||||
|
},
|
||||||
|
err: fmt.Errorf("conflicting digests for sha1 found"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
fatalf := func(format string, args ...interface{}) {
|
||||||
|
t.Fatalf(testcase.description+": "+format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var assembled []digest.Digest
|
||||||
|
|
||||||
|
for _, ds := range testcase.input {
|
||||||
|
assembled = append(assembled, ds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
merged, err := uniqifyDigests(assembled...)
|
||||||
|
if err != testcase.err {
|
||||||
|
if testcase.err == nil {
|
||||||
|
fatalf("unexpected error uniqifying digests: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != testcase.err && err.Error() != testcase.err.Error() {
|
||||||
|
// compare by string till we create nice error type
|
||||||
|
fatalf("unexpected error uniqifying digests: %v != %v", err, testcase.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(merged, testcase.expected) {
|
||||||
|
fatalf("unexpected uniquification: %v != %v", merged, testcase.expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
162
vendor/github.com/containerd/continuity/driver/driver.go
generated
vendored
Normal file
162
vendor/github.com/containerd/continuity/driver/driver.go
generated
vendored
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotSupported = fmt.Errorf("not supported")
|
||||||
|
|
||||||
|
// Driver provides all of the system-level functions in a common interface.
|
||||||
|
// The context should call these with full paths and should never use the `os`
|
||||||
|
// package or any other package to access resources on the filesystem. This
|
||||||
|
// mechanism let's us carefully control access to the context and maintain
|
||||||
|
// path and resource integrity. It also gives us an interface to reason about
|
||||||
|
// direct resource access.
|
||||||
|
//
|
||||||
|
// Implementations don't need to do much other than meet the interface. For
|
||||||
|
// example, it is not required to wrap os.FileInfo to return correct paths for
|
||||||
|
// the call to Name().
|
||||||
|
type Driver interface {
|
||||||
|
// Note that Open() returns a File interface instead of *os.File. This
|
||||||
|
// is because os.File is a struct, so if Open was to return *os.File,
|
||||||
|
// the only way to fulfill the interface would be to call os.Open()
|
||||||
|
Open(path string) (File, error)
|
||||||
|
OpenFile(path string, flag int, perm os.FileMode) (File, error)
|
||||||
|
|
||||||
|
Stat(path string) (os.FileInfo, error)
|
||||||
|
Lstat(path string) (os.FileInfo, error)
|
||||||
|
Readlink(p string) (string, error)
|
||||||
|
Mkdir(path string, mode os.FileMode) error
|
||||||
|
Remove(path string) error
|
||||||
|
|
||||||
|
Link(oldname, newname string) error
|
||||||
|
Lchmod(path string, mode os.FileMode) error
|
||||||
|
Lchown(path string, uid, gid int64) error
|
||||||
|
Symlink(oldname, newname string) error
|
||||||
|
|
||||||
|
MkdirAll(path string, perm os.FileMode) error
|
||||||
|
RemoveAll(path string) error
|
||||||
|
|
||||||
|
// TODO(aaronl): These methods might move outside the main Driver
|
||||||
|
// interface in the future as more platforms are added.
|
||||||
|
Mknod(path string, mode os.FileMode, major int, minor int) error
|
||||||
|
Mkfifo(path string, mode os.FileMode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// File is the interface for interacting with files returned by continuity's Open
|
||||||
|
// This is needed since os.File is a struct, instead of an interface, so it can't
|
||||||
|
// be used.
|
||||||
|
type File interface {
|
||||||
|
io.ReadWriteCloser
|
||||||
|
io.Seeker
|
||||||
|
Readdir(n int) ([]os.FileInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSystemDriver() (Driver, error) {
|
||||||
|
// TODO(stevvooe): Consider having this take a "hint" path argument, which
|
||||||
|
// would be the context root. The hint could be used to resolve required
|
||||||
|
// filesystem support when assembling the driver to use.
|
||||||
|
return &driver{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XAttrDriver should be implemented on operation systems and filesystems that
|
||||||
|
// have xattr support for regular files and directories.
|
||||||
|
type XAttrDriver interface {
|
||||||
|
// Getxattr returns all of the extended attributes for the file at path.
|
||||||
|
// Typically, this takes a syscall call to Listxattr and Getxattr.
|
||||||
|
Getxattr(path string) (map[string][]byte, error)
|
||||||
|
|
||||||
|
// Setxattr sets all of the extended attributes on file at path, following
|
||||||
|
// any symbolic links, if necessary. All attributes on the target are
|
||||||
|
// replaced by the values from attr. If the operation fails to set any
|
||||||
|
// attribute, those already applied will not be rolled back.
|
||||||
|
Setxattr(path string, attr map[string][]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// LXAttrDriver should be implemented by drivers on operating systems and
|
||||||
|
// filesystems that support setting and getting extended attributes on
|
||||||
|
// symbolic links. If this is not implemented, extended attributes will be
|
||||||
|
// ignored on symbolic links.
|
||||||
|
type LXAttrDriver interface {
|
||||||
|
// LGetxattr returns all of the extended attributes for the file at path
|
||||||
|
// and does not follow symlinks. Typically, this takes a syscall call to
|
||||||
|
// Llistxattr and Lgetxattr.
|
||||||
|
LGetxattr(path string) (map[string][]byte, error)
|
||||||
|
|
||||||
|
// LSetxattr sets all of the extended attributes on file at path, without
|
||||||
|
// following symbolic links. All attributes on the target are replaced by
|
||||||
|
// the values from attr. If the operation fails to set any attribute,
|
||||||
|
// those already applied will not be rolled back.
|
||||||
|
LSetxattr(path string, attr map[string][]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceInfoDriver interface {
|
||||||
|
DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// driver is a simple default implementation that sends calls out to the "os"
|
||||||
|
// package. Extend the "driver" type in system-specific files to add support,
|
||||||
|
// such as xattrs, which can add support at compile time.
|
||||||
|
type driver struct{}
|
||||||
|
|
||||||
|
var _ File = &os.File{}
|
||||||
|
|
||||||
|
// LocalDriver is the exported Driver struct for convenience.
|
||||||
|
var LocalDriver Driver = &driver{}
|
||||||
|
|
||||||
|
func (d *driver) Open(p string) (File, error) {
|
||||||
|
return os.Open(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) OpenFile(path string, flag int, perm os.FileMode) (File, error) {
|
||||||
|
return os.OpenFile(path, flag, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Stat(p string) (os.FileInfo, error) {
|
||||||
|
return os.Stat(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Lstat(p string) (os.FileInfo, error) {
|
||||||
|
return os.Lstat(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Readlink(p string) (string, error) {
|
||||||
|
return os.Readlink(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Mkdir(p string, mode os.FileMode) error {
|
||||||
|
return os.Mkdir(p, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove is used to unlink files and remove directories.
|
||||||
|
// This is following the golang os package api which
|
||||||
|
// combines the operations into a higher level Remove
|
||||||
|
// function. If explicit unlinking or directory removal
|
||||||
|
// to mirror system call is required, they should be
|
||||||
|
// split up at that time.
|
||||||
|
func (d *driver) Remove(path string) error {
|
||||||
|
return os.Remove(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Link(oldname, newname string) error {
|
||||||
|
return os.Link(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Lchown(name string, uid, gid int64) error {
|
||||||
|
// TODO: error out if uid excesses int bit width?
|
||||||
|
return os.Lchown(name, int(uid), int(gid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Symlink(oldname, newname string) error {
|
||||||
|
return os.Symlink(oldname, newname)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
return os.MkdirAll(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) RemoveAll(path string) error {
|
||||||
|
return os.RemoveAll(path)
|
||||||
|
}
|
122
vendor/github.com/containerd/continuity/driver/driver_unix.go
generated
vendored
Normal file
122
vendor/github.com/containerd/continuity/driver/driver_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
// +build linux darwin freebsd solaris
|
||||||
|
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/devices"
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
|
||||||
|
return devices.Mknod(path, mode, major, minor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
||||||
|
if mode&os.ModeNamedPipe == 0 {
|
||||||
|
return errors.New("mode passed to Mkfifo does not have the named pipe bit set")
|
||||||
|
}
|
||||||
|
// mknod with a mode that has ModeNamedPipe set creates a fifo, not a
|
||||||
|
// device.
|
||||||
|
return devices.Mknod(path, mode, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lchmod changes the mode of an file not following symlinks.
|
||||||
|
func (d *driver) Lchmod(path string, mode os.FileMode) (err error) {
|
||||||
|
if !filepath.IsAbs(path) {
|
||||||
|
path, err = filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sysx.Fchmodat(0, path, uint32(mode), sysx.AtSymlinkNofollow)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getxattr returns all of the extended attributes for the file at path p.
|
||||||
|
func (d *driver) Getxattr(p string) (map[string][]byte, error) {
|
||||||
|
xattrs, err := sysx.Listxattr(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(xattrs)
|
||||||
|
m := make(map[string][]byte, len(xattrs))
|
||||||
|
|
||||||
|
for _, attr := range xattrs {
|
||||||
|
value, err := sysx.Getxattr(p, attr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(stevvooe): This append/copy tricky relies on unique
|
||||||
|
// xattrs. Break this out into an alloc/copy if xattrs are no
|
||||||
|
// longer unique.
|
||||||
|
m[attr] = append(m[attr], value...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setxattr sets all of the extended attributes on file at path, following
|
||||||
|
// any symbolic links, if necessary. All attributes on the target are
|
||||||
|
// replaced by the values from attr. If the operation fails to set any
|
||||||
|
// attribute, those already applied will not be rolled back.
|
||||||
|
func (d *driver) Setxattr(path string, attrMap map[string][]byte) error {
|
||||||
|
for attr, value := range attrMap {
|
||||||
|
if err := sysx.Setxattr(path, attr, value, 0); err != nil {
|
||||||
|
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LGetxattr returns all of the extended attributes for the file at path p
|
||||||
|
// not following symbolic links.
|
||||||
|
func (d *driver) LGetxattr(p string) (map[string][]byte, error) {
|
||||||
|
xattrs, err := sysx.LListxattr(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("listing %s xattrs: %v", p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(xattrs)
|
||||||
|
m := make(map[string][]byte, len(xattrs))
|
||||||
|
|
||||||
|
for _, attr := range xattrs {
|
||||||
|
value, err := sysx.LGetxattr(p, attr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("getting %q xattr on %s: %v", attr, p, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE(stevvooe): This append/copy tricky relies on unique
|
||||||
|
// xattrs. Break this out into an alloc/copy if xattrs are no
|
||||||
|
// longer unique.
|
||||||
|
m[attr] = append(m[attr], value...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LSetxattr sets all of the extended attributes on file at path, not
|
||||||
|
// following any symbolic links. All attributes on the target are
|
||||||
|
// replaced by the values from attr. If the operation fails to set any
|
||||||
|
// attribute, those already applied will not be rolled back.
|
||||||
|
func (d *driver) LSetxattr(path string, attrMap map[string][]byte) error {
|
||||||
|
for attr, value := range attrMap {
|
||||||
|
if err := sysx.LSetxattr(path, attr, value, 0); err != nil {
|
||||||
|
return fmt.Errorf("error setting xattr %q on %s: %v", attr, path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) DeviceInfo(fi os.FileInfo) (maj uint64, min uint64, err error) {
|
||||||
|
return devices.DeviceInfo(fi)
|
||||||
|
}
|
21
vendor/github.com/containerd/continuity/driver/driver_windows.go
generated
vendored
Normal file
21
vendor/github.com/containerd/continuity/driver/driver_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *driver) Mknod(path string, mode os.FileMode, major, minor int) error {
|
||||||
|
return errors.Wrap(ErrNotSupported, "cannot create device node on Windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *driver) Mkfifo(path string, mode os.FileMode) error {
|
||||||
|
return errors.Wrap(ErrNotSupported, "cannot create fifo on Windows")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lchmod changes the mode of an file not following symlinks.
|
||||||
|
func (d *driver) Lchmod(path string, mode os.FileMode) (err error) {
|
||||||
|
// TODO: Use Window's equivalent
|
||||||
|
return os.Chmod(path, mode)
|
||||||
|
}
|
74
vendor/github.com/containerd/continuity/driver/utils.go
generated
vendored
Normal file
74
vendor/github.com/containerd/continuity/driver/utils.go
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadFile works the same as ioutil.ReadFile with the Driver abstraction
|
||||||
|
func ReadFile(r Driver, filename string) ([]byte, error) {
|
||||||
|
f, err := r.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile works the same as ioutil.WriteFile with the Driver abstraction
|
||||||
|
func WriteFile(r Driver, filename string, data []byte, perm os.FileMode) error {
|
||||||
|
f, err := r.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
n, err := f.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if n != len(data) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDir works the same as ioutil.ReadDir with the Driver abstraction
|
||||||
|
func ReadDir(r Driver, dirname string) ([]os.FileInfo, error) {
|
||||||
|
f, err := r.Open(dirname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
dirs, err := f.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(fileInfos(dirs))
|
||||||
|
return dirs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple implementation of the sort.Interface for os.FileInfo
|
||||||
|
type fileInfos []os.FileInfo
|
||||||
|
|
||||||
|
func (fis fileInfos) Len() int {
|
||||||
|
return len(fis)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fis fileInfos) Less(i, j int) bool {
|
||||||
|
return fis[i].Name() < fis[j].Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fis fileInfos) Swap(i, j int) {
|
||||||
|
fis[i], fis[j] = fis[j], fis[i]
|
||||||
|
}
|
119
vendor/github.com/containerd/continuity/fs/copy.go
generated
vendored
Normal file
119
vendor/github.com/containerd/continuity/fs/copy.go
generated
vendored
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bufferPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
buffer := make([]byte, 32*1024)
|
||||||
|
return &buffer
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDir copies the directory from src to dst.
|
||||||
|
// Most efficient copy of files is attempted.
|
||||||
|
func CopyDir(dst, src string) error {
|
||||||
|
inodes := map[uint64]string{}
|
||||||
|
return copyDirectory(dst, src, inodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDirectory(dst, src string, inodes map[uint64]string) error {
|
||||||
|
stat, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to stat %s", src)
|
||||||
|
}
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return errors.Errorf("source is not directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
if st, err := os.Stat(dst); err != nil {
|
||||||
|
if err := os.Mkdir(dst, stat.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to mkdir %s", dst)
|
||||||
|
}
|
||||||
|
} else if !st.IsDir() {
|
||||||
|
return errors.Errorf("cannot copy to non-directory: %s", dst)
|
||||||
|
} else {
|
||||||
|
if err := os.Chmod(dst, stat.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod on %s", dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fis, err := ioutil.ReadDir(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read %s", src)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyFileInfo(stat, dst); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to copy file info for %s", dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fi := range fis {
|
||||||
|
source := filepath.Join(src, fi.Name())
|
||||||
|
target := filepath.Join(dst, fi.Name())
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fi.IsDir():
|
||||||
|
if err := copyDirectory(target, source, inodes); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case (fi.Mode() & os.ModeType) == 0:
|
||||||
|
link, err := getLinkSource(target, fi, inodes)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to get hardlink")
|
||||||
|
}
|
||||||
|
if link != "" {
|
||||||
|
if err := os.Link(link, target); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create hard link")
|
||||||
|
}
|
||||||
|
} else if err := copyFile(source, target); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy files")
|
||||||
|
}
|
||||||
|
case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
|
||||||
|
link, err := os.Readlink(source)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read link: %s", source)
|
||||||
|
}
|
||||||
|
if err := os.Symlink(link, target); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create symlink: %s", target)
|
||||||
|
}
|
||||||
|
case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
|
||||||
|
if err := copyDevice(target, fi); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create device")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: Support pipes and sockets
|
||||||
|
return errors.Wrapf(err, "unsupported mode %s", fi.Mode())
|
||||||
|
}
|
||||||
|
if err := copyFileInfo(fi, target); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy file info")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copyXAttrs(target, source); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy xattrs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(source, target string) error {
|
||||||
|
src, err := os.Open(source)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open source %s", source)
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
tgt, err := os.Create(target)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to open target %s", target)
|
||||||
|
}
|
||||||
|
defer tgt.Close()
|
||||||
|
|
||||||
|
return copyFileContent(tgt, src)
|
||||||
|
}
|
95
vendor/github.com/containerd/continuity/fs/copy_linux.go
generated
vendored
Normal file
95
vendor/github.com/containerd/continuity/fs/copy_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||||
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
// Normally if uid/gid are the same this would be a no-op, but some
|
||||||
|
// filesystems may still return EPERM... for instance NFS does this.
|
||||||
|
// In such a case, this is not an error.
|
||||||
|
if dstStat, err2 := os.Lstat(name); err2 == nil {
|
||||||
|
st2 := dstStat.Sys().(*syscall.Stat_t)
|
||||||
|
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chown %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||||
|
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timespec := []unix.Timespec{unix.Timespec(StatAtime(st)), unix.Timespec(StatMtime(st))}
|
||||||
|
if err := unix.UtimesNanoAt(unix.AT_FDCWD, name, timespec, unix.AT_SYMLINK_NOFOLLOW); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to utime %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFileContent(dst, src *os.File) error {
|
||||||
|
st, err := src.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "unable to stat source")
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := unix.CopyFileRange(int(src.Fd()), nil, int(dst.Fd()), nil, int(st.Size()), 0)
|
||||||
|
if err != nil {
|
||||||
|
if err != unix.ENOSYS && err != unix.EXDEV {
|
||||||
|
return errors.Wrap(err, "copy file range failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bufferPool.Get().(*[]byte)
|
||||||
|
_, err = io.CopyBuffer(dst, src, *buf)
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if int64(n) != st.Size() {
|
||||||
|
return errors.Wrapf(err, "short copy: %d of %d", int64(n), st.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyXAttrs(dst, src string) error {
|
||||||
|
xattrKeys, err := sysx.LListxattr(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||||
|
}
|
||||||
|
for _, xattr := range xattrKeys {
|
||||||
|
data, err := sysx.LGetxattr(src, xattr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||||
|
}
|
||||||
|
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDevice(dst string, fi os.FileInfo) error {
|
||||||
|
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unsupported stat type")
|
||||||
|
}
|
||||||
|
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||||
|
}
|
71
vendor/github.com/containerd/continuity/fs/copy_test.go
generated
vendored
Normal file
71
vendor/github.com/containerd/continuity/fs/copy_test.go
generated
vendored
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
_ "crypto/sha256"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/fs/fstest"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Create copy directory which requires privilege
|
||||||
|
// chown
|
||||||
|
// mknod
|
||||||
|
// setxattr fstest.SetXAttr("/home", "trusted.overlay.opaque", "y"),
|
||||||
|
|
||||||
|
func TestCopyDirectory(t *testing.T) {
|
||||||
|
apply := fstest.Apply(
|
||||||
|
fstest.CreateDir("/etc/", 0755),
|
||||||
|
fstest.CreateFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644),
|
||||||
|
fstest.Link("/etc/hosts", "/etc/hosts.allow"),
|
||||||
|
fstest.CreateDir("/usr/local/lib", 0755),
|
||||||
|
fstest.CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755),
|
||||||
|
fstest.Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"),
|
||||||
|
fstest.CreateDir("/home", 0755),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := testCopy(apply); err != nil {
|
||||||
|
t.Fatalf("Copy test failed: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test used to fail because link-no-nothing.txt would be copied first,
|
||||||
|
// then file operations in dst during the CopyDir would follow the symlink and
|
||||||
|
// fail.
|
||||||
|
func TestCopyDirectoryWithLocalSymlink(t *testing.T) {
|
||||||
|
apply := fstest.Apply(
|
||||||
|
fstest.CreateFile("nothing.txt", []byte{0x00, 0x00}, 0755),
|
||||||
|
fstest.Symlink("nothing.txt", "link-no-nothing.txt"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := testCopy(apply); err != nil {
|
||||||
|
t.Fatalf("Copy test failed: %+v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCopy(apply fstest.Applier) error {
|
||||||
|
t1, err := ioutil.TempDir("", "test-copy-src-")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create temporary directory")
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(t1)
|
||||||
|
|
||||||
|
t2, err := ioutil.TempDir("", "test-copy-dst-")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create temporary directory")
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(t2)
|
||||||
|
|
||||||
|
if err := apply.Apply(t1); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to apply changes")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := CopyDir(t2, t1); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to copy")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fstest.CheckDirectoryEqual(t1, t2)
|
||||||
|
}
|
80
vendor/github.com/containerd/continuity/fs/copy_unix.go
generated
vendored
Normal file
80
vendor/github.com/containerd/continuity/fs/copy_unix.go
generated
vendored
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
// +build solaris darwin freebsd
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/containerd/continuity/sysx"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||||
|
st := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if err := os.Lchown(name, int(st.Uid), int(st.Gid)); err != nil {
|
||||||
|
if os.IsPermission(err) {
|
||||||
|
// Normally if uid/gid are the same this would be a no-op, but some
|
||||||
|
// filesystems may still return EPERM... for instance NFS does this.
|
||||||
|
// In such a case, this is not an error.
|
||||||
|
if dstStat, err2 := os.Lstat(name); err2 == nil {
|
||||||
|
st2 := dstStat.Sys().(*syscall.Stat_t)
|
||||||
|
if st.Uid == st2.Uid && st.Gid == st2.Gid {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chown %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||||
|
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
timespec := []syscall.Timespec{StatAtime(st), StatMtime(st)}
|
||||||
|
if err := syscall.UtimesNano(name, timespec); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to utime %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFileContent(dst, src *os.File) error {
|
||||||
|
buf := bufferPool.Get().(*[]byte)
|
||||||
|
_, err := io.CopyBuffer(dst, src, *buf)
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyXAttrs(dst, src string) error {
|
||||||
|
xattrKeys, err := sysx.LListxattr(src)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to list xattrs on %s", src)
|
||||||
|
}
|
||||||
|
for _, xattr := range xattrKeys {
|
||||||
|
data, err := sysx.LGetxattr(src, xattr)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to get xattr %q on %s", xattr, src)
|
||||||
|
}
|
||||||
|
if err := sysx.LSetxattr(dst, xattr, data, 0); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to set xattr %q on %s", xattr, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDevice(dst string, fi os.FileInfo) error {
|
||||||
|
st, ok := fi.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("unsupported stat type")
|
||||||
|
}
|
||||||
|
return unix.Mknod(dst, uint32(fi.Mode()), int(st.Rdev))
|
||||||
|
}
|
33
vendor/github.com/containerd/continuity/fs/copy_windows.go
generated
vendored
Normal file
33
vendor/github.com/containerd/continuity/fs/copy_windows.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyFileInfo(fi os.FileInfo, name string) error {
|
||||||
|
if err := os.Chmod(name, fi.Mode()); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to chmod %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: copy windows specific metadata
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFileContent(dst, src *os.File) error {
|
||||||
|
buf := bufferPool.Get().(*[]byte)
|
||||||
|
_, err := io.CopyBuffer(dst, src, *buf)
|
||||||
|
bufferPool.Put(buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyXAttrs(dst, src string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDevice(dst string, fi os.FileInfo) error {
|
||||||
|
return errors.New("device copy not supported")
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue