vendor: initial vendor
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
109
vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go
generated
vendored
Normal file
109
vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
package homedir // import "github.com/docker/docker/pkg/homedir"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
)
|
||||
|
||||
// GetStatic returns the home directory for the current user without calling
|
||||
// os/user.Current(). This is useful for static-linked binary on glibc-based
|
||||
// system, because a call to os/user.Current() in a static binary leads to
|
||||
// segfault due to a glibc issue that won't be fixed in a short term.
|
||||
// (#29344, golang/go#13470, https://sourceware.org/bugzilla/show_bug.cgi?id=19341)
|
||||
func GetStatic() (string, error) {
|
||||
uid := os.Getuid()
|
||||
usr, err := idtools.LookupUID(uid)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return usr.Home, nil
|
||||
}
|
||||
|
||||
// GetRuntimeDir returns XDG_RUNTIME_DIR.
|
||||
// XDG_RUNTIME_DIR is typically configured via pam_systemd.
|
||||
// GetRuntimeDir returns non-nil error if XDG_RUNTIME_DIR is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetRuntimeDir() (string, error) {
|
||||
if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" {
|
||||
return xdgRuntimeDir, nil
|
||||
}
|
||||
return "", errors.New("could not get XDG_RUNTIME_DIR")
|
||||
}
|
||||
|
||||
// StickRuntimeDirContents sets the sticky bit on files that are under
|
||||
// XDG_RUNTIME_DIR, so that the files won't be periodically removed by the system.
|
||||
//
|
||||
// StickyRuntimeDir returns slice of sticked files.
|
||||
// StickyRuntimeDir returns nil error if XDG_RUNTIME_DIR is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func StickRuntimeDirContents(files []string) ([]string, error) {
|
||||
runtimeDir, err := GetRuntimeDir()
|
||||
if err != nil {
|
||||
// ignore error if runtimeDir is empty
|
||||
return nil, nil
|
||||
}
|
||||
runtimeDir, err = filepath.Abs(runtimeDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var sticked []string
|
||||
for _, f := range files {
|
||||
f, err = filepath.Abs(f)
|
||||
if err != nil {
|
||||
return sticked, err
|
||||
}
|
||||
if strings.HasPrefix(f, runtimeDir+"/") {
|
||||
if err = stick(f); err != nil {
|
||||
return sticked, err
|
||||
}
|
||||
sticked = append(sticked, f)
|
||||
}
|
||||
}
|
||||
return sticked, nil
|
||||
}
|
||||
|
||||
func stick(f string) error {
|
||||
st, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m := st.Mode()
|
||||
m |= os.ModeSticky
|
||||
return os.Chmod(f, m)
|
||||
}
|
||||
|
||||
// GetDataHome returns XDG_DATA_HOME.
|
||||
// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetDataHome() (string, error) {
|
||||
if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" {
|
||||
return xdgDataHome, nil
|
||||
}
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
return "", errors.New("could not get either XDG_DATA_HOME or HOME")
|
||||
}
|
||||
return filepath.Join(home, ".local", "share"), nil
|
||||
}
|
||||
|
||||
// GetConfigHome returns XDG_CONFIG_HOME.
|
||||
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set.
|
||||
//
|
||||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
|
||||
func GetConfigHome() (string, error) {
|
||||
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
|
||||
return xdgConfigHome, nil
|
||||
}
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
return "", errors.New("could not get either XDG_CONFIG_HOME or HOME")
|
||||
}
|
||||
return filepath.Join(home, ".config"), nil
|
||||
}
|
||||
33
vendor/github.com/docker/docker/pkg/homedir/homedir_others.go
generated
vendored
Normal file
33
vendor/github.com/docker/docker/pkg/homedir/homedir_others.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// +build !linux
|
||||
|
||||
package homedir // import "github.com/docker/docker/pkg/homedir"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// GetStatic is not needed for non-linux systems.
|
||||
// (Precisely, it is needed only for glibc-based linux systems.)
|
||||
func GetStatic() (string, error) {
|
||||
return "", errors.New("homedir.GetStatic() is not supported on this system")
|
||||
}
|
||||
|
||||
// GetRuntimeDir is unsupported on non-linux system.
|
||||
func GetRuntimeDir() (string, error) {
|
||||
return "", errors.New("homedir.GetRuntimeDir() is not supported on this system")
|
||||
}
|
||||
|
||||
// StickRuntimeDirContents is unsupported on non-linux system.
|
||||
func StickRuntimeDirContents(files []string) ([]string, error) {
|
||||
return nil, errors.New("homedir.StickRuntimeDirContents() is not supported on this system")
|
||||
}
|
||||
|
||||
// GetDataHome is unsupported on non-linux system.
|
||||
func GetDataHome() (string, error) {
|
||||
return "", errors.New("homedir.GetDataHome() is not supported on this system")
|
||||
}
|
||||
|
||||
// GetConfigHome is unsupported on non-linux system.
|
||||
func GetConfigHome() (string, error) {
|
||||
return "", errors.New("homedir.GetConfigHome() is not supported on this system")
|
||||
}
|
||||
34
vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go
generated
vendored
Normal file
34
vendor/github.com/docker/docker/pkg/homedir/homedir_unix.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
// +build !windows
|
||||
|
||||
package homedir // import "github.com/docker/docker/pkg/homedir"
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
)
|
||||
|
||||
// Key returns the env var name for the user's home dir based on
|
||||
// the platform being run on
|
||||
func Key() string {
|
||||
return "HOME"
|
||||
}
|
||||
|
||||
// Get returns the home directory of the current user with the help of
|
||||
// environment variables depending on the target operating system.
|
||||
// Returned path should be used with "path/filepath" to form new paths.
|
||||
func Get() string {
|
||||
home := os.Getenv(Key())
|
||||
if home == "" {
|
||||
if u, err := user.CurrentUser(); err == nil {
|
||||
return u.Home
|
||||
}
|
||||
}
|
||||
return home
|
||||
}
|
||||
|
||||
// GetShortcutString returns the string that is shortcut to user's home directory
|
||||
// in the native shell of the platform running on.
|
||||
func GetShortcutString() string {
|
||||
return "~"
|
||||
}
|
||||
24
vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go
generated
vendored
Normal file
24
vendor/github.com/docker/docker/pkg/homedir/homedir_windows.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package homedir // import "github.com/docker/docker/pkg/homedir"
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Key returns the env var name for the user's home dir based on
|
||||
// the platform being run on
|
||||
func Key() string {
|
||||
return "USERPROFILE"
|
||||
}
|
||||
|
||||
// Get returns the home directory of the current user with the help of
|
||||
// environment variables depending on the target operating system.
|
||||
// Returned path should be used with "path/filepath" to form new paths.
|
||||
func Get() string {
|
||||
return os.Getenv(Key())
|
||||
}
|
||||
|
||||
// GetShortcutString returns the string that is shortcut to user's home directory
|
||||
// in the native shell of the platform running on.
|
||||
func GetShortcutString() string {
|
||||
return "%USERPROFILE%" // be careful while using in format functions
|
||||
}
|
||||
267
vendor/github.com/docker/docker/pkg/idtools/idtools.go
generated
vendored
Normal file
267
vendor/github.com/docker/docker/pkg/idtools/idtools.go
generated
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IDMap contains a single entry for user namespace range remapping. An array
|
||||
// of IDMap entries represents the structure that will be provided to the Linux
|
||||
// kernel for creating a user namespace.
|
||||
type IDMap struct {
|
||||
ContainerID int `json:"container_id"`
|
||||
HostID int `json:"host_id"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
type subIDRange struct {
|
||||
Start int
|
||||
Length int
|
||||
}
|
||||
|
||||
type ranges []subIDRange
|
||||
|
||||
func (e ranges) Len() int { return len(e) }
|
||||
func (e ranges) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
|
||||
func (e ranges) Less(i, j int) bool { return e[i].Start < e[j].Start }
|
||||
|
||||
const (
|
||||
subuidFileName = "/etc/subuid"
|
||||
subgidFileName = "/etc/subgid"
|
||||
)
|
||||
|
||||
// MkdirAllAndChown creates a directory (include any along the path) and then modifies
|
||||
// ownership to the requested uid/gid. If the directory already exists, this
|
||||
// function will still change ownership to the requested uid/gid pair.
|
||||
func MkdirAllAndChown(path string, mode os.FileMode, owner Identity) error {
|
||||
return mkdirAs(path, mode, owner, true, true)
|
||||
}
|
||||
|
||||
// MkdirAndChown creates a directory and then modifies ownership to the requested uid/gid.
|
||||
// If the directory already exists, this function still changes ownership.
|
||||
// Note that unlike os.Mkdir(), this function does not return IsExist error
|
||||
// in case path already exists.
|
||||
func MkdirAndChown(path string, mode os.FileMode, owner Identity) error {
|
||||
return mkdirAs(path, mode, owner, false, true)
|
||||
}
|
||||
|
||||
// MkdirAllAndChownNew creates a directory (include any along the path) and then modifies
|
||||
// ownership ONLY of newly created directories to the requested uid/gid. If the
|
||||
// directories along the path exist, no change of ownership will be performed
|
||||
func MkdirAllAndChownNew(path string, mode os.FileMode, owner Identity) error {
|
||||
return mkdirAs(path, mode, owner, true, false)
|
||||
}
|
||||
|
||||
// GetRootUIDGID retrieves the remapped root uid/gid pair from the set of maps.
|
||||
// If the maps are empty, then the root uid/gid will default to "real" 0/0
|
||||
func GetRootUIDGID(uidMap, gidMap []IDMap) (int, int, error) {
|
||||
uid, err := toHost(0, uidMap)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
gid, err := toHost(0, gidMap)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
// toContainer takes an id mapping, and uses it to translate a
|
||||
// host ID to the remapped ID. If no map is provided, then the translation
|
||||
// assumes a 1-to-1 mapping and returns the passed in id
|
||||
func toContainer(hostID int, idMap []IDMap) (int, error) {
|
||||
if idMap == nil {
|
||||
return hostID, nil
|
||||
}
|
||||
for _, m := range idMap {
|
||||
if (hostID >= m.HostID) && (hostID <= (m.HostID + m.Size - 1)) {
|
||||
contID := m.ContainerID + (hostID - m.HostID)
|
||||
return contID, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("Host ID %d cannot be mapped to a container ID", hostID)
|
||||
}
|
||||
|
||||
// toHost takes an id mapping and a remapped ID, and translates the
|
||||
// ID to the mapped host ID. If no map is provided, then the translation
|
||||
// assumes a 1-to-1 mapping and returns the passed in id #
|
||||
func toHost(contID int, idMap []IDMap) (int, error) {
|
||||
if idMap == nil {
|
||||
return contID, nil
|
||||
}
|
||||
for _, m := range idMap {
|
||||
if (contID >= m.ContainerID) && (contID <= (m.ContainerID + m.Size - 1)) {
|
||||
hostID := m.HostID + (contID - m.ContainerID)
|
||||
return hostID, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("Container ID %d cannot be mapped to a host ID", contID)
|
||||
}
|
||||
|
||||
// Identity is either a UID and GID pair or a SID (but not both)
|
||||
type Identity struct {
|
||||
UID int
|
||||
GID int
|
||||
SID string
|
||||
}
|
||||
|
||||
// IdentityMapping contains a mappings of UIDs and GIDs
|
||||
type IdentityMapping struct {
|
||||
uids []IDMap
|
||||
gids []IDMap
|
||||
}
|
||||
|
||||
// NewIdentityMapping takes a requested user and group name and
|
||||
// using the data from /etc/sub{uid,gid} ranges, creates the
|
||||
// proper uid and gid remapping ranges for that user/group pair
|
||||
func NewIdentityMapping(username, groupname string) (*IdentityMapping, error) {
|
||||
subuidRanges, err := parseSubuid(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subgidRanges, err := parseSubgid(groupname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(subuidRanges) == 0 {
|
||||
return nil, fmt.Errorf("No subuid ranges found for user %q", username)
|
||||
}
|
||||
if len(subgidRanges) == 0 {
|
||||
return nil, fmt.Errorf("No subgid ranges found for group %q", groupname)
|
||||
}
|
||||
|
||||
return &IdentityMapping{
|
||||
uids: createIDMap(subuidRanges),
|
||||
gids: createIDMap(subgidRanges),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewIDMappingsFromMaps creates a new mapping from two slices
|
||||
// Deprecated: this is a temporary shim while transitioning to IDMapping
|
||||
func NewIDMappingsFromMaps(uids []IDMap, gids []IDMap) *IdentityMapping {
|
||||
return &IdentityMapping{uids: uids, gids: gids}
|
||||
}
|
||||
|
||||
// RootPair returns a uid and gid pair for the root user. The error is ignored
|
||||
// because a root user always exists, and the defaults are correct when the uid
|
||||
// and gid maps are empty.
|
||||
func (i *IdentityMapping) RootPair() Identity {
|
||||
uid, gid, _ := GetRootUIDGID(i.uids, i.gids)
|
||||
return Identity{UID: uid, GID: gid}
|
||||
}
|
||||
|
||||
// ToHost returns the host UID and GID for the container uid, gid.
|
||||
// Remapping is only performed if the ids aren't already the remapped root ids
|
||||
func (i *IdentityMapping) ToHost(pair Identity) (Identity, error) {
|
||||
var err error
|
||||
target := i.RootPair()
|
||||
|
||||
if pair.UID != target.UID {
|
||||
target.UID, err = toHost(pair.UID, i.uids)
|
||||
if err != nil {
|
||||
return target, err
|
||||
}
|
||||
}
|
||||
|
||||
if pair.GID != target.GID {
|
||||
target.GID, err = toHost(pair.GID, i.gids)
|
||||
}
|
||||
return target, err
|
||||
}
|
||||
|
||||
// ToContainer returns the container UID and GID for the host uid and gid
|
||||
func (i *IdentityMapping) ToContainer(pair Identity) (int, int, error) {
|
||||
uid, err := toContainer(pair.UID, i.uids)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
gid, err := toContainer(pair.GID, i.gids)
|
||||
return uid, gid, err
|
||||
}
|
||||
|
||||
// Empty returns true if there are no id mappings
|
||||
func (i *IdentityMapping) Empty() bool {
|
||||
return len(i.uids) == 0 && len(i.gids) == 0
|
||||
}
|
||||
|
||||
// UIDs return the UID mapping
|
||||
// TODO: remove this once everything has been refactored to use pairs
|
||||
func (i *IdentityMapping) UIDs() []IDMap {
|
||||
return i.uids
|
||||
}
|
||||
|
||||
// GIDs return the UID mapping
|
||||
// TODO: remove this once everything has been refactored to use pairs
|
||||
func (i *IdentityMapping) GIDs() []IDMap {
|
||||
return i.gids
|
||||
}
|
||||
|
||||
func createIDMap(subidRanges ranges) []IDMap {
|
||||
idMap := []IDMap{}
|
||||
|
||||
// sort the ranges by lowest ID first
|
||||
sort.Sort(subidRanges)
|
||||
containerID := 0
|
||||
for _, idrange := range subidRanges {
|
||||
idMap = append(idMap, IDMap{
|
||||
ContainerID: containerID,
|
||||
HostID: idrange.Start,
|
||||
Size: idrange.Length,
|
||||
})
|
||||
containerID = containerID + idrange.Length
|
||||
}
|
||||
return idMap
|
||||
}
|
||||
|
||||
func parseSubuid(username string) (ranges, error) {
|
||||
return parseSubidFile(subuidFileName, username)
|
||||
}
|
||||
|
||||
func parseSubgid(username string) (ranges, error) {
|
||||
return parseSubidFile(subgidFileName, username)
|
||||
}
|
||||
|
||||
// parseSubidFile will read the appropriate file (/etc/subuid or /etc/subgid)
|
||||
// and return all found ranges for a specified username. If the special value
|
||||
// "ALL" is supplied for username, then all ranges in the file will be returned
|
||||
func parseSubidFile(path, username string) (ranges, error) {
|
||||
var rangeList ranges
|
||||
|
||||
subidFile, err := os.Open(path)
|
||||
if err != nil {
|
||||
return rangeList, err
|
||||
}
|
||||
defer subidFile.Close()
|
||||
|
||||
s := bufio.NewScanner(subidFile)
|
||||
for s.Scan() {
|
||||
if err := s.Err(); err != nil {
|
||||
return rangeList, err
|
||||
}
|
||||
|
||||
text := strings.TrimSpace(s.Text())
|
||||
if text == "" || strings.HasPrefix(text, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(text, ":")
|
||||
if len(parts) != 3 {
|
||||
return rangeList, fmt.Errorf("Cannot parse subuid/gid information: Format not correct for %s file", path)
|
||||
}
|
||||
if parts[0] == username || username == "ALL" {
|
||||
startid, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
|
||||
}
|
||||
length, err := strconv.Atoi(parts[2])
|
||||
if err != nil {
|
||||
return rangeList, fmt.Errorf("String to int conversion failed during subuid/gid parsing of %s: %v", path, err)
|
||||
}
|
||||
rangeList = append(rangeList, subIDRange{startid, length})
|
||||
}
|
||||
}
|
||||
return rangeList, nil
|
||||
}
|
||||
231
vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go
generated
vendored
Normal file
231
vendor/github.com/docker/docker/pkg/idtools/idtools_unix.go
generated
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
// +build !windows
|
||||
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
)
|
||||
|
||||
var (
|
||||
entOnce sync.Once
|
||||
getentCmd string
|
||||
)
|
||||
|
||||
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
|
||||
// make an array containing the original path asked for, plus (for mkAll == true)
|
||||
// all path components leading up to the complete path that don't exist before we MkdirAll
|
||||
// so that we can chown all of them properly at the end. If chownExisting is false, we won't
|
||||
// chown the full directory path if it exists
|
||||
|
||||
var paths []string
|
||||
|
||||
stat, err := system.Stat(path)
|
||||
if err == nil {
|
||||
if !stat.IsDir() {
|
||||
return &os.PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR}
|
||||
}
|
||||
if !chownExisting {
|
||||
return nil
|
||||
}
|
||||
|
||||
// short-circuit--we were called with an existing directory and chown was requested
|
||||
return lazyChown(path, owner.UID, owner.GID, stat)
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
paths = []string{path}
|
||||
}
|
||||
|
||||
if mkAll {
|
||||
// walk back to "/" looking for directories which do not exist
|
||||
// and add them to the paths array for chown after creation
|
||||
dirPath := path
|
||||
for {
|
||||
dirPath = filepath.Dir(dirPath)
|
||||
if dirPath == "/" {
|
||||
break
|
||||
}
|
||||
if _, err := os.Stat(dirPath); err != nil && os.IsNotExist(err) {
|
||||
paths = append(paths, dirPath)
|
||||
}
|
||||
}
|
||||
if err := system.MkdirAll(path, mode, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := os.Mkdir(path, mode); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// even if it existed, we will chown the requested path + any subpaths that
|
||||
// didn't exist when we called MkdirAll
|
||||
for _, pathComponent := range paths {
|
||||
if err := lazyChown(pathComponent, owner.UID, owner.GID, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||
// if that uid, gid pair has access (execute bit) to the directory
|
||||
func CanAccess(path string, pair Identity) bool {
|
||||
statInfo, err := system.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fileMode := os.FileMode(statInfo.Mode())
|
||||
permBits := fileMode.Perm()
|
||||
return accessible(statInfo.UID() == uint32(pair.UID),
|
||||
statInfo.GID() == uint32(pair.GID), permBits)
|
||||
}
|
||||
|
||||
func accessible(isOwner, isGroup bool, perms os.FileMode) bool {
|
||||
if isOwner && (perms&0100 == 0100) {
|
||||
return true
|
||||
}
|
||||
if isGroup && (perms&0010 == 0010) {
|
||||
return true
|
||||
}
|
||||
if perms&0001 == 0001 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LookupUser uses traditional local system files lookup (from libcontainer/user) on a username,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupUser(username string) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUser(username)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
usr, err = getentUser(fmt.Sprintf("%s %s", "passwd", username))
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
return usr, nil
|
||||
}
|
||||
|
||||
// LookupUID uses traditional local system files lookup (from libcontainer/user) on a uid,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupUID(uid int) (user.User, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
usr, err := user.LookupUid(uid)
|
||||
if err == nil {
|
||||
return usr, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured passwd dbs
|
||||
return getentUser(fmt.Sprintf("%s %d", "passwd", uid))
|
||||
}
|
||||
|
||||
func getentUser(args string) (user.User, error) {
|
||||
reader, err := callGetent(args)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
users, err := user.ParsePasswd(reader)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return user.User{}, fmt.Errorf("getent failed to find passwd entry for %q", strings.Split(args, " ")[1])
|
||||
}
|
||||
return users[0], nil
|
||||
}
|
||||
|
||||
// LookupGroup uses traditional local system files lookup (from libcontainer/user) on a group name,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupGroup(groupname string) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGroup(groupname)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(fmt.Sprintf("%s %s", "group", groupname))
|
||||
}
|
||||
|
||||
// LookupGID uses traditional local system files lookup (from libcontainer/user) on a group ID,
|
||||
// followed by a call to `getent` for supporting host configured non-files passwd and group dbs
|
||||
func LookupGID(gid int) (user.Group, error) {
|
||||
// first try a local system files lookup using existing capabilities
|
||||
group, err := user.LookupGid(gid)
|
||||
if err == nil {
|
||||
return group, nil
|
||||
}
|
||||
// local files lookup failed; attempt to call `getent` to query configured group dbs
|
||||
return getentGroup(fmt.Sprintf("%s %d", "group", gid))
|
||||
}
|
||||
|
||||
func getentGroup(args string) (user.Group, error) {
|
||||
reader, err := callGetent(args)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
groups, err := user.ParseGroup(reader)
|
||||
if err != nil {
|
||||
return user.Group{}, err
|
||||
}
|
||||
if len(groups) == 0 {
|
||||
return user.Group{}, fmt.Errorf("getent failed to find groups entry for %q", strings.Split(args, " ")[1])
|
||||
}
|
||||
return groups[0], nil
|
||||
}
|
||||
|
||||
func callGetent(args string) (io.Reader, error) {
|
||||
entOnce.Do(func() { getentCmd, _ = resolveBinary("getent") })
|
||||
// if no `getent` command on host, can't do anything else
|
||||
if getentCmd == "" {
|
||||
return nil, fmt.Errorf("")
|
||||
}
|
||||
out, err := execCmd(getentCmd, args)
|
||||
if err != nil {
|
||||
exitCode, errC := system.GetExitCode(err)
|
||||
if errC != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch exitCode {
|
||||
case 1:
|
||||
return nil, fmt.Errorf("getent reported invalid parameters/database unknown")
|
||||
case 2:
|
||||
terms := strings.Split(args, " ")
|
||||
return nil, fmt.Errorf("getent unable to find entry %q in %s database", terms[1], terms[0])
|
||||
case 3:
|
||||
return nil, fmt.Errorf("getent database doesn't support enumeration")
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return bytes.NewReader(out), nil
|
||||
}
|
||||
|
||||
// lazyChown performs a chown only if the uid/gid don't match what's requested
|
||||
// Normally a Chown is a no-op if uid/gid match, but in some cases this can still cause an error, e.g. if the
|
||||
// dir is on an NFS share, so don't call chown unless we absolutely must.
|
||||
func lazyChown(p string, uid, gid int, stat *system.StatT) error {
|
||||
if stat == nil {
|
||||
var err error
|
||||
stat, err = system.Stat(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if stat.UID() == uint32(uid) && stat.GID() == uint32(gid) {
|
||||
return nil
|
||||
}
|
||||
return os.Chown(p, uid, gid)
|
||||
}
|
||||
25
vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
25
vendor/github.com/docker/docker/pkg/idtools/idtools_windows.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
// This is currently a wrapper around MkdirAll, however, since currently
|
||||
// permissions aren't set through this path, the identity isn't utilized.
|
||||
// Ownership is handled elsewhere, but in the future could be support here
|
||||
// too.
|
||||
func mkdirAs(path string, mode os.FileMode, owner Identity, mkAll, chownExisting bool) error {
|
||||
if err := system.MkdirAll(path, mode, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanAccess takes a valid (existing) directory and a uid, gid pair and determines
|
||||
// if that uid, gid pair has access (execute bit) to the directory
|
||||
// Windows does not require/support this function, so always return true
|
||||
func CanAccess(path string, identity Identity) bool {
|
||||
return true
|
||||
}
|
||||
164
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
164
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_linux.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// add a user and/or group to Linux /etc/passwd, /etc/group using standard
|
||||
// Linux distribution commands:
|
||||
// adduser --system --shell /bin/false --disabled-login --disabled-password --no-create-home --group <username>
|
||||
// useradd -r -s /bin/false <username>
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
userCommand string
|
||||
|
||||
cmdTemplates = map[string]string{
|
||||
"adduser": "--system --shell /bin/false --no-create-home --disabled-login --disabled-password --group %s",
|
||||
"useradd": "-r -s /bin/false %s",
|
||||
"usermod": "-%s %d-%d %s",
|
||||
}
|
||||
|
||||
idOutRegexp = regexp.MustCompile(`uid=([0-9]+).*gid=([0-9]+)`)
|
||||
// default length for a UID/GID subordinate range
|
||||
defaultRangeLen = 65536
|
||||
defaultRangeStart = 100000
|
||||
userMod = "usermod"
|
||||
)
|
||||
|
||||
// AddNamespaceRangesUser takes a username and uses the standard system
|
||||
// utility to create a system user/group pair used to hold the
|
||||
// /etc/sub{uid,gid} ranges which will be used for user namespace
|
||||
// mapping ranges in containers.
|
||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||
if err := addUser(name); err != nil {
|
||||
return -1, -1, fmt.Errorf("Error adding user %q: %v", name, err)
|
||||
}
|
||||
|
||||
// Query the system for the created uid and gid pair
|
||||
out, err := execCmd("id", name)
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("Error trying to find uid/gid for new user %q: %v", name, err)
|
||||
}
|
||||
matches := idOutRegexp.FindStringSubmatch(strings.TrimSpace(string(out)))
|
||||
if len(matches) != 3 {
|
||||
return -1, -1, fmt.Errorf("Can't find uid, gid from `id` output: %q", string(out))
|
||||
}
|
||||
uid, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("Can't convert found uid (%s) to int: %v", matches[1], err)
|
||||
}
|
||||
gid, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return -1, -1, fmt.Errorf("Can't convert found gid (%s) to int: %v", matches[2], err)
|
||||
}
|
||||
|
||||
// Now we need to create the subuid/subgid ranges for our new user/group (system users
|
||||
// do not get auto-created ranges in subuid/subgid)
|
||||
|
||||
if err := createSubordinateRanges(name); err != nil {
|
||||
return -1, -1, fmt.Errorf("Couldn't create subordinate ID ranges: %v", err)
|
||||
}
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
func addUser(userName string) error {
|
||||
once.Do(func() {
|
||||
// set up which commands are used for adding users/groups dependent on distro
|
||||
if _, err := resolveBinary("adduser"); err == nil {
|
||||
userCommand = "adduser"
|
||||
} else if _, err := resolveBinary("useradd"); err == nil {
|
||||
userCommand = "useradd"
|
||||
}
|
||||
})
|
||||
if userCommand == "" {
|
||||
return fmt.Errorf("Cannot add user; no useradd/adduser binary found")
|
||||
}
|
||||
args := fmt.Sprintf(cmdTemplates[userCommand], userName)
|
||||
out, err := execCmd(userCommand, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to add user with error: %v; output: %q", err, string(out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createSubordinateRanges(name string) error {
|
||||
|
||||
// first, we should verify that ranges weren't automatically created
|
||||
// by the distro tooling
|
||||
ranges, err := parseSubuid(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while looking for subuid ranges for user %q: %v", name, err)
|
||||
}
|
||||
if len(ranges) == 0 {
|
||||
// no UID ranges; let's create one
|
||||
startID, err := findNextUIDRange()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't find available subuid range: %v", err)
|
||||
}
|
||||
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "v", startID, startID+defaultRangeLen-1, name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to add subuid range to user: %q; output: %s, err: %v", name, out, err)
|
||||
}
|
||||
}
|
||||
|
||||
ranges, err = parseSubgid(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error while looking for subgid ranges for user %q: %v", name, err)
|
||||
}
|
||||
if len(ranges) == 0 {
|
||||
// no GID ranges; let's create one
|
||||
startID, err := findNextGIDRange()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Can't find available subgid range: %v", err)
|
||||
}
|
||||
out, err := execCmd(userMod, fmt.Sprintf(cmdTemplates[userMod], "w", startID, startID+defaultRangeLen-1, name))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to add subgid range to user: %q; output: %s, err: %v", name, out, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findNextUIDRange() (int, error) {
|
||||
ranges, err := parseSubuid("ALL")
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subuid file: %v", err)
|
||||
}
|
||||
sort.Sort(ranges)
|
||||
return findNextRangeStart(ranges)
|
||||
}
|
||||
|
||||
func findNextGIDRange() (int, error) {
|
||||
ranges, err := parseSubgid("ALL")
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("Couldn't parse all ranges in /etc/subgid file: %v", err)
|
||||
}
|
||||
sort.Sort(ranges)
|
||||
return findNextRangeStart(ranges)
|
||||
}
|
||||
|
||||
func findNextRangeStart(rangeList ranges) (int, error) {
|
||||
startID := defaultRangeStart
|
||||
for _, arange := range rangeList {
|
||||
if wouldOverlap(arange, startID) {
|
||||
startID = arange.Start + arange.Length
|
||||
}
|
||||
}
|
||||
return startID, nil
|
||||
}
|
||||
|
||||
func wouldOverlap(arange subIDRange, ID int) bool {
|
||||
low := ID
|
||||
high := ID + defaultRangeLen
|
||||
if (low >= arange.Start && low <= arange.Start+arange.Length) ||
|
||||
(high <= arange.Start+arange.Length && high >= arange.Start) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
12
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/docker/docker/pkg/idtools/usergroupadd_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build !linux
|
||||
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
|
||||
import "fmt"
|
||||
|
||||
// AddNamespaceRangesUser takes a name and finds an unused uid, gid pair
|
||||
// and calls the appropriate helper function to add the group and then
|
||||
// the user to the group in /etc/group and /etc/passwd respectively.
|
||||
func AddNamespaceRangesUser(name string) (int, int, error) {
|
||||
return -1, -1, fmt.Errorf("No support for adding users or groups on this OS")
|
||||
}
|
||||
32
vendor/github.com/docker/docker/pkg/idtools/utils_unix.go
generated
vendored
Normal file
32
vendor/github.com/docker/docker/pkg/idtools/utils_unix.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// +build !windows
|
||||
|
||||
package idtools // import "github.com/docker/docker/pkg/idtools"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func resolveBinary(binname string) (string, error) {
|
||||
binaryPath, err := exec.LookPath(binname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resolvedPath, err := filepath.EvalSymlinks(binaryPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
//only return no error if the final resolved binary basename
|
||||
//matches what was searched for
|
||||
if filepath.Base(resolvedPath) == binname {
|
||||
return resolvedPath, nil
|
||||
}
|
||||
return "", fmt.Errorf("Binary %q does not resolve to a binary of that name in $PATH (%q)", binname, resolvedPath)
|
||||
}
|
||||
|
||||
func execCmd(cmd, args string) ([]byte, error) {
|
||||
execCmd := exec.Command(cmd, strings.Split(args, " ")...)
|
||||
return execCmd.CombinedOutput()
|
||||
}
|
||||
51
vendor/github.com/docker/docker/pkg/ioutils/buffer.go
generated
vendored
Normal file
51
vendor/github.com/docker/docker/pkg/ioutils/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
var errBufferFull = errors.New("buffer is full")
|
||||
|
||||
type fixedBuffer struct {
|
||||
buf []byte
|
||||
pos int
|
||||
lastRead int
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Write(p []byte) (int, error) {
|
||||
n := copy(b.buf[b.pos:cap(b.buf)], p)
|
||||
b.pos += n
|
||||
|
||||
if n < len(p) {
|
||||
if b.pos == cap(b.buf) {
|
||||
return n, errBufferFull
|
||||
}
|
||||
return n, io.ErrShortWrite
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Read(p []byte) (int, error) {
|
||||
n := copy(p, b.buf[b.lastRead:b.pos])
|
||||
b.lastRead += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Len() int {
|
||||
return b.pos - b.lastRead
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Cap() int {
|
||||
return cap(b.buf)
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) Reset() {
|
||||
b.pos = 0
|
||||
b.lastRead = 0
|
||||
b.buf = b.buf[:0]
|
||||
}
|
||||
|
||||
func (b *fixedBuffer) String() string {
|
||||
return string(b.buf[b.lastRead:b.pos])
|
||||
}
|
||||
186
vendor/github.com/docker/docker/pkg/ioutils/bytespipe.go
generated
vendored
Normal file
186
vendor/github.com/docker/docker/pkg/ioutils/bytespipe.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// maxCap is the highest capacity to use in byte slices that buffer data.
|
||||
const maxCap = 1e6
|
||||
|
||||
// minCap is the lowest capacity to use in byte slices that buffer data
|
||||
const minCap = 64
|
||||
|
||||
// blockThreshold is the minimum number of bytes in the buffer which will cause
|
||||
// a write to BytesPipe to block when allocating a new slice.
|
||||
const blockThreshold = 1e6
|
||||
|
||||
var (
|
||||
// ErrClosed is returned when Write is called on a closed BytesPipe.
|
||||
ErrClosed = errors.New("write to closed BytesPipe")
|
||||
|
||||
bufPools = make(map[int]*sync.Pool)
|
||||
bufPoolsLock sync.Mutex
|
||||
)
|
||||
|
||||
// BytesPipe is io.ReadWriteCloser which works similarly to pipe(queue).
|
||||
// All written data may be read at most once. Also, BytesPipe allocates
|
||||
// and releases new byte slices to adjust to current needs, so the buffer
|
||||
// won't be overgrown after peak loads.
|
||||
type BytesPipe struct {
|
||||
mu sync.Mutex
|
||||
wait *sync.Cond
|
||||
buf []*fixedBuffer
|
||||
bufLen int
|
||||
closeErr error // error to return from next Read. set to nil if not closed.
|
||||
}
|
||||
|
||||
// NewBytesPipe creates new BytesPipe, initialized by specified slice.
|
||||
// If buf is nil, then it will be initialized with slice which cap is 64.
|
||||
// buf will be adjusted in a way that len(buf) == 0, cap(buf) == cap(buf).
|
||||
func NewBytesPipe() *BytesPipe {
|
||||
bp := &BytesPipe{}
|
||||
bp.buf = append(bp.buf, getBuffer(minCap))
|
||||
bp.wait = sync.NewCond(&bp.mu)
|
||||
return bp
|
||||
}
|
||||
|
||||
// Write writes p to BytesPipe.
|
||||
// It can allocate new []byte slices in a process of writing.
|
||||
func (bp *BytesPipe) Write(p []byte) (int, error) {
|
||||
bp.mu.Lock()
|
||||
|
||||
written := 0
|
||||
loop0:
|
||||
for {
|
||||
if bp.closeErr != nil {
|
||||
bp.mu.Unlock()
|
||||
return written, ErrClosed
|
||||
}
|
||||
|
||||
if len(bp.buf) == 0 {
|
||||
bp.buf = append(bp.buf, getBuffer(64))
|
||||
}
|
||||
// get the last buffer
|
||||
b := bp.buf[len(bp.buf)-1]
|
||||
|
||||
n, err := b.Write(p)
|
||||
written += n
|
||||
bp.bufLen += n
|
||||
|
||||
// errBufferFull is an error we expect to get if the buffer is full
|
||||
if err != nil && err != errBufferFull {
|
||||
bp.wait.Broadcast()
|
||||
bp.mu.Unlock()
|
||||
return written, err
|
||||
}
|
||||
|
||||
// if there was enough room to write all then break
|
||||
if len(p) == n {
|
||||
break
|
||||
}
|
||||
|
||||
// more data: write to the next slice
|
||||
p = p[n:]
|
||||
|
||||
// make sure the buffer doesn't grow too big from this write
|
||||
for bp.bufLen >= blockThreshold {
|
||||
bp.wait.Wait()
|
||||
if bp.closeErr != nil {
|
||||
continue loop0
|
||||
}
|
||||
}
|
||||
|
||||
// add new byte slice to the buffers slice and continue writing
|
||||
nextCap := b.Cap() * 2
|
||||
if nextCap > maxCap {
|
||||
nextCap = maxCap
|
||||
}
|
||||
bp.buf = append(bp.buf, getBuffer(nextCap))
|
||||
}
|
||||
bp.wait.Broadcast()
|
||||
bp.mu.Unlock()
|
||||
return written, nil
|
||||
}
|
||||
|
||||
// CloseWithError causes further reads from a BytesPipe to return immediately.
|
||||
func (bp *BytesPipe) CloseWithError(err error) error {
|
||||
bp.mu.Lock()
|
||||
if err != nil {
|
||||
bp.closeErr = err
|
||||
} else {
|
||||
bp.closeErr = io.EOF
|
||||
}
|
||||
bp.wait.Broadcast()
|
||||
bp.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close causes further reads from a BytesPipe to return immediately.
|
||||
func (bp *BytesPipe) Close() error {
|
||||
return bp.CloseWithError(nil)
|
||||
}
|
||||
|
||||
// Read reads bytes from BytesPipe.
|
||||
// Data could be read only once.
|
||||
func (bp *BytesPipe) Read(p []byte) (n int, err error) {
|
||||
bp.mu.Lock()
|
||||
if bp.bufLen == 0 {
|
||||
if bp.closeErr != nil {
|
||||
bp.mu.Unlock()
|
||||
return 0, bp.closeErr
|
||||
}
|
||||
bp.wait.Wait()
|
||||
if bp.bufLen == 0 && bp.closeErr != nil {
|
||||
err := bp.closeErr
|
||||
bp.mu.Unlock()
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
for bp.bufLen > 0 {
|
||||
b := bp.buf[0]
|
||||
read, _ := b.Read(p) // ignore error since fixedBuffer doesn't really return an error
|
||||
n += read
|
||||
bp.bufLen -= read
|
||||
|
||||
if b.Len() == 0 {
|
||||
// it's empty so return it to the pool and move to the next one
|
||||
returnBuffer(b)
|
||||
bp.buf[0] = nil
|
||||
bp.buf = bp.buf[1:]
|
||||
}
|
||||
|
||||
if len(p) == read {
|
||||
break
|
||||
}
|
||||
|
||||
p = p[read:]
|
||||
}
|
||||
|
||||
bp.wait.Broadcast()
|
||||
bp.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func returnBuffer(b *fixedBuffer) {
|
||||
b.Reset()
|
||||
bufPoolsLock.Lock()
|
||||
pool := bufPools[b.Cap()]
|
||||
bufPoolsLock.Unlock()
|
||||
if pool != nil {
|
||||
pool.Put(b)
|
||||
}
|
||||
}
|
||||
|
||||
func getBuffer(size int) *fixedBuffer {
|
||||
bufPoolsLock.Lock()
|
||||
pool, ok := bufPools[size]
|
||||
if !ok {
|
||||
pool = &sync.Pool{New: func() interface{} { return &fixedBuffer{buf: make([]byte, 0, size)} }}
|
||||
bufPools[size] = pool
|
||||
}
|
||||
bufPoolsLock.Unlock()
|
||||
return pool.Get().(*fixedBuffer)
|
||||
}
|
||||
162
vendor/github.com/docker/docker/pkg/ioutils/fswriters.go
generated
vendored
Normal file
162
vendor/github.com/docker/docker/pkg/ioutils/fswriters.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// NewAtomicFileWriter returns WriteCloser so that writing to it writes to a
|
||||
// temporary file and closing it atomically changes the temporary file to
|
||||
// destination path. Writing and closing concurrently is not allowed.
|
||||
func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, error) {
|
||||
f, err := ioutil.TempFile(filepath.Dir(filename), ".tmp-"+filepath.Base(filename))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
abspath, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &atomicFileWriter{
|
||||
f: f,
|
||||
fn: abspath,
|
||||
perm: perm,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// AtomicWriteFile atomically writes data to a file named by filename.
|
||||
func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
f, err := NewAtomicFileWriter(filename, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := f.Write(data)
|
||||
if err == nil && n < len(data) {
|
||||
err = io.ErrShortWrite
|
||||
f.(*atomicFileWriter).writeErr = err
|
||||
}
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type atomicFileWriter struct {
|
||||
f *os.File
|
||||
fn string
|
||||
writeErr error
|
||||
perm os.FileMode
|
||||
}
|
||||
|
||||
func (w *atomicFileWriter) Write(dt []byte) (int, error) {
|
||||
n, err := w.f.Write(dt)
|
||||
if err != nil {
|
||||
w.writeErr = err
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (w *atomicFileWriter) Close() (retErr error) {
|
||||
defer func() {
|
||||
if retErr != nil || w.writeErr != nil {
|
||||
os.Remove(w.f.Name())
|
||||
}
|
||||
}()
|
||||
if err := w.f.Sync(); err != nil {
|
||||
w.f.Close()
|
||||
return err
|
||||
}
|
||||
if err := w.f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(w.f.Name(), w.perm); err != nil {
|
||||
return err
|
||||
}
|
||||
if w.writeErr == nil {
|
||||
return os.Rename(w.f.Name(), w.fn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AtomicWriteSet is used to atomically write a set
|
||||
// of files and ensure they are visible at the same time.
|
||||
// Must be committed to a new directory.
|
||||
type AtomicWriteSet struct {
|
||||
root string
|
||||
}
|
||||
|
||||
// NewAtomicWriteSet creates a new atomic write set to
|
||||
// atomically create a set of files. The given directory
|
||||
// is used as the base directory for storing files before
|
||||
// commit. If no temporary directory is given the system
|
||||
// default is used.
|
||||
func NewAtomicWriteSet(tmpDir string) (*AtomicWriteSet, error) {
|
||||
td, err := ioutil.TempDir(tmpDir, "write-set-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &AtomicWriteSet{
|
||||
root: td,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WriteFile writes a file to the set, guaranteeing the file
|
||||
// has been synced.
|
||||
func (ws *AtomicWriteSet) WriteFile(filename string, data []byte, perm os.FileMode) error {
|
||||
f, err := ws.FileWriter(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := f.Write(data)
|
||||
if err == nil && n < len(data) {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type syncFileCloser struct {
|
||||
*os.File
|
||||
}
|
||||
|
||||
func (w syncFileCloser) Close() error {
|
||||
err := w.File.Sync()
|
||||
if err1 := w.File.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// FileWriter opens a file writer inside the set. The file
|
||||
// should be synced and closed before calling commit.
|
||||
func (ws *AtomicWriteSet) FileWriter(name string, flag int, perm os.FileMode) (io.WriteCloser, error) {
|
||||
f, err := os.OpenFile(filepath.Join(ws.root, name), flag, perm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return syncFileCloser{f}, nil
|
||||
}
|
||||
|
||||
// Cancel cancels the set and removes all temporary data
|
||||
// created in the set.
|
||||
func (ws *AtomicWriteSet) Cancel() error {
|
||||
return os.RemoveAll(ws.root)
|
||||
}
|
||||
|
||||
// Commit moves all created files to the target directory. The
|
||||
// target directory must not exist and the parent of the target
|
||||
// directory must exist.
|
||||
func (ws *AtomicWriteSet) Commit(target string) error {
|
||||
return os.Rename(ws.root, target)
|
||||
}
|
||||
|
||||
// String returns the location the set is writing to.
|
||||
func (ws *AtomicWriteSet) String() string {
|
||||
return ws.root
|
||||
}
|
||||
157
vendor/github.com/docker/docker/pkg/ioutils/readers.go
generated
vendored
Normal file
157
vendor/github.com/docker/docker/pkg/ioutils/readers.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
)
|
||||
|
||||
// ReadCloserWrapper wraps an io.Reader, and implements an io.ReadCloser
|
||||
// It calls the given callback function when closed. It should be constructed
|
||||
// with NewReadCloserWrapper
|
||||
type ReadCloserWrapper struct {
|
||||
io.Reader
|
||||
closer func() error
|
||||
}
|
||||
|
||||
// Close calls back the passed closer function
|
||||
func (r *ReadCloserWrapper) Close() error {
|
||||
return r.closer()
|
||||
}
|
||||
|
||||
// NewReadCloserWrapper returns a new io.ReadCloser.
|
||||
func NewReadCloserWrapper(r io.Reader, closer func() error) io.ReadCloser {
|
||||
return &ReadCloserWrapper{
|
||||
Reader: r,
|
||||
closer: closer,
|
||||
}
|
||||
}
|
||||
|
||||
type readerErrWrapper struct {
|
||||
reader io.Reader
|
||||
closer func()
|
||||
}
|
||||
|
||||
func (r *readerErrWrapper) Read(p []byte) (int, error) {
|
||||
n, err := r.reader.Read(p)
|
||||
if err != nil {
|
||||
r.closer()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// NewReaderErrWrapper returns a new io.Reader.
|
||||
func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader {
|
||||
return &readerErrWrapper{
|
||||
reader: r,
|
||||
closer: closer,
|
||||
}
|
||||
}
|
||||
|
||||
// HashData returns the sha256 sum of src.
|
||||
func HashData(src io.Reader) (string, error) {
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, src); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// OnEOFReader wraps an io.ReadCloser and a function
|
||||
// the function will run at the end of file or close the file.
|
||||
type OnEOFReader struct {
|
||||
Rc io.ReadCloser
|
||||
Fn func()
|
||||
}
|
||||
|
||||
func (r *OnEOFReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.Rc.Read(p)
|
||||
if err == io.EOF {
|
||||
r.runFunc()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the file and run the function.
|
||||
func (r *OnEOFReader) Close() error {
|
||||
err := r.Rc.Close()
|
||||
r.runFunc()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *OnEOFReader) runFunc() {
|
||||
if fn := r.Fn; fn != nil {
|
||||
fn()
|
||||
r.Fn = nil
|
||||
}
|
||||
}
|
||||
|
||||
// cancelReadCloser wraps an io.ReadCloser with a context for cancelling read
|
||||
// operations.
|
||||
type cancelReadCloser struct {
|
||||
cancel func()
|
||||
pR *io.PipeReader // Stream to read from
|
||||
pW *io.PipeWriter
|
||||
}
|
||||
|
||||
// NewCancelReadCloser creates a wrapper that closes the ReadCloser when the
|
||||
// context is cancelled. The returned io.ReadCloser must be closed when it is
|
||||
// no longer needed.
|
||||
func NewCancelReadCloser(ctx context.Context, in io.ReadCloser) io.ReadCloser {
|
||||
pR, pW := io.Pipe()
|
||||
|
||||
// Create a context used to signal when the pipe is closed
|
||||
doneCtx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
p := &cancelReadCloser{
|
||||
cancel: cancel,
|
||||
pR: pR,
|
||||
pW: pW,
|
||||
}
|
||||
|
||||
go func() {
|
||||
_, err := io.Copy(pW, in)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// If the context was closed, p.closeWithError
|
||||
// was already called. Calling it again would
|
||||
// change the error that Read returns.
|
||||
default:
|
||||
p.closeWithError(err)
|
||||
}
|
||||
in.Close()
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
p.closeWithError(ctx.Err())
|
||||
case <-doneCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Read wraps the Read method of the pipe that provides data from the wrapped
|
||||
// ReadCloser.
|
||||
func (p *cancelReadCloser) Read(buf []byte) (n int, err error) {
|
||||
return p.pR.Read(buf)
|
||||
}
|
||||
|
||||
// closeWithError closes the wrapper and its underlying reader. It will
|
||||
// cause future calls to Read to return err.
|
||||
func (p *cancelReadCloser) closeWithError(err error) {
|
||||
p.pW.CloseWithError(err)
|
||||
p.cancel()
|
||||
}
|
||||
|
||||
// Close closes the wrapper its underlying reader. It will cause
|
||||
// future calls to Read to return io.EOF.
|
||||
func (p *cancelReadCloser) Close() error {
|
||||
p.closeWithError(io.EOF)
|
||||
return nil
|
||||
}
|
||||
10
vendor/github.com/docker/docker/pkg/ioutils/temp_unix.go
generated
vendored
Normal file
10
vendor/github.com/docker/docker/pkg/ioutils/temp_unix.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// +build !windows
|
||||
|
||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
||||
|
||||
import "io/ioutil"
|
||||
|
||||
// TempDir on Unix systems is equivalent to ioutil.TempDir.
|
||||
func TempDir(dir, prefix string) (string, error) {
|
||||
return ioutil.TempDir(dir, prefix)
|
||||
}
|
||||
16
vendor/github.com/docker/docker/pkg/ioutils/temp_windows.go
generated
vendored
Normal file
16
vendor/github.com/docker/docker/pkg/ioutils/temp_windows.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/docker/docker/pkg/longpath"
|
||||
)
|
||||
|
||||
// TempDir is the equivalent of ioutil.TempDir, except that the result is in Windows longpath format.
|
||||
func TempDir(dir, prefix string) (string, error) {
|
||||
tempDir, err := ioutil.TempDir(dir, prefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return longpath.AddPrefix(tempDir), nil
|
||||
}
|
||||
92
vendor/github.com/docker/docker/pkg/ioutils/writeflusher.go
generated
vendored
Normal file
92
vendor/github.com/docker/docker/pkg/ioutils/writeflusher.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// WriteFlusher wraps the Write and Flush operation ensuring that every write
|
||||
// is a flush. In addition, the Close method can be called to intercept
|
||||
// Read/Write calls if the targets lifecycle has already ended.
|
||||
type WriteFlusher struct {
|
||||
w io.Writer
|
||||
flusher flusher
|
||||
flushed chan struct{}
|
||||
flushedOnce sync.Once
|
||||
closed chan struct{}
|
||||
closeLock sync.Mutex
|
||||
}
|
||||
|
||||
type flusher interface {
|
||||
Flush()
|
||||
}
|
||||
|
||||
var errWriteFlusherClosed = io.EOF
|
||||
|
||||
func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-wf.closed:
|
||||
return 0, errWriteFlusherClosed
|
||||
default:
|
||||
}
|
||||
|
||||
n, err = wf.w.Write(b)
|
||||
wf.Flush() // every write is a flush.
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Flush the stream immediately.
|
||||
func (wf *WriteFlusher) Flush() {
|
||||
select {
|
||||
case <-wf.closed:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
wf.flushedOnce.Do(func() {
|
||||
close(wf.flushed)
|
||||
})
|
||||
wf.flusher.Flush()
|
||||
}
|
||||
|
||||
// Flushed returns the state of flushed.
|
||||
// If it's flushed, return true, or else it return false.
|
||||
func (wf *WriteFlusher) Flushed() bool {
|
||||
// BUG(stevvooe): Remove this method. Its use is inherently racy. Seems to
|
||||
// be used to detect whether or a response code has been issued or not.
|
||||
// Another hook should be used instead.
|
||||
var flushed bool
|
||||
select {
|
||||
case <-wf.flushed:
|
||||
flushed = true
|
||||
default:
|
||||
}
|
||||
return flushed
|
||||
}
|
||||
|
||||
// Close closes the write flusher, disallowing any further writes to the
|
||||
// target. After the flusher is closed, all calls to write or flush will
|
||||
// result in an error.
|
||||
func (wf *WriteFlusher) Close() error {
|
||||
wf.closeLock.Lock()
|
||||
defer wf.closeLock.Unlock()
|
||||
|
||||
select {
|
||||
case <-wf.closed:
|
||||
return errWriteFlusherClosed
|
||||
default:
|
||||
close(wf.closed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewWriteFlusher returns a new WriteFlusher.
|
||||
func NewWriteFlusher(w io.Writer) *WriteFlusher {
|
||||
var fl flusher
|
||||
if f, ok := w.(flusher); ok {
|
||||
fl = f
|
||||
} else {
|
||||
fl = &NopFlusher{}
|
||||
}
|
||||
return &WriteFlusher{w: w, flusher: fl, closed: make(chan struct{}), flushed: make(chan struct{})}
|
||||
}
|
||||
66
vendor/github.com/docker/docker/pkg/ioutils/writers.go
generated
vendored
Normal file
66
vendor/github.com/docker/docker/pkg/ioutils/writers.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package ioutils // import "github.com/docker/docker/pkg/ioutils"
|
||||
|
||||
import "io"
|
||||
|
||||
// NopWriter represents a type which write operation is nop.
|
||||
type NopWriter struct{}
|
||||
|
||||
func (*NopWriter) Write(buf []byte) (int, error) {
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
type nopWriteCloser struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (w *nopWriteCloser) Close() error { return nil }
|
||||
|
||||
// NopWriteCloser returns a nopWriteCloser.
|
||||
func NopWriteCloser(w io.Writer) io.WriteCloser {
|
||||
return &nopWriteCloser{w}
|
||||
}
|
||||
|
||||
// NopFlusher represents a type which flush operation is nop.
|
||||
type NopFlusher struct{}
|
||||
|
||||
// Flush is a nop operation.
|
||||
func (f *NopFlusher) Flush() {}
|
||||
|
||||
type writeCloserWrapper struct {
|
||||
io.Writer
|
||||
closer func() error
|
||||
}
|
||||
|
||||
func (r *writeCloserWrapper) Close() error {
|
||||
return r.closer()
|
||||
}
|
||||
|
||||
// NewWriteCloserWrapper returns a new io.WriteCloser.
|
||||
func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser {
|
||||
return &writeCloserWrapper{
|
||||
Writer: r,
|
||||
closer: closer,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteCounter wraps a concrete io.Writer and hold a count of the number
|
||||
// of bytes written to the writer during a "session".
|
||||
// This can be convenient when write return is masked
|
||||
// (e.g., json.Encoder.Encode())
|
||||
type WriteCounter struct {
|
||||
Count int64
|
||||
Writer io.Writer
|
||||
}
|
||||
|
||||
// NewWriteCounter returns a new WriteCounter.
|
||||
func NewWriteCounter(w io.Writer) *WriteCounter {
|
||||
return &WriteCounter{
|
||||
Writer: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (wc *WriteCounter) Write(p []byte) (count int, err error) {
|
||||
count, err = wc.Writer.Write(p)
|
||||
wc.Count += int64(count)
|
||||
return
|
||||
}
|
||||
283
vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go
generated
vendored
Normal file
283
vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go
generated
vendored
Normal file
@@ -0,0 +1,283 @@
|
||||
package jsonmessage // import "github.com/docker/docker/pkg/jsonmessage"
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/morikuni/aec"
|
||||
)
|
||||
|
||||
// RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to
|
||||
// ensure the formatted time isalways the same number of characters.
|
||||
const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
|
||||
|
||||
// JSONError wraps a concrete Code and Message, `Code` is
|
||||
// is an integer error code, `Message` is the error message.
|
||||
type JSONError struct {
|
||||
Code int `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (e *JSONError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// JSONProgress describes a Progress. terminalFd is the fd of the current terminal,
|
||||
// Start is the initial value for the operation. Current is the current status and
|
||||
// value of the progress made towards Total. Total is the end value describing when
|
||||
// we made 100% progress for an operation.
|
||||
type JSONProgress struct {
|
||||
terminalFd uintptr
|
||||
Current int64 `json:"current,omitempty"`
|
||||
Total int64 `json:"total,omitempty"`
|
||||
Start int64 `json:"start,omitempty"`
|
||||
// If true, don't show xB/yB
|
||||
HideCounts bool `json:"hidecounts,omitempty"`
|
||||
Units string `json:"units,omitempty"`
|
||||
nowFunc func() time.Time
|
||||
winSize int
|
||||
}
|
||||
|
||||
func (p *JSONProgress) String() string {
|
||||
var (
|
||||
width = p.width()
|
||||
pbBox string
|
||||
numbersBox string
|
||||
timeLeftBox string
|
||||
)
|
||||
if p.Current <= 0 && p.Total <= 0 {
|
||||
return ""
|
||||
}
|
||||
if p.Total <= 0 {
|
||||
switch p.Units {
|
||||
case "":
|
||||
current := units.HumanSize(float64(p.Current))
|
||||
return fmt.Sprintf("%8v", current)
|
||||
default:
|
||||
return fmt.Sprintf("%d %s", p.Current, p.Units)
|
||||
}
|
||||
}
|
||||
|
||||
percentage := int(float64(p.Current)/float64(p.Total)*100) / 2
|
||||
if percentage > 50 {
|
||||
percentage = 50
|
||||
}
|
||||
if width > 110 {
|
||||
// this number can't be negative gh#7136
|
||||
numSpaces := 0
|
||||
if 50-percentage > 0 {
|
||||
numSpaces = 50 - percentage
|
||||
}
|
||||
pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces))
|
||||
}
|
||||
|
||||
switch {
|
||||
case p.HideCounts:
|
||||
case p.Units == "": // no units, use bytes
|
||||
current := units.HumanSize(float64(p.Current))
|
||||
total := units.HumanSize(float64(p.Total))
|
||||
|
||||
numbersBox = fmt.Sprintf("%8v/%v", current, total)
|
||||
|
||||
if p.Current > p.Total {
|
||||
// remove total display if the reported current is wonky.
|
||||
numbersBox = fmt.Sprintf("%8v", current)
|
||||
}
|
||||
default:
|
||||
numbersBox = fmt.Sprintf("%d/%d %s", p.Current, p.Total, p.Units)
|
||||
|
||||
if p.Current > p.Total {
|
||||
// remove total display if the reported current is wonky.
|
||||
numbersBox = fmt.Sprintf("%d %s", p.Current, p.Units)
|
||||
}
|
||||
}
|
||||
|
||||
if p.Current > 0 && p.Start > 0 && percentage < 50 {
|
||||
fromStart := p.now().Sub(time.Unix(p.Start, 0))
|
||||
perEntry := fromStart / time.Duration(p.Current)
|
||||
left := time.Duration(p.Total-p.Current) * perEntry
|
||||
left = (left / time.Second) * time.Second
|
||||
|
||||
if width > 50 {
|
||||
timeLeftBox = " " + left.String()
|
||||
}
|
||||
}
|
||||
return pbBox + numbersBox + timeLeftBox
|
||||
}
|
||||
|
||||
// shim for testing
|
||||
func (p *JSONProgress) now() time.Time {
|
||||
if p.nowFunc == nil {
|
||||
p.nowFunc = func() time.Time {
|
||||
return time.Now().UTC()
|
||||
}
|
||||
}
|
||||
return p.nowFunc()
|
||||
}
|
||||
|
||||
// shim for testing
|
||||
func (p *JSONProgress) width() int {
|
||||
if p.winSize != 0 {
|
||||
return p.winSize
|
||||
}
|
||||
ws, err := term.GetWinsize(p.terminalFd)
|
||||
if err == nil {
|
||||
return int(ws.Width)
|
||||
}
|
||||
return 200
|
||||
}
|
||||
|
||||
// JSONMessage defines a message struct. It describes
|
||||
// the created time, where it from, status, ID of the
|
||||
// message. It's used for docker events.
|
||||
type JSONMessage struct {
|
||||
Stream string `json:"stream,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Progress *JSONProgress `json:"progressDetail,omitempty"`
|
||||
ProgressMessage string `json:"progress,omitempty"` //deprecated
|
||||
ID string `json:"id,omitempty"`
|
||||
From string `json:"from,omitempty"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
TimeNano int64 `json:"timeNano,omitempty"`
|
||||
Error *JSONError `json:"errorDetail,omitempty"`
|
||||
ErrorMessage string `json:"error,omitempty"` //deprecated
|
||||
// Aux contains out-of-band data, such as digests for push signing and image id after building.
|
||||
Aux *json.RawMessage `json:"aux,omitempty"`
|
||||
}
|
||||
|
||||
func clearLine(out io.Writer) {
|
||||
eraseMode := aec.EraseModes.All
|
||||
cl := aec.EraseLine(eraseMode)
|
||||
fmt.Fprint(out, cl)
|
||||
}
|
||||
|
||||
func cursorUp(out io.Writer, l uint) {
|
||||
fmt.Fprint(out, aec.Up(l))
|
||||
}
|
||||
|
||||
func cursorDown(out io.Writer, l uint) {
|
||||
fmt.Fprint(out, aec.Down(l))
|
||||
}
|
||||
|
||||
// Display displays the JSONMessage to `out`. If `isTerminal` is true, it will erase the
|
||||
// entire current line when displaying the progressbar.
|
||||
func (jm *JSONMessage) Display(out io.Writer, isTerminal bool) error {
|
||||
if jm.Error != nil {
|
||||
if jm.Error.Code == 401 {
|
||||
return fmt.Errorf("authentication is required")
|
||||
}
|
||||
return jm.Error
|
||||
}
|
||||
var endl string
|
||||
if isTerminal && jm.Stream == "" && jm.Progress != nil {
|
||||
clearLine(out)
|
||||
endl = "\r"
|
||||
fmt.Fprintf(out, endl)
|
||||
} else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal
|
||||
return nil
|
||||
}
|
||||
if jm.TimeNano != 0 {
|
||||
fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(RFC3339NanoFixed))
|
||||
} else if jm.Time != 0 {
|
||||
fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(RFC3339NanoFixed))
|
||||
}
|
||||
if jm.ID != "" {
|
||||
fmt.Fprintf(out, "%s: ", jm.ID)
|
||||
}
|
||||
if jm.From != "" {
|
||||
fmt.Fprintf(out, "(from %s) ", jm.From)
|
||||
}
|
||||
if jm.Progress != nil && isTerminal {
|
||||
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl)
|
||||
} else if jm.ProgressMessage != "" { //deprecated
|
||||
fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl)
|
||||
} else if jm.Stream != "" {
|
||||
fmt.Fprintf(out, "%s%s", jm.Stream, endl)
|
||||
} else {
|
||||
fmt.Fprintf(out, "%s%s\n", jm.Status, endl)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal`
|
||||
// describes if `out` is a terminal. If this is the case, it will print `\n` at the end of
|
||||
// each line and move the cursor while displaying.
|
||||
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(JSONMessage)) error {
|
||||
var (
|
||||
dec = json.NewDecoder(in)
|
||||
ids = make(map[string]uint)
|
||||
)
|
||||
|
||||
for {
|
||||
var diff uint
|
||||
var jm JSONMessage
|
||||
if err := dec.Decode(&jm); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if jm.Aux != nil {
|
||||
if auxCallback != nil {
|
||||
auxCallback(jm)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if jm.Progress != nil {
|
||||
jm.Progress.terminalFd = terminalFd
|
||||
}
|
||||
if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") {
|
||||
line, ok := ids[jm.ID]
|
||||
if !ok {
|
||||
// NOTE: This approach of using len(id) to
|
||||
// figure out the number of lines of history
|
||||
// only works as long as we clear the history
|
||||
// when we output something that's not
|
||||
// accounted for in the map, such as a line
|
||||
// with no ID.
|
||||
line = uint(len(ids))
|
||||
ids[jm.ID] = line
|
||||
if isTerminal {
|
||||
fmt.Fprintf(out, "\n")
|
||||
}
|
||||
}
|
||||
diff = uint(len(ids)) - line
|
||||
if isTerminal {
|
||||
cursorUp(out, diff)
|
||||
}
|
||||
} else {
|
||||
// When outputting something that isn't progress
|
||||
// output, clear the history of previous lines. We
|
||||
// don't want progress entries from some previous
|
||||
// operation to be updated (for example, pull -a
|
||||
// with multiple tags).
|
||||
ids = make(map[string]uint)
|
||||
}
|
||||
err := jm.Display(out, isTerminal)
|
||||
if jm.ID != "" && isTerminal {
|
||||
cursorDown(out, diff)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type stream interface {
|
||||
io.Writer
|
||||
FD() uintptr
|
||||
IsTerminal() bool
|
||||
}
|
||||
|
||||
// DisplayJSONMessagesToStream prints json messages to the output stream
|
||||
func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(JSONMessage)) error {
|
||||
return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback)
|
||||
}
|
||||
26
vendor/github.com/docker/docker/pkg/longpath/longpath.go
generated
vendored
Normal file
26
vendor/github.com/docker/docker/pkg/longpath/longpath.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// longpath introduces some constants and helper functions for handling long paths
|
||||
// in Windows, which are expected to be prepended with `\\?\` and followed by either
|
||||
// a drive letter, a UNC server\share, or a volume identifier.
|
||||
|
||||
package longpath // import "github.com/docker/docker/pkg/longpath"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Prefix is the longpath prefix for Windows file paths.
|
||||
const Prefix = `\\?\`
|
||||
|
||||
// AddPrefix will add the Windows long path prefix to the path provided if
|
||||
// it does not already have it.
|
||||
func AddPrefix(path string) string {
|
||||
if !strings.HasPrefix(path, Prefix) {
|
||||
if strings.HasPrefix(path, `\\`) {
|
||||
// This is a UNC path, so we need to add 'UNC' to the path as well.
|
||||
path = Prefix + `UNC` + path[1:]
|
||||
} else {
|
||||
path = Prefix + path
|
||||
}
|
||||
}
|
||||
return path
|
||||
}
|
||||
137
vendor/github.com/docker/docker/pkg/mount/flags.go
generated
vendored
Normal file
137
vendor/github.com/docker/docker/pkg/mount/flags.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var flags = map[string]struct {
|
||||
clear bool
|
||||
flag int
|
||||
}{
|
||||
"defaults": {false, 0},
|
||||
"ro": {false, RDONLY},
|
||||
"rw": {true, RDONLY},
|
||||
"suid": {true, NOSUID},
|
||||
"nosuid": {false, NOSUID},
|
||||
"dev": {true, NODEV},
|
||||
"nodev": {false, NODEV},
|
||||
"exec": {true, NOEXEC},
|
||||
"noexec": {false, NOEXEC},
|
||||
"sync": {false, SYNCHRONOUS},
|
||||
"async": {true, SYNCHRONOUS},
|
||||
"dirsync": {false, DIRSYNC},
|
||||
"remount": {false, REMOUNT},
|
||||
"mand": {false, MANDLOCK},
|
||||
"nomand": {true, MANDLOCK},
|
||||
"atime": {true, NOATIME},
|
||||
"noatime": {false, NOATIME},
|
||||
"diratime": {true, NODIRATIME},
|
||||
"nodiratime": {false, NODIRATIME},
|
||||
"bind": {false, BIND},
|
||||
"rbind": {false, RBIND},
|
||||
"unbindable": {false, UNBINDABLE},
|
||||
"runbindable": {false, RUNBINDABLE},
|
||||
"private": {false, PRIVATE},
|
||||
"rprivate": {false, RPRIVATE},
|
||||
"shared": {false, SHARED},
|
||||
"rshared": {false, RSHARED},
|
||||
"slave": {false, SLAVE},
|
||||
"rslave": {false, RSLAVE},
|
||||
"relatime": {false, RELATIME},
|
||||
"norelatime": {true, RELATIME},
|
||||
"strictatime": {false, STRICTATIME},
|
||||
"nostrictatime": {true, STRICTATIME},
|
||||
}
|
||||
|
||||
var validFlags = map[string]bool{
|
||||
"": true,
|
||||
"size": true,
|
||||
"mode": true,
|
||||
"uid": true,
|
||||
"gid": true,
|
||||
"nr_inodes": true,
|
||||
"nr_blocks": true,
|
||||
"mpol": true,
|
||||
}
|
||||
|
||||
var propagationFlags = map[string]bool{
|
||||
"bind": true,
|
||||
"rbind": true,
|
||||
"unbindable": true,
|
||||
"runbindable": true,
|
||||
"private": true,
|
||||
"rprivate": true,
|
||||
"shared": true,
|
||||
"rshared": true,
|
||||
"slave": true,
|
||||
"rslave": true,
|
||||
}
|
||||
|
||||
// MergeTmpfsOptions merge mount options to make sure there is no duplicate.
|
||||
func MergeTmpfsOptions(options []string) ([]string, error) {
|
||||
// We use collisions maps to remove duplicates.
|
||||
// For flag, the key is the flag value (the key for propagation flag is -1)
|
||||
// For data=value, the key is the data
|
||||
flagCollisions := map[int]bool{}
|
||||
dataCollisions := map[string]bool{}
|
||||
|
||||
var newOptions []string
|
||||
// We process in reverse order
|
||||
for i := len(options) - 1; i >= 0; i-- {
|
||||
option := options[i]
|
||||
if option == "defaults" {
|
||||
continue
|
||||
}
|
||||
if f, ok := flags[option]; ok && f.flag != 0 {
|
||||
// There is only one propagation mode
|
||||
key := f.flag
|
||||
if propagationFlags[option] {
|
||||
key = -1
|
||||
}
|
||||
// Check to see if there is collision for flag
|
||||
if !flagCollisions[key] {
|
||||
// We prepend the option and add to collision map
|
||||
newOptions = append([]string{option}, newOptions...)
|
||||
flagCollisions[key] = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
opt := strings.SplitN(option, "=", 2)
|
||||
if len(opt) != 2 || !validFlags[opt[0]] {
|
||||
return nil, fmt.Errorf("Invalid tmpfs option %q", opt)
|
||||
}
|
||||
if !dataCollisions[opt[0]] {
|
||||
// We prepend the option and add to collision map
|
||||
newOptions = append([]string{option}, newOptions...)
|
||||
dataCollisions[opt[0]] = true
|
||||
}
|
||||
}
|
||||
|
||||
return newOptions, nil
|
||||
}
|
||||
|
||||
// Parse fstab type mount options into mount() flags
|
||||
// and device specific data
|
||||
func parseOptions(options string) (int, string) {
|
||||
var (
|
||||
flag int
|
||||
data []string
|
||||
)
|
||||
|
||||
for _, o := range strings.Split(options, ",") {
|
||||
// If the option does not exist in the flags table or the flag
|
||||
// is not supported on the platform,
|
||||
// then it is a data value for a specific fs type
|
||||
if f, exists := flags[o]; exists && f.flag != 0 {
|
||||
if f.clear {
|
||||
flag &= ^f.flag
|
||||
} else {
|
||||
flag |= f.flag
|
||||
}
|
||||
} else {
|
||||
data = append(data, o)
|
||||
}
|
||||
}
|
||||
return flag, strings.Join(data, ",")
|
||||
}
|
||||
49
vendor/github.com/docker/docker/pkg/mount/flags_freebsd.go
generated
vendored
Normal file
49
vendor/github.com/docker/docker/pkg/mount/flags_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// +build freebsd,cgo
|
||||
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
/*
|
||||
#include <sys/mount.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
// RDONLY will mount the filesystem as read-only.
|
||||
RDONLY = C.MNT_RDONLY
|
||||
|
||||
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
|
||||
// take effect.
|
||||
NOSUID = C.MNT_NOSUID
|
||||
|
||||
// NOEXEC will not allow execution of any binaries on the mounted file system.
|
||||
NOEXEC = C.MNT_NOEXEC
|
||||
|
||||
// SYNCHRONOUS will allow any I/O to the file system to be done synchronously.
|
||||
SYNCHRONOUS = C.MNT_SYNCHRONOUS
|
||||
|
||||
// NOATIME will not update the file access time when reading from a file.
|
||||
NOATIME = C.MNT_NOATIME
|
||||
)
|
||||
|
||||
// These flags are unsupported.
|
||||
const (
|
||||
BIND = 0
|
||||
DIRSYNC = 0
|
||||
MANDLOCK = 0
|
||||
NODEV = 0
|
||||
NODIRATIME = 0
|
||||
UNBINDABLE = 0
|
||||
RUNBINDABLE = 0
|
||||
PRIVATE = 0
|
||||
RPRIVATE = 0
|
||||
SHARED = 0
|
||||
RSHARED = 0
|
||||
SLAVE = 0
|
||||
RSLAVE = 0
|
||||
RBIND = 0
|
||||
RELATIVE = 0
|
||||
RELATIME = 0
|
||||
REMOUNT = 0
|
||||
STRICTATIME = 0
|
||||
mntDetach = 0
|
||||
)
|
||||
87
vendor/github.com/docker/docker/pkg/mount/flags_linux.go
generated
vendored
Normal file
87
vendor/github.com/docker/docker/pkg/mount/flags_linux.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// RDONLY will mount the file system read-only.
|
||||
RDONLY = unix.MS_RDONLY
|
||||
|
||||
// NOSUID will not allow set-user-identifier or set-group-identifier bits to
|
||||
// take effect.
|
||||
NOSUID = unix.MS_NOSUID
|
||||
|
||||
// NODEV will not interpret character or block special devices on the file
|
||||
// system.
|
||||
NODEV = unix.MS_NODEV
|
||||
|
||||
// NOEXEC will not allow execution of any binaries on the mounted file system.
|
||||
NOEXEC = unix.MS_NOEXEC
|
||||
|
||||
// SYNCHRONOUS will allow I/O to the file system to be done synchronously.
|
||||
SYNCHRONOUS = unix.MS_SYNCHRONOUS
|
||||
|
||||
// DIRSYNC will force all directory updates within the file system to be done
|
||||
// synchronously. This affects the following system calls: create, link,
|
||||
// unlink, symlink, mkdir, rmdir, mknod and rename.
|
||||
DIRSYNC = unix.MS_DIRSYNC
|
||||
|
||||
// REMOUNT will attempt to remount an already-mounted file system. This is
|
||||
// commonly used to change the mount flags for a file system, especially to
|
||||
// make a readonly file system writeable. It does not change device or mount
|
||||
// point.
|
||||
REMOUNT = unix.MS_REMOUNT
|
||||
|
||||
// MANDLOCK will force mandatory locks on a filesystem.
|
||||
MANDLOCK = unix.MS_MANDLOCK
|
||||
|
||||
// NOATIME will not update the file access time when reading from a file.
|
||||
NOATIME = unix.MS_NOATIME
|
||||
|
||||
// NODIRATIME will not update the directory access time.
|
||||
NODIRATIME = unix.MS_NODIRATIME
|
||||
|
||||
// BIND remounts a subtree somewhere else.
|
||||
BIND = unix.MS_BIND
|
||||
|
||||
// RBIND remounts a subtree and all possible submounts somewhere else.
|
||||
RBIND = unix.MS_BIND | unix.MS_REC
|
||||
|
||||
// UNBINDABLE creates a mount which cannot be cloned through a bind operation.
|
||||
UNBINDABLE = unix.MS_UNBINDABLE
|
||||
|
||||
// RUNBINDABLE marks the entire mount tree as UNBINDABLE.
|
||||
RUNBINDABLE = unix.MS_UNBINDABLE | unix.MS_REC
|
||||
|
||||
// PRIVATE creates a mount which carries no propagation abilities.
|
||||
PRIVATE = unix.MS_PRIVATE
|
||||
|
||||
// RPRIVATE marks the entire mount tree as PRIVATE.
|
||||
RPRIVATE = unix.MS_PRIVATE | unix.MS_REC
|
||||
|
||||
// SLAVE creates a mount which receives propagation from its master, but not
|
||||
// vice versa.
|
||||
SLAVE = unix.MS_SLAVE
|
||||
|
||||
// RSLAVE marks the entire mount tree as SLAVE.
|
||||
RSLAVE = unix.MS_SLAVE | unix.MS_REC
|
||||
|
||||
// SHARED creates a mount which provides the ability to create mirrors of
|
||||
// that mount such that mounts and unmounts within any of the mirrors
|
||||
// propagate to the other mirrors.
|
||||
SHARED = unix.MS_SHARED
|
||||
|
||||
// RSHARED marks the entire mount tree as SHARED.
|
||||
RSHARED = unix.MS_SHARED | unix.MS_REC
|
||||
|
||||
// RELATIME updates inode access times relative to modify or change time.
|
||||
RELATIME = unix.MS_RELATIME
|
||||
|
||||
// STRICTATIME allows to explicitly request full atime updates. This makes
|
||||
// it possible for the kernel to default to relatime or noatime but still
|
||||
// allow userspace to override it.
|
||||
STRICTATIME = unix.MS_STRICTATIME
|
||||
|
||||
mntDetach = unix.MNT_DETACH
|
||||
)
|
||||
31
vendor/github.com/docker/docker/pkg/mount/flags_unsupported.go
generated
vendored
Normal file
31
vendor/github.com/docker/docker/pkg/mount/flags_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// +build !linux,!freebsd freebsd,!cgo
|
||||
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
// These flags are unsupported.
|
||||
const (
|
||||
BIND = 0
|
||||
DIRSYNC = 0
|
||||
MANDLOCK = 0
|
||||
NOATIME = 0
|
||||
NODEV = 0
|
||||
NODIRATIME = 0
|
||||
NOEXEC = 0
|
||||
NOSUID = 0
|
||||
UNBINDABLE = 0
|
||||
RUNBINDABLE = 0
|
||||
PRIVATE = 0
|
||||
RPRIVATE = 0
|
||||
SHARED = 0
|
||||
RSHARED = 0
|
||||
SLAVE = 0
|
||||
RSLAVE = 0
|
||||
RBIND = 0
|
||||
RELATIME = 0
|
||||
RELATIVE = 0
|
||||
REMOUNT = 0
|
||||
STRICTATIME = 0
|
||||
SYNCHRONOUS = 0
|
||||
RDONLY = 0
|
||||
mntDetach = 0
|
||||
)
|
||||
159
vendor/github.com/docker/docker/pkg/mount/mount.go
generated
vendored
Normal file
159
vendor/github.com/docker/docker/pkg/mount/mount.go
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// mountError records an error from mount or unmount operation
|
||||
type mountError struct {
|
||||
op string
|
||||
source, target string
|
||||
flags uintptr
|
||||
data string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *mountError) Error() string {
|
||||
out := e.op + " "
|
||||
|
||||
if e.source != "" {
|
||||
out += e.source + ":" + e.target
|
||||
} else {
|
||||
out += e.target
|
||||
}
|
||||
|
||||
if e.flags != uintptr(0) {
|
||||
out += ", flags: 0x" + strconv.FormatUint(uint64(e.flags), 16)
|
||||
}
|
||||
if e.data != "" {
|
||||
out += ", data: " + e.data
|
||||
}
|
||||
|
||||
out += ": " + e.err.Error()
|
||||
return out
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error
|
||||
func (e *mountError) Cause() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
// FilterFunc is a type defining a callback function
|
||||
// to filter out unwanted entries. It takes a pointer
|
||||
// to an Info struct (not fully populated, currently
|
||||
// only Mountpoint is filled in), and returns two booleans:
|
||||
// - skip: true if the entry should be skipped
|
||||
// - stop: true if parsing should be stopped after the entry
|
||||
type FilterFunc func(*Info) (skip, stop bool)
|
||||
|
||||
// PrefixFilter discards all entries whose mount points
|
||||
// do not start with a prefix specified
|
||||
func PrefixFilter(prefix string) FilterFunc {
|
||||
return func(m *Info) (bool, bool) {
|
||||
skip := !strings.HasPrefix(m.Mountpoint, prefix)
|
||||
return skip, false
|
||||
}
|
||||
}
|
||||
|
||||
// SingleEntryFilter looks for a specific entry
|
||||
func SingleEntryFilter(mp string) FilterFunc {
|
||||
return func(m *Info) (bool, bool) {
|
||||
if m.Mountpoint == mp {
|
||||
return false, true // don't skip, stop now
|
||||
}
|
||||
return true, false // skip, keep going
|
||||
}
|
||||
}
|
||||
|
||||
// ParentsFilter returns all entries whose mount points
|
||||
// can be parents of a path specified, discarding others.
|
||||
// For example, given `/var/lib/docker/something`, entries
|
||||
// like `/var/lib/docker`, `/var` and `/` are returned.
|
||||
func ParentsFilter(path string) FilterFunc {
|
||||
return func(m *Info) (bool, bool) {
|
||||
skip := !strings.HasPrefix(path, m.Mountpoint)
|
||||
return skip, false
|
||||
}
|
||||
}
|
||||
|
||||
// GetMounts retrieves a list of mounts for the current running process,
|
||||
// with an optional filter applied (use nil for no filter).
|
||||
func GetMounts(f FilterFunc) ([]*Info, error) {
|
||||
return parseMountTable(f)
|
||||
}
|
||||
|
||||
// Mounted determines if a specified mountpoint has been mounted.
|
||||
// On Linux it looks at /proc/self/mountinfo.
|
||||
func Mounted(mountpoint string) (bool, error) {
|
||||
entries, err := GetMounts(SingleEntryFilter(mountpoint))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(entries) > 0, nil
|
||||
}
|
||||
|
||||
// Mount will mount filesystem according to the specified configuration, on the
|
||||
// condition that the target path is *not* already mounted. Options must be
|
||||
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
|
||||
// flags.go for supported option flags.
|
||||
func Mount(device, target, mType, options string) error {
|
||||
flag, _ := parseOptions(options)
|
||||
if flag&REMOUNT != REMOUNT {
|
||||
if mounted, err := Mounted(target); err != nil || mounted {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return ForceMount(device, target, mType, options)
|
||||
}
|
||||
|
||||
// ForceMount will mount a filesystem according to the specified configuration,
|
||||
// *regardless* if the target path is not already mounted. Options must be
|
||||
// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See
|
||||
// flags.go for supported option flags.
|
||||
func ForceMount(device, target, mType, options string) error {
|
||||
flag, data := parseOptions(options)
|
||||
return mount(device, target, mType, uintptr(flag), data)
|
||||
}
|
||||
|
||||
// Unmount lazily unmounts a filesystem on supported platforms, otherwise
|
||||
// does a normal unmount.
|
||||
func Unmount(target string) error {
|
||||
return unmount(target, mntDetach)
|
||||
}
|
||||
|
||||
// RecursiveUnmount unmounts the target and all mounts underneath, starting with
|
||||
// the deepsest mount first.
|
||||
func RecursiveUnmount(target string) error {
|
||||
mounts, err := parseMountTable(PrefixFilter(target))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make the deepest mount be first
|
||||
sort.Slice(mounts, func(i, j int) bool {
|
||||
return len(mounts[i].Mountpoint) > len(mounts[j].Mountpoint)
|
||||
})
|
||||
|
||||
for i, m := range mounts {
|
||||
logrus.Debugf("Trying to unmount %s", m.Mountpoint)
|
||||
err = unmount(m.Mountpoint, mntDetach)
|
||||
if err != nil {
|
||||
if i == len(mounts)-1 { // last mount
|
||||
if mounted, e := Mounted(m.Mountpoint); e != nil || mounted {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// This is some submount, we can ignore this error for now, the final unmount will fail if this is a real problem
|
||||
logrus.WithError(err).Warnf("Failed to unmount submount %s", m.Mountpoint)
|
||||
}
|
||||
}
|
||||
|
||||
logrus.Debugf("Unmounted %s", m.Mountpoint)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
59
vendor/github.com/docker/docker/pkg/mount/mounter_freebsd.go
generated
vendored
Normal file
59
vendor/github.com/docker/docker/pkg/mount/mounter_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
/*
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/_iovec.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/param.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func allocateIOVecs(options []string) []C.struct_iovec {
|
||||
out := make([]C.struct_iovec, len(options))
|
||||
for i, option := range options {
|
||||
out[i].iov_base = unsafe.Pointer(C.CString(option))
|
||||
out[i].iov_len = C.size_t(len(option) + 1)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func mount(device, target, mType string, flag uintptr, data string) error {
|
||||
isNullFS := false
|
||||
|
||||
xs := strings.Split(data, ",")
|
||||
for _, x := range xs {
|
||||
if x == "bind" {
|
||||
isNullFS = true
|
||||
}
|
||||
}
|
||||
|
||||
options := []string{"fspath", target}
|
||||
if isNullFS {
|
||||
options = append(options, "fstype", "nullfs", "target", device)
|
||||
} else {
|
||||
options = append(options, "fstype", mType, "from", device)
|
||||
}
|
||||
rawOptions := allocateIOVecs(options)
|
||||
for _, rawOption := range rawOptions {
|
||||
defer C.free(rawOption.iov_base)
|
||||
}
|
||||
|
||||
if errno := C.nmount(&rawOptions[0], C.uint(len(options)), C.int(flag)); errno != 0 {
|
||||
return &mountError{
|
||||
op: "mount",
|
||||
source: device,
|
||||
target: target,
|
||||
flags: flag,
|
||||
err: syscall.Errno(errno),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
73
vendor/github.com/docker/docker/pkg/mount/mounter_linux.go
generated
vendored
Normal file
73
vendor/github.com/docker/docker/pkg/mount/mounter_linux.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
// ptypes is the set propagation types.
|
||||
ptypes = unix.MS_SHARED | unix.MS_PRIVATE | unix.MS_SLAVE | unix.MS_UNBINDABLE
|
||||
|
||||
// pflags is the full set valid flags for a change propagation call.
|
||||
pflags = ptypes | unix.MS_REC | unix.MS_SILENT
|
||||
|
||||
// broflags is the combination of bind and read only
|
||||
broflags = unix.MS_BIND | unix.MS_RDONLY
|
||||
)
|
||||
|
||||
// isremount returns true if either device name or flags identify a remount request, false otherwise.
|
||||
func isremount(device string, flags uintptr) bool {
|
||||
switch {
|
||||
// We treat device "" and "none" as a remount request to provide compatibility with
|
||||
// requests that don't explicitly set MS_REMOUNT such as those manipulating bind mounts.
|
||||
case flags&unix.MS_REMOUNT != 0, device == "", device == "none":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func mount(device, target, mType string, flags uintptr, data string) error {
|
||||
oflags := flags &^ ptypes
|
||||
if !isremount(device, flags) || data != "" {
|
||||
// Initial call applying all non-propagation flags for mount
|
||||
// or remount with changed data
|
||||
if err := unix.Mount(device, target, mType, oflags, data); err != nil {
|
||||
return &mountError{
|
||||
op: "mount",
|
||||
source: device,
|
||||
target: target,
|
||||
flags: oflags,
|
||||
data: data,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if flags&ptypes != 0 {
|
||||
// Change the propagation type.
|
||||
if err := unix.Mount("", target, "", flags&pflags, ""); err != nil {
|
||||
return &mountError{
|
||||
op: "remount",
|
||||
target: target,
|
||||
flags: flags & pflags,
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if oflags&broflags == broflags {
|
||||
// Remount the bind to apply read only.
|
||||
if err := unix.Mount("", target, "", oflags|unix.MS_REMOUNT, ""); err != nil {
|
||||
return &mountError{
|
||||
op: "remount-ro",
|
||||
target: target,
|
||||
flags: oflags | unix.MS_REMOUNT,
|
||||
err: err,
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
7
vendor/github.com/docker/docker/pkg/mount/mounter_unsupported.go
generated
vendored
Normal file
7
vendor/github.com/docker/docker/pkg/mount/mounter_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build !linux,!freebsd freebsd,!cgo
|
||||
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
func mount(device, target, mType string, flag uintptr, data string) error {
|
||||
panic("Not implemented")
|
||||
}
|
||||
40
vendor/github.com/docker/docker/pkg/mount/mountinfo.go
generated
vendored
Normal file
40
vendor/github.com/docker/docker/pkg/mount/mountinfo.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
// Info reveals information about a particular mounted filesystem. This
|
||||
// struct is populated from the content in the /proc/<pid>/mountinfo file.
|
||||
type Info struct {
|
||||
// ID is a unique identifier of the mount (may be reused after umount).
|
||||
ID int
|
||||
|
||||
// Parent indicates the ID of the mount parent (or of self for the top of the
|
||||
// mount tree).
|
||||
Parent int
|
||||
|
||||
// Major indicates one half of the device ID which identifies the device class.
|
||||
Major int
|
||||
|
||||
// Minor indicates one half of the device ID which identifies a specific
|
||||
// instance of device.
|
||||
Minor int
|
||||
|
||||
// Root of the mount within the filesystem.
|
||||
Root string
|
||||
|
||||
// Mountpoint indicates the mount point relative to the process's root.
|
||||
Mountpoint string
|
||||
|
||||
// Opts represents mount-specific options.
|
||||
Opts string
|
||||
|
||||
// Optional represents optional fields.
|
||||
Optional string
|
||||
|
||||
// Fstype indicates the type of filesystem, such as EXT3.
|
||||
Fstype string
|
||||
|
||||
// Source indicates filesystem specific information or "none".
|
||||
Source string
|
||||
|
||||
// VfsOpts represents per super block options.
|
||||
VfsOpts string
|
||||
}
|
||||
55
vendor/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go
generated
vendored
Normal file
55
vendor/github.com/docker/docker/pkg/mount/mountinfo_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
/*
|
||||
#include <sys/param.h>
|
||||
#include <sys/ucred.h>
|
||||
#include <sys/mount.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
|
||||
// bind mounts.
|
||||
func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
||||
var rawEntries *C.struct_statfs
|
||||
|
||||
count := int(C.getmntinfo(&rawEntries, C.MNT_WAIT))
|
||||
if count == 0 {
|
||||
return nil, fmt.Errorf("Failed to call getmntinfo")
|
||||
}
|
||||
|
||||
var entries []C.struct_statfs
|
||||
header := (*reflect.SliceHeader)(unsafe.Pointer(&entries))
|
||||
header.Cap = count
|
||||
header.Len = count
|
||||
header.Data = uintptr(unsafe.Pointer(rawEntries))
|
||||
|
||||
var out []*Info
|
||||
for _, entry := range entries {
|
||||
var mountinfo Info
|
||||
var skip, stop bool
|
||||
mountinfo.Mountpoint = C.GoString(&entry.f_mntonname[0])
|
||||
|
||||
if filter != nil {
|
||||
// filter out entries we're not interested in
|
||||
skip, stop = filter(p)
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
mountinfo.Source = C.GoString(&entry.f_mntfromname[0])
|
||||
mountinfo.Fstype = C.GoString(&entry.f_fstypename[0])
|
||||
|
||||
out = append(out, &mountinfo)
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
132
vendor/github.com/docker/docker/pkg/mount/mountinfo_linux.go
generated
vendored
Normal file
132
vendor/github.com/docker/docker/pkg/mount/mountinfo_linux.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func parseInfoFile(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
||||
s := bufio.NewScanner(r)
|
||||
out := []*Info{}
|
||||
for s.Scan() {
|
||||
if err := s.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
/*
|
||||
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
|
||||
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
|
||||
|
||||
(1) mount ID: unique identifier of the mount (may be reused after umount)
|
||||
(2) parent ID: ID of parent (or of self for the top of the mount tree)
|
||||
(3) major:minor: value of st_dev for files on filesystem
|
||||
(4) root: root of the mount within the filesystem
|
||||
(5) mount point: mount point relative to the process's root
|
||||
(6) mount options: per mount options
|
||||
(7) optional fields: zero or more fields of the form "tag[:value]"
|
||||
(8) separator: marks the end of the optional fields
|
||||
(9) filesystem type: name of filesystem of the form "type[.subtype]"
|
||||
(10) mount source: filesystem specific information or "none"
|
||||
(11) super options: per super block options
|
||||
*/
|
||||
|
||||
text := s.Text()
|
||||
fields := strings.Split(text, " ")
|
||||
numFields := len(fields)
|
||||
if numFields < 10 {
|
||||
// should be at least 10 fields
|
||||
return nil, fmt.Errorf("Parsing '%s' failed: not enough fields (%d)", text, numFields)
|
||||
}
|
||||
|
||||
p := &Info{}
|
||||
// ignore any numbers parsing errors, as there should not be any
|
||||
p.ID, _ = strconv.Atoi(fields[0])
|
||||
p.Parent, _ = strconv.Atoi(fields[1])
|
||||
mm := strings.Split(fields[2], ":")
|
||||
if len(mm) != 2 {
|
||||
return nil, fmt.Errorf("Parsing '%s' failed: unexpected minor:major pair %s", text, mm)
|
||||
}
|
||||
p.Major, _ = strconv.Atoi(mm[0])
|
||||
p.Minor, _ = strconv.Atoi(mm[1])
|
||||
|
||||
p.Root = fields[3]
|
||||
p.Mountpoint = fields[4]
|
||||
p.Opts = fields[5]
|
||||
|
||||
var skip, stop bool
|
||||
if filter != nil {
|
||||
// filter out entries we're not interested in
|
||||
skip, stop = filter(p)
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// one or more optional fields, when a separator (-)
|
||||
i := 6
|
||||
for ; i < numFields && fields[i] != "-"; i++ {
|
||||
switch i {
|
||||
case 6:
|
||||
p.Optional = fields[6]
|
||||
default:
|
||||
/* NOTE there might be more optional fields before the such as
|
||||
fields[7]...fields[N] (where N < sepIndex), although
|
||||
as of Linux kernel 4.15 the only known ones are
|
||||
mount propagation flags in fields[6]. The correct
|
||||
behavior is to ignore any unknown optional fields.
|
||||
*/
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == numFields {
|
||||
return nil, fmt.Errorf("Parsing '%s' failed: missing separator ('-')", text)
|
||||
}
|
||||
|
||||
// There should be 3 fields after the separator...
|
||||
if i+4 > numFields {
|
||||
return nil, fmt.Errorf("Parsing '%s' failed: not enough fields after a separator", text)
|
||||
}
|
||||
// ... but in Linux <= 3.9 mounting a cifs with spaces in a share name
|
||||
// (like "//serv/My Documents") _may_ end up having a space in the last field
|
||||
// of mountinfo (like "unc=//serv/My Documents"). Since kernel 3.10-rc1, cifs
|
||||
// option unc= is ignored, so a space should not appear. In here we ignore
|
||||
// those "extra" fields caused by extra spaces.
|
||||
p.Fstype = fields[i+1]
|
||||
p.Source = fields[i+2]
|
||||
p.VfsOpts = fields[i+3]
|
||||
|
||||
out = append(out, p)
|
||||
if stop {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from
|
||||
// bind mounts
|
||||
func parseMountTable(filter FilterFunc) ([]*Info, error) {
|
||||
f, err := os.Open("/proc/self/mountinfo")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return parseInfoFile(f, filter)
|
||||
}
|
||||
|
||||
// PidMountInfo collects the mounts for a specific process ID. If the process
|
||||
// ID is unknown, it is better to use `GetMounts` which will inspect
|
||||
// "/proc/self/mountinfo" instead.
|
||||
func PidMountInfo(pid int) ([]*Info, error) {
|
||||
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return parseInfoFile(f, nil)
|
||||
}
|
||||
12
vendor/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go
generated
vendored
Normal file
12
vendor/github.com/docker/docker/pkg/mount/mountinfo_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
// +build !windows,!linux,!freebsd freebsd,!cgo
|
||||
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func parseMountTable(f FilterFunc) ([]*Info, error) {
|
||||
return nil, fmt.Errorf("mount.parseMountTable is not implemented on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
6
vendor/github.com/docker/docker/pkg/mount/mountinfo_windows.go
generated
vendored
Normal file
6
vendor/github.com/docker/docker/pkg/mount/mountinfo_windows.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
func parseMountTable(f FilterFunc) ([]*Info, error) {
|
||||
// Do NOT return an error!
|
||||
return nil, nil
|
||||
}
|
||||
71
vendor/github.com/docker/docker/pkg/mount/sharedsubtree_linux.go
generated
vendored
Normal file
71
vendor/github.com/docker/docker/pkg/mount/sharedsubtree_linux.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
// MakeShared ensures a mounted filesystem has the SHARED mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakeShared(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, "shared")
|
||||
}
|
||||
|
||||
// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakeRShared(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, "rshared")
|
||||
}
|
||||
|
||||
// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakePrivate(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, "private")
|
||||
}
|
||||
|
||||
// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option
|
||||
// enabled. See the supported options in flags.go for further reference.
|
||||
func MakeRPrivate(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, "rprivate")
|
||||
}
|
||||
|
||||
// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakeSlave(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, "slave")
|
||||
}
|
||||
|
||||
// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled.
|
||||
// See the supported options in flags.go for further reference.
|
||||
func MakeRSlave(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, "rslave")
|
||||
}
|
||||
|
||||
// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option
|
||||
// enabled. See the supported options in flags.go for further reference.
|
||||
func MakeUnbindable(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, "unbindable")
|
||||
}
|
||||
|
||||
// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount
|
||||
// option enabled. See the supported options in flags.go for further reference.
|
||||
func MakeRUnbindable(mountPoint string) error {
|
||||
return ensureMountedAs(mountPoint, "runbindable")
|
||||
}
|
||||
|
||||
// MakeMount ensures that the file or directory given is a mount point,
|
||||
// bind mounting it to itself it case it is not.
|
||||
func MakeMount(mnt string) error {
|
||||
mounted, err := Mounted(mnt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mounted {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Mount(mnt, mnt, "none", "bind")
|
||||
}
|
||||
|
||||
func ensureMountedAs(mountPoint, options string) error {
|
||||
if err := MakeMount(mountPoint); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ForceMount("", mountPoint, "none", options)
|
||||
}
|
||||
22
vendor/github.com/docker/docker/pkg/mount/unmount_unix.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/mount/unmount_unix.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// +build !windows
|
||||
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
func unmount(target string, flags int) error {
|
||||
err := unix.Unmount(target, flags)
|
||||
if err == nil || err == unix.EINVAL {
|
||||
// Ignore "not mounted" error here. Note the same error
|
||||
// can be returned if flags are invalid, so this code
|
||||
// assumes that the flags value is always correct.
|
||||
return nil
|
||||
}
|
||||
|
||||
return &mountError{
|
||||
op: "umount",
|
||||
target: target,
|
||||
flags: uintptr(flags),
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
7
vendor/github.com/docker/docker/pkg/mount/unmount_unsupported.go
generated
vendored
Normal file
7
vendor/github.com/docker/docker/pkg/mount/unmount_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build windows
|
||||
|
||||
package mount // import "github.com/docker/docker/pkg/mount"
|
||||
|
||||
func unmount(target string, flag int) error {
|
||||
panic("Not implemented")
|
||||
}
|
||||
1
vendor/github.com/docker/docker/pkg/stringid/README.md
generated
vendored
Normal file
1
vendor/github.com/docker/docker/pkg/stringid/README.md
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
This package provides helper functions for dealing with string identifiers
|
||||
99
vendor/github.com/docker/docker/pkg/stringid/stringid.go
generated
vendored
Normal file
99
vendor/github.com/docker/docker/pkg/stringid/stringid.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// Package stringid provides helper functions for dealing with string identifiers
|
||||
package stringid // import "github.com/docker/docker/pkg/stringid"
|
||||
|
||||
import (
|
||||
cryptorand "crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const shortLen = 12
|
||||
|
||||
var (
|
||||
validShortID = regexp.MustCompile("^[a-f0-9]{12}$")
|
||||
validHex = regexp.MustCompile(`^[a-f0-9]{64}$`)
|
||||
)
|
||||
|
||||
// IsShortID determines if an arbitrary string *looks like* a short ID.
|
||||
func IsShortID(id string) bool {
|
||||
return validShortID.MatchString(id)
|
||||
}
|
||||
|
||||
// TruncateID returns a shorthand version of a string identifier for convenience.
|
||||
// A collision with other shorthands is very unlikely, but possible.
|
||||
// In case of a collision a lookup with TruncIndex.Get() will fail, and the caller
|
||||
// will need to use a longer prefix, or the full-length Id.
|
||||
func TruncateID(id string) string {
|
||||
if i := strings.IndexRune(id, ':'); i >= 0 {
|
||||
id = id[i+1:]
|
||||
}
|
||||
if len(id) > shortLen {
|
||||
id = id[:shortLen]
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func generateID(r io.Reader) string {
|
||||
b := make([]byte, 32)
|
||||
for {
|
||||
if _, err := io.ReadFull(r, b); err != nil {
|
||||
panic(err) // This shouldn't happen
|
||||
}
|
||||
id := hex.EncodeToString(b)
|
||||
// if we try to parse the truncated for as an int and we don't have
|
||||
// an error then the value is all numeric and causes issues when
|
||||
// used as a hostname. ref #3869
|
||||
if _, err := strconv.ParseInt(TruncateID(id), 10, 64); err == nil {
|
||||
continue
|
||||
}
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateRandomID returns a unique id.
|
||||
func GenerateRandomID() string {
|
||||
return generateID(cryptorand.Reader)
|
||||
}
|
||||
|
||||
// GenerateNonCryptoID generates unique id without using cryptographically
|
||||
// secure sources of random.
|
||||
// It helps you to save entropy.
|
||||
func GenerateNonCryptoID() string {
|
||||
return generateID(readerFunc(rand.Read))
|
||||
}
|
||||
|
||||
// ValidateID checks whether an ID string is a valid image ID.
|
||||
func ValidateID(id string) error {
|
||||
if ok := validHex.MatchString(id); !ok {
|
||||
return fmt.Errorf("image ID %q is invalid", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// safely set the seed globally so we generate random ids. Tries to use a
|
||||
// crypto seed before falling back to time.
|
||||
var seed int64
|
||||
if cryptoseed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64)); err != nil {
|
||||
// This should not happen, but worst-case fallback to time-based seed.
|
||||
seed = time.Now().UnixNano()
|
||||
} else {
|
||||
seed = cryptoseed.Int64()
|
||||
}
|
||||
|
||||
rand.Seed(seed)
|
||||
}
|
||||
|
||||
type readerFunc func(p []byte) (int, error)
|
||||
|
||||
func (fn readerFunc) Read(p []byte) (int, error) {
|
||||
return fn(p)
|
||||
}
|
||||
31
vendor/github.com/docker/docker/pkg/system/chtimes.go
generated
vendored
Normal file
31
vendor/github.com/docker/docker/pkg/system/chtimes.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Chtimes changes the access time and modified time of a file at the given path
|
||||
func Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
unixMinTime := time.Unix(0, 0)
|
||||
unixMaxTime := maxTime
|
||||
|
||||
// If the modified time is prior to the Unix Epoch, or after the
|
||||
// end of Unix Time, os.Chtimes has undefined behavior
|
||||
// default to Unix Epoch in this case, just in case
|
||||
|
||||
if atime.Before(unixMinTime) || atime.After(unixMaxTime) {
|
||||
atime = unixMinTime
|
||||
}
|
||||
|
||||
if mtime.Before(unixMinTime) || mtime.After(unixMaxTime) {
|
||||
mtime = unixMinTime
|
||||
}
|
||||
|
||||
if err := os.Chtimes(name, atime, mtime); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Take platform specific action for setting create time.
|
||||
return setCTime(name, mtime)
|
||||
}
|
||||
14
vendor/github.com/docker/docker/pkg/system/chtimes_unix.go
generated
vendored
Normal file
14
vendor/github.com/docker/docker/pkg/system/chtimes_unix.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
// +build !windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
//setCTime will set the create time on a file. On Unix, the create
|
||||
//time is updated as a side effect of setting the modified time, so
|
||||
//no action is required.
|
||||
func setCTime(path string, ctime time.Time) error {
|
||||
return nil
|
||||
}
|
||||
26
vendor/github.com/docker/docker/pkg/system/chtimes_windows.go
generated
vendored
Normal file
26
vendor/github.com/docker/docker/pkg/system/chtimes_windows.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//setCTime will set the create time on a file. On Windows, this requires
|
||||
//calling SetFileTime and explicitly including the create time.
|
||||
func setCTime(path string, ctime time.Time) error {
|
||||
ctimespec := windows.NsecToTimespec(ctime.UnixNano())
|
||||
pathp, e := windows.UTF16PtrFromString(path)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
h, e := windows.CreateFile(pathp,
|
||||
windows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,
|
||||
windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
defer windows.Close(h)
|
||||
c := windows.NsecToFiletime(windows.TimespecToNsec(ctimespec))
|
||||
return windows.SetFileTime(h, &c, nil, nil)
|
||||
}
|
||||
13
vendor/github.com/docker/docker/pkg/system/errors.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/errors.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNotSupportedPlatform means the platform is not supported.
|
||||
ErrNotSupportedPlatform = errors.New("platform and architecture is not supported")
|
||||
|
||||
// ErrNotSupportedOperatingSystem means the operating system is not supported.
|
||||
ErrNotSupportedOperatingSystem = errors.New("operating system is not supported")
|
||||
)
|
||||
19
vendor/github.com/docker/docker/pkg/system/exitcode.go
generated
vendored
Normal file
19
vendor/github.com/docker/docker/pkg/system/exitcode.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// GetExitCode returns the ExitStatus of the specified error if its type is
|
||||
// exec.ExitError, returns 0 and an error otherwise.
|
||||
func GetExitCode(err error) (int, error) {
|
||||
exitCode := 0
|
||||
if exiterr, ok := err.(*exec.ExitError); ok {
|
||||
if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
|
||||
return procExit.ExitStatus(), nil
|
||||
}
|
||||
}
|
||||
return exitCode, fmt.Errorf("failed to get exit code")
|
||||
}
|
||||
67
vendor/github.com/docker/docker/pkg/system/filesys.go
generated
vendored
Normal file
67
vendor/github.com/docker/docker/pkg/system/filesys.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// +build !windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// MkdirAllWithACL is a wrapper for MkdirAll on unix systems.
|
||||
func MkdirAllWithACL(path string, perm os.FileMode, sddl string) error {
|
||||
return MkdirAll(path, perm, sddl)
|
||||
}
|
||||
|
||||
// MkdirAll creates a directory named path along with any necessary parents,
|
||||
// with permission specified by attribute perm for all dir created.
|
||||
func MkdirAll(path string, perm os.FileMode, sddl string) error {
|
||||
return os.MkdirAll(path, perm)
|
||||
}
|
||||
|
||||
// IsAbs is a platform-specific wrapper for filepath.IsAbs.
|
||||
func IsAbs(path string) bool {
|
||||
return filepath.IsAbs(path)
|
||||
}
|
||||
|
||||
// The functions below here are wrappers for the equivalents in the os and ioutils packages.
|
||||
// They are passthrough on Unix platforms, and only relevant on Windows.
|
||||
|
||||
// CreateSequential creates the named file with mode 0666 (before umask), truncating
|
||||
// it if it already exists. If successful, methods on the returned
|
||||
// File can be used for I/O; the associated file descriptor has mode
|
||||
// O_RDWR.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func CreateSequential(name string) (*os.File, error) {
|
||||
return os.Create(name)
|
||||
}
|
||||
|
||||
// OpenSequential opens the named file for reading. If successful, methods on
|
||||
// the returned file can be used for reading; the associated file
|
||||
// descriptor has mode O_RDONLY.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenSequential(name string) (*os.File, error) {
|
||||
return os.Open(name)
|
||||
}
|
||||
|
||||
// OpenFileSequential is the generalized open call; most users will use Open
|
||||
// or Create instead. It opens the named file with specified flag
|
||||
// (O_RDONLY etc.) and perm, (0666 etc.) if applicable. If successful,
|
||||
// methods on the returned File can be used for I/O.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenFileSequential(name string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
return os.OpenFile(name, flag, perm)
|
||||
}
|
||||
|
||||
// TempFileSequential creates a new temporary file in the directory dir
|
||||
// with a name beginning with prefix, opens the file for reading
|
||||
// and writing, and returns the resulting *os.File.
|
||||
// If dir is the empty string, TempFile uses the default directory
|
||||
// for temporary files (see os.TempDir).
|
||||
// Multiple programs calling TempFile simultaneously
|
||||
// will not choose the same file. The caller can use f.Name()
|
||||
// to find the pathname of the file. It is the caller's responsibility
|
||||
// to remove the file when no longer needed.
|
||||
func TempFileSequential(dir, prefix string) (f *os.File, err error) {
|
||||
return ioutil.TempFile(dir, prefix)
|
||||
}
|
||||
296
vendor/github.com/docker/docker/pkg/system/filesys_windows.go
generated
vendored
Normal file
296
vendor/github.com/docker/docker/pkg/system/filesys_windows.go
generated
vendored
Normal file
@@ -0,0 +1,296 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
winio "github.com/Microsoft/go-winio"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
// SddlAdministratorsLocalSystem is local administrators plus NT AUTHORITY\System
|
||||
SddlAdministratorsLocalSystem = "D:P(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
|
||||
// SddlNtvmAdministratorsLocalSystem is NT VIRTUAL MACHINE\Virtual Machines plus local administrators plus NT AUTHORITY\System
|
||||
SddlNtvmAdministratorsLocalSystem = "D:P(A;OICI;GA;;;S-1-5-83-0)(A;OICI;GA;;;BA)(A;OICI;GA;;;SY)"
|
||||
)
|
||||
|
||||
// MkdirAllWithACL is a wrapper for MkdirAll that creates a directory
|
||||
// with an appropriate SDDL defined ACL.
|
||||
func MkdirAllWithACL(path string, perm os.FileMode, sddl string) error {
|
||||
return mkdirall(path, true, sddl)
|
||||
}
|
||||
|
||||
// MkdirAll implementation that is volume path aware for Windows.
|
||||
func MkdirAll(path string, _ os.FileMode, sddl string) error {
|
||||
return mkdirall(path, false, sddl)
|
||||
}
|
||||
|
||||
// mkdirall is a custom version of os.MkdirAll modified for use on Windows
|
||||
// so that it is both volume path aware, and can create a directory with
|
||||
// a DACL.
|
||||
func mkdirall(path string, applyACL bool, sddl string) error {
|
||||
if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The rest of this method is largely copied from os.MkdirAll and should be kept
|
||||
// as-is to ensure compatibility.
|
||||
|
||||
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
||||
dir, err := os.Stat(path)
|
||||
if err == nil {
|
||||
if dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return &os.PathError{
|
||||
Op: "mkdir",
|
||||
Path: path,
|
||||
Err: syscall.ENOTDIR,
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path: make sure parent exists and then call Mkdir for path.
|
||||
i := len(path)
|
||||
for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.
|
||||
i--
|
||||
}
|
||||
|
||||
j := i
|
||||
for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
|
||||
j--
|
||||
}
|
||||
|
||||
if j > 1 {
|
||||
// Create parent
|
||||
err = mkdirall(path[0:j-1], false, sddl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Parent now exists; invoke os.Mkdir or mkdirWithACL and use its result.
|
||||
if applyACL {
|
||||
err = mkdirWithACL(path, sddl)
|
||||
} else {
|
||||
err = os.Mkdir(path, 0)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Handle arguments like "foo/." by
|
||||
// double-checking that directory doesn't exist.
|
||||
dir, err1 := os.Lstat(path)
|
||||
if err1 == nil && dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mkdirWithACL creates a new directory. If there is an error, it will be of
|
||||
// type *PathError. .
|
||||
//
|
||||
// This is a modified and combined version of os.Mkdir and windows.Mkdir
|
||||
// in golang to cater for creating a directory am ACL permitting full
|
||||
// access, with inheritance, to any subfolder/file for Built-in Administrators
|
||||
// and Local System.
|
||||
func mkdirWithACL(name string, sddl string) error {
|
||||
sa := windows.SecurityAttributes{Length: 0}
|
||||
sd, err := winio.SddlToSecurityDescriptor(sddl)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||
}
|
||||
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||
sa.InheritHandle = 1
|
||||
sa.SecurityDescriptor = uintptr(unsafe.Pointer(&sd[0]))
|
||||
|
||||
namep, err := windows.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return &os.PathError{Op: "mkdir", Path: name, Err: err}
|
||||
}
|
||||
|
||||
e := windows.CreateDirectory(namep, &sa)
|
||||
if e != nil {
|
||||
return &os.PathError{Op: "mkdir", Path: name, Err: e}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsAbs is a platform-specific wrapper for filepath.IsAbs. On Windows,
|
||||
// golang filepath.IsAbs does not consider a path \windows\system32 as absolute
|
||||
// as it doesn't start with a drive-letter/colon combination. However, in
|
||||
// docker we need to verify things such as WORKDIR /windows/system32 in
|
||||
// a Dockerfile (which gets translated to \windows\system32 when being processed
|
||||
// by the daemon. This SHOULD be treated as absolute from a docker processing
|
||||
// perspective.
|
||||
func IsAbs(path string) bool {
|
||||
if !filepath.IsAbs(path) {
|
||||
if !strings.HasPrefix(path, string(os.PathSeparator)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// The origin of the functions below here are the golang OS and windows packages,
|
||||
// slightly modified to only cope with files, not directories due to the
|
||||
// specific use case.
|
||||
//
|
||||
// The alteration is to allow a file on Windows to be opened with
|
||||
// FILE_FLAG_SEQUENTIAL_SCAN (particular for docker load), to avoid eating
|
||||
// the standby list, particularly when accessing large files such as layer.tar.
|
||||
|
||||
// CreateSequential creates the named file with mode 0666 (before umask), truncating
|
||||
// it if it already exists. If successful, methods on the returned
|
||||
// File can be used for I/O; the associated file descriptor has mode
|
||||
// O_RDWR.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func CreateSequential(name string) (*os.File, error) {
|
||||
return OpenFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0)
|
||||
}
|
||||
|
||||
// OpenSequential opens the named file for reading. If successful, methods on
|
||||
// the returned file can be used for reading; the associated file
|
||||
// descriptor has mode O_RDONLY.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenSequential(name string) (*os.File, error) {
|
||||
return OpenFileSequential(name, os.O_RDONLY, 0)
|
||||
}
|
||||
|
||||
// OpenFileSequential is the generalized open call; most users will use Open
|
||||
// or Create instead.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func OpenFileSequential(name string, flag int, _ os.FileMode) (*os.File, error) {
|
||||
if name == "" {
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: syscall.ENOENT}
|
||||
}
|
||||
r, errf := windowsOpenFileSequential(name, flag, 0)
|
||||
if errf == nil {
|
||||
return r, nil
|
||||
}
|
||||
return nil, &os.PathError{Op: "open", Path: name, Err: errf}
|
||||
}
|
||||
|
||||
func windowsOpenFileSequential(name string, flag int, _ os.FileMode) (file *os.File, err error) {
|
||||
r, e := windowsOpenSequential(name, flag|windows.O_CLOEXEC, 0)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
return os.NewFile(uintptr(r), name), nil
|
||||
}
|
||||
|
||||
func makeInheritSa() *windows.SecurityAttributes {
|
||||
var sa windows.SecurityAttributes
|
||||
sa.Length = uint32(unsafe.Sizeof(sa))
|
||||
sa.InheritHandle = 1
|
||||
return &sa
|
||||
}
|
||||
|
||||
func windowsOpenSequential(path string, mode int, _ uint32) (fd windows.Handle, err error) {
|
||||
if len(path) == 0 {
|
||||
return windows.InvalidHandle, windows.ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
pathp, err := windows.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
return windows.InvalidHandle, err
|
||||
}
|
||||
var access uint32
|
||||
switch mode & (windows.O_RDONLY | windows.O_WRONLY | windows.O_RDWR) {
|
||||
case windows.O_RDONLY:
|
||||
access = windows.GENERIC_READ
|
||||
case windows.O_WRONLY:
|
||||
access = windows.GENERIC_WRITE
|
||||
case windows.O_RDWR:
|
||||
access = windows.GENERIC_READ | windows.GENERIC_WRITE
|
||||
}
|
||||
if mode&windows.O_CREAT != 0 {
|
||||
access |= windows.GENERIC_WRITE
|
||||
}
|
||||
if mode&windows.O_APPEND != 0 {
|
||||
access &^= windows.GENERIC_WRITE
|
||||
access |= windows.FILE_APPEND_DATA
|
||||
}
|
||||
sharemode := uint32(windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE)
|
||||
var sa *windows.SecurityAttributes
|
||||
if mode&windows.O_CLOEXEC == 0 {
|
||||
sa = makeInheritSa()
|
||||
}
|
||||
var createmode uint32
|
||||
switch {
|
||||
case mode&(windows.O_CREAT|windows.O_EXCL) == (windows.O_CREAT | windows.O_EXCL):
|
||||
createmode = windows.CREATE_NEW
|
||||
case mode&(windows.O_CREAT|windows.O_TRUNC) == (windows.O_CREAT | windows.O_TRUNC):
|
||||
createmode = windows.CREATE_ALWAYS
|
||||
case mode&windows.O_CREAT == windows.O_CREAT:
|
||||
createmode = windows.OPEN_ALWAYS
|
||||
case mode&windows.O_TRUNC == windows.O_TRUNC:
|
||||
createmode = windows.TRUNCATE_EXISTING
|
||||
default:
|
||||
createmode = windows.OPEN_EXISTING
|
||||
}
|
||||
// Use FILE_FLAG_SEQUENTIAL_SCAN rather than FILE_ATTRIBUTE_NORMAL as implemented in golang.
|
||||
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx
|
||||
const fileFlagSequentialScan = 0x08000000 // FILE_FLAG_SEQUENTIAL_SCAN
|
||||
h, e := windows.CreateFile(pathp, access, sharemode, sa, createmode, fileFlagSequentialScan, 0)
|
||||
return h, e
|
||||
}
|
||||
|
||||
// Helpers for TempFileSequential
|
||||
var rand uint32
|
||||
var randmu sync.Mutex
|
||||
|
||||
func reseed() uint32 {
|
||||
return uint32(time.Now().UnixNano() + int64(os.Getpid()))
|
||||
}
|
||||
func nextSuffix() string {
|
||||
randmu.Lock()
|
||||
r := rand
|
||||
if r == 0 {
|
||||
r = reseed()
|
||||
}
|
||||
r = r*1664525 + 1013904223 // constants from Numerical Recipes
|
||||
rand = r
|
||||
randmu.Unlock()
|
||||
return strconv.Itoa(int(1e9 + r%1e9))[1:]
|
||||
}
|
||||
|
||||
// TempFileSequential is a copy of ioutil.TempFile, modified to use sequential
|
||||
// file access. Below is the original comment from golang:
|
||||
// TempFile creates a new temporary file in the directory dir
|
||||
// with a name beginning with prefix, opens the file for reading
|
||||
// and writing, and returns the resulting *os.File.
|
||||
// If dir is the empty string, TempFile uses the default directory
|
||||
// for temporary files (see os.TempDir).
|
||||
// Multiple programs calling TempFile simultaneously
|
||||
// will not choose the same file. The caller can use f.Name()
|
||||
// to find the pathname of the file. It is the caller's responsibility
|
||||
// to remove the file when no longer needed.
|
||||
func TempFileSequential(dir, prefix string) (f *os.File, err error) {
|
||||
if dir == "" {
|
||||
dir = os.TempDir()
|
||||
}
|
||||
|
||||
nconflict := 0
|
||||
for i := 0; i < 10000; i++ {
|
||||
name := filepath.Join(dir, prefix+nextSuffix())
|
||||
f, err = OpenFileSequential(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if os.IsExist(err) {
|
||||
if nconflict++; nconflict > 10 {
|
||||
randmu.Lock()
|
||||
rand = reseed()
|
||||
randmu.Unlock()
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
22
vendor/github.com/docker/docker/pkg/system/init.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/system/init.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Used by chtimes
|
||||
var maxTime time.Time
|
||||
|
||||
func init() {
|
||||
// chtimes initialization
|
||||
if unsafe.Sizeof(syscall.Timespec{}.Nsec) == 8 {
|
||||
// This is a 64 bit timespec
|
||||
// os.Chtimes limits time to the following
|
||||
maxTime = time.Unix(0, 1<<63-1)
|
||||
} else {
|
||||
// This is a 32 bit timespec
|
||||
maxTime = time.Unix(1<<31-1, 0)
|
||||
}
|
||||
}
|
||||
7
vendor/github.com/docker/docker/pkg/system/init_unix.go
generated
vendored
Normal file
7
vendor/github.com/docker/docker/pkg/system/init_unix.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build !windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// InitLCOW does nothing since LCOW is a windows only feature
|
||||
func InitLCOW(experimental bool) {
|
||||
}
|
||||
12
vendor/github.com/docker/docker/pkg/system/init_windows.go
generated
vendored
Normal file
12
vendor/github.com/docker/docker/pkg/system/init_windows.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// lcowSupported determines if Linux Containers on Windows are supported.
|
||||
var lcowSupported = false
|
||||
|
||||
// InitLCOW sets whether LCOW is supported or not
|
||||
func InitLCOW(experimental bool) {
|
||||
v := GetOSVersion()
|
||||
if experimental && v.Build >= 16299 {
|
||||
lcowSupported = true
|
||||
}
|
||||
}
|
||||
32
vendor/github.com/docker/docker/pkg/system/lcow.go
generated
vendored
Normal file
32
vendor/github.com/docker/docker/pkg/system/lcow.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// IsOSSupported determines if an operating system is supported by the host
|
||||
func IsOSSupported(os string) bool {
|
||||
if strings.EqualFold(runtime.GOOS, os) {
|
||||
return true
|
||||
}
|
||||
if LCOWSupported() && strings.EqualFold(os, "linux") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidatePlatform determines if a platform structure is valid.
|
||||
// TODO This is a temporary windows-only function, should be replaced by
|
||||
// comparison of worker capabilities
|
||||
func ValidatePlatform(platform specs.Platform) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
if !(platform.OS == runtime.GOOS || (LCOWSupported() && platform.OS == "linux")) {
|
||||
return errors.Errorf("unsupported os %s", platform.OS)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
8
vendor/github.com/docker/docker/pkg/system/lcow_unix.go
generated
vendored
Normal file
8
vendor/github.com/docker/docker/pkg/system/lcow_unix.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build !windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// LCOWSupported returns true if Linux containers on Windows are supported.
|
||||
func LCOWSupported() bool {
|
||||
return false
|
||||
}
|
||||
6
vendor/github.com/docker/docker/pkg/system/lcow_windows.go
generated
vendored
Normal file
6
vendor/github.com/docker/docker/pkg/system/lcow_windows.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// LCOWSupported returns true if Linux containers on Windows are supported.
|
||||
func LCOWSupported() bool {
|
||||
return lcowSupported
|
||||
}
|
||||
20
vendor/github.com/docker/docker/pkg/system/lstat_unix.go
generated
vendored
Normal file
20
vendor/github.com/docker/docker/pkg/system/lstat_unix.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// +build !windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Lstat takes a path to a file and returns
|
||||
// a system.StatT type pertaining to that file.
|
||||
//
|
||||
// Throws an error if the file does not exist
|
||||
func Lstat(path string) (*StatT, error) {
|
||||
s := &syscall.Stat_t{}
|
||||
if err := syscall.Lstat(path, s); err != nil {
|
||||
return nil, &os.PathError{Op: "Lstat", Path: path, Err: err}
|
||||
}
|
||||
return fromStatT(s)
|
||||
}
|
||||
14
vendor/github.com/docker/docker/pkg/system/lstat_windows.go
generated
vendored
Normal file
14
vendor/github.com/docker/docker/pkg/system/lstat_windows.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "os"
|
||||
|
||||
// Lstat calls os.Lstat to get a fileinfo interface back.
|
||||
// This is then copied into our own locally defined structure.
|
||||
func Lstat(path string) (*StatT, error) {
|
||||
fi, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fromStatT(&fi)
|
||||
}
|
||||
17
vendor/github.com/docker/docker/pkg/system/meminfo.go
generated
vendored
Normal file
17
vendor/github.com/docker/docker/pkg/system/meminfo.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// MemInfo contains memory statistics of the host system.
|
||||
type MemInfo struct {
|
||||
// Total usable RAM (i.e. physical RAM minus a few reserved bits and the
|
||||
// kernel binary code).
|
||||
MemTotal int64
|
||||
|
||||
// Amount of free memory.
|
||||
MemFree int64
|
||||
|
||||
// Total amount of swap space available.
|
||||
SwapTotal int64
|
||||
|
||||
// Amount of swap space that is currently unused.
|
||||
SwapFree int64
|
||||
}
|
||||
65
vendor/github.com/docker/docker/pkg/system/meminfo_linux.go
generated
vendored
Normal file
65
vendor/github.com/docker/docker/pkg/system/meminfo_linux.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||
// MemInfo type.
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
file, err := os.Open("/proc/meminfo")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
return parseMemInfo(file)
|
||||
}
|
||||
|
||||
// parseMemInfo parses the /proc/meminfo file into
|
||||
// a MemInfo object given an io.Reader to the file.
|
||||
// Throws error if there are problems reading from the file
|
||||
func parseMemInfo(reader io.Reader) (*MemInfo, error) {
|
||||
meminfo := &MemInfo{}
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
// Expected format: ["MemTotal:", "1234", "kB"]
|
||||
parts := strings.Fields(scanner.Text())
|
||||
|
||||
// Sanity checks: Skip malformed entries.
|
||||
if len(parts) < 3 || parts[2] != "kB" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Convert to bytes.
|
||||
size, err := strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
bytes := int64(size) * units.KiB
|
||||
|
||||
switch parts[0] {
|
||||
case "MemTotal:":
|
||||
meminfo.MemTotal = bytes
|
||||
case "MemFree:":
|
||||
meminfo.MemFree = bytes
|
||||
case "SwapTotal:":
|
||||
meminfo.SwapTotal = bytes
|
||||
case "SwapFree:":
|
||||
meminfo.SwapFree = bytes
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle errors that may have occurred during the reading of the file.
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return meminfo, nil
|
||||
}
|
||||
8
vendor/github.com/docker/docker/pkg/system/meminfo_unsupported.go
generated
vendored
Normal file
8
vendor/github.com/docker/docker/pkg/system/meminfo_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
// +build !linux,!windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// ReadMemInfo is not supported on platforms other than linux and windows.
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
return nil, ErrNotSupportedPlatform
|
||||
}
|
||||
45
vendor/github.com/docker/docker/pkg/system/meminfo_windows.go
generated
vendored
Normal file
45
vendor/github.com/docker/docker/pkg/system/meminfo_windows.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
|
||||
procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx")
|
||||
)
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366589(v=vs.85).aspx
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa366770(v=vs.85).aspx
|
||||
type memorystatusex struct {
|
||||
dwLength uint32
|
||||
dwMemoryLoad uint32
|
||||
ullTotalPhys uint64
|
||||
ullAvailPhys uint64
|
||||
ullTotalPageFile uint64
|
||||
ullAvailPageFile uint64
|
||||
ullTotalVirtual uint64
|
||||
ullAvailVirtual uint64
|
||||
ullAvailExtendedVirtual uint64
|
||||
}
|
||||
|
||||
// ReadMemInfo retrieves memory statistics of the host system and returns a
|
||||
// MemInfo type.
|
||||
func ReadMemInfo() (*MemInfo, error) {
|
||||
msi := &memorystatusex{
|
||||
dwLength: 64,
|
||||
}
|
||||
r1, _, _ := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(msi)))
|
||||
if r1 == 0 {
|
||||
return &MemInfo{}, nil
|
||||
}
|
||||
return &MemInfo{
|
||||
MemTotal: int64(msi.ullTotalPhys),
|
||||
MemFree: int64(msi.ullAvailPhys),
|
||||
SwapTotal: int64(msi.ullTotalPageFile),
|
||||
SwapFree: int64(msi.ullAvailPageFile),
|
||||
}, nil
|
||||
}
|
||||
22
vendor/github.com/docker/docker/pkg/system/mknod.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/system/mknod.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// +build !windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Mknod creates a filesystem node (file, device special file or named pipe) named path
|
||||
// with attributes specified by mode and dev.
|
||||
func Mknod(path string, mode uint32, dev int) error {
|
||||
return unix.Mknod(path, mode, dev)
|
||||
}
|
||||
|
||||
// Mkdev is used to build the value of linux devices (in /dev/) which specifies major
|
||||
// and minor number of the newly created device special file.
|
||||
// Linux device nodes are a bit weird due to backwards compat with 16 bit device nodes.
|
||||
// They are, from low to high: the lower 8 bits of the minor, then 12 bits of the major,
|
||||
// then the top 12 bits of the minor.
|
||||
func Mkdev(major int64, minor int64) uint32 {
|
||||
return uint32(unix.Mkdev(uint32(major), uint32(minor)))
|
||||
}
|
||||
11
vendor/github.com/docker/docker/pkg/system/mknod_windows.go
generated
vendored
Normal file
11
vendor/github.com/docker/docker/pkg/system/mknod_windows.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// Mknod is not implemented on Windows.
|
||||
func Mknod(path string, mode uint32, dev int) error {
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
||||
|
||||
// Mkdev is not implemented on Windows.
|
||||
func Mkdev(major int64, minor int64) uint32 {
|
||||
panic("Mkdev not implemented on Windows.")
|
||||
}
|
||||
60
vendor/github.com/docker/docker/pkg/system/path.go
generated
vendored
Normal file
60
vendor/github.com/docker/docker/pkg/system/path.go
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/continuity/pathdriver"
|
||||
)
|
||||
|
||||
const defaultUnixPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
// DefaultPathEnv is unix style list of directories to search for
|
||||
// executables. Each directory is separated from the next by a colon
|
||||
// ':' character .
|
||||
func DefaultPathEnv(os string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
if os != runtime.GOOS {
|
||||
return defaultUnixPathEnv
|
||||
}
|
||||
// Deliberately empty on Windows containers on Windows as the default path will be set by
|
||||
// the container. Docker has no context of what the default path should be.
|
||||
return ""
|
||||
}
|
||||
return defaultUnixPathEnv
|
||||
|
||||
}
|
||||
|
||||
// CheckSystemDriveAndRemoveDriveLetter verifies that a path, if it includes a drive letter,
|
||||
// is the system drive.
|
||||
// On Linux: this is a no-op.
|
||||
// On Windows: this does the following>
|
||||
// CheckSystemDriveAndRemoveDriveLetter verifies and manipulates a Windows path.
|
||||
// This is used, for example, when validating a user provided path in docker cp.
|
||||
// If a drive letter is supplied, it must be the system drive. The drive letter
|
||||
// is always removed. Also, it translates it to OS semantics (IOW / to \). We
|
||||
// need the path in this syntax so that it can ultimately be concatenated with
|
||||
// a Windows long-path which doesn't support drive-letters. Examples:
|
||||
// C: --> Fail
|
||||
// C:\ --> \
|
||||
// a --> a
|
||||
// /a --> \a
|
||||
// d:\ --> Fail
|
||||
func CheckSystemDriveAndRemoveDriveLetter(path string, driver pathdriver.PathDriver) (string, error) {
|
||||
if runtime.GOOS != "windows" || LCOWSupported() {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
if len(path) == 2 && string(path[1]) == ":" {
|
||||
return "", fmt.Errorf("No relative path specified in %q", path)
|
||||
}
|
||||
if !driver.IsAbs(path) || len(path) < 2 {
|
||||
return filepath.FromSlash(path), nil
|
||||
}
|
||||
if string(path[1]) == ":" && !strings.EqualFold(string(path[0]), "c") {
|
||||
return "", fmt.Errorf("The specified path is not on the system drive (C:)")
|
||||
}
|
||||
return filepath.FromSlash(path[2:]), nil
|
||||
}
|
||||
10
vendor/github.com/docker/docker/pkg/system/path_unix.go
generated
vendored
Normal file
10
vendor/github.com/docker/docker/pkg/system/path_unix.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// +build !windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// GetLongPathName converts Windows short pathnames to full pathnames.
|
||||
// For example C:\Users\ADMIN~1 --> C:\Users\Administrator.
|
||||
// It is a no-op on non-Windows platforms
|
||||
func GetLongPathName(path string) (string, error) {
|
||||
return path, nil
|
||||
}
|
||||
24
vendor/github.com/docker/docker/pkg/system/path_windows.go
generated
vendored
Normal file
24
vendor/github.com/docker/docker/pkg/system/path_windows.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "syscall"
|
||||
|
||||
// GetLongPathName converts Windows short pathnames to full pathnames.
|
||||
// For example C:\Users\ADMIN~1 --> C:\Users\Administrator.
|
||||
// It is a no-op on non-Windows platforms
|
||||
func GetLongPathName(path string) (string, error) {
|
||||
// See https://groups.google.com/forum/#!topic/golang-dev/1tufzkruoTg
|
||||
p := syscall.StringToUTF16(path)
|
||||
b := p // GetLongPathName says we can reuse buffer
|
||||
n, err := syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n > uint32(len(b)) {
|
||||
b = make([]uint16, n)
|
||||
_, err = syscall.GetLongPathName(&p[0], &b[0], uint32(len(b)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return syscall.UTF16ToString(b), nil
|
||||
}
|
||||
24
vendor/github.com/docker/docker/pkg/system/process_unix.go
generated
vendored
Normal file
24
vendor/github.com/docker/docker/pkg/system/process_unix.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// +build linux freebsd darwin
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// IsProcessAlive returns true if process with a given pid is running.
|
||||
func IsProcessAlive(pid int) bool {
|
||||
err := unix.Kill(pid, syscall.Signal(0))
|
||||
if err == nil || err == unix.EPERM {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// KillProcess force-stops a process.
|
||||
func KillProcess(pid int) {
|
||||
unix.Kill(pid, unix.SIGKILL)
|
||||
}
|
||||
18
vendor/github.com/docker/docker/pkg/system/process_windows.go
generated
vendored
Normal file
18
vendor/github.com/docker/docker/pkg/system/process_windows.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "os"
|
||||
|
||||
// IsProcessAlive returns true if process with a given pid is running.
|
||||
func IsProcessAlive(pid int) bool {
|
||||
_, err := os.FindProcess(pid)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// KillProcess force-stops a process.
|
||||
func KillProcess(pid int) {
|
||||
p, err := os.FindProcess(pid)
|
||||
if err == nil {
|
||||
p.Kill()
|
||||
}
|
||||
}
|
||||
80
vendor/github.com/docker/docker/pkg/system/rm.go
generated
vendored
Normal file
80
vendor/github.com/docker/docker/pkg/system/rm.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/mount"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// EnsureRemoveAll wraps `os.RemoveAll` to check for specific errors that can
|
||||
// often be remedied.
|
||||
// Only use `EnsureRemoveAll` if you really want to make every effort to remove
|
||||
// a directory.
|
||||
//
|
||||
// Because of the way `os.Remove` (and by extension `os.RemoveAll`) works, there
|
||||
// can be a race between reading directory entries and then actually attempting
|
||||
// to remove everything in the directory.
|
||||
// These types of errors do not need to be returned since it's ok for the dir to
|
||||
// be gone we can just retry the remove operation.
|
||||
//
|
||||
// This should not return a `os.ErrNotExist` kind of error under any circumstances
|
||||
func EnsureRemoveAll(dir string) error {
|
||||
notExistErr := make(map[string]bool)
|
||||
|
||||
// track retries
|
||||
exitOnErr := make(map[string]int)
|
||||
maxRetry := 50
|
||||
|
||||
// Attempt to unmount anything beneath this dir first
|
||||
mount.RecursiveUnmount(dir)
|
||||
|
||||
for {
|
||||
err := os.RemoveAll(dir)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pe, ok := err.(*os.PathError)
|
||||
if !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
if notExistErr[pe.Path] {
|
||||
return err
|
||||
}
|
||||
notExistErr[pe.Path] = true
|
||||
|
||||
// There is a race where some subdir can be removed but after the parent
|
||||
// dir entries have been read.
|
||||
// So the path could be from `os.Remove(subdir)`
|
||||
// If the reported non-existent path is not the passed in `dir` we
|
||||
// should just retry, but otherwise return with no error.
|
||||
if pe.Path == dir {
|
||||
return nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if pe.Err != syscall.EBUSY {
|
||||
return err
|
||||
}
|
||||
|
||||
if mounted, _ := mount.Mounted(pe.Path); mounted {
|
||||
if e := mount.Unmount(pe.Path); e != nil {
|
||||
if mounted, _ := mount.Mounted(pe.Path); mounted {
|
||||
return errors.Wrapf(e, "error while removing %s", dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if exitOnErr[pe.Path] == maxRetry {
|
||||
return err
|
||||
}
|
||||
exitOnErr[pe.Path]++
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
13
vendor/github.com/docker/docker/pkg/system/stat_darwin.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/stat_darwin.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "syscall"
|
||||
|
||||
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
||||
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
|
||||
return &StatT{size: s.Size,
|
||||
mode: uint32(s.Mode),
|
||||
uid: s.Uid,
|
||||
gid: s.Gid,
|
||||
rdev: uint64(s.Rdev),
|
||||
mtim: s.Mtimespec}, nil
|
||||
}
|
||||
13
vendor/github.com/docker/docker/pkg/system/stat_freebsd.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/stat_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "syscall"
|
||||
|
||||
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
||||
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
|
||||
return &StatT{size: s.Size,
|
||||
mode: uint32(s.Mode),
|
||||
uid: s.Uid,
|
||||
gid: s.Gid,
|
||||
rdev: uint64(s.Rdev),
|
||||
mtim: s.Mtimespec}, nil
|
||||
}
|
||||
19
vendor/github.com/docker/docker/pkg/system/stat_linux.go
generated
vendored
Normal file
19
vendor/github.com/docker/docker/pkg/system/stat_linux.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "syscall"
|
||||
|
||||
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
||||
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
|
||||
return &StatT{size: s.Size,
|
||||
mode: s.Mode,
|
||||
uid: s.Uid,
|
||||
gid: s.Gid,
|
||||
rdev: s.Rdev,
|
||||
mtim: s.Mtim}, nil
|
||||
}
|
||||
|
||||
// FromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
||||
// This is exposed on Linux as pkg/archive/changes uses it.
|
||||
func FromStatT(s *syscall.Stat_t) (*StatT, error) {
|
||||
return fromStatT(s)
|
||||
}
|
||||
13
vendor/github.com/docker/docker/pkg/system/stat_openbsd.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/stat_openbsd.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "syscall"
|
||||
|
||||
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
||||
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
|
||||
return &StatT{size: s.Size,
|
||||
mode: uint32(s.Mode),
|
||||
uid: s.Uid,
|
||||
gid: s.Gid,
|
||||
rdev: uint64(s.Rdev),
|
||||
mtim: s.Mtim}, nil
|
||||
}
|
||||
13
vendor/github.com/docker/docker/pkg/system/stat_solaris.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/stat_solaris.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "syscall"
|
||||
|
||||
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
|
||||
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
|
||||
return &StatT{size: s.Size,
|
||||
mode: uint32(s.Mode),
|
||||
uid: s.Uid,
|
||||
gid: s.Gid,
|
||||
rdev: uint64(s.Rdev),
|
||||
mtim: s.Mtim}, nil
|
||||
}
|
||||
66
vendor/github.com/docker/docker/pkg/system/stat_unix.go
generated
vendored
Normal file
66
vendor/github.com/docker/docker/pkg/system/stat_unix.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// +build !windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// StatT type contains status of a file. It contains metadata
|
||||
// like permission, owner, group, size, etc about a file.
|
||||
type StatT struct {
|
||||
mode uint32
|
||||
uid uint32
|
||||
gid uint32
|
||||
rdev uint64
|
||||
size int64
|
||||
mtim syscall.Timespec
|
||||
}
|
||||
|
||||
// Mode returns file's permission mode.
|
||||
func (s StatT) Mode() uint32 {
|
||||
return s.mode
|
||||
}
|
||||
|
||||
// UID returns file's user id of owner.
|
||||
func (s StatT) UID() uint32 {
|
||||
return s.uid
|
||||
}
|
||||
|
||||
// GID returns file's group id of owner.
|
||||
func (s StatT) GID() uint32 {
|
||||
return s.gid
|
||||
}
|
||||
|
||||
// Rdev returns file's device ID (if it's special file).
|
||||
func (s StatT) Rdev() uint64 {
|
||||
return s.rdev
|
||||
}
|
||||
|
||||
// Size returns file's size.
|
||||
func (s StatT) Size() int64 {
|
||||
return s.size
|
||||
}
|
||||
|
||||
// Mtim returns file's last modification time.
|
||||
func (s StatT) Mtim() syscall.Timespec {
|
||||
return s.mtim
|
||||
}
|
||||
|
||||
// IsDir reports whether s describes a directory.
|
||||
func (s StatT) IsDir() bool {
|
||||
return s.mode&syscall.S_IFDIR != 0
|
||||
}
|
||||
|
||||
// Stat takes a path to a file and returns
|
||||
// a system.StatT type pertaining to that file.
|
||||
//
|
||||
// Throws an error if the file does not exist
|
||||
func Stat(path string) (*StatT, error) {
|
||||
s := &syscall.Stat_t{}
|
||||
if err := syscall.Stat(path, s); err != nil {
|
||||
return nil, &os.PathError{Op: "Stat", Path: path, Err: err}
|
||||
}
|
||||
return fromStatT(s)
|
||||
}
|
||||
49
vendor/github.com/docker/docker/pkg/system/stat_windows.go
generated
vendored
Normal file
49
vendor/github.com/docker/docker/pkg/system/stat_windows.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// StatT type contains status of a file. It contains metadata
|
||||
// like permission, size, etc about a file.
|
||||
type StatT struct {
|
||||
mode os.FileMode
|
||||
size int64
|
||||
mtim time.Time
|
||||
}
|
||||
|
||||
// Size returns file's size.
|
||||
func (s StatT) Size() int64 {
|
||||
return s.size
|
||||
}
|
||||
|
||||
// Mode returns file's permission mode.
|
||||
func (s StatT) Mode() os.FileMode {
|
||||
return os.FileMode(s.mode)
|
||||
}
|
||||
|
||||
// Mtim returns file's last modification time.
|
||||
func (s StatT) Mtim() time.Time {
|
||||
return time.Time(s.mtim)
|
||||
}
|
||||
|
||||
// Stat takes a path to a file and returns
|
||||
// a system.StatT type pertaining to that file.
|
||||
//
|
||||
// Throws an error if the file does not exist
|
||||
func Stat(path string) (*StatT, error) {
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fromStatT(&fi)
|
||||
}
|
||||
|
||||
// fromStatT converts a os.FileInfo type to a system.StatT type
|
||||
func fromStatT(fi *os.FileInfo) (*StatT, error) {
|
||||
return &StatT{
|
||||
size: (*fi).Size(),
|
||||
mode: (*fi).Mode(),
|
||||
mtim: (*fi).ModTime()}, nil
|
||||
}
|
||||
17
vendor/github.com/docker/docker/pkg/system/syscall_unix.go
generated
vendored
Normal file
17
vendor/github.com/docker/docker/pkg/system/syscall_unix.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// +build linux freebsd
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// Unmount is a platform-specific helper function to call
|
||||
// the unmount syscall.
|
||||
func Unmount(dest string) error {
|
||||
return unix.Unmount(dest, 0)
|
||||
}
|
||||
|
||||
// CommandLineToArgv should not be used on Unix.
|
||||
// It simply returns commandLine in the only element in the returned array.
|
||||
func CommandLineToArgv(commandLine string) ([]string, error) {
|
||||
return []string{commandLine}, nil
|
||||
}
|
||||
193
vendor/github.com/docker/docker/pkg/system/syscall_windows.go
generated
vendored
Normal file
193
vendor/github.com/docker/docker/pkg/system/syscall_windows.go
generated
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
OWNER_SECURITY_INFORMATION = 0x00000001
|
||||
GROUP_SECURITY_INFORMATION = 0x00000002
|
||||
DACL_SECURITY_INFORMATION = 0x00000004
|
||||
SACL_SECURITY_INFORMATION = 0x00000008
|
||||
LABEL_SECURITY_INFORMATION = 0x00000010
|
||||
ATTRIBUTE_SECURITY_INFORMATION = 0x00000020
|
||||
SCOPE_SECURITY_INFORMATION = 0x00000040
|
||||
PROCESS_TRUST_LABEL_SECURITY_INFORMATION = 0x00000080
|
||||
ACCESS_FILTER_SECURITY_INFORMATION = 0x00000100
|
||||
BACKUP_SECURITY_INFORMATION = 0x00010000
|
||||
PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000
|
||||
PROTECTED_SACL_SECURITY_INFORMATION = 0x40000000
|
||||
UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000
|
||||
UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000
|
||||
)
|
||||
|
||||
const (
|
||||
SE_UNKNOWN_OBJECT_TYPE = iota
|
||||
SE_FILE_OBJECT
|
||||
SE_SERVICE
|
||||
SE_PRINTER
|
||||
SE_REGISTRY_KEY
|
||||
SE_LMSHARE
|
||||
SE_KERNEL_OBJECT
|
||||
SE_WINDOW_OBJECT
|
||||
SE_DS_OBJECT
|
||||
SE_DS_OBJECT_ALL
|
||||
SE_PROVIDER_DEFINED_OBJECT
|
||||
SE_WMIGUID_OBJECT
|
||||
SE_REGISTRY_WOW64_32KEY
|
||||
)
|
||||
|
||||
const (
|
||||
SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege"
|
||||
)
|
||||
|
||||
const (
|
||||
ContainerAdministratorSidString = "S-1-5-93-2-1"
|
||||
ContainerUserSidString = "S-1-5-93-2-2"
|
||||
)
|
||||
|
||||
var (
|
||||
ntuserApiset = windows.NewLazyDLL("ext-ms-win-ntuser-window-l1-1-0")
|
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||
procGetVersionExW = modkernel32.NewProc("GetVersionExW")
|
||||
procGetProductInfo = modkernel32.NewProc("GetProductInfo")
|
||||
procSetNamedSecurityInfo = modadvapi32.NewProc("SetNamedSecurityInfoW")
|
||||
procGetSecurityDescriptorDacl = modadvapi32.NewProc("GetSecurityDescriptorDacl")
|
||||
)
|
||||
|
||||
// OSVersion is a wrapper for Windows version information
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
|
||||
type OSVersion struct {
|
||||
Version uint32
|
||||
MajorVersion uint8
|
||||
MinorVersion uint8
|
||||
Build uint16
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724833(v=vs.85).aspx
|
||||
type osVersionInfoEx struct {
|
||||
OSVersionInfoSize uint32
|
||||
MajorVersion uint32
|
||||
MinorVersion uint32
|
||||
BuildNumber uint32
|
||||
PlatformID uint32
|
||||
CSDVersion [128]uint16
|
||||
ServicePackMajor uint16
|
||||
ServicePackMinor uint16
|
||||
SuiteMask uint16
|
||||
ProductType byte
|
||||
Reserve byte
|
||||
}
|
||||
|
||||
// GetOSVersion gets the operating system version on Windows. Note that
|
||||
// docker.exe must be manifested to get the correct version information.
|
||||
func GetOSVersion() OSVersion {
|
||||
var err error
|
||||
osv := OSVersion{}
|
||||
osv.Version, err = windows.GetVersion()
|
||||
if err != nil {
|
||||
// GetVersion never fails.
|
||||
panic(err)
|
||||
}
|
||||
osv.MajorVersion = uint8(osv.Version & 0xFF)
|
||||
osv.MinorVersion = uint8(osv.Version >> 8 & 0xFF)
|
||||
osv.Build = uint16(osv.Version >> 16)
|
||||
return osv
|
||||
}
|
||||
|
||||
func (osv OSVersion) ToString() string {
|
||||
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
|
||||
}
|
||||
|
||||
// IsWindowsClient returns true if the SKU is client
|
||||
// @engine maintainers - this function should not be removed or modified as it
|
||||
// is used to enforce licensing restrictions on Windows.
|
||||
func IsWindowsClient() bool {
|
||||
osviex := &osVersionInfoEx{OSVersionInfoSize: 284}
|
||||
r1, _, err := procGetVersionExW.Call(uintptr(unsafe.Pointer(osviex)))
|
||||
if r1 == 0 {
|
||||
logrus.Warnf("GetVersionExW failed - assuming server SKU: %v", err)
|
||||
return false
|
||||
}
|
||||
const verNTWorkstation = 0x00000001
|
||||
return osviex.ProductType == verNTWorkstation
|
||||
}
|
||||
|
||||
// IsIoTCore returns true if the currently running image is based off of
|
||||
// Windows 10 IoT Core.
|
||||
// @engine maintainers - this function should not be removed or modified as it
|
||||
// is used to enforce licensing restrictions on Windows.
|
||||
func IsIoTCore() bool {
|
||||
var returnedProductType uint32
|
||||
r1, _, err := procGetProductInfo.Call(6, 1, 0, 0, uintptr(unsafe.Pointer(&returnedProductType)))
|
||||
if r1 == 0 {
|
||||
logrus.Warnf("GetProductInfo failed - assuming this is not IoT: %v", err)
|
||||
return false
|
||||
}
|
||||
const productIoTUAP = 0x0000007B
|
||||
const productIoTUAPCommercial = 0x00000083
|
||||
return returnedProductType == productIoTUAP || returnedProductType == productIoTUAPCommercial
|
||||
}
|
||||
|
||||
// Unmount is a platform-specific helper function to call
|
||||
// the unmount syscall. Not supported on Windows
|
||||
func Unmount(dest string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandLineToArgv wraps the Windows syscall to turn a commandline into an argument array.
|
||||
func CommandLineToArgv(commandLine string) ([]string, error) {
|
||||
var argc int32
|
||||
|
||||
argsPtr, err := windows.UTF16PtrFromString(commandLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
argv, err := windows.CommandLineToArgv(argsPtr, &argc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer windows.LocalFree(windows.Handle(uintptr(unsafe.Pointer(argv))))
|
||||
|
||||
newArgs := make([]string, argc)
|
||||
for i, v := range (*argv)[:argc] {
|
||||
newArgs[i] = string(windows.UTF16ToString((*v)[:]))
|
||||
}
|
||||
|
||||
return newArgs, nil
|
||||
}
|
||||
|
||||
// HasWin32KSupport determines whether containers that depend on win32k can
|
||||
// run on this machine. Win32k is the driver used to implement windowing.
|
||||
func HasWin32KSupport() bool {
|
||||
// For now, check for ntuser API support on the host. In the future, a host
|
||||
// may support win32k in containers even if the host does not support ntuser
|
||||
// APIs.
|
||||
return ntuserApiset.Load() == nil
|
||||
}
|
||||
|
||||
func SetNamedSecurityInfo(objectName *uint16, objectType uint32, securityInformation uint32, sidOwner *windows.SID, sidGroup *windows.SID, dacl *byte, sacl *byte) (result error) {
|
||||
r0, _, _ := syscall.Syscall9(procSetNamedSecurityInfo.Addr(), 7, uintptr(unsafe.Pointer(objectName)), uintptr(objectType), uintptr(securityInformation), uintptr(unsafe.Pointer(sidOwner)), uintptr(unsafe.Pointer(sidGroup)), uintptr(unsafe.Pointer(dacl)), uintptr(unsafe.Pointer(sacl)), 0, 0)
|
||||
if r0 != 0 {
|
||||
result = syscall.Errno(r0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetSecurityDescriptorDacl(securityDescriptor *byte, daclPresent *uint32, dacl **byte, daclDefaulted *uint32) (result error) {
|
||||
r1, _, e1 := syscall.Syscall6(procGetSecurityDescriptorDacl.Addr(), 4, uintptr(unsafe.Pointer(securityDescriptor)), uintptr(unsafe.Pointer(daclPresent)), uintptr(unsafe.Pointer(dacl)), uintptr(unsafe.Pointer(daclDefaulted)), 0, 0)
|
||||
if r1 == 0 {
|
||||
if e1 != 0 {
|
||||
result = syscall.Errno(e1)
|
||||
} else {
|
||||
result = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
13
vendor/github.com/docker/docker/pkg/system/umask.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/umask.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build !windows
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Umask sets current process's file mode creation mask to newmask
|
||||
// and returns oldmask.
|
||||
func Umask(newmask int) (oldmask int, err error) {
|
||||
return unix.Umask(newmask), nil
|
||||
}
|
||||
7
vendor/github.com/docker/docker/pkg/system/umask_windows.go
generated
vendored
Normal file
7
vendor/github.com/docker/docker/pkg/system/umask_windows.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// Umask is not supported on the windows platform.
|
||||
func Umask(newmask int) (oldmask int, err error) {
|
||||
// should not be called on cli code path
|
||||
return 0, ErrNotSupportedPlatform
|
||||
}
|
||||
24
vendor/github.com/docker/docker/pkg/system/utimes_freebsd.go
generated
vendored
Normal file
24
vendor/github.com/docker/docker/pkg/system/utimes_freebsd.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// LUtimesNano is used to change access and modification time of the specified path.
|
||||
// It's used for symbol link file because unix.UtimesNano doesn't support a NOFOLLOW flag atm.
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
var _path *byte
|
||||
_path, err := unix.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, _, err := unix.Syscall(unix.SYS_LUTIMES, uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), 0); err != 0 && err != unix.ENOSYS {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
25
vendor/github.com/docker/docker/pkg/system/utimes_linux.go
generated
vendored
Normal file
25
vendor/github.com/docker/docker/pkg/system/utimes_linux.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// LUtimesNano is used to change access and modification time of the specified path.
|
||||
// It's used for symbol link file because unix.UtimesNano doesn't support a NOFOLLOW flag atm.
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
atFdCwd := unix.AT_FDCWD
|
||||
|
||||
var _path *byte
|
||||
_path, err := unix.BytePtrFromString(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, _, err := unix.Syscall6(unix.SYS_UTIMENSAT, uintptr(atFdCwd), uintptr(unsafe.Pointer(_path)), uintptr(unsafe.Pointer(&ts[0])), unix.AT_SYMLINK_NOFOLLOW, 0, 0); err != 0 && err != unix.ENOSYS {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
10
vendor/github.com/docker/docker/pkg/system/utimes_unsupported.go
generated
vendored
Normal file
10
vendor/github.com/docker/docker/pkg/system/utimes_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// +build !linux,!freebsd
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "syscall"
|
||||
|
||||
// LUtimesNano is only supported on linux and freebsd.
|
||||
func LUtimesNano(path string, ts []syscall.Timespec) error {
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
||||
29
vendor/github.com/docker/docker/pkg/system/xattrs_linux.go
generated
vendored
Normal file
29
vendor/github.com/docker/docker/pkg/system/xattrs_linux.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// Lgetxattr retrieves the value of the extended attribute identified by attr
|
||||
// and associated with the given path in the file system.
|
||||
// It will returns a nil slice and nil error if the xattr is not set.
|
||||
func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
dest := make([]byte, 128)
|
||||
sz, errno := unix.Lgetxattr(path, attr, dest)
|
||||
if errno == unix.ENODATA {
|
||||
return nil, nil
|
||||
}
|
||||
if errno == unix.ERANGE {
|
||||
dest = make([]byte, sz)
|
||||
sz, errno = unix.Lgetxattr(path, attr, dest)
|
||||
}
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
|
||||
return dest[:sz], nil
|
||||
}
|
||||
|
||||
// Lsetxattr sets the value of the extended attribute identified by attr
|
||||
// and associated with the given path in the file system.
|
||||
func Lsetxattr(path string, attr string, data []byte, flags int) error {
|
||||
return unix.Lsetxattr(path, attr, data, flags)
|
||||
}
|
||||
13
vendor/github.com/docker/docker/pkg/system/xattrs_unsupported.go
generated
vendored
Normal file
13
vendor/github.com/docker/docker/pkg/system/xattrs_unsupported.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build !linux
|
||||
|
||||
package system // import "github.com/docker/docker/pkg/system"
|
||||
|
||||
// Lgetxattr is not supported on platforms other than linux.
|
||||
func Lgetxattr(path string, attr string) ([]byte, error) {
|
||||
return nil, ErrNotSupportedPlatform
|
||||
}
|
||||
|
||||
// Lsetxattr is not supported on platforms other than linux.
|
||||
func Lsetxattr(path string, attr string, data []byte, flags int) error {
|
||||
return ErrNotSupportedPlatform
|
||||
}
|
||||
21
vendor/github.com/docker/docker/pkg/tarsum/builder_context.go
generated
vendored
Normal file
21
vendor/github.com/docker/docker/pkg/tarsum/builder_context.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
package tarsum // import "github.com/docker/docker/pkg/tarsum"
|
||||
|
||||
// BuilderContext is an interface extending TarSum by adding the Remove method.
|
||||
// In general there was concern about adding this method to TarSum itself
|
||||
// so instead it is being added just to "BuilderContext" which will then
|
||||
// only be used during the .dockerignore file processing
|
||||
// - see builder/evaluator.go
|
||||
type BuilderContext interface {
|
||||
TarSum
|
||||
Remove(string)
|
||||
}
|
||||
|
||||
func (bc *tarSum) Remove(filename string) {
|
||||
for i, fis := range bc.sums {
|
||||
if fis.Name() == filename {
|
||||
bc.sums = append(bc.sums[:i], bc.sums[i+1:]...)
|
||||
// Note, we don't just return because there could be
|
||||
// more than one with this name
|
||||
}
|
||||
}
|
||||
}
|
||||
133
vendor/github.com/docker/docker/pkg/tarsum/fileinfosums.go
generated
vendored
Normal file
133
vendor/github.com/docker/docker/pkg/tarsum/fileinfosums.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
package tarsum // import "github.com/docker/docker/pkg/tarsum"
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FileInfoSumInterface provides an interface for accessing file checksum
|
||||
// information within a tar file. This info is accessed through interface
|
||||
// so the actual name and sum cannot be melded with.
|
||||
type FileInfoSumInterface interface {
|
||||
// File name
|
||||
Name() string
|
||||
// Checksum of this particular file and its headers
|
||||
Sum() string
|
||||
// Position of file in the tar
|
||||
Pos() int64
|
||||
}
|
||||
|
||||
type fileInfoSum struct {
|
||||
name string
|
||||
sum string
|
||||
pos int64
|
||||
}
|
||||
|
||||
func (fis fileInfoSum) Name() string {
|
||||
return fis.name
|
||||
}
|
||||
func (fis fileInfoSum) Sum() string {
|
||||
return fis.sum
|
||||
}
|
||||
func (fis fileInfoSum) Pos() int64 {
|
||||
return fis.pos
|
||||
}
|
||||
|
||||
// FileInfoSums provides a list of FileInfoSumInterfaces.
|
||||
type FileInfoSums []FileInfoSumInterface
|
||||
|
||||
// GetFile returns the first FileInfoSumInterface with a matching name.
|
||||
func (fis FileInfoSums) GetFile(name string) FileInfoSumInterface {
|
||||
// We do case insensitive matching on Windows as c:\APP and c:\app are
|
||||
// the same. See issue #33107.
|
||||
for i := range fis {
|
||||
if (runtime.GOOS == "windows" && strings.EqualFold(fis[i].Name(), name)) ||
|
||||
(runtime.GOOS != "windows" && fis[i].Name() == name) {
|
||||
return fis[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllFile returns a FileInfoSums with all matching names.
|
||||
func (fis FileInfoSums) GetAllFile(name string) FileInfoSums {
|
||||
f := FileInfoSums{}
|
||||
for i := range fis {
|
||||
if fis[i].Name() == name {
|
||||
f = append(f, fis[i])
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// GetDuplicatePaths returns a FileInfoSums with all duplicated paths.
|
||||
func (fis FileInfoSums) GetDuplicatePaths() (dups FileInfoSums) {
|
||||
seen := make(map[string]int, len(fis)) // allocate earl. no need to grow this map.
|
||||
for i := range fis {
|
||||
f := fis[i]
|
||||
if _, ok := seen[f.Name()]; ok {
|
||||
dups = append(dups, f)
|
||||
} else {
|
||||
seen[f.Name()] = 0
|
||||
}
|
||||
}
|
||||
return dups
|
||||
}
|
||||
|
||||
// Len returns the size of the FileInfoSums.
|
||||
func (fis FileInfoSums) Len() int { return len(fis) }
|
||||
|
||||
// Swap swaps two FileInfoSum values if a FileInfoSums list.
|
||||
func (fis FileInfoSums) Swap(i, j int) { fis[i], fis[j] = fis[j], fis[i] }
|
||||
|
||||
// SortByPos sorts FileInfoSums content by position.
|
||||
func (fis FileInfoSums) SortByPos() {
|
||||
sort.Sort(byPos{fis})
|
||||
}
|
||||
|
||||
// SortByNames sorts FileInfoSums content by name.
|
||||
func (fis FileInfoSums) SortByNames() {
|
||||
sort.Sort(byName{fis})
|
||||
}
|
||||
|
||||
// SortBySums sorts FileInfoSums content by sums.
|
||||
func (fis FileInfoSums) SortBySums() {
|
||||
dups := fis.GetDuplicatePaths()
|
||||
if len(dups) > 0 {
|
||||
sort.Sort(bySum{fis, dups})
|
||||
} else {
|
||||
sort.Sort(bySum{fis, nil})
|
||||
}
|
||||
}
|
||||
|
||||
// byName is a sort.Sort helper for sorting by file names.
|
||||
// If names are the same, order them by their appearance in the tar archive
|
||||
type byName struct{ FileInfoSums }
|
||||
|
||||
func (bn byName) Less(i, j int) bool {
|
||||
if bn.FileInfoSums[i].Name() == bn.FileInfoSums[j].Name() {
|
||||
return bn.FileInfoSums[i].Pos() < bn.FileInfoSums[j].Pos()
|
||||
}
|
||||
return bn.FileInfoSums[i].Name() < bn.FileInfoSums[j].Name()
|
||||
}
|
||||
|
||||
// bySum is a sort.Sort helper for sorting by the sums of all the fileinfos in the tar archive
|
||||
type bySum struct {
|
||||
FileInfoSums
|
||||
dups FileInfoSums
|
||||
}
|
||||
|
||||
func (bs bySum) Less(i, j int) bool {
|
||||
if bs.dups != nil && bs.FileInfoSums[i].Name() == bs.FileInfoSums[j].Name() {
|
||||
return bs.FileInfoSums[i].Pos() < bs.FileInfoSums[j].Pos()
|
||||
}
|
||||
return bs.FileInfoSums[i].Sum() < bs.FileInfoSums[j].Sum()
|
||||
}
|
||||
|
||||
// byPos is a sort.Sort helper for sorting by the sums of all the fileinfos by their original order
|
||||
type byPos struct{ FileInfoSums }
|
||||
|
||||
func (bp byPos) Less(i, j int) bool {
|
||||
return bp.FileInfoSums[i].Pos() < bp.FileInfoSums[j].Pos()
|
||||
}
|
||||
301
vendor/github.com/docker/docker/pkg/tarsum/tarsum.go
generated
vendored
Normal file
301
vendor/github.com/docker/docker/pkg/tarsum/tarsum.go
generated
vendored
Normal file
@@ -0,0 +1,301 @@
|
||||
// Package tarsum provides algorithms to perform checksum calculation on
|
||||
// filesystem layers.
|
||||
//
|
||||
// The transportation of filesystems, regarding Docker, is done with tar(1)
|
||||
// archives. There are a variety of tar serialization formats [2], and a key
|
||||
// concern here is ensuring a repeatable checksum given a set of inputs from a
|
||||
// generic tar archive. Types of transportation include distribution to and from a
|
||||
// registry endpoint, saving and loading through commands or Docker daemon APIs,
|
||||
// transferring the build context from client to Docker daemon, and committing the
|
||||
// filesystem of a container to become an image.
|
||||
//
|
||||
// As tar archives are used for transit, but not preserved in many situations, the
|
||||
// focus of the algorithm is to ensure the integrity of the preserved filesystem,
|
||||
// while maintaining a deterministic accountability. This includes neither
|
||||
// constraining the ordering or manipulation of the files during the creation or
|
||||
// unpacking of the archive, nor include additional metadata state about the file
|
||||
// system attributes.
|
||||
package tarsum // import "github.com/docker/docker/pkg/tarsum"
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
buf8K = 8 * 1024
|
||||
buf16K = 16 * 1024
|
||||
buf32K = 32 * 1024
|
||||
)
|
||||
|
||||
// NewTarSum creates a new interface for calculating a fixed time checksum of a
|
||||
// tar archive.
|
||||
//
|
||||
// This is used for calculating checksums of layers of an image, in some cases
|
||||
// including the byte payload of the image's json metadata as well, and for
|
||||
// calculating the checksums for buildcache.
|
||||
func NewTarSum(r io.Reader, dc bool, v Version) (TarSum, error) {
|
||||
return NewTarSumHash(r, dc, v, DefaultTHash)
|
||||
}
|
||||
|
||||
// NewTarSumHash creates a new TarSum, providing a THash to use rather than
|
||||
// the DefaultTHash.
|
||||
func NewTarSumHash(r io.Reader, dc bool, v Version, tHash THash) (TarSum, error) {
|
||||
headerSelector, err := getTarHeaderSelector(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ts := &tarSum{Reader: r, DisableCompression: dc, tarSumVersion: v, headerSelector: headerSelector, tHash: tHash}
|
||||
err = ts.initTarSum()
|
||||
return ts, err
|
||||
}
|
||||
|
||||
// NewTarSumForLabel creates a new TarSum using the provided TarSum version+hash label.
|
||||
func NewTarSumForLabel(r io.Reader, disableCompression bool, label string) (TarSum, error) {
|
||||
parts := strings.SplitN(label, "+", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, errors.New("tarsum label string should be of the form: {tarsum_version}+{hash_name}")
|
||||
}
|
||||
|
||||
versionName, hashName := parts[0], parts[1]
|
||||
|
||||
version, ok := tarSumVersionsByName[versionName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown TarSum version name: %q", versionName)
|
||||
}
|
||||
|
||||
hashConfig, ok := standardHashConfigs[hashName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown TarSum hash name: %q", hashName)
|
||||
}
|
||||
|
||||
tHash := NewTHash(hashConfig.name, hashConfig.hash.New)
|
||||
|
||||
return NewTarSumHash(r, disableCompression, version, tHash)
|
||||
}
|
||||
|
||||
// TarSum is the generic interface for calculating fixed time
|
||||
// checksums of a tar archive.
|
||||
type TarSum interface {
|
||||
io.Reader
|
||||
GetSums() FileInfoSums
|
||||
Sum([]byte) string
|
||||
Version() Version
|
||||
Hash() THash
|
||||
}
|
||||
|
||||
// tarSum struct is the structure for a Version0 checksum calculation.
|
||||
type tarSum struct {
|
||||
io.Reader
|
||||
tarR *tar.Reader
|
||||
tarW *tar.Writer
|
||||
writer writeCloseFlusher
|
||||
bufTar *bytes.Buffer
|
||||
bufWriter *bytes.Buffer
|
||||
bufData []byte
|
||||
h hash.Hash
|
||||
tHash THash
|
||||
sums FileInfoSums
|
||||
fileCounter int64
|
||||
currentFile string
|
||||
finished bool
|
||||
first bool
|
||||
DisableCompression bool // false by default. When false, the output gzip compressed.
|
||||
tarSumVersion Version // this field is not exported so it can not be mutated during use
|
||||
headerSelector tarHeaderSelector // handles selecting and ordering headers for files in the archive
|
||||
}
|
||||
|
||||
func (ts tarSum) Hash() THash {
|
||||
return ts.tHash
|
||||
}
|
||||
|
||||
func (ts tarSum) Version() Version {
|
||||
return ts.tarSumVersion
|
||||
}
|
||||
|
||||
// THash provides a hash.Hash type generator and its name.
|
||||
type THash interface {
|
||||
Hash() hash.Hash
|
||||
Name() string
|
||||
}
|
||||
|
||||
// NewTHash is a convenience method for creating a THash.
|
||||
func NewTHash(name string, h func() hash.Hash) THash {
|
||||
return simpleTHash{n: name, h: h}
|
||||
}
|
||||
|
||||
type tHashConfig struct {
|
||||
name string
|
||||
hash crypto.Hash
|
||||
}
|
||||
|
||||
var (
|
||||
// NOTE: DO NOT include MD5 or SHA1, which are considered insecure.
|
||||
standardHashConfigs = map[string]tHashConfig{
|
||||
"sha256": {name: "sha256", hash: crypto.SHA256},
|
||||
"sha512": {name: "sha512", hash: crypto.SHA512},
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultTHash is default TarSum hashing algorithm - "sha256".
|
||||
var DefaultTHash = NewTHash("sha256", sha256.New)
|
||||
|
||||
type simpleTHash struct {
|
||||
n string
|
||||
h func() hash.Hash
|
||||
}
|
||||
|
||||
func (sth simpleTHash) Name() string { return sth.n }
|
||||
func (sth simpleTHash) Hash() hash.Hash { return sth.h() }
|
||||
|
||||
func (ts *tarSum) encodeHeader(h *tar.Header) error {
|
||||
for _, elem := range ts.headerSelector.selectHeaders(h) {
|
||||
// Ignore these headers to be compatible with versions
|
||||
// before go 1.10
|
||||
if elem[0] == "gname" || elem[0] == "uname" {
|
||||
elem[1] = ""
|
||||
}
|
||||
if _, err := ts.h.Write([]byte(elem[0] + elem[1])); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *tarSum) initTarSum() error {
|
||||
ts.bufTar = bytes.NewBuffer([]byte{})
|
||||
ts.bufWriter = bytes.NewBuffer([]byte{})
|
||||
ts.tarR = tar.NewReader(ts.Reader)
|
||||
ts.tarW = tar.NewWriter(ts.bufTar)
|
||||
if !ts.DisableCompression {
|
||||
ts.writer = gzip.NewWriter(ts.bufWriter)
|
||||
} else {
|
||||
ts.writer = &nopCloseFlusher{Writer: ts.bufWriter}
|
||||
}
|
||||
if ts.tHash == nil {
|
||||
ts.tHash = DefaultTHash
|
||||
}
|
||||
ts.h = ts.tHash.Hash()
|
||||
ts.h.Reset()
|
||||
ts.first = true
|
||||
ts.sums = FileInfoSums{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *tarSum) Read(buf []byte) (int, error) {
|
||||
if ts.finished {
|
||||
return ts.bufWriter.Read(buf)
|
||||
}
|
||||
if len(ts.bufData) < len(buf) {
|
||||
switch {
|
||||
case len(buf) <= buf8K:
|
||||
ts.bufData = make([]byte, buf8K)
|
||||
case len(buf) <= buf16K:
|
||||
ts.bufData = make([]byte, buf16K)
|
||||
case len(buf) <= buf32K:
|
||||
ts.bufData = make([]byte, buf32K)
|
||||
default:
|
||||
ts.bufData = make([]byte, len(buf))
|
||||
}
|
||||
}
|
||||
buf2 := ts.bufData[:len(buf)]
|
||||
|
||||
n, err := ts.tarR.Read(buf2)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if _, err := ts.h.Write(buf2[:n]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !ts.first {
|
||||
ts.sums = append(ts.sums, fileInfoSum{name: ts.currentFile, sum: hex.EncodeToString(ts.h.Sum(nil)), pos: ts.fileCounter})
|
||||
ts.fileCounter++
|
||||
ts.h.Reset()
|
||||
} else {
|
||||
ts.first = false
|
||||
}
|
||||
|
||||
if _, err := ts.tarW.Write(buf2[:n]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
currentHeader, err := ts.tarR.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
if err := ts.tarW.Close(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := io.Copy(ts.writer, ts.bufTar); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := ts.writer.Close(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ts.finished = true
|
||||
return ts.bufWriter.Read(buf)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ts.currentFile = path.Join(".", path.Join("/", currentHeader.Name))
|
||||
if err := ts.encodeHeader(currentHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := ts.tarW.WriteHeader(currentHeader); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(ts.writer, ts.bufTar); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ts.writer.Flush()
|
||||
|
||||
return ts.bufWriter.Read(buf)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Filling the hash buffer
|
||||
if _, err = ts.h.Write(buf2[:n]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Filling the tar writer
|
||||
if _, err = ts.tarW.Write(buf2[:n]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Filling the output writer
|
||||
if _, err = io.Copy(ts.writer, ts.bufTar); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ts.writer.Flush()
|
||||
|
||||
return ts.bufWriter.Read(buf)
|
||||
}
|
||||
|
||||
func (ts *tarSum) Sum(extra []byte) string {
|
||||
ts.sums.SortBySums()
|
||||
h := ts.tHash.Hash()
|
||||
if extra != nil {
|
||||
h.Write(extra)
|
||||
}
|
||||
for _, fis := range ts.sums {
|
||||
h.Write([]byte(fis.Sum()))
|
||||
}
|
||||
checksum := ts.Version().String() + "+" + ts.tHash.Name() + ":" + hex.EncodeToString(h.Sum(nil))
|
||||
return checksum
|
||||
}
|
||||
|
||||
func (ts *tarSum) GetSums() FileInfoSums {
|
||||
return ts.sums
|
||||
}
|
||||
230
vendor/github.com/docker/docker/pkg/tarsum/tarsum_spec.md
generated
vendored
Normal file
230
vendor/github.com/docker/docker/pkg/tarsum/tarsum_spec.md
generated
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
page_title: TarSum checksum specification
|
||||
page_description: Documentation for algorithms used in the TarSum checksum calculation
|
||||
page_keywords: docker, checksum, validation, tarsum
|
||||
|
||||
# TarSum Checksum Specification
|
||||
|
||||
## Abstract
|
||||
|
||||
This document describes the algorithms used in performing the TarSum checksum
|
||||
calculation on filesystem layers, the need for this method over existing
|
||||
methods, and the versioning of this calculation.
|
||||
|
||||
## Warning
|
||||
|
||||
This checksum algorithm is for best-effort comparison of file trees with fuzzy logic.
|
||||
|
||||
This is _not_ a cryptographic attestation, and should not be considered secure.
|
||||
|
||||
## Introduction
|
||||
|
||||
The transportation of filesystems, regarding Docker, is done with tar(1)
|
||||
archives. There are a variety of tar serialization formats [2], and a key
|
||||
concern here is ensuring a repeatable checksum given a set of inputs from a
|
||||
generic tar archive. Types of transportation include distribution to and from a
|
||||
registry endpoint, saving and loading through commands or Docker daemon APIs,
|
||||
transferring the build context from client to Docker daemon, and committing the
|
||||
filesystem of a container to become an image.
|
||||
|
||||
As tar archives are used for transit, but not preserved in many situations, the
|
||||
focus of the algorithm is to ensure the integrity of the preserved filesystem,
|
||||
while maintaining a deterministic accountability. This includes neither
|
||||
constraining the ordering or manipulation of the files during the creation or
|
||||
unpacking of the archive, nor include additional metadata state about the file
|
||||
system attributes.
|
||||
|
||||
## Intended Audience
|
||||
|
||||
This document is outlining the methods used for consistent checksum calculation
|
||||
for filesystems transported via tar archives.
|
||||
|
||||
Auditing these methodologies is an open and iterative process. This document
|
||||
should accommodate the review of source code. Ultimately, this document should
|
||||
be the starting point of further refinements to the algorithm and its future
|
||||
versions.
|
||||
|
||||
## Concept
|
||||
|
||||
The checksum mechanism must ensure the integrity and assurance of the
|
||||
filesystem payload.
|
||||
|
||||
## Checksum Algorithm Profile
|
||||
|
||||
A checksum mechanism must define the following operations and attributes:
|
||||
|
||||
* Associated hashing cipher - used to checksum each file payload and attribute
|
||||
information.
|
||||
* Checksum list - each file of the filesystem archive has its checksum
|
||||
calculated from the payload and attributes of the file. The final checksum is
|
||||
calculated from this list, with specific ordering.
|
||||
* Version - as the algorithm adapts to requirements, there are behaviors of the
|
||||
algorithm to manage by versioning.
|
||||
* Archive being calculated - the tar archive having its checksum calculated
|
||||
|
||||
## Elements of TarSum checksum
|
||||
|
||||
The calculated sum output is a text string. The elements included in the output
|
||||
of the calculated sum comprise the information needed for validation of the sum
|
||||
(TarSum version and hashing cipher used) and the expected checksum in hexadecimal
|
||||
form.
|
||||
|
||||
There are two delimiters used:
|
||||
* '+' separates TarSum version from hashing cipher
|
||||
* ':' separates calculation mechanics from expected hash
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
"tarsum.v1+sha256:220a60ecd4a3c32c282622a625a54db9ba0ff55b5ba9c29c7064a2bc358b6a3e"
|
||||
| | \ |
|
||||
| | \ |
|
||||
|_version_|_cipher__|__ |
|
||||
| \ |
|
||||
|_calculation_mechanics_|______________________expected_sum_______________________|
|
||||
```
|
||||
|
||||
## Versioning
|
||||
|
||||
Versioning was introduced [0] to accommodate differences in calculation needed,
|
||||
and ability to maintain reverse compatibility.
|
||||
|
||||
The general algorithm will be describe further in the 'Calculation'.
|
||||
|
||||
### Version0
|
||||
|
||||
This is the initial version of TarSum.
|
||||
|
||||
Its element in the TarSum checksum string is `tarsum`.
|
||||
|
||||
### Version1
|
||||
|
||||
Its element in the TarSum checksum is `tarsum.v1`.
|
||||
|
||||
The notable changes in this version:
|
||||
* Exclusion of file `mtime` from the file information headers, in each file
|
||||
checksum calculation
|
||||
* Inclusion of extended attributes (`xattrs`. Also seen as `SCHILY.xattr.` prefixed Pax
|
||||
tar file info headers) keys and values in each file checksum calculation
|
||||
|
||||
### VersionDev
|
||||
|
||||
*Do not use unless validating refinements to the checksum algorithm*
|
||||
|
||||
Its element in the TarSum checksum is `tarsum.dev`.
|
||||
|
||||
This is a floating place holder for a next version and grounds for testing
|
||||
changes. The methods used for calculation are subject to change without notice,
|
||||
and this version is for testing and not for production use.
|
||||
|
||||
## Ciphers
|
||||
|
||||
The official default and standard hashing cipher used in the calculation mechanic
|
||||
is `sha256`. This refers to SHA256 hash algorithm as defined in FIPS 180-4.
|
||||
|
||||
Though the TarSum algorithm itself is not exclusively bound to the single
|
||||
hashing cipher `sha256`, support for alternate hashing ciphers was later added
|
||||
[1]. Use cases for alternate cipher could include future-proofing TarSum
|
||||
checksum format and using faster cipher hashes for tar filesystem checksums.
|
||||
|
||||
## Calculation
|
||||
|
||||
### Requirement
|
||||
|
||||
As mentioned earlier, the calculation is such that it takes into consideration
|
||||
the lifecycle of the tar archive. In that the tar archive is not an immutable,
|
||||
permanent artifact. Otherwise options like relying on a known hashing cipher
|
||||
checksum of the archive itself would be reliable enough. The tar archive of the
|
||||
filesystem is used as a transportation medium for Docker images, and the
|
||||
archive is discarded once its contents are extracted. Therefore, for consistent
|
||||
validation items such as order of files in the tar archive and time stamps are
|
||||
subject to change once an image is received.
|
||||
|
||||
### Process
|
||||
|
||||
The method is typically iterative due to reading tar info headers from the
|
||||
archive stream, though this is not a strict requirement.
|
||||
|
||||
#### Files
|
||||
|
||||
Each file in the tar archive have their contents (headers and body) checksummed
|
||||
individually using the designated associated hashing cipher. The ordered
|
||||
headers of the file are written to the checksum calculation first, and then the
|
||||
payload of the file body.
|
||||
|
||||
The resulting checksum of the file is appended to the list of file sums. The
|
||||
sum is encoded as a string of the hexadecimal digest. Additionally, the file
|
||||
name and position in the archive is kept as reference for special ordering.
|
||||
|
||||
#### Headers
|
||||
|
||||
The following headers are read, in this
|
||||
order ( and the corresponding representation of its value):
|
||||
* 'name' - string
|
||||
* 'mode' - string of the base10 integer
|
||||
* 'uid' - string of the integer
|
||||
* 'gid' - string of the integer
|
||||
* 'size' - string of the integer
|
||||
* 'mtime' (_Version0 only_) - string of integer of the seconds since 1970-01-01 00:00:00 UTC
|
||||
* 'typeflag' - string of the char
|
||||
* 'linkname' - string
|
||||
* 'uname' - string
|
||||
* 'gname' - string
|
||||
* 'devmajor' - string of the integer
|
||||
* 'devminor' - string of the integer
|
||||
|
||||
For >= Version1, the extended attribute headers ("SCHILY.xattr." prefixed pax
|
||||
headers) included after the above list. These xattrs key/values are first
|
||||
sorted by the keys.
|
||||
|
||||
#### Header Format
|
||||
|
||||
The ordered headers are written to the hash in the format of
|
||||
|
||||
"{.key}{.value}"
|
||||
|
||||
with no newline.
|
||||
|
||||
#### Body
|
||||
|
||||
After the order headers of the file have been added to the checksum for the
|
||||
file, the body of the file is written to the hash.
|
||||
|
||||
#### List of file sums
|
||||
|
||||
The list of file sums is sorted by the string of the hexadecimal digest.
|
||||
|
||||
If there are two files in the tar with matching paths, the order of occurrence
|
||||
for that path is reflected for the sums of the corresponding file header and
|
||||
body.
|
||||
|
||||
#### Final Checksum
|
||||
|
||||
Begin with a fresh or initial state of the associated hash cipher. If there is
|
||||
additional payload to include in the TarSum calculation for the archive, it is
|
||||
written first. Then each checksum from the ordered list of file sums is written
|
||||
to the hash.
|
||||
|
||||
The resulting digest is formatted per the Elements of TarSum checksum,
|
||||
including the TarSum version, the associated hash cipher and the hexadecimal
|
||||
encoded checksum digest.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
The initial version of TarSum has undergone one update that could invalidate
|
||||
handcrafted tar archives. The tar archive format supports appending of files
|
||||
with same names as prior files in the archive. The latter file will clobber the
|
||||
prior file of the same path. Due to this the algorithm now accounts for files
|
||||
with matching paths, and orders the list of file sums accordingly [3].
|
||||
|
||||
## Footnotes
|
||||
|
||||
* [0] Versioning https://github.com/docker/docker/commit/747f89cd327db9d50251b17797c4d825162226d0
|
||||
* [1] Alternate ciphers https://github.com/docker/docker/commit/4e9925d780665149b8bc940d5ba242ada1973c4e
|
||||
* [2] Tar http://en.wikipedia.org/wiki/Tar_%28computing%29
|
||||
* [3] Name collision https://github.com/docker/docker/commit/c5e6362c53cbbc09ddbabd5a7323e04438b57d31
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Joffrey F (shin-) and Guillaume J. Charmes (creack) on the initial work of the
|
||||
TarSum calculation.
|
||||
|
||||
158
vendor/github.com/docker/docker/pkg/tarsum/versioning.go
generated
vendored
Normal file
158
vendor/github.com/docker/docker/pkg/tarsum/versioning.go
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
package tarsum // import "github.com/docker/docker/pkg/tarsum"
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Version is used for versioning of the TarSum algorithm
|
||||
// based on the prefix of the hash used
|
||||
// i.e. "tarsum+sha256:e58fcf7418d4390dec8e8fb69d88c06ec07039d651fedd3aa72af9972e7d046b"
|
||||
type Version int
|
||||
|
||||
// Prefix of "tarsum"
|
||||
const (
|
||||
Version0 Version = iota
|
||||
Version1
|
||||
// VersionDev this constant will be either the latest or an unsettled next-version of the TarSum calculation
|
||||
VersionDev
|
||||
)
|
||||
|
||||
// WriteV1Header writes a tar header to a writer in V1 tarsum format.
|
||||
func WriteV1Header(h *tar.Header, w io.Writer) {
|
||||
for _, elem := range v1TarHeaderSelect(h) {
|
||||
w.Write([]byte(elem[0] + elem[1]))
|
||||
}
|
||||
}
|
||||
|
||||
// VersionLabelForChecksum returns the label for the given tarsum
|
||||
// checksum, i.e., everything before the first `+` character in
|
||||
// the string or an empty string if no label separator is found.
|
||||
func VersionLabelForChecksum(checksum string) string {
|
||||
// Checksums are in the form: {versionLabel}+{hashID}:{hex}
|
||||
sepIndex := strings.Index(checksum, "+")
|
||||
if sepIndex < 0 {
|
||||
return ""
|
||||
}
|
||||
return checksum[:sepIndex]
|
||||
}
|
||||
|
||||
// GetVersions gets a list of all known tarsum versions.
|
||||
func GetVersions() []Version {
|
||||
v := []Version{}
|
||||
for k := range tarSumVersions {
|
||||
v = append(v, k)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
var (
|
||||
tarSumVersions = map[Version]string{
|
||||
Version0: "tarsum",
|
||||
Version1: "tarsum.v1",
|
||||
VersionDev: "tarsum.dev",
|
||||
}
|
||||
tarSumVersionsByName = map[string]Version{
|
||||
"tarsum": Version0,
|
||||
"tarsum.v1": Version1,
|
||||
"tarsum.dev": VersionDev,
|
||||
}
|
||||
)
|
||||
|
||||
func (tsv Version) String() string {
|
||||
return tarSumVersions[tsv]
|
||||
}
|
||||
|
||||
// GetVersionFromTarsum returns the Version from the provided string.
|
||||
func GetVersionFromTarsum(tarsum string) (Version, error) {
|
||||
tsv := tarsum
|
||||
if strings.Contains(tarsum, "+") {
|
||||
tsv = strings.SplitN(tarsum, "+", 2)[0]
|
||||
}
|
||||
for v, s := range tarSumVersions {
|
||||
if s == tsv {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return -1, ErrNotVersion
|
||||
}
|
||||
|
||||
// Errors that may be returned by functions in this package
|
||||
var (
|
||||
ErrNotVersion = errors.New("string does not include a TarSum Version")
|
||||
ErrVersionNotImplemented = errors.New("TarSum Version is not yet implemented")
|
||||
)
|
||||
|
||||
// tarHeaderSelector is the interface which different versions
|
||||
// of tarsum should use for selecting and ordering tar headers
|
||||
// for each item in the archive.
|
||||
type tarHeaderSelector interface {
|
||||
selectHeaders(h *tar.Header) (orderedHeaders [][2]string)
|
||||
}
|
||||
|
||||
type tarHeaderSelectFunc func(h *tar.Header) (orderedHeaders [][2]string)
|
||||
|
||||
func (f tarHeaderSelectFunc) selectHeaders(h *tar.Header) (orderedHeaders [][2]string) {
|
||||
return f(h)
|
||||
}
|
||||
|
||||
func v0TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
|
||||
return [][2]string{
|
||||
{"name", h.Name},
|
||||
{"mode", strconv.FormatInt(h.Mode, 10)},
|
||||
{"uid", strconv.Itoa(h.Uid)},
|
||||
{"gid", strconv.Itoa(h.Gid)},
|
||||
{"size", strconv.FormatInt(h.Size, 10)},
|
||||
{"mtime", strconv.FormatInt(h.ModTime.UTC().Unix(), 10)},
|
||||
{"typeflag", string([]byte{h.Typeflag})},
|
||||
{"linkname", h.Linkname},
|
||||
{"uname", h.Uname},
|
||||
{"gname", h.Gname},
|
||||
{"devmajor", strconv.FormatInt(h.Devmajor, 10)},
|
||||
{"devminor", strconv.FormatInt(h.Devminor, 10)},
|
||||
}
|
||||
}
|
||||
|
||||
func v1TarHeaderSelect(h *tar.Header) (orderedHeaders [][2]string) {
|
||||
// Get extended attributes.
|
||||
xAttrKeys := make([]string, len(h.Xattrs))
|
||||
for k := range h.Xattrs {
|
||||
xAttrKeys = append(xAttrKeys, k)
|
||||
}
|
||||
sort.Strings(xAttrKeys)
|
||||
|
||||
// Make the slice with enough capacity to hold the 11 basic headers
|
||||
// we want from the v0 selector plus however many xattrs we have.
|
||||
orderedHeaders = make([][2]string, 0, 11+len(xAttrKeys))
|
||||
|
||||
// Copy all headers from v0 excluding the 'mtime' header (the 5th element).
|
||||
v0headers := v0TarHeaderSelect(h)
|
||||
orderedHeaders = append(orderedHeaders, v0headers[0:5]...)
|
||||
orderedHeaders = append(orderedHeaders, v0headers[6:]...)
|
||||
|
||||
// Finally, append the sorted xattrs.
|
||||
for _, k := range xAttrKeys {
|
||||
orderedHeaders = append(orderedHeaders, [2]string{k, h.Xattrs[k]})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var registeredHeaderSelectors = map[Version]tarHeaderSelectFunc{
|
||||
Version0: v0TarHeaderSelect,
|
||||
Version1: v1TarHeaderSelect,
|
||||
VersionDev: v1TarHeaderSelect,
|
||||
}
|
||||
|
||||
func getTarHeaderSelector(v Version) (tarHeaderSelector, error) {
|
||||
headerSelector, ok := registeredHeaderSelectors[v]
|
||||
if !ok {
|
||||
return nil, ErrVersionNotImplemented
|
||||
}
|
||||
|
||||
return headerSelector, nil
|
||||
}
|
||||
22
vendor/github.com/docker/docker/pkg/tarsum/writercloser.go
generated
vendored
Normal file
22
vendor/github.com/docker/docker/pkg/tarsum/writercloser.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package tarsum // import "github.com/docker/docker/pkg/tarsum"
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type writeCloseFlusher interface {
|
||||
io.WriteCloser
|
||||
Flush() error
|
||||
}
|
||||
|
||||
type nopCloseFlusher struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func (n *nopCloseFlusher) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *nopCloseFlusher) Flush() error {
|
||||
return nil
|
||||
}
|
||||
66
vendor/github.com/docker/docker/pkg/term/ascii.go
generated
vendored
Normal file
66
vendor/github.com/docker/docker/pkg/term/ascii.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package term // import "github.com/docker/docker/pkg/term"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ASCII list the possible supported ASCII key sequence
|
||||
var ASCII = []string{
|
||||
"ctrl-@",
|
||||
"ctrl-a",
|
||||
"ctrl-b",
|
||||
"ctrl-c",
|
||||
"ctrl-d",
|
||||
"ctrl-e",
|
||||
"ctrl-f",
|
||||
"ctrl-g",
|
||||
"ctrl-h",
|
||||
"ctrl-i",
|
||||
"ctrl-j",
|
||||
"ctrl-k",
|
||||
"ctrl-l",
|
||||
"ctrl-m",
|
||||
"ctrl-n",
|
||||
"ctrl-o",
|
||||
"ctrl-p",
|
||||
"ctrl-q",
|
||||
"ctrl-r",
|
||||
"ctrl-s",
|
||||
"ctrl-t",
|
||||
"ctrl-u",
|
||||
"ctrl-v",
|
||||
"ctrl-w",
|
||||
"ctrl-x",
|
||||
"ctrl-y",
|
||||
"ctrl-z",
|
||||
"ctrl-[",
|
||||
"ctrl-\\",
|
||||
"ctrl-]",
|
||||
"ctrl-^",
|
||||
"ctrl-_",
|
||||
}
|
||||
|
||||
// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code.
|
||||
func ToBytes(keys string) ([]byte, error) {
|
||||
codes := []byte{}
|
||||
next:
|
||||
for _, key := range strings.Split(keys, ",") {
|
||||
if len(key) != 1 {
|
||||
for code, ctrl := range ASCII {
|
||||
if ctrl == key {
|
||||
codes = append(codes, byte(code))
|
||||
continue next
|
||||
}
|
||||
}
|
||||
if key == "DEL" {
|
||||
codes = append(codes, 127)
|
||||
} else {
|
||||
return nil, fmt.Errorf("Unknown character: '%s'", key)
|
||||
}
|
||||
} else {
|
||||
codes = append(codes, key[0])
|
||||
}
|
||||
}
|
||||
return codes, nil
|
||||
}
|
||||
78
vendor/github.com/docker/docker/pkg/term/proxy.go
generated
vendored
Normal file
78
vendor/github.com/docker/docker/pkg/term/proxy.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package term // import "github.com/docker/docker/pkg/term"
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// EscapeError is special error which returned by a TTY proxy reader's Read()
|
||||
// method in case its detach escape sequence is read.
|
||||
type EscapeError struct{}
|
||||
|
||||
func (EscapeError) Error() string {
|
||||
return "read escape sequence"
|
||||
}
|
||||
|
||||
// escapeProxy is used only for attaches with a TTY. It is used to proxy
|
||||
// stdin keypresses from the underlying reader and look for the passed in
|
||||
// escape key sequence to signal a detach.
|
||||
type escapeProxy struct {
|
||||
escapeKeys []byte
|
||||
escapeKeyPos int
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader
|
||||
// and detects when the specified escape keys are read, in which case the Read
|
||||
// method will return an error of type EscapeError.
|
||||
func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader {
|
||||
return &escapeProxy{
|
||||
escapeKeys: escapeKeys,
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *escapeProxy) Read(buf []byte) (int, error) {
|
||||
nr, err := r.r.Read(buf)
|
||||
|
||||
if len(r.escapeKeys) == 0 {
|
||||
return nr, err
|
||||
}
|
||||
|
||||
preserve := func() {
|
||||
// this preserves the original key presses in the passed in buffer
|
||||
nr += r.escapeKeyPos
|
||||
preserve := make([]byte, 0, r.escapeKeyPos+len(buf))
|
||||
preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...)
|
||||
preserve = append(preserve, buf...)
|
||||
r.escapeKeyPos = 0
|
||||
copy(buf[0:nr], preserve)
|
||||
}
|
||||
|
||||
if nr != 1 || err != nil {
|
||||
if r.escapeKeyPos > 0 {
|
||||
preserve()
|
||||
}
|
||||
return nr, err
|
||||
}
|
||||
|
||||
if buf[0] != r.escapeKeys[r.escapeKeyPos] {
|
||||
if r.escapeKeyPos > 0 {
|
||||
preserve()
|
||||
}
|
||||
return nr, nil
|
||||
}
|
||||
|
||||
if r.escapeKeyPos == len(r.escapeKeys)-1 {
|
||||
return 0, EscapeError{}
|
||||
}
|
||||
|
||||
// Looks like we've got an escape key, but we need to match again on the next
|
||||
// read.
|
||||
// Store the current escape key we found so we can look for the next one on
|
||||
// the next read.
|
||||
// Since this is an escape key, make sure we don't let the caller read it
|
||||
// If later on we find that this is not the escape sequence, we'll add the
|
||||
// keys back
|
||||
r.escapeKeyPos++
|
||||
return nr - r.escapeKeyPos, nil
|
||||
}
|
||||
20
vendor/github.com/docker/docker/pkg/term/tc.go
generated
vendored
Normal file
20
vendor/github.com/docker/docker/pkg/term/tc.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// +build !windows
|
||||
|
||||
package term // import "github.com/docker/docker/pkg/term"
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func tcget(fd uintptr, p *Termios) syscall.Errno {
|
||||
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(p)))
|
||||
return err
|
||||
}
|
||||
|
||||
func tcset(fd uintptr, p *Termios) syscall.Errno {
|
||||
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(p)))
|
||||
return err
|
||||
}
|
||||
124
vendor/github.com/docker/docker/pkg/term/term.go
generated
vendored
Normal file
124
vendor/github.com/docker/docker/pkg/term/term.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
// +build !windows
|
||||
|
||||
// Package term provides structures and helper functions to work with
|
||||
// terminal (state, sizes).
|
||||
package term // import "github.com/docker/docker/pkg/term"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidState is returned if the state of the terminal is invalid.
|
||||
ErrInvalidState = errors.New("Invalid terminal state")
|
||||
)
|
||||
|
||||
// State represents the state of the terminal.
|
||||
type State struct {
|
||||
termios Termios
|
||||
}
|
||||
|
||||
// Winsize represents the size of the terminal window.
|
||||
type Winsize struct {
|
||||
Height uint16
|
||||
Width uint16
|
||||
x uint16
|
||||
y uint16
|
||||
}
|
||||
|
||||
// StdStreams returns the standard streams (stdin, stdout, stderr).
|
||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
return os.Stdin, os.Stdout, os.Stderr
|
||||
}
|
||||
|
||||
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
|
||||
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||
var inFd uintptr
|
||||
var isTerminalIn bool
|
||||
if file, ok := in.(*os.File); ok {
|
||||
inFd = file.Fd()
|
||||
isTerminalIn = IsTerminal(inFd)
|
||||
}
|
||||
return inFd, isTerminalIn
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var termios Termios
|
||||
return tcget(fd, &termios) == 0
|
||||
}
|
||||
|
||||
// RestoreTerminal restores the terminal connected to the given file descriptor
|
||||
// to a previous state.
|
||||
func RestoreTerminal(fd uintptr, state *State) error {
|
||||
if state == nil {
|
||||
return ErrInvalidState
|
||||
}
|
||||
if err := tcset(fd, &state.termios); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveState saves the state of the terminal connected to the given file descriptor.
|
||||
func SaveState(fd uintptr) (*State, error) {
|
||||
var oldState State
|
||||
if err := tcget(fd, &oldState.termios); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
// DisableEcho applies the specified state to the terminal connected to the file
|
||||
// descriptor, with echo disabled.
|
||||
func DisableEcho(fd uintptr, state *State) error {
|
||||
newState := state.termios
|
||||
newState.Lflag &^= unix.ECHO
|
||||
|
||||
if err := tcset(fd, &newState); err != 0 {
|
||||
return err
|
||||
}
|
||||
handleInterrupt(fd, state)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRawTerminal puts the terminal connected to the given file descriptor into
|
||||
// raw mode and returns the previous state. On UNIX, this puts both the input
|
||||
// and output into raw mode. On Windows, it only puts the input into raw mode.
|
||||
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||
oldState, err := MakeRaw(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
handleInterrupt(fd, oldState)
|
||||
return oldState, err
|
||||
}
|
||||
|
||||
// SetRawTerminalOutput puts the output of terminal connected to the given file
|
||||
// descriptor into raw mode. On UNIX, this does nothing and returns nil for the
|
||||
// state. On Windows, it disables LF -> CRLF translation.
|
||||
func SetRawTerminalOutput(fd uintptr) (*State, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func handleInterrupt(fd uintptr, state *State) {
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigchan, os.Interrupt)
|
||||
go func() {
|
||||
for range sigchan {
|
||||
// quit cleanly and the new terminal item is on a new line
|
||||
fmt.Println()
|
||||
signal.Stop(sigchan)
|
||||
close(sigchan)
|
||||
RestoreTerminal(fd, state)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
}
|
||||
221
vendor/github.com/docker/docker/pkg/term/term_windows.go
generated
vendored
Normal file
221
vendor/github.com/docker/docker/pkg/term/term_windows.go
generated
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
package term // import "github.com/docker/docker/pkg/term"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall" // used for STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and STD_ERROR_HANDLE
|
||||
|
||||
"github.com/Azure/go-ansiterm/winterm"
|
||||
"github.com/docker/docker/pkg/term/windows"
|
||||
)
|
||||
|
||||
// State holds the console mode for the terminal.
|
||||
type State struct {
|
||||
mode uint32
|
||||
}
|
||||
|
||||
// Winsize is used for window size.
|
||||
type Winsize struct {
|
||||
Height uint16
|
||||
Width uint16
|
||||
}
|
||||
|
||||
// vtInputSupported is true if winterm.ENABLE_VIRTUAL_TERMINAL_INPUT is supported by the console
|
||||
var vtInputSupported bool
|
||||
|
||||
// StdStreams returns the standard streams (stdin, stdout, stderr).
|
||||
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
|
||||
// Turn on VT handling on all std handles, if possible. This might
|
||||
// fail, in which case we will fall back to terminal emulation.
|
||||
var emulateStdin, emulateStdout, emulateStderr bool
|
||||
fd := os.Stdin.Fd()
|
||||
if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
||||
// Validate that winterm.ENABLE_VIRTUAL_TERMINAL_INPUT is supported, but do not set it.
|
||||
if err = winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_INPUT); err != nil {
|
||||
emulateStdin = true
|
||||
} else {
|
||||
vtInputSupported = true
|
||||
}
|
||||
// Unconditionally set the console mode back even on failure because SetConsoleMode
|
||||
// remembers invalid bits on input handles.
|
||||
winterm.SetConsoleMode(fd, mode)
|
||||
}
|
||||
|
||||
fd = os.Stdout.Fd()
|
||||
if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
||||
// Validate winterm.DISABLE_NEWLINE_AUTO_RETURN is supported, but do not set it.
|
||||
if err = winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING|winterm.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
|
||||
emulateStdout = true
|
||||
} else {
|
||||
winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
fd = os.Stderr.Fd()
|
||||
if mode, err := winterm.GetConsoleMode(fd); err == nil {
|
||||
// Validate winterm.DISABLE_NEWLINE_AUTO_RETURN is supported, but do not set it.
|
||||
if err = winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING|winterm.DISABLE_NEWLINE_AUTO_RETURN); err != nil {
|
||||
emulateStderr = true
|
||||
} else {
|
||||
winterm.SetConsoleMode(fd, mode|winterm.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
}
|
||||
}
|
||||
|
||||
// Temporarily use STD_INPUT_HANDLE, STD_OUTPUT_HANDLE and
|
||||
// STD_ERROR_HANDLE from syscall rather than x/sys/windows as long as
|
||||
// go-ansiterm hasn't switch to x/sys/windows.
|
||||
// TODO: switch back to x/sys/windows once go-ansiterm has switched
|
||||
if emulateStdin {
|
||||
stdIn = windowsconsole.NewAnsiReader(syscall.STD_INPUT_HANDLE)
|
||||
} else {
|
||||
stdIn = os.Stdin
|
||||
}
|
||||
|
||||
if emulateStdout {
|
||||
stdOut = windowsconsole.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE)
|
||||
} else {
|
||||
stdOut = os.Stdout
|
||||
}
|
||||
|
||||
if emulateStderr {
|
||||
stdErr = windowsconsole.NewAnsiWriter(syscall.STD_ERROR_HANDLE)
|
||||
} else {
|
||||
stdErr = os.Stderr
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
|
||||
func GetFdInfo(in interface{}) (uintptr, bool) {
|
||||
return windowsconsole.GetHandleInfo(in)
|
||||
}
|
||||
|
||||
// GetWinsize returns the window size based on the specified file descriptor.
|
||||
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||
info, err := winterm.GetConsoleScreenBufferInfo(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
winsize := &Winsize{
|
||||
Width: uint16(info.Window.Right - info.Window.Left + 1),
|
||||
Height: uint16(info.Window.Bottom - info.Window.Top + 1),
|
||||
}
|
||||
|
||||
return winsize, nil
|
||||
}
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
return windowsconsole.IsConsole(fd)
|
||||
}
|
||||
|
||||
// RestoreTerminal restores the terminal connected to the given file descriptor
|
||||
// to a previous state.
|
||||
func RestoreTerminal(fd uintptr, state *State) error {
|
||||
return winterm.SetConsoleMode(fd, state.mode)
|
||||
}
|
||||
|
||||
// SaveState saves the state of the terminal connected to the given file descriptor.
|
||||
func SaveState(fd uintptr) (*State, error) {
|
||||
mode, e := winterm.GetConsoleMode(fd)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return &State{mode: mode}, nil
|
||||
}
|
||||
|
||||
// DisableEcho disables echo for the terminal connected to the given file descriptor.
|
||||
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
|
||||
func DisableEcho(fd uintptr, state *State) error {
|
||||
mode := state.mode
|
||||
mode &^= winterm.ENABLE_ECHO_INPUT
|
||||
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
|
||||
err := winterm.SetConsoleMode(fd, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Register an interrupt handler to catch and restore prior state
|
||||
restoreAtInterrupt(fd, state)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRawTerminal puts the terminal connected to the given file descriptor into
|
||||
// raw mode and returns the previous state. On UNIX, this puts both the input
|
||||
// and output into raw mode. On Windows, it only puts the input into raw mode.
|
||||
func SetRawTerminal(fd uintptr) (*State, error) {
|
||||
state, err := MakeRaw(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Register an interrupt handler to catch and restore prior state
|
||||
restoreAtInterrupt(fd, state)
|
||||
return state, err
|
||||
}
|
||||
|
||||
// SetRawTerminalOutput puts the output of terminal connected to the given file
|
||||
// descriptor into raw mode. On UNIX, this does nothing and returns nil for the
|
||||
// state. On Windows, it disables LF -> CRLF translation.
|
||||
func SetRawTerminalOutput(fd uintptr) (*State, error) {
|
||||
state, err := SaveState(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ignore failures, since winterm.DISABLE_NEWLINE_AUTO_RETURN might not be supported on this
|
||||
// version of Windows.
|
||||
winterm.SetConsoleMode(fd, state.mode|winterm.DISABLE_NEWLINE_AUTO_RETURN)
|
||||
return state, err
|
||||
}
|
||||
|
||||
// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be restored.
|
||||
func MakeRaw(fd uintptr) (*State, error) {
|
||||
state, err := SaveState(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mode := state.mode
|
||||
|
||||
// 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 &^= winterm.ENABLE_ECHO_INPUT
|
||||
mode &^= winterm.ENABLE_LINE_INPUT
|
||||
mode &^= winterm.ENABLE_MOUSE_INPUT
|
||||
mode &^= winterm.ENABLE_WINDOW_INPUT
|
||||
mode &^= winterm.ENABLE_PROCESSED_INPUT
|
||||
|
||||
// Enable these modes
|
||||
mode |= winterm.ENABLE_EXTENDED_FLAGS
|
||||
mode |= winterm.ENABLE_INSERT_MODE
|
||||
mode |= winterm.ENABLE_QUICK_EDIT_MODE
|
||||
if vtInputSupported {
|
||||
mode |= winterm.ENABLE_VIRTUAL_TERMINAL_INPUT
|
||||
}
|
||||
|
||||
err = winterm.SetConsoleMode(fd, mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func restoreAtInterrupt(fd uintptr, state *State) {
|
||||
sigchan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigchan, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
_ = <-sigchan
|
||||
RestoreTerminal(fd, state)
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
42
vendor/github.com/docker/docker/pkg/term/termios_bsd.go
generated
vendored
Normal file
42
vendor/github.com/docker/docker/pkg/term/termios_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// +build darwin freebsd openbsd netbsd
|
||||
|
||||
package term // import "github.com/docker/docker/pkg/term"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
getTermios = unix.TIOCGETA
|
||||
setTermios = unix.TIOCSETA
|
||||
)
|
||||
|
||||
// Termios is the Unix API for terminal I/O.
|
||||
type Termios unix.Termios
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd uintptr) (*State, error) {
|
||||
var oldState State
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := oldState.termios
|
||||
newState.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON)
|
||||
newState.Oflag &^= unix.OPOST
|
||||
newState.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN)
|
||||
newState.Cflag &^= (unix.CSIZE | unix.PARENB)
|
||||
newState.Cflag |= unix.CS8
|
||||
newState.Cc[unix.VMIN] = 1
|
||||
newState.Cc[unix.VTIME] = 0
|
||||
|
||||
if _, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
39
vendor/github.com/docker/docker/pkg/term/termios_linux.go
generated
vendored
Normal file
39
vendor/github.com/docker/docker/pkg/term/termios_linux.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
package term // import "github.com/docker/docker/pkg/term"
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
getTermios = unix.TCGETS
|
||||
setTermios = unix.TCSETS
|
||||
)
|
||||
|
||||
// Termios is the Unix API for terminal I/O.
|
||||
type Termios unix.Termios
|
||||
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
||||
// mode and returns the previous state of the terminal so that it can be
|
||||
// restored.
|
||||
func MakeRaw(fd uintptr) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(int(fd), getTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var oldState State
|
||||
oldState.termios = Termios(*termios)
|
||||
|
||||
termios.Iflag &^= (unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON)
|
||||
termios.Oflag &^= unix.OPOST
|
||||
termios.Lflag &^= (unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN)
|
||||
termios.Cflag &^= (unix.CSIZE | unix.PARENB)
|
||||
termios.Cflag |= unix.CS8
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
|
||||
if err := unix.IoctlSetTermios(int(fd), setTermios, termios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &oldState, nil
|
||||
}
|
||||
263
vendor/github.com/docker/docker/pkg/term/windows/ansi_reader.go
generated
vendored
Normal file
263
vendor/github.com/docker/docker/pkg/term/windows/ansi_reader.go
generated
vendored
Normal file
@@ -0,0 +1,263 @@
|
||||
// +build windows
|
||||
|
||||
package windowsconsole // import "github.com/docker/docker/pkg/term/windows"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
ansiterm "github.com/Azure/go-ansiterm"
|
||||
"github.com/Azure/go-ansiterm/winterm"
|
||||
)
|
||||
|
||||
const (
|
||||
escapeSequence = ansiterm.KEY_ESC_CSI
|
||||
)
|
||||
|
||||
// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation.
|
||||
type ansiReader struct {
|
||||
file *os.File
|
||||
fd uintptr
|
||||
buffer []byte
|
||||
cbBuffer int
|
||||
command []byte
|
||||
}
|
||||
|
||||
// NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a
|
||||
// Windows console input handle.
|
||||
func NewAnsiReader(nFile int) io.ReadCloser {
|
||||
initLogger()
|
||||
file, fd := winterm.GetStdFile(nFile)
|
||||
return &ansiReader{
|
||||
file: file,
|
||||
fd: fd,
|
||||
command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH),
|
||||
buffer: make([]byte, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the wrapped file.
|
||||
func (ar *ansiReader) Close() (err error) {
|
||||
return ar.file.Close()
|
||||
}
|
||||
|
||||
// Fd returns the file descriptor of the wrapped file.
|
||||
func (ar *ansiReader) Fd() uintptr {
|
||||
return ar.fd
|
||||
}
|
||||
|
||||
// Read reads up to len(p) bytes of translated input events into p.
|
||||
func (ar *ansiReader) Read(p []byte) (int, error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Previously read bytes exist, read as much as we can and return
|
||||
if len(ar.buffer) > 0 {
|
||||
logger.Debugf("Reading previously cached bytes")
|
||||
|
||||
originalLength := len(ar.buffer)
|
||||
copiedLength := copy(p, ar.buffer)
|
||||
|
||||
if copiedLength == originalLength {
|
||||
ar.buffer = make([]byte, 0, len(p))
|
||||
} else {
|
||||
ar.buffer = ar.buffer[copiedLength:]
|
||||
}
|
||||
|
||||
logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
|
||||
return copiedLength, nil
|
||||
}
|
||||
|
||||
// Read and translate key events
|
||||
events, err := readInputEvents(ar.fd, len(p))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if len(events) == 0 {
|
||||
logger.Debug("No input events detected")
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
keyBytes := translateKeyEvents(events, []byte(escapeSequence))
|
||||
|
||||
// Save excess bytes and right-size keyBytes
|
||||
if len(keyBytes) > len(p) {
|
||||
logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
|
||||
ar.buffer = keyBytes[len(p):]
|
||||
keyBytes = keyBytes[:len(p)]
|
||||
} else if len(keyBytes) == 0 {
|
||||
logger.Debug("No key bytes returned from the translator")
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
copiedLength := copy(p, keyBytes)
|
||||
if copiedLength != len(keyBytes) {
|
||||
return 0, errors.New("unexpected copy length encountered")
|
||||
}
|
||||
|
||||
logger.Debugf("Read p[%d]: % x", copiedLength, p)
|
||||
logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
|
||||
return copiedLength, nil
|
||||
}
|
||||
|
||||
// readInputEvents polls until at least one event is available.
|
||||
func readInputEvents(fd uintptr, maxBytes int) ([]winterm.INPUT_RECORD, error) {
|
||||
// Determine the maximum number of records to retrieve
|
||||
// -- Cast around the type system to obtain the size of a single INPUT_RECORD.
|
||||
// unsafe.Sizeof requires an expression vs. a type-reference; the casting
|
||||
// tricks the type system into believing it has such an expression.
|
||||
recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes)))))
|
||||
countRecords := maxBytes / recordSize
|
||||
if countRecords > ansiterm.MAX_INPUT_EVENTS {
|
||||
countRecords = ansiterm.MAX_INPUT_EVENTS
|
||||
} else if countRecords == 0 {
|
||||
countRecords = 1
|
||||
}
|
||||
logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)
|
||||
|
||||
// Wait for and read input events
|
||||
events := make([]winterm.INPUT_RECORD, countRecords)
|
||||
nEvents := uint32(0)
|
||||
eventsExist, err := winterm.WaitForSingleObject(fd, winterm.WAIT_INFINITE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if eventsExist {
|
||||
err = winterm.ReadConsoleInput(fd, events, &nEvents)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Return a slice restricted to the number of returned records
|
||||
logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
|
||||
return events[:nEvents], nil
|
||||
}
|
||||
|
||||
// KeyEvent Translation Helpers
|
||||
|
||||
var arrowKeyMapPrefix = map[uint16]string{
|
||||
winterm.VK_UP: "%s%sA",
|
||||
winterm.VK_DOWN: "%s%sB",
|
||||
winterm.VK_RIGHT: "%s%sC",
|
||||
winterm.VK_LEFT: "%s%sD",
|
||||
}
|
||||
|
||||
var keyMapPrefix = map[uint16]string{
|
||||
winterm.VK_UP: "\x1B[%sA",
|
||||
winterm.VK_DOWN: "\x1B[%sB",
|
||||
winterm.VK_RIGHT: "\x1B[%sC",
|
||||
winterm.VK_LEFT: "\x1B[%sD",
|
||||
winterm.VK_HOME: "\x1B[1%s~", // showkey shows ^[[1
|
||||
winterm.VK_END: "\x1B[4%s~", // showkey shows ^[[4
|
||||
winterm.VK_INSERT: "\x1B[2%s~",
|
||||
winterm.VK_DELETE: "\x1B[3%s~",
|
||||
winterm.VK_PRIOR: "\x1B[5%s~",
|
||||
winterm.VK_NEXT: "\x1B[6%s~",
|
||||
winterm.VK_F1: "",
|
||||
winterm.VK_F2: "",
|
||||
winterm.VK_F3: "\x1B[13%s~",
|
||||
winterm.VK_F4: "\x1B[14%s~",
|
||||
winterm.VK_F5: "\x1B[15%s~",
|
||||
winterm.VK_F6: "\x1B[17%s~",
|
||||
winterm.VK_F7: "\x1B[18%s~",
|
||||
winterm.VK_F8: "\x1B[19%s~",
|
||||
winterm.VK_F9: "\x1B[20%s~",
|
||||
winterm.VK_F10: "\x1B[21%s~",
|
||||
winterm.VK_F11: "\x1B[23%s~",
|
||||
winterm.VK_F12: "\x1B[24%s~",
|
||||
}
|
||||
|
||||
// translateKeyEvents converts the input events into the appropriate ANSI string.
|
||||
func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte {
|
||||
var buffer bytes.Buffer
|
||||
for _, event := range events {
|
||||
if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 {
|
||||
buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence))
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.Bytes()
|
||||
}
|
||||
|
||||
// keyToString maps the given input event record to the corresponding string.
|
||||
func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) string {
|
||||
if keyEvent.UnicodeChar == 0 {
|
||||
return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
|
||||
}
|
||||
|
||||
_, alt, control := getControlKeys(keyEvent.ControlKeyState)
|
||||
if control {
|
||||
// TODO(azlinux): Implement following control sequences
|
||||
// <Ctrl>-D Signals the end of input from the keyboard; also exits current shell.
|
||||
// <Ctrl>-H Deletes the first character to the left of the cursor. Also called the ERASE key.
|
||||
// <Ctrl>-Q Restarts printing after it has been stopped with <Ctrl>-s.
|
||||
// <Ctrl>-S Suspends printing on the screen (does not stop the program).
|
||||
// <Ctrl>-U Deletes all characters on the current line. Also called the KILL key.
|
||||
// <Ctrl>-E Quits current command and creates a core
|
||||
|
||||
}
|
||||
|
||||
// <Alt>+Key generates ESC N Key
|
||||
if !control && alt {
|
||||
return ansiterm.KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
|
||||
}
|
||||
|
||||
return string(keyEvent.UnicodeChar)
|
||||
}
|
||||
|
||||
// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string.
|
||||
func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) string {
|
||||
shift, alt, control := getControlKeys(controlState)
|
||||
modifier := getControlKeysModifier(shift, alt, control)
|
||||
|
||||
if format, ok := arrowKeyMapPrefix[key]; ok {
|
||||
return fmt.Sprintf(format, escapeSequence, modifier)
|
||||
}
|
||||
|
||||
if format, ok := keyMapPrefix[key]; ok {
|
||||
return fmt.Sprintf(format, modifier)
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// getControlKeys extracts the shift, alt, and ctrl key states.
|
||||
func getControlKeys(controlState uint32) (shift, alt, control bool) {
|
||||
shift = 0 != (controlState & winterm.SHIFT_PRESSED)
|
||||
alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED))
|
||||
control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED))
|
||||
return shift, alt, control
|
||||
}
|
||||
|
||||
// getControlKeysModifier returns the ANSI modifier for the given combination of control keys.
|
||||
func getControlKeysModifier(shift, alt, control bool) string {
|
||||
if shift && alt && control {
|
||||
return ansiterm.KEY_CONTROL_PARAM_8
|
||||
}
|
||||
if alt && control {
|
||||
return ansiterm.KEY_CONTROL_PARAM_7
|
||||
}
|
||||
if shift && control {
|
||||
return ansiterm.KEY_CONTROL_PARAM_6
|
||||
}
|
||||
if control {
|
||||
return ansiterm.KEY_CONTROL_PARAM_5
|
||||
}
|
||||
if shift && alt {
|
||||
return ansiterm.KEY_CONTROL_PARAM_4
|
||||
}
|
||||
if alt {
|
||||
return ansiterm.KEY_CONTROL_PARAM_3
|
||||
}
|
||||
if shift {
|
||||
return ansiterm.KEY_CONTROL_PARAM_2
|
||||
}
|
||||
return ""
|
||||
}
|
||||
64
vendor/github.com/docker/docker/pkg/term/windows/ansi_writer.go
generated
vendored
Normal file
64
vendor/github.com/docker/docker/pkg/term/windows/ansi_writer.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
// +build windows
|
||||
|
||||
package windowsconsole // import "github.com/docker/docker/pkg/term/windows"
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
ansiterm "github.com/Azure/go-ansiterm"
|
||||
"github.com/Azure/go-ansiterm/winterm"
|
||||
)
|
||||
|
||||
// ansiWriter wraps a standard output file (e.g., os.Stdout) providing ANSI sequence translation.
|
||||
type ansiWriter struct {
|
||||
file *os.File
|
||||
fd uintptr
|
||||
infoReset *winterm.CONSOLE_SCREEN_BUFFER_INFO
|
||||
command []byte
|
||||
escapeSequence []byte
|
||||
inAnsiSequence bool
|
||||
parser *ansiterm.AnsiParser
|
||||
}
|
||||
|
||||
// NewAnsiWriter returns an io.Writer that provides VT100 terminal emulation on top of a
|
||||
// Windows console output handle.
|
||||
func NewAnsiWriter(nFile int) io.Writer {
|
||||
initLogger()
|
||||
file, fd := winterm.GetStdFile(nFile)
|
||||
info, err := winterm.GetConsoleScreenBufferInfo(fd)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
parser := ansiterm.CreateParser("Ground", winterm.CreateWinEventHandler(fd, file))
|
||||
logger.Infof("newAnsiWriter: parser %p", parser)
|
||||
|
||||
aw := &ansiWriter{
|
||||
file: file,
|
||||
fd: fd,
|
||||
infoReset: info,
|
||||
command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH),
|
||||
escapeSequence: []byte(ansiterm.KEY_ESC_CSI),
|
||||
parser: parser,
|
||||
}
|
||||
|
||||
logger.Infof("newAnsiWriter: aw.parser %p", aw.parser)
|
||||
logger.Infof("newAnsiWriter: %v", aw)
|
||||
return aw
|
||||
}
|
||||
|
||||
func (aw *ansiWriter) Fd() uintptr {
|
||||
return aw.fd
|
||||
}
|
||||
|
||||
// Write writes len(p) bytes from p to the underlying data stream.
|
||||
func (aw *ansiWriter) Write(p []byte) (total int, err error) {
|
||||
if len(p) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
logger.Infof("Write: % x", p)
|
||||
logger.Infof("Write: %s", string(p))
|
||||
return aw.parser.Parse(p)
|
||||
}
|
||||
35
vendor/github.com/docker/docker/pkg/term/windows/console.go
generated
vendored
Normal file
35
vendor/github.com/docker/docker/pkg/term/windows/console.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// +build windows
|
||||
|
||||
package windowsconsole // import "github.com/docker/docker/pkg/term/windows"
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Azure/go-ansiterm/winterm"
|
||||
)
|
||||
|
||||
// GetHandleInfo returns file descriptor and bool indicating whether the file is a console.
|
||||
func GetHandleInfo(in interface{}) (uintptr, bool) {
|
||||
switch t := in.(type) {
|
||||
case *ansiReader:
|
||||
return t.Fd(), true
|
||||
case *ansiWriter:
|
||||
return t.Fd(), true
|
||||
}
|
||||
|
||||
var inFd uintptr
|
||||
var isTerminal bool
|
||||
|
||||
if file, ok := in.(*os.File); ok {
|
||||
inFd = file.Fd()
|
||||
isTerminal = IsConsole(inFd)
|
||||
}
|
||||
return inFd, isTerminal
|
||||
}
|
||||
|
||||
// IsConsole returns true if the given file descriptor is a Windows Console.
|
||||
// The code assumes that GetConsoleMode will return an error for file descriptors that are not a console.
|
||||
func IsConsole(fd uintptr) bool {
|
||||
_, e := winterm.GetConsoleMode(fd)
|
||||
return e == nil
|
||||
}
|
||||
33
vendor/github.com/docker/docker/pkg/term/windows/windows.go
generated
vendored
Normal file
33
vendor/github.com/docker/docker/pkg/term/windows/windows.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// These files implement ANSI-aware input and output streams for use by the Docker Windows client.
|
||||
// When asked for the set of standard streams (e.g., stdin, stdout, stderr), the code will create
|
||||
// and return pseudo-streams that convert ANSI sequences to / from Windows Console API calls.
|
||||
|
||||
package windowsconsole // import "github.com/docker/docker/pkg/term/windows"
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/Azure/go-ansiterm"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var logger *logrus.Logger
|
||||
var initOnce sync.Once
|
||||
|
||||
func initLogger() {
|
||||
initOnce.Do(func() {
|
||||
logFile := ioutil.Discard
|
||||
|
||||
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
|
||||
logFile, _ = os.Create("ansiReaderWriter.log")
|
||||
}
|
||||
|
||||
logger = &logrus.Logger{
|
||||
Out: logFile,
|
||||
Formatter: new(logrus.TextFormatter),
|
||||
Level: logrus.DebugLevel,
|
||||
}
|
||||
})
|
||||
}
|
||||
20
vendor/github.com/docker/docker/pkg/term/winsize.go
generated
vendored
Normal file
20
vendor/github.com/docker/docker/pkg/term/winsize.go
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// +build !windows
|
||||
|
||||
package term // import "github.com/docker/docker/pkg/term"
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// GetWinsize returns the window size based on the specified file descriptor.
|
||||
func GetWinsize(fd uintptr) (*Winsize, error) {
|
||||
uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
|
||||
ws := &Winsize{Height: uws.Row, Width: uws.Col, x: uws.Xpixel, y: uws.Ypixel}
|
||||
return ws, err
|
||||
}
|
||||
|
||||
// SetWinsize tries to set the specified window size for the specified file descriptor.
|
||||
func SetWinsize(fd uintptr, ws *Winsize) error {
|
||||
uws := &unix.Winsize{Row: ws.Height, Col: ws.Width, Xpixel: ws.x, Ypixel: ws.y}
|
||||
return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, uws)
|
||||
}
|
||||
Reference in New Issue
Block a user