diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index eb797fca..5b16387e 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -36,10 +36,10 @@ jobs:
goarch: amd64
os: macos-latest
ext: tar.gz
- # - goos: windows
- # goarch: amd64
- # os: windows-latest
- # ext: zip
+ - goos: windows
+ goarch: amd64
+ os: windows-latest
+ ext: zip
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
@@ -78,6 +78,15 @@ jobs:
## tar czvf "$_TAR" -C "$PWD/package" completion bin man .tar2package.yml
tar czvf "$_TAR" -C "$PWD/package" completion bin .tar2package.yml
+ - name: Create package file(Windows)
+ if: matrix.ext == 'zip'
+ run: |
+ mkdir package/bin
+ move lssh.${{ matrix.goos }}_${{ matrix.goarch }} package/bin/lssh.exe
+ move lscp.${{ matrix.goos }}_${{ matrix.goarch }} package/bin/lscp.exe
+ move lsftp.${{ matrix.goos }}_${{ matrix.goarch }} package/bin/lsftp.exe
+ powershell Compress-Archive package lssh_${{ steps.package_version.outputs.version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip
+
# use: https://github.com/greymd/tar2package
- name: Build rpm
id: rpm
@@ -127,6 +136,13 @@ jobs:
name: build-${{ matrix.goos }}_${{ matrix.goarch }}
path: lssh_${{ steps.package_version.outputs.version }}_${{ matrix.goos }}_${{ matrix.goarch }}.tar.gz
+ - name: Upload artifact
+ if: matrix.ext == 'zip'
+ uses: actions/upload-artifact@v1
+ with:
+ name: build-${{ matrix.goos }}_${{ matrix.goarch }}
+ path: lssh_${{ steps.package_version.outputs.version }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip
+
# create package release
create-release:
needs:
@@ -182,6 +198,11 @@ jobs:
goarch: amd64
os: macos-latest
ext: tar.gz
+ - goos: windows
+ goarch: amd64
+ os: windows-latest
+ ext: zip
+
needs: [create-release]
runs-on: ubuntu-latest
steps:
diff --git a/README.md b/README.md
index c60b87be..31af9712 100644
--- a/README.md
+++ b/README.md
@@ -21,24 +21,42 @@ Supported multiple ssh proxy, http/socks5 proxy, x11 forward, and port forwardin
## Features
* List selection type ssh client.
+* It can run on **Linux**, **macOS** and **Windows**.
* Pure Go.
-* Commands can be executed by ssh connection in parallel.
-* Supported ssh multiple proxy, http/socks5 proxy.
-* Supported ssh-agent.
-* Supported Port forward, x11 forward.
+* Commands can be executed by ssh connection in **parallel**.
+* There is a shell function that connects to multiple hosts in parallel for interactive operation and connects with local commands via pipes.
+* Supported multiple proxy, **ssh**, **http**, and **socks5** proxy. It's supported multi-stage proxy.
+* Supported **ssh-agent**.
+* Supported **Local** and **Remote Port forward**, **Dynamic Forward**, **Reverse Dynamic Forward** and **x11 forward**.
* Can use bashrc of local machine at ssh connection destination.
+* It supports various authentication methods. Password, Public key, Certificate and PKCS11(Yubikey etc.).
+* Can read the OpenSSH config (~/.ssh/config) and use it as it is.
## Demo
+### run MacOSX
+
+
+
+
+
+### run Linux(Manjaro)
+
+
+
+
+
+### run Windows(Windows 10)
+
-
+
## Install
### compile
-compile gofile(tested go1.15.5).
+compile gofile(tested go1.17.6).
GO111MODULE=auto go get -u github.com/blacknon/lssh/cmd/lssh
GO111MODULE=auto go get -u github.com/blacknon/lssh/cmd/lscp
@@ -110,7 +128,7 @@ option(lssh)
blacknon(blacknon@orebibou.com)
VERSION:
- 0.6.5
+ 0.6.7
USAGE:
# connect ssh
@@ -151,7 +169,7 @@ option(lscp)
blacknon(blacknon@orebibou.com)
VERSION:
- 0.6.5
+ 0.6.7
USAGE:
# local to remote scp
@@ -186,7 +204,7 @@ option(lsftp)
blacknon(blacknon@orebibou.com)
VERSION:
- 0.6.5
+ 0.6.7
USAGE:
# start lsftp shell
@@ -219,6 +237,7 @@ You can connect using a local bashrc file (if ssh login shell is bash).
key = "/path/to/private_key"
note = "Use local bashrc files."
local_rc = 'yes'
+ local_rc_compress = true # gzip compress localrc file data
local_rc_file = [
"~/dotfiles/.bashrc"
,"~/dotfiles/bash_prompt"
@@ -602,7 +621,7 @@ You can specify from the command line or from the configuration file.
lssh -L 8080:localhost:80 # local port forwarding
lssh -R 80:localhost:8080 # remote port forwarding
lssh -D 10080 # dynamic port forwarding
-
+ lssh -R 10080 # Reverse Dynamic port forwarding
#### config file
@@ -623,6 +642,22 @@ You can specify from the command line or from the configuration file.
port_forward_remote = "localhost:8080"
note = "remote port forwawrd example"
+ [server.DynamicForward]
+ addr = "dynamicforward.local"
+ user = "user"
+ agentauth = true
+ dynamic_port_forward = "11080"
+ note = "dynamic forwawrd example"
+
+ [server.ReverseDynamicForward]
+ addr = "reversedynamicforward.local"
+ user = "user"
+ agentauth = true
+ reverse_dynamic_port_forward = "11080"
+ note = "reverse dynamic forwawrd example"
+
+
+
If OpenSsh config is loaded, it will be loaded as it is.
diff --git a/check/check.go b/check/check.go
index d85dd021..eb36fc39 100644
--- a/check/check.go
+++ b/check/check.go
@@ -1,3 +1,7 @@
+// Copyright (c) 2022 Blacknon. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+
/*
check is a package used mainly for check processing required by lssh, content check of configuration file.
*/
diff --git a/check/check_test.go b/check/check_test.go
index 88045889..9ac09aad 100644
--- a/check/check_test.go
+++ b/check/check_test.go
@@ -1,3 +1,7 @@
+// Copyright (c) 2022 Blacknon. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+
package check
import (
diff --git a/cmd/lscp/.gitkeep b/cmd/lscp/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/cmd/lscp/args.go b/cmd/lscp/args.go
index c8075912..c1915174 100644
--- a/cmd/lscp/args.go
+++ b/cmd/lscp/args.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -60,7 +60,7 @@ USAGE:
app.Name = "lscp"
app.Usage = "TUI list select and parallel scp client command."
app.Copyright = "blacknon(blacknon@orebibou.com)"
- app.Version = "0.6.6"
+ app.Version = "0.6.7"
// options
// TODO(blacknon): オプションの追加(0.7.0)
@@ -114,7 +114,7 @@ USAGE:
check.CheckTypeError(isFromInRemote, isFromInLocal, isToRemote, len(hosts))
// Get config data
- data := conf.ReadConf(confpath)
+ data := conf.Read(confpath)
// Get Server Name List (and sort List)
names := conf.GetNameList(data)
diff --git a/cmd/lscp/main.go b/cmd/lscp/main.go
index eb8830e8..948663b2 100644
--- a/cmd/lscp/main.go
+++ b/cmd/lscp/main.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/cmd/lsftp/args.go b/cmd/lsftp/args.go
index f25ac482..361f20ae 100644
--- a/cmd/lsftp/args.go
+++ b/cmd/lsftp/args.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -51,7 +51,7 @@ USAGE:
app.Name = "lsftp"
app.Usage = "TUI list select and parallel sftp client command."
app.Copyright = "blacknon(blacknon@orebibou.com)"
- app.Version = "0.6.6"
+ app.Version = "0.6.7"
app.Flags = []cli.Flag{
cli.StringFlag{Name: "file,F", Value: defConf, Usage: "config file path"},
@@ -72,7 +72,7 @@ USAGE:
confpath := c.String("file")
// Get config data
- data := conf.ReadConf(confpath)
+ data := conf.Read(confpath)
// Get Server Name List (and sort List)
names := conf.GetNameList(data)
diff --git a/cmd/lsftp/main.go b/cmd/lsftp/main.go
index f57520e3..514be39f 100644
--- a/cmd/lsftp/main.go
+++ b/cmd/lsftp/main.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/cmd/lssh/args.go b/cmd/lssh/args.go
index 2f0e7f24..e6797eb9 100644
--- a/cmd/lssh/args.go
+++ b/cmd/lssh/args.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -10,6 +10,7 @@ import (
"fmt"
"os"
"os/user"
+ "regexp"
"sort"
"github.com/blacknon/lssh/check"
@@ -65,7 +66,7 @@ USAGE:
app.Name = "lssh"
app.Usage = "TUI list select and parallel ssh client command."
app.Copyright = "blacknon(blacknon@orebibou.com)"
- app.Version = "0.6.6"
+ app.Version = "0.6.7"
// TODO(blacknon): オプションの追加
// -f ... バックグラウンドでの接続(X11接続やport forwardingをバックグラウンドで実行する場合など)。
@@ -116,7 +117,7 @@ USAGE:
confpath := c.String("file")
// Get config data
- data := conf.ReadConf(confpath)
+ data := conf.Read(confpath)
// Set `exec command` or `shell` flag
isMulti := false
@@ -214,8 +215,14 @@ USAGE:
for _, forwardargs := range c.StringSlice("R") {
f := new(conf.PortForward)
f.Mode = "R"
- f.Local, f.Remote, err = common.ParseForwardPort(forwardargs)
- forwards = append(forwards, f)
+
+ // If only numbers are passed as arguments, treat as Reverse Dynamic Port Forward
+ if regexp.MustCompile(`^[0-9]+$`).Match([]byte(forwardargs)) {
+ r.ReverseDynamicPortForward = forwardargs
+ } else {
+ f.Local, f.Remote, err = common.ParseForwardPort(forwardargs)
+ forwards = append(forwards, f)
+ }
}
// if err
diff --git a/cmd/lssh/main.go b/cmd/lssh/main.go
index 883cb426..364abfa8 100644
--- a/cmd/lssh/main.go
+++ b/cmd/lssh/main.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/common/common.go b/common/common.go
index d14bb257..2aa5f05f 100644
--- a/common/common.go
+++ b/common/common.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -9,10 +9,13 @@ package common
import (
"bufio"
+ "bytes"
+ "compress/gzip"
"crypto/sha1"
"encoding/base64"
"errors"
"fmt"
+ "io"
"io/ioutil"
"log"
"math/rand"
@@ -23,7 +26,6 @@ import (
"regexp"
"strconv"
"strings"
- "time"
"github.com/urfave/cli"
"golang.org/x/crypto/ssh/terminal"
@@ -31,12 +33,40 @@ import (
var characterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+// enum
+const (
+ ARCHIVE_NONE = iota
+ ARCHIVE_GZIP
+)
+
// IsExist returns existence of file.
func IsExist(filename string) bool {
_, err := os.Stat(filename)
return err == nil
}
+//
+func Contains(list interface{}, elem interface{}) bool {
+ listV := reflect.ValueOf(list)
+
+ if listV.Kind() == reflect.Slice {
+ for i := 0; i < listV.Len(); i++ {
+ item := listV.Index(i).Interface()
+ // check conver
+ if !reflect.TypeOf(elem).ConvertibleTo(reflect.TypeOf(item)) {
+ continue
+ }
+ // convert type
+ target := reflect.ValueOf(elem).Convert(reflect.TypeOf(item)).Interface()
+ // check
+ if ok := reflect.DeepEqual(item, target); ok {
+ return true
+ }
+ }
+ }
+ return false
+}
+
// MapReduce sets map1 value to map2 if map1 and map2 have same key, and value
// of map2 is zero value. Available interface type is string or []string or
// bool.
@@ -65,6 +95,18 @@ func MapReduce(map1, map2 map[string]interface{}) map[string]interface{} {
return map2
}
+// MapMerge merges multiple Maps
+func MapMerge(m ...map[string]interface{}) map[string]interface{} {
+ ans := make(map[string]interface{}, 0)
+
+ for _, c := range m {
+ for k, v := range c {
+ ans[k] = v
+ }
+ }
+ return ans
+}
+
// StructToMap returns a map that converted struct to map.
// Keys of map are set from public field of struct.
//
@@ -133,7 +175,7 @@ func GetMaxLength(list []string) (MaxLength int) {
}
// GetFilesBase64 returns a base64 encoded string of file content of paths.
-func GetFilesBase64(paths []string) (result string, err error) {
+func GetFilesBase64(paths []string, iscompress int) (result string, err error) {
var data []byte
for _, path := range paths {
@@ -155,7 +197,15 @@ func GetFilesBase64(paths []string) (result string, err error) {
data = append(data, '\n')
}
- result = base64.StdEncoding.EncodeToString(data)
+ switch iscompress {
+ case ARCHIVE_NONE:
+ result = base64.StdEncoding.EncodeToString(data)
+
+ case ARCHIVE_GZIP:
+ data, err = StringCompression(ARCHIVE_GZIP, data)
+ result = base64.StdEncoding.EncodeToString(data)
+ }
+
return result, err
}
@@ -183,10 +233,6 @@ func GetPassPhrase(msg string) (input string, err error) {
return
}
-func init() {
- rand.Seed(time.Now().UnixNano())
-}
-
// NewSHA1Hash generates a new SHA1 hash based on
// a random number of characters.
func NewSHA1Hash(n ...int) string {
@@ -320,6 +366,20 @@ func ParseForwardPort(value string) (local, remote string, err error) {
return
}
+// ParseHostPath return host and path, from host:/path/to/dir/file.
+func ParseHostPath(value string) (host []string, path string) {
+ if !strings.Contains(value, ":") {
+ path = value
+ return
+ }
+
+ parseValue := strings.SplitN(value, ":", 2)
+ host = strings.Split(parseValue[0], ",")
+ path = parseValue[1]
+
+ return
+}
+
// ParseArgs return os.Args parse short options (ex.) [-la] => [-l,-a] )
//
// TODO(blacknon): Migrate to github.com/urfave/cli version 1.22.
@@ -407,3 +467,24 @@ func IsDirPath(path string) (isDir bool) {
return
}
+
+// StringCompression compresses bytes in the specified mode.
+func StringCompression(mode int, data []byte) (result []byte, err error) {
+ // create buffer
+ buf := new(bytes.Buffer)
+
+ switch mode {
+ case ARCHIVE_GZIP:
+ zw := gzip.NewWriter(buf)
+ defer zw.Close()
+
+ r := bytes.NewReader(data)
+
+ _, err = io.Copy(zw, r)
+ zw.Flush()
+ }
+
+ result = buf.Bytes()
+
+ return
+}
diff --git a/common/common_test.go b/common/common_test.go
index 4be06da9..ce831fba 100644
--- a/common/common_test.go
+++ b/common/common_test.go
@@ -1,10 +1,11 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
package common
import (
+ "fmt"
"testing"
"github.com/stretchr/testify/assert"
@@ -122,3 +123,28 @@ func TestGetMaxLength(t *testing.T) {
// TODO
// func TestGetFilesBase64(t *testing.T) {
// }
+
+// func TestParseHostPath(t *testing.T) {
+// type TestData
+// }
+
+//
+func TestStringCompression(t *testing.T) {
+ type TestData struct {
+ desc string
+ src string
+ mode int
+ }
+
+ src := "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
+
+ tds := []TestData{
+ {desc: "", src: src, mode: ARCHIVE_GZIP},
+ }
+ for _, v := range tds {
+ fmt.Println(v.src)
+ arc, _ := StringCompression(v.mode, []byte(v.src))
+ fmt.Println(arc)
+ }
+
+}
diff --git a/conf/conf.go b/conf/conf.go
index e7af8fc0..49aea9d3 100644
--- a/conf/conf.go
+++ b/conf/conf.go
@@ -1,10 +1,16 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
/*
conf is a package used to read configuration file (~/.lssh.conf).
*/
+
+// TODO(blacknon): 各種クラウドの踏み台経由でのアクセスに対応する
+// - AWS SSM(セッションマネージャー)
+// - Azure Bastion
+// - GCP(gcloud compute ssh)
+
package conf
import (
@@ -12,7 +18,6 @@ import (
"encoding/hex"
"fmt"
"os"
- "os/user"
"strings"
"time"
@@ -20,19 +25,6 @@ import (
"github.com/blacknon/lssh/common"
)
-// Config is Struct that stores the entire configuration file
-type Config struct {
- Log LogConfig
- Shell ShellConfig
- Include map[string]IncludeConfig
- Includes IncludesConfig
- Common ServerConfig
- Server map[string]ServerConfig
- Proxy map[string]ProxyConfig
-
- SSHConfig map[string]OpenSSHConfig
-}
-
// LogConfig store the contents about the terminal log.
// The log file name is created in "YYYYmmdd_HHMMSS_servername.log" of the specified directory.
type LogConfig struct {
@@ -73,6 +65,7 @@ type IncludeConfig struct {
// IncludesConfig specify the configuration file to include (ServerConfig only).
// Struct that can specify multiple files in array.
+// TODO: ワイルドカード指定可能にする
type IncludesConfig struct {
// example:
// path = [
@@ -124,9 +117,19 @@ type ServerConfig struct {
// local rcfile setting
// yes|no (default: yes)
- LocalRcUse string `toml:"local_rc"`
- LocalRcPath []string `toml:"local_rc_file"`
- LocalRcDecodeCmd string `toml:"local_rc_decode_cmd"`
+ LocalRcUse string `toml:"local_rc"`
+
+ // LocalRcPath
+ LocalRcPath []string `toml:"local_rc_file"`
+
+ // If LocalRcCompress is true, gzip the localrc file to base64
+ LocalRcCompress bool `toml:"local_rc_compress"`
+
+ // LocalRcDecodeCmd is localrc decode command. run remote machine.
+ LocalRcDecodeCmd string `toml:"local_rc_decode_cmd"`
+
+ // LocalRcUncompressCmd is localrc un compress command. run remote machine.
+ LocalRcUncompressCmd string `toml:"local_rc_uncompress_cmd"`
// local/remote port forwarding setting.
// ex. [`L`,`l`,`LOCAL`,`local`]|[`R`,`r`,`REMOTE`,`remote`]
@@ -145,10 +148,14 @@ type ServerConfig struct {
// local/remote Port Forwarding slice.
Forwards []*PortForward
- // Dynamic Port Forwarding setting
+ // Dynamic Port Forward setting
// ex.) "11080"
DynamicPortForward string `toml:"dynamic_port_forward"`
+ // Reverse Dynamic Port Forward setting
+ // ex.) "11080"
+ ReverseDynamicPortForward string `toml:"reverse_dynamic_port_forward"`
+
// x11 forwarding setting
X11 bool `toml:"x11"`
@@ -188,66 +195,65 @@ type PortForward struct {
Remote string // localhost:80
}
-// ReadConf load configuration file and return Config structure
-// TODO(blacknon): リファクタリング!(v0.6.5) 外出しや処理のまとめなど
-// TODO(blacknon): ~/.lssh.confがなくても、openssh用のファイルがアレばそれをみるように処理
-func ReadConf(confPath string) (config Config) {
- // user path
- usr, _ := user.Current()
-
- if !common.IsExist(confPath) {
- fmt.Printf("Config file(%s) Not Found.\nPlease create file.\n\n", confPath)
- fmt.Printf("sample: %s\n", "https://raw.githubusercontent.com/blacknon/lssh/master/example/config.tml")
- os.Exit(1)
- }
-
- config.Server = map[string]ServerConfig{}
- config.SSHConfig = map[string]OpenSSHConfig{}
+// Config is Struct that stores the entire configuration file.
+type Config struct {
+ Log LogConfig
+ Shell ShellConfig
+ Include map[string]IncludeConfig
+ Includes IncludesConfig
+ Common ServerConfig
+ Server map[string]ServerConfig
+ Proxy map[string]ProxyConfig
- // Read config file
- _, err := toml.DecodeFile(confPath, &config)
- if err != nil {
- fmt.Println(err)
- os.Exit(1)
- }
+ SSHConfig map[string]OpenSSHConfig
+}
+//
+func (c *Config) ReduceCommon() {
// reduce common setting (in .lssh.conf servers)
- for key, value := range config.Server {
- setValue := serverConfigReduct(config.Common, value)
- config.Server[key] = setValue
+ for key, value := range c.Server {
+ setValue := serverConfigReduct(c.Common, value)
+ c.Server[key] = setValue
}
+}
- // Read OpensSH configs
- if len(config.SSHConfig) == 0 {
+//
+func (c *Config) ReadOpenSSHConfig() {
+ if len(c.SSHConfig) == 0 {
openSSHServerConfig, err := getOpenSSHConfig("~/.ssh/config", "")
if err == nil {
// append data
for key, value := range openSSHServerConfig {
- value := serverConfigReduct(config.Common, value)
- config.Server[key] = value
+ value := serverConfigReduct(c.Common, value)
+ c.Server[key] = value
}
}
} else {
- for _, sshConfig := range config.SSHConfig {
- openSSHServerConfig, err := getOpenSSHConfig(sshConfig.Path, sshConfig.Command)
+ for _, sc := range c.SSHConfig {
+ openSSHServerConfig, err := getOpenSSHConfig(sc.Path, sc.Command)
if err == nil {
// append data
for key, value := range openSSHServerConfig {
- setCommon := serverConfigReduct(config.Common, sshConfig.ServerConfig)
+ setCommon := serverConfigReduct(c.Common, sc.ServerConfig)
value = serverConfigReduct(setCommon, value)
- config.Server[key] = value
+ c.Server[key] = value
}
}
}
}
+}
- // for append includes to include.path
- if config.Includes.Path != nil {
- if config.Include == nil {
- config.Include = map[string]IncludeConfig{}
+//
+func (c *Config) ReadIncludeFiles() {
+ if c.Includes.Path != nil {
+ if c.Include == nil {
+ c.Include = map[string]IncludeConfig{}
}
- for _, includePath := range config.Includes.Path {
+ for _, includePath := range c.Includes.Path {
+ // get abs path
+ includePath = common.GetFullPath(includePath)
+
unixTime := time.Now().Unix()
keyString := strings.Join([]string{string(unixTime), includePath}, "_")
@@ -256,18 +262,18 @@ func ReadConf(confPath string) (config Config) {
hasher.Write([]byte(keyString))
key := string(hex.EncodeToString(hasher.Sum(nil)))
- // append config.Include[key]
- config.Include[key] = IncludeConfig{strings.Replace(includePath, "~", usr.HomeDir, 1)}
+ // append c.Include[key]
+ c.Include[key] = IncludeConfig{includePath}
}
}
// Read include files
- if config.Include != nil {
- for _, v := range config.Include {
+ if c.Include != nil {
+ for _, v := range c.Include {
var includeConf Config
// user path
- path := strings.Replace(v.Path, "~", usr.HomeDir, 1)
+ path := common.GetFullPath(v.Path)
// Read include config file
_, err := toml.DecodeFile(path, &includeConf)
@@ -277,29 +283,16 @@ func ReadConf(confPath string) (config Config) {
}
// reduce common setting
- setCommon := serverConfigReduct(config.Common, includeConf.Common)
-
- // map init
- if len(config.Server) == 0 {
- config.Server = map[string]ServerConfig{}
- }
+ setCommon := serverConfigReduct(c.Common, includeConf.Common)
// add include file serverconf
for key, value := range includeConf.Server {
// reduce common setting
setValue := serverConfigReduct(setCommon, value)
- config.Server[key] = setValue
+ c.Server[key] = setValue
}
}
}
-
- // Check Config Parameter
- checkAlertFlag := checkFormatServerConf(config)
- if !checkAlertFlag {
- os.Exit(1)
- }
-
- return
}
// checkFormatServerConf checkes format of server config.
@@ -308,52 +301,86 @@ func ReadConf(confPath string) (config Config) {
// having a value. No checking a validity of each fields.
//
// See also: checkFormatServerConfAuth function.
-func checkFormatServerConf(c Config) (isFormat bool) {
- isFormat = true
+func (c *Config) checkFormatServerConf() (ok bool) {
+ ok = true
for k, v := range c.Server {
// Address Set Check
if v.Addr == "" {
fmt.Printf("%s: 'addr' is not set.\n", k)
- isFormat = false
+ ok = false
}
// User Set Check
if v.User == "" {
fmt.Printf("%s: 'user' is not set.\n", k)
- isFormat = false
+ ok = false
}
if !checkFormatServerConfAuth(v) {
fmt.Printf("%s: Authentication information is not set.\n", k)
- isFormat = false
+ ok = false
}
}
return
}
+// ReadConf load configuration file and return Config structure
+// TODO(blacknon): リファクタリング!(v0.6.5) 外出しや処理のまとめなど
+func Read(confPath string) (c Config) {
+ c.Server = map[string]ServerConfig{}
+ c.SSHConfig = map[string]OpenSSHConfig{}
+
+ // TODO(blacknon): ~/.lssh.confがなくても、openssh用のファイルがアレばそれをみるように処理
+ if common.IsExist(confPath) {
+ // Read config file
+ _, err := toml.DecodeFile(confPath, &c)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ }
+
+ // reduce common setting (in .lssh.conf servers)
+ c.ReduceCommon()
+
+ // Read OpensSH configs
+ c.ReadOpenSSHConfig()
+
+ // for append includes to include.path
+ c.ReadIncludeFiles()
+
+ // Check Config Parameter
+ ok := c.checkFormatServerConf()
+ if !ok {
+ os.Exit(1)
+ }
+
+ return
+}
+
// checkFormatServerConfAuth checkes format of server config authentication.
//
// Note: Checking Pass, Key, Cert, AgentAuth, PKCS11Use, PKCS11Provider, Keys or
// Passes having a value. No checking a validity of each fields.
-func checkFormatServerConfAuth(c ServerConfig) (isFormat bool) {
- isFormat = false
+func checkFormatServerConfAuth(c ServerConfig) (ok bool) {
+ ok = false
if c.Pass != "" || c.Key != "" || c.Cert != "" {
- isFormat = true
+ ok = true
}
if c.AgentAuth == true {
- isFormat = true
+ ok = true
}
if c.PKCS11Use == true {
_, err := os.Stat(c.PKCS11Provider)
if err == nil {
- isFormat = true
+ ok = true
}
}
if len(c.Keys) > 0 || len(c.Passes) > 0 {
- isFormat = true
+ ok = true
}
return
diff --git a/conf/conf_test.go b/conf/conf_test.go
index ed4d5733..67365f7e 100644
--- a/conf/conf_test.go
+++ b/conf/conf_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -66,7 +66,7 @@ func TestCheckFormatServerConf(t *testing.T) {
},
}
for _, v := range tds {
- got := checkFormatServerConf(v.c)
+ got := v.c.checkFormatServerConf()
assert.Equal(t, v.expect, got, v.desc)
}
}
diff --git a/conf/openssh_conf.go b/conf/openssh_.go
similarity index 92%
rename from conf/openssh_conf.go
rename to conf/openssh_.go
index 70d325b8..f3d27989 100644
--- a/conf/openssh_conf.go
+++ b/conf/openssh_.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -17,8 +17,8 @@ import (
"github.com/kevinburke/ssh_config"
)
-// openOpenSSHConfig open the OpenSSH configuration file, return *ssh_config.Config.
-func openOpenSSHConfig(path, command string) (cfg *ssh_config.Config, err error) {
+// readOpenSSHConfig open the OpenSSH configuration file, return *ssh_config.Config.
+func readOpenSSHConfig(path, command string) (cfg *ssh_config.Config, err error) {
var rd io.Reader
switch {
case path != "": // 1st
@@ -46,7 +46,7 @@ func getOpenSSHConfig(path, command string) (config map[string]ServerConfig, err
config = map[string]ServerConfig{}
// open openSSH config
- cfg, err := openOpenSSHConfig(path, command)
+ cfg, err := readOpenSSHConfig(path, command)
if err != nil {
return
}
@@ -70,7 +70,6 @@ func getOpenSSHConfig(path, command string) (config map[string]ServerConfig, err
}
// append ServerConfig
- // TODO(blacknon): port forwardingとx11の設定も読み込むよう処理を追加!!
for _, host := range hostList {
serverConfig := ServerConfig{
Addr: ssh_config.Get(host, "HostName"),
diff --git a/example/config.tml b/example/config.tml
index b0324cd9..9b4648c3 100644
--- a/example/config.tml
+++ b/example/config.tml
@@ -1,4 +1,4 @@
-# Copyright (c) 2019 Blacknon. All rights reserved.
+# Copyright (c) 2022 Blacknon. All rights reserved.
# Use of this source code is governed by an MIT license
# that can be found in the LICENSE file.
diff --git a/go.mod b/go.mod
index 0145a1d9..901d8d03 100644
--- a/go.mod
+++ b/go.mod
@@ -2,24 +2,27 @@ module github.com/blacknon/lssh
require (
github.com/BurntSushi/toml v0.3.1
- github.com/blacknon/go-sshlib v0.1.5
+ github.com/blacknon/go-sshlib v0.1.7
github.com/blacknon/textcol v0.0.1
github.com/c-bata/go-prompt v0.2.5
github.com/dustin/go-humanize v1.0.0
+ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8
github.com/mattn/go-runewidth v0.0.13
- github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e
- github.com/pkg/sftp v1.10.1
+ github.com/nsf/termbox-go v1.1.1
+ github.com/pkg/sftp v1.13.4
github.com/sevlyar/go-daemon v0.1.5
- github.com/stretchr/testify v1.5.1
+ github.com/stretchr/testify v1.7.0
github.com/urfave/cli v1.21.0
github.com/vbauerster/mpb v3.4.0+incompatible
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d
+ golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7
mvdan.cc/sh v2.6.3+incompatible
)
require (
+ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 // indirect
github.com/ThalesIgnite/crypto11 v1.2.5 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
@@ -35,14 +38,14 @@ require (
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-tty v0.0.3 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
+ github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/term v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/thales-e-security/pool v0.0.2 // indirect
- golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
- gopkg.in/yaml.v2 v2.2.8 // indirect
+ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
go 1.17
diff --git a/go.sum b/go.sum
index 5dcc7d40..a71e620a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
+github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ScaleFT/sshkeys v0.0.0-20200327173127-6142f742bca5 h1:VauE2GcJNZFun2Och6tIT2zJZK1v6jxALQDA9BIji/E=
@@ -10,20 +12,25 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/blacknon/go-sshlib v0.1.5 h1:nHuIi022DS72kf5/WTBRE15NHW43Qb+1umM76GmsydA=
-github.com/blacknon/go-sshlib v0.1.5/go.mod h1:sgMpYTYseacjT4Bt9pPwjMd+eMXsLGCjDtoFugXKYDE=
+github.com/blacknon/go-sshlib v0.1.7 h1:LuPzmc4FOeNgNTfxCf4m4w7N77yqNS9+lKmVvW/gsIQ=
+github.com/blacknon/go-sshlib v0.1.7/go.mod h1:oSPJmsArrtlxWfgYDhMScNX6M5DuqJmHeFIYa5hQHsI=
github.com/blacknon/textcol v0.0.1 h1:x9h7yLPGyr8Pdz12XJ30h7Iz5mJlKd0CzfGYxhrmnk8=
github.com/blacknon/textcol v0.0.1/go.mod h1:1x1tHA4cEgiQ8BsKysc60OALSZMG9WjmbjmJvPqIInQ=
github.com/c-bata/go-prompt v0.2.5 h1:3zg6PecEywxNn0xiqcXHD96fkbxghD+gdB2tbsYfl+Y=
github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
+github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8 h1:AUkD9wwFc/ezYjdnFbQ8by/6oeL+jgBfcemmOJiQOMs=
github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
@@ -51,13 +58,15 @@ github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvr
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
-github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e h1:Vbib8wJAaMEF9jusI/kMSYMr/LtRzM7+F9MJgt/nH8k=
-github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
+github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
+github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
+github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
+github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/sftp v1.10.1 h1:VasscCm72135zRysgrJDKsntdmPN+OuU3+nnHYA9wyc=
-github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
+github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
+github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -66,11 +75,12 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk=
github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/thales-e-security/pool v0.0.2 h1:RAPs4q2EbWsTit6tpzuvTFlgFRJ3S8Evf5gtvVDbmPg=
github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpupY8mv0Phz0gjhU=
github.com/urfave/cli v1.21.0 h1:wYSSj06510qPIzGSua9ZqsncMmWE3Zr55KBERygyrxE=
@@ -78,14 +88,17 @@ github.com/urfave/cli v1.21.0/go.mod h1:lxDj6qX9Q6lWQxIrbrT0nwecwUtRnhVZAJjJZrVU
github.com/vbauerster/mpb v3.4.0+incompatible h1:mfiiYw87ARaeRW6x5gWwYRUawxaW1tLAD8IceomUCNw=
github.com/vbauerster/mpb v3.4.0+incompatible/go.mod h1:zAHG26FUhVKETRu+MWqYXcI70POlC6N8up9p1dID7SU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d h1:62NvYBuaanGXR2ZOfwDFkhhl6X1DUgf8qg3GuQvxZsE=
golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -99,19 +112,26 @@ golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 h1:BXxu8t6QN0G1uff4bzZzSkpsax8+ALqTGUtz08QrV00=
+golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
mvdan.cc/sh v2.6.3+incompatible h1:uXnnFNSBQbKUwwh2iBSkVjG+GbwoMuI+UmBVPnNiWhA=
mvdan.cc/sh v2.6.3+incompatible/go.mod h1:IeeQbZq+x2SUGBensq/jge5lLQbS3XT2ktyp3wrt4x8=
diff --git a/images/lssh.gif b/images/lssh_linux.gif
similarity index 100%
rename from images/lssh.gif
rename to images/lssh_linux.gif
diff --git a/images/lssh_macosx.gif b/images/lssh_macosx.gif
new file mode 100644
index 00000000..404dc30d
Binary files /dev/null and b/images/lssh_macosx.gif differ
diff --git a/images/lssh_windows.gif b/images/lssh_windows.gif
new file mode 100644
index 00000000..b044fc9e
Binary files /dev/null and b/images/lssh_windows.gif differ
diff --git a/list/draw.go b/list/draw.go
index 1fda5292..1bce5e19 100644
--- a/list/draw.go
+++ b/list/draw.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -16,6 +16,7 @@ import (
func drawLine(x, y int, str string, colorNum int, backColorNum int) {
color := termbox.Attribute(colorNum + 1)
backColor := termbox.Attribute(backColorNum + 1)
+
// View Multi-Byte
for _, char := range str {
termbox.SetCell(x, y, char, color, backColor)
diff --git a/list/event.go b/list/event.go
index fb92f9a9..929fc98d 100644
--- a/list/event.go
+++ b/list/event.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/list/event_test.go b/list/event_test.go
index 817859c8..6d5ba14c 100644
--- a/list/event_test.go
+++ b/list/event_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/list/list.go b/list/list.go
index b0297ee4..8670ba06 100644
--- a/list/list.go
+++ b/list/list.go
@@ -1,10 +1,11 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
/*
list package creates a TUI list based on the contents specified in a structure, and returns the selected row.
*/
+
package list
import (
@@ -136,6 +137,8 @@ func (l *ListInfo) getText() {
tabWriterBuffer.Flush()
line, err := buffer.ReadString('\n')
for err == nil {
+ line = convNewline(line, "")
+
str := strings.Replace(line, "\t", " ", -1)
l.DataText = append(l.DataText, str)
line, err = buffer.ReadString('\n')
diff --git a/list/list_test.go b/list/list_test.go
index 50f96cb9..5bcd7e11 100644
--- a/list/list_test.go
+++ b/list/list_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/output/output.go b/output/output.go
index 0b436d45..77222dd3 100644
--- a/output/output.go
+++ b/output/output.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -142,11 +142,10 @@ loop:
func (o *Output) ProgressPrinter(size int64, reader io.Reader, path string) {
// print header
oPrompt := ""
- name := decor.Name(oPrompt)
if len(o.ServerList) > 1 {
oPrompt = o.GetPrompt()
- name = decor.Name(oPrompt, decor.WC{W: len(path) + 1})
}
+ name := decor.Name(oPrompt)
// trim space
path = strings.TrimSpace(path)
diff --git a/scp/main.go b/scp/main.go
index 9d937a40..614fa686 100644
--- a/scp/main.go
+++ b/scp/main.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"os"
+ "path"
"path/filepath"
"sort"
"sync"
@@ -130,8 +131,9 @@ func (cp *Scp) push() {
}
sort.Strings(data)
+
dataset := PathSet{
- Base: filepath.Dir(p),
+ Base: path.Dir(p),
PathSlice: data,
}
@@ -179,11 +181,11 @@ func (cp *Scp) push() {
}
//
-func (cp *Scp) pushPath(ftp *sftp.Client, ow *io.PipeWriter, output *output.Output, base, path string) (err error) {
+func (cp *Scp) pushPath(ftp *sftp.Client, ow *io.PipeWriter, output *output.Output, base, p string) (err error) {
var rpath string
// Set remote path
- relpath, _ := filepath.Rel(base, path)
+ relpath, _ := filepath.Rel(base, p)
if common.IsDirPath(cp.To.Path[0]) || len(cp.From.Path) > 1 {
rpath = filepath.Join(cp.To.Path[0], relpath)
} else if len(cp.From.Path) == 1 {
@@ -197,12 +199,12 @@ func (cp *Scp) pushPath(ftp *sftp.Client, ow *io.PipeWriter, output *output.Outp
}
// get local file info
- fInfo, _ := os.Lstat(path)
+ fInfo, _ := os.Lstat(p)
if fInfo.IsDir() { // directory
ftp.Mkdir(rpath)
} else { //file
// open local file
- lf, err := os.Open(path)
+ lf, err := os.Open(p)
if err != nil {
fmt.Fprintf(ow, "%s\n", err)
return err
@@ -210,7 +212,7 @@ func (cp *Scp) pushPath(ftp *sftp.Client, ow *io.PipeWriter, output *output.Outp
defer lf.Close()
// get file size
- lstat, _ := os.Lstat(path)
+ lstat, _ := os.Lstat(p)
size := lstat.Size()
// copy file
@@ -235,9 +237,13 @@ func (cp *Scp) pushFile(lf io.Reader, ftp *sftp.Client, output *output.Output, p
// get output writer
ow := output.NewWriter()
- // mkdir all
+ // set path
dir := filepath.Dir(path)
+ dir = filepath.ToSlash(dir)
+
+ // mkdir all
err = ftp.MkdirAll(dir)
+
if err != nil {
fmt.Fprintf(ow, "%s\n", err)
return
@@ -394,6 +400,7 @@ func (cp *Scp) pullPath(client *ScpConnect) {
os.MkdirAll(baseDir, 0755)
}
baseDir, _ = filepath.Abs(baseDir)
+ baseDir = filepath.ToSlash(baseDir)
// walk remote path
for _, path := range cp.From.Path {
@@ -408,6 +415,7 @@ func (cp *Scp) pullPath(client *ScpConnect) {
for walker.Step() {
// basedir
remoteBase := filepath.Dir(gp)
+ remoteBase = filepath.ToSlash(remoteBase)
err := walker.Err()
if err != nil {
diff --git a/sftp/cmd.go b/sftp/cmd.go
index 06bbe687..852da573 100644
--- a/sftp/cmd.go
+++ b/sftp/cmd.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -7,14 +7,13 @@
package sftp
import (
- "os"
"sort"
"github.com/urfave/cli"
)
// FileInfos is []os.FileInfo.
-type FileInfos []os.FileInfo
+type FileInfos []sftpFileInfo
// ByName is sort by name
type ByName struct{ FileInfos }
@@ -75,7 +74,7 @@ type sftpLsData struct {
}
// SortLsData is sort []os.FileInfo.
-func (r *RunSftp) SortLsData(c *cli.Context, files []os.FileInfo) {
+func (r *RunSftp) SortLsData(c *cli.Context, files []sftpFileInfo) {
// sort
switch {
case c.Bool("f"): // do not sort
diff --git a/sftp/cmd_cat.go b/sftp/cmd_cat.go
index 14b69d1a..ec15b26b 100644
--- a/sftp/cmd_cat.go
+++ b/sftp/cmd_cat.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -31,16 +31,21 @@ func (r *RunSftp) cat(args []string) {
app.Name = "cat"
app.Usage = "lsftp build-in command: cat [remote machine cat]"
- app.ArgsUsage = "[PATH]"
+ app.ArgsUsage = "PATH..."
app.HideHelp = true
app.HideVersion = true
app.EnableBashCompletion = true
app.Action = func(c *cli.Context) error {
// 1st arg only
- path := c.Args().First()
+ pathlist := c.Args()
- for server, client := range r.Client {
+ targetmap := map[string]*TargetConnectMap{}
+ for _, p := range pathlist {
+ targetmap = r.createTargetMap(targetmap, p)
+ }
+
+ for server, client := range targetmap {
// set ftp client
ftp := client.Connect
@@ -48,25 +53,26 @@ func (r *RunSftp) cat(args []string) {
client.Output.Create(server)
w := client.Output.NewWriter()
- // set arg path
- if !filepath.IsAbs(path) {
- path = filepath.Join(client.Pwd, path)
- }
-
- // open file
- f, err := ftp.Open(path)
- if err != nil {
- fmt.Fprintln(w, err)
- return nil
+ for _, path := range client.Path {
+ // set arg path
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(client.Pwd, path)
+ }
+
+ // open file
+ f, err := ftp.Open(path)
+ if err != nil {
+ fmt.Fprintln(w, err)
+ continue
+ }
+
+ // read file to Output.Writer
+ _, err = f.WriteTo(w)
+
+ if err != nil {
+ fmt.Fprintln(w, err)
+ }
}
-
- // read file to Output.Writer
- _, err = f.WriteTo(w)
-
- if err != nil {
- fmt.Fprintln(w, err)
- }
-
}
// wait 0.3 sec
diff --git a/sftp/cmd_cd.go b/sftp/cmd_cd.go
index ae3e9032..0045be4f 100644
--- a/sftp/cmd_cd.go
+++ b/sftp/cmd_cd.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -27,15 +27,18 @@ func (r *RunSftp) cd(args []string) {
return
}
+ targetmap := map[string]*TargetConnectMap{}
+ targetmap = r.createTargetMap(targetmap, args[1])
+
// check directory
var okcounter int
- for server, client := range r.Client {
+ for server, client := range targetmap {
// get output
client.Output.Create(server)
w := client.Output.NewWriter()
// set arg path
- path = args[1]
+ path = client.Path[0]
var err error
if !filepath.IsAbs(path) {
path = filepath.Join(client.Pwd, path)
@@ -59,20 +62,18 @@ func (r *RunSftp) cd(args []string) {
continue
}
+ // set pwd
+ r.Client[server].Pwd = path
+
// add count
okcounter++
}
// check count okcounter
- if okcounter != len(r.Client) {
+ if okcounter != len(targetmap) {
return
}
- // set pwd
- for _, c := range r.Client {
- c.Pwd = path
- }
-
return
}
diff --git a/sftp/cmd_chgrp.go b/sftp/cmd_chgrp.go
index 13ec38b3..fb4117a6 100644
--- a/sftp/cmd_chgrp.go
+++ b/sftp/cmd_chgrp.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -27,37 +27,37 @@ func (r *RunSftp) chgrp(args []string) {
app.CustomAppHelpTemplate = helptext
app.Name = "chgrp"
app.Usage = "lsftp build-in command: chgrp [remote machine chgrp]"
- app.ArgsUsage = "[group path]"
+ app.ArgsUsage = "group path..."
app.HideHelp = true
app.HideVersion = true
app.EnableBashCompletion = true
// action
app.Action = func(c *cli.Context) error {
- if len(c.Args()) != 2 {
- fmt.Println("Requires two arguments")
- fmt.Println("chgrp group path")
+ if len(c.Args()) <= 1 {
+ fmt.Println("Requires over two arguments")
+ fmt.Println("chgrp group path...")
return nil
}
+ group := c.Args()[0]
+ pathlist := c.Args()[1:]
+
+ targetmap := map[string]*TargetConnectMap{}
+ for _, p := range pathlist {
+ targetmap = r.createTargetMap(targetmap, p)
+ }
+
exit := make(chan bool)
- for s, cl := range r.Client {
+ for s, cl := range targetmap {
server := s
client := cl
- group := c.Args()[0]
- path := c.Args()[1]
-
go func() {
// get writer
client.Output.Create(server)
w := client.Output.NewWriter()
- // set arg path
- if !filepath.IsAbs(path) {
- path = filepath.Join(client.Pwd, path)
- }
-
//
groupid, err := strconv.Atoi(group)
var gid, uid int
@@ -90,34 +90,42 @@ func (r *RunSftp) chgrp(args []string) {
gid = int(groupid)
}
- // get current uid
- stat, err := client.Connect.Lstat(path)
- if err != nil {
- fmt.Fprintf(w, "%s\n", err)
- exit <- true
- return
- }
+ for _, path := range client.Path {
+ // set arg path
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(client.Pwd, path)
+ }
- sys := stat.Sys()
- if fstat, ok := sys.(*sftp.FileStat); ok {
- uid = int(fstat.UID)
- }
+ // get current uid
+ stat, err := client.Connect.Lstat(path)
+ if err != nil {
+ fmt.Fprintf(w, "%s\n", err)
+ exit <- true
+ return
+ }
- // set gid
- err = client.Connect.Chown(path, uid, gid)
- if err != nil {
- fmt.Fprintf(w, "%s\n", err)
- exit <- true
- return
+ sys := stat.Sys()
+ if fstat, ok := sys.(*sftp.FileStat); ok {
+ uid = int(fstat.UID)
+ }
+
+ // set gid
+ err = client.Connect.Chown(path, uid, gid)
+ if err != nil {
+ fmt.Fprintf(w, "%s\n", err)
+ exit <- true
+ return
+ }
+
+ fmt.Fprintf(w, "chgrp: set %s's group as %s\n", path, group)
}
- fmt.Fprintf(w, "chgrp: set %s's group as %s\n", path, group)
exit <- true
return
}()
}
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
diff --git a/sftp/cmd_chmod.go b/sftp/cmd_chmod.go
index 96801255..867178fb 100644
--- a/sftp/cmd_chmod.go
+++ b/sftp/cmd_chmod.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -26,37 +26,37 @@ func (r *RunSftp) chmod(args []string) {
app.CustomAppHelpTemplate = helptext
app.Name = "chmod"
app.Usage = "lsftp build-in command: chmod [remote machine chmod]"
- app.ArgsUsage = "[perm path]"
+ app.ArgsUsage = "perm path..."
app.HideHelp = true
app.HideVersion = true
app.EnableBashCompletion = true
// action
app.Action = func(c *cli.Context) error {
- if len(c.Args()) != 2 {
- fmt.Println("Requires two arguments")
- fmt.Println("chmod mode path")
+ if len(c.Args()) <= 1 {
+ fmt.Println("Requires over two arguments")
+ fmt.Println("chmod mode path...")
return nil
}
+ mode := c.Args()[0]
+ pathlist := c.Args()[1:]
+
+ targetmap := map[string]*TargetConnectMap{}
+ for _, p := range pathlist {
+ targetmap = r.createTargetMap(targetmap, p)
+ }
+
exit := make(chan bool)
- for s, cl := range r.Client {
+ for s, cl := range targetmap {
server := s
client := cl
- mode := c.Args()[0]
- path := c.Args()[1]
-
go func() {
// get writer
client.Output.Create(server)
w := client.Output.NewWriter()
- // set arg path
- if !filepath.IsAbs(path) {
- path = filepath.Join(client.Pwd, path)
- }
-
// get mode
modeint, err := strconv.ParseUint(mode, 8, 32)
if err != nil {
@@ -66,21 +66,30 @@ func (r *RunSftp) chmod(args []string) {
}
filemode := os.FileMode(modeint)
- // set filemode
- err = client.Connect.Chmod(path, filemode)
- if err != nil {
- fmt.Fprintf(w, "%s\n", err)
- exit <- true
- return
+ for _, path := range client.Path {
+
+ // set arg path
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(client.Pwd, path)
+ }
+
+ // set filemode
+ err = client.Connect.Chmod(path, filemode)
+ if err != nil {
+ fmt.Fprintf(w, "%s\n", err)
+ exit <- true
+ return
+ }
+
+ fmt.Fprintf(w, "chmod: set %s's permission as %o(%s)\n", path, filemode.Perm(), filemode.String())
}
- fmt.Fprintf(w, "chmod: set %s's permission as %o(%s)\n", path, filemode.Perm(), filemode.String())
exit <- true
return
}()
}
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
diff --git a/sftp/cmd_chown.go b/sftp/cmd_chown.go
index bab449fe..56fa70f5 100644
--- a/sftp/cmd_chown.go
+++ b/sftp/cmd_chown.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -27,37 +27,37 @@ func (r *RunSftp) chown(args []string) {
app.CustomAppHelpTemplate = helptext
app.Name = "chown"
app.Usage = "lsftp build-in command: chown [remote machine chown]"
- app.ArgsUsage = "[user path]"
+ app.ArgsUsage = "user path..."
app.HideHelp = true
app.HideVersion = true
app.EnableBashCompletion = true
// action
app.Action = func(c *cli.Context) error {
- if len(c.Args()) != 2 {
- fmt.Println("Requires two arguments")
- fmt.Println("chown group path")
+ if len(c.Args()) <= 1 {
+ fmt.Println("Requires over two arguments")
+ fmt.Println("chown group path...")
return nil
}
+ user := c.Args()[0]
+ pathlist := c.Args()[1:]
+
+ targetmap := map[string]*TargetConnectMap{}
+ for _, p := range pathlist {
+ targetmap = r.createTargetMap(targetmap, p)
+ }
+
exit := make(chan bool)
- for s, cl := range r.Client {
+ for s, cl := range targetmap {
server := s
client := cl
- user := c.Args()[0]
- path := c.Args()[1]
-
go func() {
// get writer
client.Output.Create(server)
w := client.Output.NewWriter()
- // set arg path
- if !filepath.IsAbs(path) {
- path = filepath.Join(client.Pwd, path)
- }
-
//
userid, err := strconv.Atoi(user)
var gid, uid int
@@ -90,34 +90,42 @@ func (r *RunSftp) chown(args []string) {
uid = int(userid)
}
- // get current uid
- stat, err := client.Connect.Lstat(path)
- if err != nil {
- fmt.Fprintf(w, "%s\n", err)
- exit <- true
- return
- }
+ for _, path := range client.Path {
+ // set arg path
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(client.Pwd, path)
+ }
- sys := stat.Sys()
- if fstat, ok := sys.(*sftp.FileStat); ok {
- gid = int(fstat.GID)
- }
+ // get current uid
+ stat, err := client.Connect.Lstat(path)
+ if err != nil {
+ fmt.Fprintf(w, "%s\n", err)
+ exit <- true
+ return
+ }
- // set gid
- err = client.Connect.Chown(path, uid, gid)
- if err != nil {
- fmt.Fprintf(w, "%s\n", err)
- exit <- true
- return
+ sys := stat.Sys()
+ if fstat, ok := sys.(*sftp.FileStat); ok {
+ gid = int(fstat.GID)
+ }
+
+ // set gid
+ err = client.Connect.Chown(path, uid, gid)
+ if err != nil {
+ fmt.Fprintf(w, "%s\n", err)
+ exit <- true
+ return
+ }
+
+ fmt.Fprintf(w, "chown: set %s's user as %s\n", path, user)
}
- fmt.Fprintf(w, "chown: set %s's user as %s\n", path, user)
exit <- true
return
}()
}
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
diff --git a/sftp/cmd_copy.go b/sftp/cmd_copy.go
index 89d3fbcc..a710ba9c 100644
--- a/sftp/cmd_copy.go
+++ b/sftp/cmd_copy.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/sftp/cmd_df.go b/sftp/cmd_df.go
index 590f360c..3d2b24bb 100644
--- a/sftp/cmd_df.go
+++ b/sftp/cmd_df.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -35,87 +35,110 @@ func (r *RunSftp) df(args []string) {
}
app.Name = "df"
app.Usage = "lsftp build-in command: df [remote machine df]"
- app.ArgsUsage = "[PATH]"
+ app.ArgsUsage = "PATH..."
app.HideHelp = true
app.HideVersion = true
app.EnableBashCompletion = true
// action
app.Action = func(c *cli.Context) error {
- argpath := c.Args().First()
+ argpathlist := c.Args()
// get remote stat data
- stats := map[string]*sftp.StatVFS{}
- for server, client := range r.Client {
+ stats := map[string]map[string]*sftp.StatVFS{}
+
+ // set tabwriter
+ tabw := new(tabwriter.Writer)
+ tabw.Init(os.Stdout, 0, 8, 4, ' ', tabwriter.AlignRight)
+
+ if len(argpathlist) == 0 {
+ argpathlist = append(argpathlist, "./")
+ }
+
+ targetmap := map[string]*TargetConnectMap{}
+ for _, p := range argpathlist {
+ targetmap = r.createTargetMap(targetmap, p)
+ }
+
+ for server, client := range targetmap {
+ // set map
+ stats[server] = map[string]*sftp.StatVFS{}
+
// set ftp client
ftp := client.Connect
// set path
- path := client.Pwd
- if len(argpath) > 0 {
-
+ pathlist := []string{}
+ if len(client.Path) > 0 {
+ pathlist = append(pathlist, client.Path...)
+ } else {
+ pathlist = append(pathlist, client.Pwd)
}
// get StatVFS
- stat, err := ftp.StatVFS(path)
- if err != nil {
- fmt.Println(err)
- continue
+ for _, path := range pathlist {
+ stat, err := ftp.StatVFS(path)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %s: %s\n", server, err)
+ continue
+ }
+ stats[server][path] = stat
}
- stats[server] = stat
- }
- // set tabwriter
- tabw := new(tabwriter.Writer)
- tabw.Init(os.Stdout, 0, 8, 4, ' ', tabwriter.AlignRight)
+ }
// print header
headerTotal := "TotalSize"
if c.Bool("i") {
headerTotal = "Inodes"
}
- fmt.Fprintf(tabw, "%s\t%s\t%s\t%s\t%s\t\n", "Server", headerTotal, "Used", "(root)", "Capacity")
+ fmt.Fprintf(tabw, "%s\t%s\t%s\t%s\t%s\t%s\t\n", "Server", "Path", headerTotal, "Used", "(root)", "Capacity")
// print stat
- for server, stat := range stats {
- // set data in columns
- var column1, column2, column3, column4, column5 string
- switch {
- case c.Bool("i"):
- totals := stat.Files
- frees := stat.Ffree
- useds := totals - frees
-
- column1 = server
- column2 = strconv.FormatUint(totals, 10)
- column3 = strconv.FormatUint(useds, 10)
- column4 = strconv.FormatUint(frees, 10)
- column5 = fmt.Sprintf("%0.2f", (float64(useds)/float64(totals))*100)
-
- case c.Bool("h"):
- totals := stat.TotalSpace()
- frees := stat.FreeSpace()
- useds := stat.TotalSpace() - stat.FreeSpace()
-
- column1 = server
- column2 = humanize.IBytes(totals)
- column3 = humanize.IBytes(useds)
- column4 = humanize.IBytes(frees)
- column5 = fmt.Sprintf("%0.2f", (float64(useds)/float64(totals))*100)
-
- default:
- totals := stat.TotalSpace()
- frees := stat.FreeSpace()
- useds := stat.TotalSpace() - stat.FreeSpace()
-
- column1 = server
- column2 = strconv.FormatUint(totals/1024, 10)
- column3 = strconv.FormatUint(useds/1024, 10)
- column4 = strconv.FormatUint(frees/1024, 10)
- column5 = fmt.Sprintf("%0.2f", (float64(useds)/float64(totals))*100)
+ for server, statlist := range stats {
+ for path, stat := range statlist {
+ // set data in columns
+ var column1, column2, column3, column4, column5, column6 string
+ switch {
+ case c.Bool("i"):
+ totals := stat.Files
+ frees := stat.Ffree
+ useds := totals - frees
+
+ column1 = server
+ column2 = path
+ column3 = strconv.FormatUint(totals, 10)
+ column4 = strconv.FormatUint(useds, 10)
+ column5 = strconv.FormatUint(frees, 10)
+ column6 = fmt.Sprintf("%0.2f", (float64(useds)/float64(totals))*100)
+
+ case c.Bool("h"):
+ totals := stat.TotalSpace()
+ frees := stat.FreeSpace()
+ useds := stat.TotalSpace() - stat.FreeSpace()
+
+ column1 = server
+ column2 = path
+ column3 = humanize.IBytes(totals)
+ column4 = humanize.IBytes(useds)
+ column5 = humanize.IBytes(frees)
+ column6 = fmt.Sprintf("%0.2f", (float64(useds)/float64(totals))*100)
+
+ default:
+ totals := stat.TotalSpace()
+ frees := stat.FreeSpace()
+ useds := stat.TotalSpace() - stat.FreeSpace()
+
+ column1 = server
+ column2 = path
+ column3 = strconv.FormatUint(totals/1024, 10)
+ column4 = strconv.FormatUint(useds/1024, 10)
+ column5 = strconv.FormatUint(frees/1024, 10)
+ column6 = fmt.Sprintf("%0.2f", (float64(useds)/float64(totals))*100)
+ }
+
+ fmt.Fprintf(tabw, "%s\t%s\t%s\t%s\t%s\t%s%%\t\n", column1, column2, column3, column4, column5, column6)
}
-
- fmt.Fprintf(tabw, "%s\t%s\t%s\t%s\t%s%%\t\n", column1, column2, column3, column4, column5)
}
// write tabwriter
diff --git a/sftp/cmd_get.go b/sftp/cmd_get.go
index 0550b6bc..aa93d219 100644
--- a/sftp/cmd_get.go
+++ b/sftp/cmd_get.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -9,6 +9,7 @@ import (
"io"
"os"
"path/filepath"
+ "strings"
"sync"
"time"
@@ -17,13 +18,10 @@ import (
"github.com/vbauerster/mpb"
)
-// TODO(blacknon): リファクタリング(v0.6.2)
-
//
func (r *RunSftp) get(args []string) {
// create app
app := cli.NewApp()
- // app.UseShortOptionHandling = true
// set help message
app.CustomAppHelpTemplate = helptext
@@ -31,16 +29,16 @@ func (r *RunSftp) get(args []string) {
// set parameter
app.Name = "get"
app.Usage = "lsftp build-in command: get"
- app.ArgsUsage = "[source(remote) target(local)]"
+ app.ArgsUsage = "source(remote)... target(local)"
app.HideHelp = true
app.HideVersion = true
app.EnableBashCompletion = true
// action
app.Action = func(c *cli.Context) error {
- if len(c.Args()) != 2 {
- fmt.Println("Requires two arguments")
- fmt.Println("get source(remote) target(local)")
+ if len(c.Args()) < 2 {
+ fmt.Println("Requires over two arguments")
+ fmt.Println("get source(remote)... target(local)")
return nil
}
@@ -48,35 +46,41 @@ func (r *RunSftp) get(args []string) {
r.ProgressWG = new(sync.WaitGroup)
r.Progress = mpb.New(mpb.WithWaitGroup(r.ProgressWG))
- // set path
- source := c.Args()[0]
- target := c.Args()[1]
+ // set pathlist
+ argsSize := len(c.Args()) - 1
+ source := c.Args()[:argsSize]
+ destination := c.Args()[argsSize]
- // get target directory abs
- target, err := filepath.Abs(target)
+ // get destination directory abs
+ destination, err := filepath.Abs(destination)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
return nil
}
- // mkdir local target directory
- err = os.MkdirAll(target, 0755)
+ // mkdir local destination directory
+ err = os.MkdirAll(destination, 0755)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
return nil
}
+ targetmap := map[string]*TargetConnectMap{}
+ for _, spath := range source {
+ targetmap = r.createTargetMap(targetmap, spath)
+ }
+
// get directory data, copy remote to local
exit := make(chan bool)
- for s, c := range r.Client {
+ for s, c := range targetmap {
server := s
client := c
- targetdir := target
- if len(r.Client) > 1 {
- targetdir = filepath.Join(target, server)
+ targetDestinationDir := destination
+ if len(targetmap) > 1 {
+ targetDestinationDir = filepath.Join(targetDestinationDir, server)
// mkdir local target directory
- err = os.MkdirAll(targetdir, 0755)
+ err = os.MkdirAll(targetDestinationDir, 0755)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
return nil
@@ -91,17 +95,14 @@ func (r *RunSftp) get(args []string) {
// create output
client.Output.Create(server)
- // local target
- target, _ = filepath.Abs(target)
-
- err = r.pullPath(client, source, targetdir)
+ err = r.pullData(client, targetDestinationDir)
exit <- true
}()
}
// wait exit
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
close(exit)
@@ -123,71 +124,83 @@ func (r *RunSftp) get(args []string) {
}
//
-func (r *RunSftp) pullPath(client *SftpConnect, path, target string) (err error) {
- // set arg path
- var rpath string
- switch {
- case filepath.IsAbs(path):
- rpath = path
- case !filepath.IsAbs(path):
- rpath = filepath.Join(client.Pwd, path)
- }
- base := filepath.Dir(rpath)
-
- // get writer
- ow := client.Output.NewWriter()
-
- // expantion path
- epath, _ := client.Connect.Glob(rpath)
+func (r *RunSftp) pullData(client *TargetConnectMap, targetdir string) (err error) {
+ for _, path := range client.Path {
+ // set arg path
+ var rpath string
+ switch {
+ case filepath.IsAbs(path):
+ rpath = path
+ case !filepath.IsAbs(path):
+ rpath = filepath.Join(client.Pwd, path)
+ }
+ base := filepath.Dir(rpath)
- // for walk
- for _, ep := range epath {
- walker := client.Connect.Walk(ep)
+ // get writer
+ ow := client.Output.NewWriter()
- for walker.Step() {
- err := walker.Err()
- if err != nil {
- fmt.Fprintf(ow, "Error: %s\n", err)
- continue
- }
+ // expantion path
+ epath, _ := client.Connect.Glob(rpath)
- p := walker.Path()
- relpath, _ := filepath.Rel(base, p)
- stat := walker.Stat()
+ if len(epath) == 0 {
+ fmt.Fprintf(ow, "Error: File Not founds.\n")
+ return
+ }
- localpath := filepath.Join(target, relpath)
+ // for walk
+ for _, ep := range epath {
- //
- if stat.IsDir() { // is directory
- os.Mkdir(localpath, 0755)
- } else { // is not directory
- // get size
- size := stat.Size()
+ walker := client.Connect.Walk(ep)
- // open remote file
- remotefile, err := client.Connect.Open(p)
+ for walker.Step() {
+ err := walker.Err()
if err != nil {
fmt.Fprintf(ow, "Error: %s\n", err)
continue
}
- // open local file
- localfile, err := os.OpenFile(localpath, os.O_RDWR|os.O_CREATE, 0644)
- if err != nil {
- fmt.Fprintf(ow, "Error: %s\n", err)
- continue
+ p := walker.Path()
+ relpath, _ := filepath.Rel(base, p)
+ relpath = strings.Replace(relpath, "../", "", 1)
+ if strings.Contains(relpath, "/") {
+ os.MkdirAll(filepath.Join(targetdir, filepath.Dir(relpath)), 0755)
}
- // set tee reader
- rd := io.TeeReader(remotefile, localfile)
-
- r.ProgressWG.Add(1)
- client.Output.ProgressPrinter(size, rd, p)
- }
+ stat := walker.Stat()
+ localpath := filepath.Join(targetdir, relpath)
+
+ //
+ if stat.IsDir() { // is directory
+ os.MkdirAll(localpath, 0755)
+ } else { // is not directory
+ // get size
+ size := stat.Size()
+
+ // open remote file
+ remotefile, err := client.Connect.Open(p)
+ if err != nil {
+ fmt.Fprintf(ow, "Error: %s\n", err)
+ continue
+ }
+
+ // open local file
+ localfile, err := os.OpenFile(localpath, os.O_RDWR|os.O_CREATE, 0644)
+ if err != nil {
+ fmt.Fprintf(ow, "Error: %s\n", err)
+ continue
+ }
+
+ // set tee reader
+ rd := io.TeeReader(remotefile, localfile)
+
+ r.ProgressWG.Add(1)
+ client.Output.ProgressPrinter(size, rd, p)
+ }
- // set mode
- if r.Permission {
- os.Chmod(localpath, stat.Mode())
+ // set mode
+ if r.Permission {
+ os.Chmod(localpath, stat.Mode())
+ }
}
}
}
diff --git a/sftp/cmd_lls.go b/sftp/cmd_lls.go
index 314dd503..26a229fc 100644
--- a/sftp/cmd_lls.go
+++ b/sftp/cmd_lls.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -12,8 +12,8 @@ import (
"io/ioutil"
"os"
pkguser "os/user"
+ "runtime"
"strconv"
- "syscall"
"text/tabwriter"
"github.com/blacknon/lssh/common"
@@ -108,11 +108,9 @@ func (r *RunSftp) lls(args []string) (err error) {
timestamp := f.ModTime()
timestr = timestamp.Format("2006 01-02 15:04:05")
- sys := f.Sys()
- if stat, ok := sys.(*syscall.Stat_t); ok {
- uid = stat.Uid
- gid = stat.Gid
- size = stat.Size
+ if runtime.GOOS != "windows" {
+ system := f.Sys()
+ uid, gid, size = getFileStat(system)
}
// Switch with or without -n option.
diff --git a/sftp/cmd_ln.go b/sftp/cmd_ln.go
index 2c47345c..8e2993a8 100644
--- a/sftp/cmd_ln.go
+++ b/sftp/cmd_ln.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -9,6 +9,7 @@ package sftp
import (
"fmt"
+ "path/filepath"
"github.com/blacknon/lssh/common"
"github.com/urfave/cli"
@@ -39,16 +40,48 @@ func (r *RunSftp) ln(args []string) (err error) {
return nil
}
- // for s, cl := range r.Client {
- // server := s
- // client := cl
+ // parse old path, with server...
+ source := c.Args()[0]
+ targetmap := map[string]*TargetConnectMap{}
+ targetmap = r.createTargetMap(targetmap, source)
+ target := c.Args()[1]
- // go func() {
- // // get writer
- // client.Output.Create(server)
- // w := client.Output.NewWriter()
- // }()
- // }
+ exit := make(chan bool)
+ for s, cl := range targetmap {
+ server := s
+ client := cl
+
+ go func() {
+ // get writer
+ client.Output.Create(server)
+ w := client.Output.NewWriter()
+
+ source := client.Path[0]
+
+ // set arg path
+ if !filepath.IsAbs(source) {
+ source = filepath.Join(client.Pwd, source)
+ }
+
+ if !filepath.IsAbs(target) {
+ target = filepath.Join(client.Pwd, target)
+ }
+
+ err := client.Connect.Link(source, target)
+ if err != nil {
+ fmt.Fprintf(w, "%s\n", err)
+ exit <- true
+ return
+ }
+
+ exit <- true
+ return
+ }()
+ }
+
+ for i := 0; i < len(targetmap); i++ {
+ <-exit
+ }
return nil
}
diff --git a/sftp/cmd_ls.go b/sftp/cmd_ls.go
index 17ad216d..ad291c01 100644
--- a/sftp/cmd_ls.go
+++ b/sftp/cmd_ls.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -11,6 +11,7 @@ package sftp
import (
"fmt"
+ "io/fs"
"io/ioutil"
"os"
"path/filepath"
@@ -27,73 +28,23 @@ import (
"github.com/urfave/cli"
)
+type FileInfo interface {
+ fs.FileInfo
+}
+
+type sftpFileInfo struct {
+ FileInfo
+ Dir string
+}
+
// sftpLs
type sftpLs struct {
- Client *SftpConnect
- Files []os.FileInfo
+ Client *TargetConnectMap
+ Files []sftpFileInfo
Passwd string
Groups string
}
-// getRemoteLsData
-func (r *RunSftp) getRemoteLsData(client *SftpConnect, path string) (lsdata sftpLs, err error) {
- // get symlink
- p, err := client.Connect.ReadLink(path)
- if err == nil {
- path = p
- }
-
- // get stat
- lstat, err := client.Connect.Lstat(path)
- if err != nil {
- return
- }
-
- // get path data
- var data []os.FileInfo
- if lstat.IsDir() {
- // get directory list data
- data, err = client.Connect.ReadDir(path)
- if err != nil {
- return
- }
- } else {
- data = []os.FileInfo{lstat}
- }
-
- // read /etc/passwd
- passwdFile, err := client.Connect.Open("/etc/passwd")
- if err != nil {
- return
- }
- passwdByte, err := ioutil.ReadAll(passwdFile)
- if err != nil {
- return
- }
- passwd := string(passwdByte)
-
- // read /etc/group
- groupFile, err := client.Connect.Open("/etc/group")
- if err != nil {
- return
- }
- groupByte, err := ioutil.ReadAll(groupFile)
- if err != nil {
- return
- }
- groups := string(groupByte)
-
- // set lsdata
- lsdata = sftpLs{
- Client: client,
- Files: data,
- Passwd: passwd,
- Groups: groups,
- }
-
- return
-}
-
// ls exec and print out remote ls data.
func (r *RunSftp) ls(args []string) (err error) {
// create app
@@ -117,7 +68,7 @@ func (r *RunSftp) ls(args []string) (err error) {
}
app.Name = "ls"
app.Usage = "lsftp build-in command: ls [remote machine ls]"
- app.ArgsUsage = "[PATH]"
+ app.ArgsUsage = "[host,host...:][PATH]..."
app.HideHelp = true
app.HideVersion = true
app.EnableBashCompletion = true
@@ -125,203 +76,338 @@ func (r *RunSftp) ls(args []string) (err error) {
// action
app.Action = func(c *cli.Context) error {
// argpath
- argpath := c.Args().First()
-
- // get directory files data
- exit := make(chan bool)
- lsdata := map[string]sftpLs{}
- m := new(sync.Mutex)
- for s, cl := range r.Client {
- server := s
- client := cl
-
- go func() {
- // get output
- client.Output.Create(server)
- w := client.Output.NewWriter()
-
- // set path
- path := client.Pwd
- if len(argpath) > 0 {
- if !filepath.IsAbs(argpath) {
- path = filepath.Join(path, argpath)
- } else {
- path = argpath
+ argData := c.Args()
+
+ targetmap := map[string]*TargetConnectMap{}
+
+ if len(argData) > 0 {
+ for _, arg := range argData {
+ // sftp target host
+ targetmap = r.createTargetMap(targetmap, arg)
+ }
+ } else {
+ for server, client := range r.Client {
+ // sftp target host
+ targetmap[server] = &TargetConnectMap{}
+ targetmap[server].SftpConnect = *client
+ }
+ }
+
+ r.executeRemoteLs(c, targetmap)
+
+ return nil
+ }
+
+ // parse short options
+ args = common.ParseArgs(app.Flags, args)
+ app.Run(args)
+
+ return
+}
+
+func (r *RunSftp) executeRemoteLs(c *cli.Context, clients map[string]*TargetConnectMap) {
+ lsdata := map[string]sftpLs{}
+ exit := make(chan bool)
+ m := new(sync.Mutex)
+ for s, cl := range clients {
+ server := s
+ client := cl
+
+ go func() {
+ // get output
+ client.Output.Create(server)
+ w := client.Output.NewWriter()
+
+ if len(client.Path) > 0 {
+ for i, path := range client.Path {
+ if !filepath.IsAbs(path) {
+ client.Path[i] = filepath.Join(client.Pwd, path)
}
}
+ } else {
+ client.Path = append(client.Path, client.Pwd)
+ }
- // get ls data
- data, err := r.getRemoteLsData(client, path)
- if err != nil {
- fmt.Fprintf(w, "Error: %s\n", err)
- exit <- true
- return
- }
+ // get ls data
+ data, err := r.getRemoteLsData(client)
+ if err != nil {
+ fmt.Fprintf(w, "Error: %s\n", err)
+ exit <- true
+ return
+ }
+
+ if !c.Bool("a") {
+ }
+
+ // hidden delete data slice
+ hddata := []sftpFileInfo{}
+
+ // regex
+ rgx := regexp.MustCompile(`^\.`)
+ for _, f := range data.Files {
// if `a` flag disable, delete Hidden files...
if !c.Bool("a") {
- // hidden delete data slice
- hddata := []os.FileInfo{}
+ if !rgx.MatchString(f.Name()) {
+ hddata = append(hddata, f)
+ }
+ continue
+ }
- // regex
- rgx := regexp.MustCompile(`^\.`)
+ hddata = append(hddata, f)
- for _, f := range data.Files {
- if !rgx.MatchString(f.Name()) {
- hddata = append(hddata, f)
- }
- }
+ }
+
+ // sort
+ r.SortLsData(c, hddata)
- data.Files = hddata
+ data.Files = hddata
+
+ // write lsdata
+ m.Lock()
+ lsdata[server] = data
+ m.Unlock()
+
+ exit <- true
+ }()
+ }
+
+ // wait get directory data
+ for i := 0; i < len(clients); i++ {
+ <-exit
+ }
+
+ switch {
+ case c.Bool("l"): // long list format
+ // set tabwriter
+ tabw := new(tabwriter.Writer)
+ tabw.Init(os.Stdout, 0, 1, 1, ' ', 0)
+
+ // get maxSizeWidth
+ var maxSizeWidth int
+ var sizestr string
+ for _, data := range lsdata {
+ for _, f := range data.Files {
+ if c.Bool("h") {
+ sizestr = humanize.Bytes(uint64(f.Size()))
+ } else {
+ sizestr = strconv.FormatUint(uint64(f.Size()), 10)
+ }
+
+ // set sizestr max length
+ if maxSizeWidth < len(sizestr) {
+ maxSizeWidth = len(sizestr)
+ }
+ }
+ }
+
+ // print list ls
+ for server, data := range lsdata {
+ // get prompt
+ data.Client.Output.Create(server)
+ prompt := data.Client.Output.GetPrompt()
+
+ // for get data
+ datas := []*sftpLsData{}
+ for _, f := range lsdata[server].Files {
+ sys := f.Sys()
+
+ // TODO(blacknon): count hardlink (2列目)の取得方法がわからないため、わかったら追加。
+ var uid, gid uint32
+ var size uint64
+ var user, group, timestr, sizestr string
+
+ if stat, ok := sys.(*sftp.FileStat); ok {
+ uid = stat.UID
+ gid = stat.GID
+ size = stat.Size
+ timestamp := time.Unix(int64(stat.Mtime), 0)
+ timestr = timestamp.Format("2006 01-02 15:04:05")
}
- // sort
- r.SortLsData(c, data.Files)
+ // Switch with or without -n option.
+ if c.Bool("n") {
+ user = strconv.FormatUint(uint64(uid), 10)
+ group = strconv.FormatUint(uint64(gid), 10)
+ } else {
+ user, _ = common.GetNameFromId(lsdata[server].Passwd, uid)
+ group, _ = common.GetNameFromId(lsdata[server].Groups, gid)
+ }
- // write lsdata
- m.Lock()
- lsdata[server] = data
- m.Unlock()
+ // Switch with or without -h option.
+ if c.Bool("h") {
+ sizestr = humanize.Bytes(size)
+ } else {
+ sizestr = strconv.FormatUint(size, 10)
+ }
- exit <- true
- }()
+ // set data
+ data := new(sftpLsData)
+ data.Mode = f.Mode().String()
+ data.User = user
+ data.Group = group
+ data.Size = sizestr
+ data.Time = timestr
+ data.Path = filepath.Join(f.Dir, f.Name())
+
+ // append data
+ datas = append(datas, data)
+
+ if len(lsdata) == 1 {
+ // set print format
+ format := "%s\t%s\t%s\t%" + strconv.Itoa(maxSizeWidth) + "s\t%s\t%s\n"
+
+ // write data
+ fmt.Fprintf(tabw, format, data.Mode, data.User, data.Group, data.Size, data.Time, data.Path)
+ } else {
+ // set print format
+ format := "%s\t%s\t%s\t%s\t%" + strconv.Itoa(maxSizeWidth) + "s\t%s\t%s\n"
+
+ // write data
+ fmt.Fprintf(tabw, format, prompt, data.Mode, data.User, data.Group, data.Size, data.Time, data.Path)
+ }
+ }
}
- // wait get directory data
- for i := 0; i < len(r.Client); i++ {
- <-exit
+ tabw.Flush()
+
+ case c.Bool("1"): // list 1 file per line
+ // for list
+ for server, data := range lsdata {
+ data.Client.Output.Create(server)
+ w := data.Client.Output.NewWriter()
+
+ for _, f := range data.Files {
+ name := filepath.Join(f.Dir, f.Name())
+
+ fmt.Fprintf(w, "%s\n", name)
+ }
}
- switch {
- case c.Bool("l"): // long list format
- // set tabwriter
- tabw := new(tabwriter.Writer)
- tabw.Init(os.Stdout, 0, 1, 1, ' ', 0)
-
- // get maxSizeWidth
- var maxSizeWidth int
- var sizestr string
- for _, data := range lsdata {
- for _, f := range data.Files {
- if c.Bool("h") {
- sizestr = humanize.Bytes(uint64(f.Size()))
- } else {
- sizestr = strconv.FormatUint(uint64(f.Size()), 10)
- }
+ default: // default
+ for server, data := range lsdata {
+ // get header width
+ data.Client.Output.Create(server)
+ w := data.Client.Output.NewWriter()
+ headerWidth := len(data.Client.Output.Prompt)
- // set sizestr max length
- if maxSizeWidth < len(sizestr) {
- maxSizeWidth = len(sizestr)
- }
- }
+ var item []string
+ for _, f := range data.Files {
+ item = append(item, f.Name())
}
- // print list ls
- for server, data := range lsdata {
- // get prompt
- data.Client.Output.Create(server)
- prompt := data.Client.Output.GetPrompt()
-
- // for get data
- datas := []*sftpLsData{}
- for _, f := range lsdata[server].Files {
- sys := f.Sys()
-
- // TODO(blacknon): count hardlink (2列目)の取得方法がわからないため、わかったら追加。
- var uid, gid uint32
- var size uint64
- var user, group, timestr, sizestr string
-
- if stat, ok := sys.(*sftp.FileStat); ok {
- uid = stat.UID
- gid = stat.GID
- size = stat.Size
- timestamp := time.Unix(int64(stat.Mtime), 0)
- timestr = timestamp.Format("2006 01-02 15:04:05")
- }
+ textcol.Output = w
+ textcol.Padding = headerWidth
+ textcol.PrintColumns(&item, 2)
+ }
+ }
+}
- // Switch with or without -n option.
- if c.Bool("n") {
- user = strconv.FormatUint(uint64(uid), 10)
- group = strconv.FormatUint(uint64(gid), 10)
- } else {
- user, _ = common.GetNameFromId(lsdata[server].Passwd, uid)
- group, _ = common.GetNameFromId(lsdata[server].Groups, gid)
- }
+// getRemoteLsData
+func (r *RunSftp) getRemoteLsData(client *TargetConnectMap) (lsdata sftpLs, err error) {
+ w := client.Output.NewWriter()
- // Switch with or without -h option.
- if c.Bool("h") {
- sizestr = humanize.Bytes(size)
- } else {
- sizestr = strconv.FormatUint(size, 10)
- }
+ data := []sftpFileInfo{}
+ re := regexp.MustCompile(`(.+)/$`)
- // set data
- data := new(sftpLsData)
- data.Mode = f.Mode().String()
- data.User = user
- data.Group = group
- data.Size = sizestr
- data.Time = timestr
- data.Path = f.Name()
-
- // append data
- datas = append(datas, data)
-
- if len(lsdata) == 1 {
- // set print format
- format := "%s\t%s\t%s\t%" + strconv.Itoa(maxSizeWidth) + "s\t%s\t%s\n"
-
- // write data
- fmt.Fprintf(tabw, format, data.Mode, data.User, data.Group, data.Size, data.Time, data.Path)
- } else {
- // set print format
- format := "%s\t%s\t%s\t%s\t%" + strconv.Itoa(maxSizeWidth) + "s\t%s\t%s\n"
-
- // write data
- fmt.Fprintf(tabw, format, prompt, data.Mode, data.User, data.Group, data.Size, data.Time, data.Path)
- }
- }
+ for _, ep := range client.Path {
+ ep = re.ReplaceAllString(ep, "$1")
+
+ // get glob
+ epath, err := client.Connect.Glob(ep)
+ if err != nil {
+ fmt.Fprintf(w, "Error: %s\n", err)
+ continue
+ }
+
+ if len(epath) == 0 {
+ fmt.Fprintf(w, "Error: %s not found.\n", ep)
+ continue
+ }
+
+ for _, path := range epath {
+ // get symlink
+ p, err := client.Connect.ReadLink(path)
+ if err == nil {
+ path = p
}
- tabw.Flush()
+ // get stat
+ lstat, err := client.Connect.Lstat(path)
+ if err != nil {
+ fmt.Fprintf(w, "Error: %s\n", err)
+ continue
+ }
- case c.Bool("1"): // list 1 file per line
- // for list
- for server, data := range lsdata {
- data.Client.Output.Create(server)
- w := data.Client.Output.NewWriter()
+ // get path data
+ if lstat.IsDir() {
+ // get directory list data
+ lsdata, err := client.Connect.ReadDir(path)
+ if err != nil {
+ fmt.Fprintf(w, "Error: %s\n", err)
+ continue
+ }
- for _, f := range data.Files {
- name := f.Name()
- fmt.Fprintf(w, "%s\n", name)
+ if len(lsdata) == 0 {
+ dir := filepath.Dir(path)
+ fi := sftpFileInfo{
+ FileInfo: lstat,
+ Dir: dir,
+ }
+ data = append(data, fi)
}
- }
- default: // default
- for server, data := range lsdata {
- // get header width
- data.Client.Output.Create(server)
- w := data.Client.Output.NewWriter()
- headerWidth := len(data.Client.Output.Prompt)
+ for _, d := range lsdata {
+ dir := path
+ fi := sftpFileInfo{
+ FileInfo: d,
+ Dir: dir,
+ }
+
+ data = append(data, fi)
+ }
- var item []string
- for _, f := range data.Files {
- item = append(item, f.Name())
+ } else {
+ dir := filepath.Dir(path)
+ fi := sftpFileInfo{
+ FileInfo: lstat,
+ Dir: dir,
}
- textcol.Output = w
- textcol.Padding = headerWidth
- textcol.PrintColumns(&item, 2)
+ data = append(data, fi)
}
}
+ }
- return nil
+ // read /etc/passwd
+ passwdFile, err := client.Connect.Open("/etc/passwd")
+ if err != nil {
+ return
+ }
+ passwdByte, err := ioutil.ReadAll(passwdFile)
+ if err != nil {
+ return
}
+ passwd := string(passwdByte)
- // parse short options
- args = common.ParseArgs(app.Flags, args)
- app.Run(args)
+ // read /etc/group
+ groupFile, err := client.Connect.Open("/etc/group")
+ if err != nil {
+ return
+ }
+ groupByte, err := ioutil.ReadAll(groupFile)
+ if err != nil {
+ return
+ }
+ groups := string(groupByte)
+
+ // set lsdata
+ lsdata = sftpLs{
+ Client: client,
+ Files: data,
+ Passwd: passwd,
+ Groups: groups,
+ }
return
}
diff --git a/sftp/cmd_mkdir.go b/sftp/cmd_mkdir.go
index b94aa5e2..827532ae 100644
--- a/sftp/cmd_mkdir.go
+++ b/sftp/cmd_mkdir.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -31,7 +31,7 @@ func (r *RunSftp) mkdir(args []string) {
app.CustomAppHelpTemplate = helptext
app.Name = "mkdir"
app.Usage = "lsftp build-in command: mkdir [remote machine mkdir]"
- app.ArgsUsage = "[path]"
+ app.ArgsUsage = "path..."
app.HideHelp = true
app.HideVersion = true
app.EnableBashCompletion = true
@@ -39,47 +39,54 @@ func (r *RunSftp) mkdir(args []string) {
// action
app.Action = func(c *cli.Context) error {
// TODO(blacknon): 複数のディレクトリ受付(v0.6.2以降)
- if len(c.Args()) != 1 {
- fmt.Println("Requires one arguments")
- fmt.Println("mkdir [path]")
+ if len(c.Args()) < 1 {
+ fmt.Println("Requires more one arguments")
+ fmt.Println("mkdir path...")
return nil
}
+ targetmap := map[string]*TargetConnectMap{}
+ for _, p := range c.Args() {
+ targetmap = r.createTargetMap(targetmap, p)
+ }
+
exit := make(chan bool)
- for s, cl := range r.Client {
+ for s, cl := range targetmap {
server := s
client := cl
- path := c.Args()[0]
go func() {
// get writer
client.Output.Create(server)
w := client.Output.NewWriter()
- // set arg path
- if !filepath.IsAbs(path) {
- path = filepath.Join(client.Pwd, path)
- }
-
- // create directory
- var err error
- if c.Bool("p") {
- err = client.Connect.MkdirAll(path)
- } else {
- err = client.Connect.Mkdir(path)
+ for _, path := range client.Path {
+ // set arg path
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(client.Pwd, path)
+ }
+
+ // create directory
+ var err error
+ if c.Bool("p") {
+ err = client.Connect.MkdirAll(path)
+ } else {
+ err = client.Connect.Mkdir(path)
+ }
+
+ // check error
+ if err != nil {
+ fmt.Fprintf(w, "%s\n", err)
+ }
+
+ fmt.Fprintf(w, "make directory: %s\n", path)
}
- // check error
- if err != nil {
- fmt.Fprintf(w, "%s\n", err)
- }
-
- fmt.Fprintf(w, "make directory: %s\n", path)
exit <- true
}()
}
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
@@ -116,22 +123,25 @@ func (r *RunSftp) lmkdir(args []string) {
// action
app.Action = func(c *cli.Context) error {
// TODO(blacknon): 複数のディレクトリ受付(v0.6.2以降)
- if len(c.Args()) != 1 {
- fmt.Println("Requires one arguments")
- fmt.Println("lmkdir [path]")
+ if len(c.Args()) < 1 {
+ fmt.Println("Requires more one arguments")
+ fmt.Println("lmkdir path...")
return nil
}
- path := c.Args()[0]
+ pathlist := c.Args()
var err error
- if c.Bool("p") {
- err = os.MkdirAll(path, 0755)
- } else {
- err = os.Mkdir(path, 0755)
- }
- if err != nil {
- fmt.Fprintf(os.Stderr, "%s\n", err)
+ for _, path := range pathlist {
+ if c.Bool("p") {
+ err = os.MkdirAll(path, 0755)
+ } else {
+ err = os.Mkdir(path, 0755)
+ }
+
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s\n", err)
+ }
}
return nil
diff --git a/sftp/cmd_put.go b/sftp/cmd_put.go
index dc24922a..2cff881b 100644
--- a/sftp/cmd_put.go
+++ b/sftp/cmd_put.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -32,16 +32,16 @@ func (r *RunSftp) put(args []string) {
// set parameter
app.Name = "put"
app.Usage = "lsftp build-in command: put"
- app.ArgsUsage = "[source(local) target(remote)]"
+ app.ArgsUsage = "source(local)... target(remote)"
app.HideHelp = true
app.HideVersion = true
app.EnableBashCompletion = true
// action
app.Action = func(c *cli.Context) error {
- if len(c.Args()) != 2 {
- fmt.Println("Requires two arguments")
- fmt.Println("put source(local) target(remote)")
+ if len(c.Args()) < 2 {
+ fmt.Println("Requires over two arguments")
+ fmt.Println("put source(local)... target(remote)")
return nil
}
@@ -50,27 +50,34 @@ func (r *RunSftp) put(args []string) {
r.Progress = mpb.New(mpb.WithWaitGroup(r.ProgressWG))
// set path
- source := c.Args()[0]
- target := c.Args()[1]
+ argsSize := len(c.Args()) - 1
+ source := c.Args()[:argsSize]
+ destination := c.Args()[argsSize]
- // get local host directory walk data
pathset := []PathSet{}
- data, err := common.WalkDir(source)
- if err != nil {
- fmt.Fprintf(os.Stderr, "%s\n", err)
- return nil
- }
- sort.Strings(data)
- dataset := PathSet{
- Base: filepath.Dir(source),
- PathSlice: data,
+ for _, l := range source {
+ // get local host directory walk data
+ data, err := common.WalkDir(l)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s\n", err)
+ return nil
+ }
+
+ sort.Strings(data)
+ dataset := PathSet{
+ Base: filepath.Dir(l),
+ PathSlice: data,
+ }
+ pathset = append(pathset, dataset)
}
- pathset = append(pathset, dataset)
+
+ targetmap := map[string]*TargetConnectMap{}
+ targetmap = r.createTargetMap(targetmap, destination)
// parallel push data
exit := make(chan bool)
- for s, c := range r.Client {
+ for s, c := range targetmap {
server := s
client := c
go func() {
@@ -81,13 +88,19 @@ func (r *RunSftp) put(args []string) {
// create output
client.Output.Create(server)
+ // check source multiple
+ isMultiple := false
+ if len(pathset) > 1 {
+ isMultiple = true
+ }
+
// push path
for _, p := range pathset {
base := p.Base
data := p.PathSlice
for _, path := range data {
- r.pushPath(client, target, base, path)
+ r.pushData(client, isMultiple, base, path)
}
}
@@ -97,7 +110,7 @@ func (r *RunSftp) put(args []string) {
}
// wait exit
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
close(exit)
@@ -119,63 +132,70 @@ func (r *RunSftp) put(args []string) {
}
//
-func (r *RunSftp) pushPath(client *SftpConnect, target, base, path string) (err error) {
+func (r *RunSftp) pushData(client *TargetConnectMap, isMultiple bool, base, path string) (err error) {
var rpath string
// set arg relpath
relpath, _ := filepath.Rel(base, path)
- // check target is absolute path
- if !filepath.IsAbs(target) {
- target = filepath.Join(client.Pwd, target)
- }
-
- // set rpath
- if common.IsDirPath(target) {
- rpath = filepath.Join(target, relpath)
- } else {
- dInfo, _ := os.Lstat(path)
- if dInfo.IsDir() {
- rpath = filepath.Join(target, relpath)
- client.Connect.Mkdir(target)
- } else {
- rpath = filepath.Clean(target)
+ for _, target := range client.Path {
+ // check target is absolute path
+ if !filepath.IsAbs(target) {
+ target = filepath.Join(client.Pwd, target)
}
- }
- // get local file info
- fInfo, _ := os.Lstat(path)
- if fInfo.IsDir() { // directory
- client.Connect.Mkdir(rpath)
- } else { //file
- // open local file
- localfile, err := os.Open(path)
- if err != nil {
- return err
+ // set rpath
+ lstat, err := client.Connect.Lstat(target)
+ if err == nil {
+ if lstat.IsDir() {
+ rpath = filepath.Join(target, relpath)
+ }
}
- defer localfile.Close()
- // get file size
- lstat, _ := os.Lstat(path)
- size := lstat.Size()
+ if len(rpath) == 0 {
+ dInfo, _ := os.Lstat(path)
+ if dInfo.IsDir() || isMultiple {
+ rpath = filepath.Join(target, relpath)
+ client.Connect.Mkdir(target)
+ } else {
+ rpath = filepath.Clean(target)
+ }
+ }
- // copy file
- err = r.pushFile(client, localfile, rpath, size)
- if err != nil {
- return err
+ // get local file info
+ fInfo, _ := os.Lstat(path)
+ if fInfo.IsDir() { // directory
+ client.Connect.Mkdir(rpath)
+ } else { //file
+ // open local file
+ localfile, err := os.Open(path)
+ if err != nil {
+ return err
+ }
+ defer localfile.Close()
+
+ // get file size
+ lstat, _ := os.Lstat(path)
+ size := lstat.Size()
+
+ // copy file
+ err = r.pushFile(client, localfile, rpath, size)
+ if err != nil {
+ return err
+ }
}
- }
- // set mode
- if r.Permission {
- client.Connect.Chmod(rpath, fInfo.Mode())
+ // set mode
+ if r.Permission {
+ client.Connect.Chmod(rpath, fInfo.Mode())
+ }
}
return
}
// pushfile put file to path.
-func (r *RunSftp) pushFile(client *SftpConnect, localfile io.Reader, path string, size int64) (err error) {
+func (r *RunSftp) pushFile(client *TargetConnectMap, localfile io.Reader, path string, size int64) (err error) {
// mkdir all
dir := filepath.Dir(path)
err = client.Connect.MkdirAll(dir)
diff --git a/sftp/cmd_pwd.go b/sftp/cmd_pwd.go
index c967bf66..c2fe3015 100644
--- a/sftp/cmd_pwd.go
+++ b/sftp/cmd_pwd.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/sftp/cmd_rename.go b/sftp/cmd_rename.go
index c6302ac6..c1497c05 100644
--- a/sftp/cmd_rename.go
+++ b/sftp/cmd_rename.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -34,20 +34,24 @@ func (r *RunSftp) rename(args []string) {
return nil
}
+ // parse old path, with server...
+ oldname := c.Args()[0]
+ targetmap := map[string]*TargetConnectMap{}
+ targetmap = r.createTargetMap(targetmap, oldname)
+
exit := make(chan bool)
- for s, cl := range r.Client {
+ for s, cl := range targetmap {
server := s
client := cl
- oldname := c.Args()[0]
newname := c.Args()[1]
-
go func() {
// get writer
client.Output.Create(server)
w := client.Output.NewWriter()
// get current directory
+ oldname := client.Path[0]
err := client.Connect.Rename(oldname, newname)
if err != nil {
fmt.Fprintf(w, "%s\n", err)
@@ -61,7 +65,7 @@ func (r *RunSftp) rename(args []string) {
}()
}
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
diff --git a/sftp/cmd_rm.go b/sftp/cmd_rm.go
index 822ddcea..e6d523dc 100644
--- a/sftp/cmd_rm.go
+++ b/sftp/cmd_rm.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -35,53 +35,67 @@ func (r *RunSftp) rm(args []string) {
// action
app.Action = func(c *cli.Context) error {
- if len(c.Args()) != 1 {
- fmt.Println("Requires one arguments")
- fmt.Println("rm [path]")
+ if len(c.Args()) < 1 {
+ fmt.Println("Requires over one arguments")
+ fmt.Println("rm path...")
return nil
}
+ targetmap := map[string]*TargetConnectMap{}
+ for _, p := range c.Args() {
+ targetmap = r.createTargetMap(targetmap, p)
+ }
+
exit := make(chan bool)
- for s, cl := range r.Client {
+ for s, cl := range targetmap {
server := s
client := cl
- path := c.Args()[0]
go func() {
// get writer
client.Output.Create(server)
w := client.Output.NewWriter()
- // set arg path
- if !filepath.IsAbs(path) {
- path = filepath.Join(client.Pwd, path)
- }
-
- // get current directory
- if c.Bool("r") {
- // create walker
- walker := client.Connect.Walk(path)
+ for _, path := range client.Path {
+ // set arg path
+ if !filepath.IsAbs(path) {
+ path = filepath.Join(client.Pwd, path)
+ }
- var data []string
- for walker.Step() {
- err := walker.Err()
- if err != nil {
- fmt.Fprintf(w, "Error: %s\n", err)
- exit <- true
- return
+ // get current directory
+ if c.Bool("r") {
+ // create walker
+ walker := client.Connect.Walk(path)
+
+ var data []string
+ for walker.Step() {
+ err := walker.Err()
+ if err != nil {
+ fmt.Fprintf(w, "Error: %s\n", err)
+ exit <- true
+ return
+ }
+
+ p := walker.Path()
+ data = append(data, p)
}
- p := walker.Path()
- data = append(data, p)
- }
+ // reverse slice
+ for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
+ data[i], data[j] = data[j], data[i]
+ }
- // reverse slice
- for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
- data[i], data[j] = data[j], data[i]
- }
+ for _, p := range data {
+ err := client.Connect.Remove(p)
+ if err != nil {
+ fmt.Fprintf(w, "%s\n", err)
+ exit <- true
+ return
+ }
+ }
- for _, p := range data {
- err := client.Connect.Remove(p)
+ } else {
+ err := client.Connect.Remove(path)
if err != nil {
fmt.Fprintf(w, "%s\n", err)
exit <- true
@@ -89,21 +103,13 @@ func (r *RunSftp) rm(args []string) {
}
}
- } else {
- err := client.Connect.Remove(path)
- if err != nil {
- fmt.Fprintf(w, "%s\n", err)
- exit <- true
- return
- }
+ fmt.Fprintf(w, "remove: %s\n", path)
}
-
- fmt.Fprintf(w, "remove: %s\n", path)
exit <- true
}()
}
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
diff --git a/sftp/cmd_rmdir.go b/sftp/cmd_rmdir.go
index aeb8cd24..07e37af8 100644
--- a/sftp/cmd_rmdir.go
+++ b/sftp/cmd_rmdir.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -27,25 +27,31 @@ func (r *RunSftp) rmdir(args []string) {
// action
app.Action = func(c *cli.Context) error {
- if len(c.Args()) != 1 {
+ if len(c.Args()) < 1 {
fmt.Println("Requires one arguments")
- fmt.Println("rmdir [path]")
+ fmt.Println("rmdir path...")
return nil
}
- for server, client := range r.Client {
+ targetmap := map[string]*TargetConnectMap{}
+ for _, p := range c.Args() {
+ targetmap = r.createTargetMap(targetmap, p)
+ }
+
+ for server, client := range targetmap {
// get writer
client.Output.Create(server)
w := client.Output.NewWriter()
// remove directory
- err := client.Connect.RemoveDirectory(c.Args()[0])
- if err != nil {
- fmt.Fprintf(w, "%s\n", err)
- return nil
+ for _, path := range client.Path {
+ err := client.Connect.RemoveDirectory(path)
+ if err != nil {
+ fmt.Fprintf(w, "%s\n", err)
+ continue
+ }
+ fmt.Fprintf(w, "remove dir: %s\n", path)
}
-
- fmt.Fprintf(w, "remove dir: %s\n", c.Args()[0])
}
return nil
diff --git a/sftp/cmd_symlink.go b/sftp/cmd_symlink.go
index abbc2649..456df92a 100644
--- a/sftp/cmd_symlink.go
+++ b/sftp/cmd_symlink.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -36,19 +36,24 @@ func (r *RunSftp) symlink(args []string) {
return nil
}
+ // parse old path, with server...
+ source := c.Args()[0]
+ targetmap := map[string]*TargetConnectMap{}
+ targetmap = r.createTargetMap(targetmap, source)
+ target := c.Args()[1]
+
exit := make(chan bool)
- for s, cl := range r.Client {
+ for s, cl := range targetmap {
server := s
client := cl
- source := c.Args()[0]
- target := c.Args()[1]
-
go func() {
// get writer
client.Output.Create(server)
w := client.Output.NewWriter()
+ source := client.Path[0]
+
// set arg path
if !filepath.IsAbs(source) {
source = filepath.Join(client.Pwd, source)
@@ -70,7 +75,7 @@ func (r *RunSftp) symlink(args []string) {
}()
}
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
diff --git a/sftp/cmd_tree.go b/sftp/cmd_tree.go
index 811ae0dd..4a78bc81 100644
--- a/sftp/cmd_tree.go
+++ b/sftp/cmd_tree.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/sftp/common.go b/sftp/common.go
new file mode 100644
index 00000000..c63df6f5
--- /dev/null
+++ b/sftp/common.go
@@ -0,0 +1,19 @@
+package sftp
+
+func DupPermutationsRecursive0(n, k int) [][]int {
+ if k == 0 {
+ pattern := []int{}
+ return [][]int{pattern}
+ }
+
+ ans := [][]int{}
+ for num := 0; num < n; num++ {
+ childPatterns := DupPermutationsRecursive0(n, k-1)
+ for _, childPattern := range childPatterns {
+ pattern := append([]int{num}, childPattern...)
+ ans = append(ans, pattern)
+ }
+ }
+
+ return ans
+}
diff --git a/sftp/common_unix.go b/sftp/common_unix.go
new file mode 100644
index 00000000..40307e4e
--- /dev/null
+++ b/sftp/common_unix.go
@@ -0,0 +1,25 @@
+//go:build !windows
+// +build !windows
+
+// Copyright (c) 2022 Blacknon. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+
+/*
+common is a package that summarizes the common processing of lssh package.
+*/
+
+package sftp
+
+import "golang.org/x/sys/unix"
+
+func getFileStat(i interface{}) (uid, gid uint32, size int64) {
+ if stat, ok := i.(*unix.Stat_t); ok {
+
+ uid = stat.Uid
+ gid = stat.Gid
+ size = stat.Size
+ }
+
+ return
+}
diff --git a/sftp/common_windows.go b/sftp/common_windows.go
new file mode 100644
index 00000000..b75bf533
--- /dev/null
+++ b/sftp/common_windows.go
@@ -0,0 +1,25 @@
+//go:build windows
+// +build windows
+
+// Copyright (c) 2022 Blacknon. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+
+/*
+common is a package that summarizes the common processing of lssh package.
+*/
+
+package sftp
+
+import "golang.org/x/sys/windows"
+
+func getFileStat(i interface{}) (uid, gid uint32, size int64) {
+ if stat, ok := i.(*windows.Win32FileAttributeData); ok {
+ size = int64(stat.FileSizeHigh)
+ }
+
+ uid = 0
+ gid = 0
+
+ return
+}
diff --git a/sftp/main.go b/sftp/main.go
index d208ed8f..dab4b241 100644
--- a/sftp/main.go
+++ b/sftp/main.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -9,6 +9,7 @@ import (
"os"
"sync"
+ "github.com/blacknon/lssh/common"
"github.com/blacknon/lssh/conf"
"github.com/blacknon/lssh/output"
sshl "github.com/blacknon/lssh/ssh"
@@ -28,16 +29,15 @@ type RunSftp struct {
Config conf.Config
// Client
- // TODO(blacknon): Clientと、ターゲットを分けて処理する(ホストをコマンド実行時に指定できるようにするため)
Client map[string]*SftpConnect
- // Target
- Target map[string]*SftpConnect
+ // Complete select client
+ TargetClient map[string]*SftpConnect
// ssh Run
Run *sshl.Run
- // now not use. delete at 0.6.1
+ //
Permission bool
// progress bar
@@ -61,6 +61,13 @@ type SftpConnect struct {
Pwd string
}
+type TargetConnectMap struct {
+ SftpConnect
+
+ // Target Path list
+ Path []string
+}
+
// PathSet struct at path data
type PathSet struct {
Base string
@@ -143,3 +150,45 @@ func (r *RunSftp) createSftpConnect(targets []string) (result map[string]*SftpCo
return result
}
+
+//
+func (r *RunSftp) createTargetMap(srcTargetMap map[string]*TargetConnectMap, pathline string) (targetMap map[string]*TargetConnectMap) {
+ // sftp target host
+ targetMap = srcTargetMap
+
+ // get r.Client keys
+ servers := make([]string, 0, len(r.Client))
+ for k := range r.Client {
+ servers = append(servers, k)
+ }
+
+ // parse pathline
+ targetList, path := common.ParseHostPath(pathline)
+
+ if len(targetList) == 0 {
+ targetList = servers
+ }
+
+ // check exist server.
+ for _, t := range targetList {
+ if !common.Contains(servers, t) {
+ fmt.Fprintf(os.Stderr, "Error: host %s not found.\n", t)
+ continue
+ }
+ }
+
+ // create targetMap
+ for server, client := range r.Client {
+ if common.Contains(targetList, server) {
+ if _, ok := targetMap[server]; !ok {
+ targetMap[server] = &TargetConnectMap{}
+ targetMap[server].SftpConnect = *client
+ }
+
+ // append path
+ targetMap[server].Path = append(targetMap[server].Path, path)
+ }
+ }
+
+ return targetMap
+}
diff --git a/sftp/shell.go b/sftp/shell.go
index 2b9f0c89..4b658510 100644
--- a/sftp/shell.go
+++ b/sftp/shell.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -8,16 +8,17 @@ import (
"fmt"
"os"
"path/filepath"
+ "regexp"
"sort"
"strings"
"sync"
+ "github.com/blacknon/lssh/common"
"github.com/c-bata/go-prompt"
- "github.com/c-bata/go-prompt/completer"
+ "github.com/kballard/go-shellquote"
)
// TODO(blacknon): 補完処理が遅い・不安定になってるので対処する
-// TODO(blacknon): 補完処理で、複数ホスト接続時に一部ホストにしか存在しないディレクトリを指定した場合、そこで処理が止まってしまう挙動を修正する
// sftp Shell mode function
func (r *RunSftp) shell() {
@@ -34,7 +35,7 @@ func (r *RunSftp) shell() {
prompt.OptionLivePrefix(r.CreatePrompt),
prompt.OptionInputTextColor(prompt.Green),
prompt.OptionPrefixTextColor(prompt.Blue),
- prompt.OptionCompletionWordSeparator(completer.FilePathCompletionSeparator), // test
+ prompt.OptionCompletionWordSeparator(" /\\,:"),
)
// start go-prompt
@@ -47,9 +48,14 @@ func (r *RunSftp) shell() {
func (r *RunSftp) Executor(command string) {
// trim space
command = strings.TrimSpace(command)
+ if len(command) == 0 {
+ return
+ }
+ // re-escape
+ reescape := regexp.MustCompile(`(\\)`)
+ command = reescape.ReplaceAllString(command, `\\$1`)
- // parse command
- cmdline := strings.Split(command, " ")
+ cmdline, _ := shellquote.Split(command)
// switch command
switch cmdline[0] {
@@ -80,7 +86,8 @@ func (r *RunSftp) Executor(command string) {
r.lls(cmdline)
case "lmkdir":
r.lmkdir(cmdline)
- // case "ln":
+ case "ln":
+ r.ln(cmdline)
case "lpwd":
r.lpwd(cmdline)
case "ls":
@@ -129,8 +136,9 @@ func (r *RunSftp) Completer(t prompt.Document) []prompt.Suggest {
{Text: "bye", Description: "Quit lsftp"},
{Text: "cat", Description: "Open file"},
{Text: "cd", Description: "Change remote directory to 'path'"},
- // {Text: "chgrp", Description: "Change group of file 'path' to 'grp'"},
- // {Text: "chown", Description: "Change owner of file 'path' to 'own'"},
+ {Text: "chgrp", Description: "Change group of file 'path' to 'grp'"},
+ {Text: "chmod", Description: "Change mode of file 'path' to 'mode'"},
+ {Text: "chown", Description: "Change owner of file 'path' to 'own'"},
// {Text: "copy", Description: "Copy to file from 'remote' or 'local' to 'remote' or 'local'"},
{Text: "df", Description: "Display statistics for current directory or filesystem containing 'path'"},
{Text: "exit", Description: "Quit lsftp"},
@@ -140,7 +148,7 @@ func (r *RunSftp) Completer(t prompt.Document) []prompt.Suggest {
{Text: "lcd", Description: "Change local directory to 'path'"},
{Text: "lls", Description: "Display local directory listing"},
{Text: "lmkdir", Description: "Create local directory"},
- // {Text: "ln", Description: "Link remote file (-s for symlink)"},
+ {Text: "ln", Description: "Link remote file (-s for symlink)"},
{Text: "lpwd", Description: "Print local working directory"},
{Text: "ls", Description: "Display remote directory listing"},
// {Text: "lumask", Description: "Set local umask to 'umask'"},
@@ -160,32 +168,57 @@ func (r *RunSftp) Completer(t prompt.Document) []prompt.Suggest {
} else { // command pattern
switch cmdline[0] {
case "cd":
- return r.PathComplete(true, 1, t)
+ switch {
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") == 1:
+ return r.PathComplete(true, false, false, t)
+ }
case "cat":
// TODO(blacknon): ファイル容量が大きいと途中で止まるっぽい。
- return r.PathComplete(true, 1, t)
+ return r.PathComplete(true, false, false, t)
case "chgrp":
- // TODO(blacknon): そのうち追加 ver0.6.3
+ switch {
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") >= 2:
+ return r.PathComplete(true, false, false, t)
+ }
+ case "chmod":
+ switch {
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") == 1:
+ return prompt.FilterHasPrefix(r.CreateModeComplete(), t.GetWordBeforeCursor(), false)
+
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") >= 2:
+ return r.PathComplete(true, false, false, t)
+ }
case "chown":
- // TODO(blacknon): そのうち追加 ver0.6.3
+ switch {
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") >= 2:
+ return r.PathComplete(true, false, false, t)
+ }
case "df":
- suggest = []prompt.Suggest{
- {Text: "-h", Description: "print sizes in powers of 1024 (e.g., 1023M)"},
- {Text: "-i", Description: "list inode information instead of block usage"},
+ // switch options or path
+ switch {
+ case contains([]string{"-"}, char):
+ suggest = []prompt.Suggest{
+ {Text: "-h", Description: "print sizes in powers of 1024 (e.g., 1023M)"},
+ {Text: "-i", Description: "list inode information instead of block usage"},
+ }
+ return prompt.FilterHasPrefix(suggest, t.GetWordBeforeCursor(), false)
+
+ default:
+ return r.PathComplete(true, false, false, t)
}
- return prompt.FilterHasPrefix(suggest, t.GetWordBeforeCursor(), false)
+
case "get":
// TODO(blacknon): オプションを追加したら引数の数から減らす処理が必要
switch {
case strings.Count(t.CurrentLineBeforeCursor(), " ") == 1: // remote
- return r.PathComplete(true, 1, t)
- case strings.Count(t.CurrentLineBeforeCursor(), " ") == 2: // local
- return r.PathComplete(false, 2, t)
+ return r.PathComplete(true, false, false, t)
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") >= 2: // remote and local
+ return r.PathComplete(true, true, false, t)
}
case "lcat":
- return r.PathComplete(false, 1, t)
+ return r.PathComplete(false, true, false, t)
case "lcd":
- return r.PathComplete(false, 1, t)
+ return r.PathComplete(false, true, false, t)
case "lls":
// switch options or path
switch {
@@ -204,7 +237,7 @@ func (r *RunSftp) Completer(t prompt.Document) []prompt.Suggest {
return prompt.FilterHasPrefix(suggest, t.GetWordBeforeCursor(), false)
default:
- return r.PathComplete(false, 1, t)
+ return r.PathComplete(false, true, false, t)
}
case "lmkdir":
switch {
@@ -215,10 +248,17 @@ func (r *RunSftp) Completer(t prompt.Document) []prompt.Suggest {
return prompt.FilterHasPrefix(suggest, t.GetWordBeforeCursor(), false)
default:
- return r.PathComplete(false, 1, t)
+ return r.PathComplete(false, true, false, t)
+ }
+
+ case "ln":
+ switch {
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") == 1: // with select server
+ return r.PathComplete(true, false, false, t)
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") == 2: // not with select server
+ return r.PathComplete(true, false, true, t)
}
- // case "ln":
case "lpwd":
case "ls":
// switch options or path
@@ -238,7 +278,7 @@ func (r *RunSftp) Completer(t prompt.Document) []prompt.Suggest {
return prompt.FilterHasPrefix(suggest, t.GetWordBeforeCursor(), false)
default:
- return r.PathComplete(true, 1, t)
+ return r.PathComplete(true, false, false, t)
}
// case "lumask":
@@ -250,26 +290,37 @@ func (r *RunSftp) Completer(t prompt.Document) []prompt.Suggest {
}
default:
- return r.PathComplete(true, 1, t)
+ return r.PathComplete(true, false, false, t)
}
case "put":
// TODO(blacknon): オプションを追加したら引数の数から減らす処理が必要
switch {
case strings.Count(t.CurrentLineBeforeCursor(), " ") == 1: // local
- return r.PathComplete(false, 1, t)
- case strings.Count(t.CurrentLineBeforeCursor(), " ") == 2: // remote
- return r.PathComplete(true, 2, t)
+ return r.PathComplete(false, true, false, t)
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") >= 2: // local and remote
+ return r.PathComplete(true, true, false, t)
}
case "pwd":
case "quit":
case "rename":
- return r.PathComplete(true, 1, t)
+ switch {
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") == 1: // with select server
+ return r.PathComplete(true, false, false, t)
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") == 2: // not with select server
+ return r.PathComplete(true, false, true, t)
+ }
case "rm":
- return r.PathComplete(true, 1, t)
+ return r.PathComplete(true, false, false, t)
case "rmdir":
- return r.PathComplete(true, 1, t)
+ return r.PathComplete(true, false, false, t)
case "symlink":
+ switch {
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") == 1: // with select server
+ return r.PathComplete(true, false, false, t)
+ case strings.Count(t.CurrentLineBeforeCursor(), " ") == 2: // not with select server
+ return r.PathComplete(true, false, true, t)
+ }
// TODO(blacknon): そのうち追加 ver0.6.2
// case "tree":
@@ -282,7 +333,7 @@ func (r *RunSftp) Completer(t prompt.Document) []prompt.Suggest {
}
// PathComplete return path complete data
-func (r *RunSftp) PathComplete(remote bool, num int, t prompt.Document) []prompt.Suggest {
+func (r *RunSftp) PathComplete(remote, local, useTargetmap bool, t prompt.Document) []prompt.Suggest {
// suggest
var suggest []prompt.Suggest
@@ -302,46 +353,114 @@ func (r *RunSftp) PathComplete(remote bool, num int, t prompt.Document) []prompt
word = word[sp+1:]
}
- switch remote {
- case true:
+ confirmRemote := false
+ if remote {
+ if strings.Count(t.CurrentLineBeforeCursor(), ":") == 0 && strings.Count(t.CurrentLineBeforeCursor(), "/") == 0 && strings.Count(t.CurrentLineBeforeCursor(), ",") >= 1 {
+ wordlist := strings.Split(word, ",")
+ word = wordlist[len(wordlist)-1]
+ }
+
// update r.RemoteComplete
switch {
- case contains([]string{"/"}, char): // char is slach or
- r.GetRemoteComplete(t.GetWordBeforeCursor())
- case contains([]string{" "}, char) && strings.Count(t.CurrentLineBeforeCursor(), " ") == num:
- r.GetRemoteComplete(t.GetWordBeforeCursor())
+ // host set
+ case contains([]string{","}, char):
+ confirmRemote = r.GetRemoteComplete(true, false, useTargetmap, t.GetWordBeforeCursor())
+
+ case contains([]string{":"}, char):
+ confirmRemote = r.GetRemoteComplete(false, true, useTargetmap, t.GetWordBeforeCursor())
+
+ // char is slach or
+ case contains([]string{"/"}, char):
+ confirmRemote = r.GetRemoteComplete(false, true, useTargetmap, t.GetWordBeforeCursor())
+
+ case contains([]string{" "}, char):
+ confirmRemote = r.GetRemoteComplete(true, true, useTargetmap, t.GetWordBeforeCursor())
+
}
- suggest = r.RemoteComplete
+ suggest = append(suggest, r.RemoteComplete...)
+ }
- case false:
+ if local && !confirmRemote {
// update r.RemoteComplete
switch {
- case contains([]string{"/"}, char): // char is slach or
- r.GetLocalComplete(t.GetWordBeforeCursor())
- case contains([]string{" "}, char) && strings.Count(t.CurrentLineBeforeCursor(), " ") == num:
+ case contains([]string{"/"}, char) || contains([]string{" "}, char): // char is slach
r.GetLocalComplete(t.GetWordBeforeCursor())
}
- suggest = r.LocalComplete
-
+ suggest = append(suggest, r.LocalComplete...)
}
- return prompt.FilterHasPrefix(suggest, word, false)
+ // return prompt.FilterHasPrefix(suggest, word, false)
+ return prompt.FilterHasPrefix(suggest, word, true)
}
// GetRemoteComplete set r.RemoteComplete
-func (r *RunSftp) GetRemoteComplete(path string) {
+func (r *RunSftp) GetRemoteComplete(ishost, ispath, useTargetmap bool, path string) (confirmRemote bool) {
+ // confirm remote
+ confirmRemote = false
+
// create map
m := map[string][]string{}
exit := make(chan bool)
// create suggest slice
var p []prompt.Suggest
+ var s []prompt.Suggest
// create sync mutex
sm := new(sync.Mutex)
+ // target maps
+ targetmap := map[string]*SftpConnect{}
+
+ // get r.Client keys
+ servers := make([]string, 0, len(r.Client))
+ for k := range r.Client {
+ servers = append(servers, k)
+ }
+
+ // create suggest (hosts)
+ for _, server := range servers {
+ // create suggest
+ suggest := prompt.Suggest{
+ Text: server,
+ Description: "remote host.",
+ }
+
+ // append ps.Complete
+ s = append(s, suggest)
+ }
+
+ // sort s
+ sort.SliceStable(s, func(i, j int) bool { return s[i].Text < s[j].Text })
+
+ // If it is confirmed that it is a completion of the host name
+ // create suggest(hostname)
+ if ishost && !ispath {
+ confirmRemote = true
+ r.RemoteComplete = s
+ return
+ }
+
+ // parse path
+ parsedservers, parsedPath := common.ParseHostPath(path)
+ if !useTargetmap {
+ if len(parsedservers) == 0 {
+ parsedservers = servers
+ }
+
+ for server, client := range r.Client {
+ if common.Contains(parsedservers, server) {
+ targetmap[server] = client
+ }
+ }
+
+ r.TargetClient = targetmap
+ } else {
+ targetmap = r.TargetClient
+ }
+
// connect client...
- for s, c := range r.Client {
+ for s, c := range targetmap {
server := s
client := c
@@ -349,10 +468,10 @@ func (r *RunSftp) GetRemoteComplete(path string) {
// set rpath
var rpath string
switch {
- case filepath.IsAbs(path):
- rpath = path
- case !filepath.IsAbs(path):
- rpath = filepath.Join(client.Pwd, path)
+ case filepath.IsAbs(parsedPath):
+ rpath = parsedPath
+ case !filepath.IsAbs(parsedPath):
+ rpath = filepath.Join(client.Pwd, parsedPath)
}
// check rpath
@@ -378,6 +497,11 @@ func (r *RunSftp) GetRemoteComplete(path string) {
// set glob list
for _, p := range globlist {
p = filepath.Base(p)
+
+ // escape blob
+ re := regexp.MustCompile(`([][ )(\\])`)
+ p = re.ReplaceAllString(p, "\\$1")
+
sm.Lock()
m[p] = append(m[p], server)
sm.Unlock()
@@ -387,11 +511,11 @@ func (r *RunSftp) GetRemoteComplete(path string) {
}
// wait
- for i := 0; i < len(r.Client); i++ {
+ for i := 0; i < len(targetmap); i++ {
<-exit
}
- // create suggest
+ // create suggest(path)
for path, hosts := range m {
// join hosts
h := strings.Join(hosts, ",")
@@ -410,7 +534,16 @@ func (r *RunSftp) GetRemoteComplete(path string) {
sort.SliceStable(p, func(i, j int) bool { return p[i].Text < p[j].Text })
// set suggest to struct
- r.RemoteComplete = p
+ if ispath && !ishost {
+ r.RemoteComplete = p
+ } else if useTargetmap {
+ r.RemoteComplete = p
+ } else {
+ r.RemoteComplete = p
+ r.RemoteComplete = append(r.RemoteComplete, s...)
+ }
+
+ return
}
// GetLocalComplete set r.LocalComplete
@@ -450,6 +583,27 @@ func (r *RunSftp) GetLocalComplete(path string) {
r.LocalComplete = p
}
+// CreateModeComplete return file permission modes suggest
+func (r *RunSftp) CreateModeComplete() (p []prompt.Suggest) {
+ modelist := DupPermutationsRecursive0(8, 3)
+
+ for _, mode := range modelist {
+ // create suggest(path)
+ data := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(mode)), ""), "[]")
+
+ // create suggest
+ suggest := prompt.Suggest{
+ Text: data,
+ Description: "",
+ }
+
+ // append ps.Complete
+ p = append(p, suggest)
+ }
+
+ return
+}
+
// CreatePrompt return prompt string.
func (r *RunSftp) CreatePrompt() (p string, result bool) {
p = "lsftp>> "
diff --git a/ssh/auth.go b/ssh/auth.go
index 8ac8b583..7d097fc0 100644
--- a/ssh/auth.go
+++ b/ssh/auth.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/ssh/cmd.go b/ssh/cmd.go
index 8953c430..c1ac624a 100644
--- a/ssh/cmd.go
+++ b/ssh/cmd.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -87,6 +87,26 @@ func (r *Run) cmd() (err error) {
// set port forwarding
config = r.setPortForwards(s, config)
+ // OverWrite dynamic port forwarding
+ if r.DynamicPortForward != "" {
+ config.DynamicPortForward = r.DynamicPortForward
+ }
+
+ // OverWrite reverse dynamic port forwarding
+ if r.ReverseDynamicPortForward != "" {
+ config.ReverseDynamicPortForward = r.ReverseDynamicPortForward
+ }
+
+ // OverWrite local bashrc use
+ if r.IsBashrc {
+ config.LocalRcUse = "yes"
+ }
+
+ // OverWrite local bashrc not use
+ if r.IsNotBashrc {
+ config.LocalRcUse = "no"
+ }
+
// print header
for _, fw := range config.Forwards {
r.printPortForward(fw.Mode, fw.Local, fw.Remote)
@@ -113,6 +133,12 @@ func (r *Run) cmd() (err error) {
go c.TCPDynamicForward("localhost", config.DynamicPortForward)
}
+ // Reverse Dynamic Port Forwarding
+ if config.ReverseDynamicPortForward != "" {
+ r.printReverseDynamicPortForward(config.ReverseDynamicPortForward)
+ go c.TCPReverseDynamicForward("localhost", config.ReverseDynamicPortForward)
+ }
+
// if tty
if r.IsTerm {
c.Stdin = os.Stdin
diff --git a/ssh/connect.go b/ssh/connect.go
index 584985a7..289666ae 100644
--- a/ssh/connect.go
+++ b/ssh/connect.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/ssh/pshell.go b/ssh/pshell.go
index 8e7204db..d252ee96 100644
--- a/ssh/pshell.go
+++ b/ssh/pshell.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -16,7 +16,6 @@ import (
"github.com/blacknon/go-sshlib"
"github.com/blacknon/lssh/output"
"github.com/c-bata/go-prompt"
- "github.com/c-bata/go-prompt/completer"
)
// TODO(blacknon): 接続が切れた場合の再接続処理、および再接続ができなかった場合のsliceからの削除対応の追加(v0.6.1)
@@ -24,6 +23,15 @@ import (
// TODO(blacknon): グループ化(`()`で囲んだりする)や三項演算子への対応(v0.6.1)
// TODO(blacknon): `サーバ名:command...` で、指定したサーバでのみコマンドを実行させる機能の追加(v0.6.1)
+// TODO(blacknon):
+// 出力をvim diffに食わせてdiffを得られるようにしたい => 変数かプロセス置換か、なにかしらの方法でローカルコマンド実行時にssh経由で得られた出力を食わせる方法を実装する?
+// => 多分、プロセス置換が良いんだと思う(プロセス置換時にssh先でコマンドを実行できるように、かつ実行したデータを個別にファイルとして扱えるようにしたい)
+// ```bash
+// !vim diff <(cat /etc/passwd)
+// => !vim diff host1:/etc/passwd host2:/etc/passwd ....
+// ```
+// やるなら普通に一時ファイルに書き出すのが良さそう(/tmp 配下とか。一応、ちゃんと権限周り気をつけないといかんね、というのと消さないといかんね、というお気持ち)
+
// Pshell is Parallel-Shell struct
type pShell struct {
Signal chan os.Signal
@@ -152,7 +160,9 @@ func (r *Run) pshell() (err error) {
}
// set signal
- signal.Notify(ps.Signal, syscall.SIGTERM, syscall.SIGINT)
+ // TODO: Windows対応
+ // - 参考: https://cad-san.hatenablog.com/entry/2017/01/09/170213
+ signal.Notify(ps.Signal, syscall.SIGTERM, syscall.SIGINT, os.Interrupt)
// old history list
var historyCommand []string
@@ -175,7 +185,7 @@ func (r *Run) pshell() (err error) {
prompt.OptionLivePrefix(ps.CreatePrompt),
prompt.OptionInputTextColor(prompt.Green),
prompt.OptionPrefixTextColor(prompt.Blue),
- prompt.OptionCompletionWordSeparator(completer.FilePathCompletionSeparator), // test
+ prompt.OptionCompletionWordSeparator("/: \\"), // test
)
// start go-prompt
diff --git a/ssh/pshell_cmd.go b/ssh/pshell_cmd.go
index 97330f00..87bfa325 100644
--- a/ssh/pshell_cmd.go
+++ b/ssh/pshell_cmd.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -12,6 +12,7 @@ import (
"os/exec"
"reflect"
"regexp"
+ "runtime"
"strconv"
"strings"
"sync"
@@ -376,7 +377,12 @@ func (ps *pShell) executeLocalPipeLine(pline pipeLine, in *io.PipeReader, out *i
command := strings.Join(pline.Args, " ")
// execute command
- cmd := exec.Command("sh", "-c", command)
+ var cmd *exec.Cmd
+ if runtime.GOOS == "windows" {
+ cmd = exec.Command("powershell.exe", "-c", command)
+ } else {
+ cmd = exec.Command("sh", "-c", command)
+ }
// set stdin, stdout, stderr
cmd.Stdin = stdin
diff --git a/ssh/pshell_complete.go b/ssh/pshell_complete.go
index 4ba9495e..c9deeeb3 100644
--- a/ssh/pshell_complete.go
+++ b/ssh/pshell_complete.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -8,7 +8,9 @@ import (
"bufio"
"bytes"
"os/exec"
+ libpath "path"
"path/filepath"
+ "runtime"
"sort"
"strconv"
"strings"
@@ -213,7 +215,9 @@ func (ps *pShell) GetPathComplete(remote bool, word string) (p []prompt.Suggest)
sc := bufio.NewScanner(buf)
for sc.Scan() {
sm.Lock()
- path := filepath.Base(sc.Text())
+
+ path := libpath.Base(sc.Text())
+
m[path] = append(m[path], con.Name)
sm.Unlock()
}
@@ -242,16 +246,18 @@ func (ps *pShell) GetPathComplete(remote bool, word string) (p []prompt.Suggest)
}
case !remote: // is local machine
- sgt, _ := exec.Command("bash", "-c", command).Output()
- rd := strings.NewReader(string(sgt))
- sc := bufio.NewScanner(rd)
- for sc.Scan() {
- suggest := prompt.Suggest{
- Text: filepath.Base(sc.Text()),
- // Text: sc.Text(),
- Description: "local path.",
+ if runtime.GOOS != "windows" {
+ sgt, _ := exec.Command("bash", "-c", command).Output()
+ rd := strings.NewReader(string(sgt))
+ sc := bufio.NewScanner(rd)
+ for sc.Scan() {
+ suggest := prompt.Suggest{
+ Text: filepath.Base(sc.Text()),
+ // Text: sc.Text(),
+ Description: "local path.",
+ }
+ p = append(p, suggest)
}
- p = append(p, suggest)
}
}
diff --git a/ssh/pshell_executor.go b/ssh/pshell_executor.go
index a7719d76..cd676927 100644
--- a/ssh/pshell_executor.go
+++ b/ssh/pshell_executor.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/ssh/pshell_history.go b/ssh/pshell_history.go
index 4a56ac1d..d0b85bdd 100644
--- a/ssh/pshell_history.go
+++ b/ssh/pshell_history.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/ssh/pshell_parse.go b/ssh/pshell_parse.go
index e0e2132d..84ad3130 100644
--- a/ssh/pshell_parse.go
+++ b/ssh/pshell_parse.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
diff --git a/ssh/run.go b/ssh/run.go
index 598c2f1a..8052dd8f 100644
--- a/ssh/run.go
+++ b/ssh/run.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -65,14 +65,23 @@ type Run struct {
PortForward []*conf.PortForward
// TODO(blacknon): Delete old keys
- PortForwardMode string // L or R
- PortForwardLocal string
+ // L or R
+ PortForwardMode string
+
+ //
+ PortForwardLocal string
+
+ //
PortForwardRemote string
// Dynamic Port Forwarding
// set localhost port num (ex. 11080).
DynamicPortForward string
+ // Reverse Dynamic Port Forwarding
+ // set remotehost port num (ex. 11080).
+ ReverseDynamicPortForward string
+
// Exec command
ExecCmd []string
@@ -218,6 +227,15 @@ func (r *Run) printDynamicPortForward(port string) {
}
}
+// printReverseDynamicPortForward is printout port forwarding.
+// use ssh command run header. only use shell().
+func (r *Run) printReverseDynamicPortForward(port string) {
+ if port != "" {
+ fmt.Fprintf(os.Stderr, "ReverseDynamicForward:%s\n", port)
+ fmt.Fprintf(os.Stderr, " %s\n", "connect Socks5.")
+ }
+}
+
// printProxy is printout proxy route.
// use ssh command run header. only use shell().
func (r *Run) printProxy(server string) {
diff --git a/ssh/shell.go b/ssh/shell.go
index f8931f85..97302be0 100644
--- a/ssh/shell.go
+++ b/ssh/shell.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2021 Blacknon. All rights reserved.
+// Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -42,6 +42,11 @@ func (r *Run) shell() (err error) {
config.DynamicPortForward = r.DynamicPortForward
}
+ // OverWrite reverse dynamic port forwarding
+ if r.ReverseDynamicPortForward != "" {
+ config.ReverseDynamicPortForward = r.ReverseDynamicPortForward
+ }
+
// OverWrite local bashrc use
if r.IsBashrc {
config.LocalRcUse = "yes"
@@ -58,6 +63,7 @@ func (r *Run) shell() (err error) {
r.printPortForward(fw.Mode, fw.Local, fw.Remote)
}
r.printDynamicPortForward(config.DynamicPortForward)
+ r.printReverseDynamicPortForward(config.ReverseDynamicPortForward)
r.printProxy(server)
if config.LocalRcUse == "yes" {
fmt.Fprintf(os.Stderr, "Information :This connect use local bashrc.\n")
@@ -101,6 +107,11 @@ func (r *Run) shell() (err error) {
go connect.TCPDynamicForward("localhost", config.DynamicPortForward)
}
+ // Reverse Dynamic Port Forwarding
+ if config.ReverseDynamicPortForward != "" {
+ go connect.TCPReverseDynamicForward("localhost", config.ReverseDynamicPortForward)
+ }
+
// switch check Not-execute flag
// TODO(blacknon): Backgroundフラグを実装したら追加
switch {
@@ -133,7 +144,7 @@ func (r *Run) shell() (err error) {
// TODO(blacknon): local rc file add
if config.LocalRcUse == "yes" {
- err = localrcShell(connect, session, config.LocalRcPath, config.LocalRcDecodeCmd)
+ err = localrcShell(connect, session, config.LocalRcPath, config.LocalRcDecodeCmd, config.LocalRcCompress, config.LocalRcUncompressCmd)
} else {
// Connect shell
err = connect.Shell(session)
@@ -182,8 +193,22 @@ func (r *Run) getLogDirPath(server string) (dir string, err error) {
return
}
-// runLocalRcShell connect to remote shell using local bashrc
-func localrcShell(connect *sshlib.Connect, session *ssh.Session, localrcPath []string, decoder string) (err error) {
+// noneExecute is not execute command and shell.
+func (r *Run) noneExecute() (err error) {
+loop:
+ for {
+ select {
+ case <-time.After(500 * time.Millisecond):
+ continue loop
+ }
+ }
+}
+
+// localRcShell connect to remote shell using local bashrc
+func localrcShell(connect *sshlib.Connect, session *ssh.Session, localrcPath []string, decoder string, compress bool, uncompress string) (err error) {
+ // var
+ var cmd string
+
// TODO(blacknon): 受け付けるrcdataをzip化するオプションの追加
// set default bashrc
@@ -192,32 +217,31 @@ func localrcShell(connect *sshlib.Connect, session *ssh.Session, localrcPath []s
}
// get bashrc base64 data
- rcData, err := common.GetFilesBase64(localrcPath)
- if err != nil {
- return
- }
+ // rcData, err := common.GetFilesBase64(localrcPath, common.ARCHIVE_NONE)
+ rcData, _ := common.GetFilesBase64(localrcPath, common.ARCHIVE_GZIP)
- // command
- // TODO(blacknon): なんの処理してるのか、もうちょっとちゃんとコメントを書く(忘れちまったよ…)
- cmd := fmt.Sprintf("bash --noprofile --rcfile <(echo %s|((base64 --help | grep -q coreutils) && base64 -d <(cat) || base64 -D <(cat) ))", rcData)
+ // set default uncompress command
+ if uncompress == "" {
+ uncompress = "gzip -d"
+ }
- // decode command
- if decoder != "" {
+ // switch
+ switch {
+ case !compress && decoder != "":
cmd = fmt.Sprintf("bash --noprofile --rcfile <(echo %s | %s)", rcData, decoder)
+
+ case !compress && decoder == "":
+ cmd = fmt.Sprintf("bash --noprofile --rcfile <(echo %s | ( (base64 --help | grep -q coreutils) && base64 -d <(cat) || base64 -D <(cat) ) )", rcData)
+
+ case compress && decoder != "":
+ cmd = fmt.Sprintf("bash --noprofile --rcfile <(echo %s | %s | %s)", rcData, decoder, uncompress)
+
+ case compress && decoder == "":
+ cmd = fmt.Sprintf("bash --noprofile --rcfile <(echo %s | ( (base64 --help | grep -q coreutils) && base64 -d <(cat) || base64 -D <(cat) ) | %s)", rcData, uncompress)
+
}
connect.CmdShell(session, cmd)
return
}
-
-// noneExecute is not execute command and shell.
-func (r *Run) noneExecute() (err error) {
-loop:
- for {
- select {
- case <-time.After(500 * time.Millisecond):
- continue loop
- }
- }
-}
diff --git a/vendor/github.com/Azure/go-ansiterm/LICENSE b/vendor/github.com/Azure/go-ansiterm/LICENSE
new file mode 100644
index 00000000..e3d9a64d
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Microsoft Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/Azure/go-ansiterm/README.md b/vendor/github.com/Azure/go-ansiterm/README.md
new file mode 100644
index 00000000..261c041e
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/README.md
@@ -0,0 +1,12 @@
+# go-ansiterm
+
+This is a cross platform Ansi Terminal Emulation library. It reads a stream of Ansi characters and produces the appropriate function calls. The results of the function calls are platform dependent.
+
+For example the parser might receive "ESC, [, A" as a stream of three characters. This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU). The parser then calls the cursor up function (CUU()) on an event handler. The event handler determines what platform specific work must be done to cause the cursor to move up one position.
+
+The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go).
+
+See parser_test.go for examples exercising the state machine and generating appropriate function calls.
+
+-----
+This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
diff --git a/vendor/github.com/Azure/go-ansiterm/constants.go b/vendor/github.com/Azure/go-ansiterm/constants.go
new file mode 100644
index 00000000..96504a33
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/constants.go
@@ -0,0 +1,188 @@
+package ansiterm
+
+const LogEnv = "DEBUG_TERMINAL"
+
+// ANSI constants
+// References:
+// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm
+// -- http://man7.org/linux/man-pages/man4/console_codes.4.html
+// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
+// -- http://en.wikipedia.org/wiki/ANSI_escape_code
+// -- http://vt100.net/emu/dec_ansi_parser
+// -- http://vt100.net/emu/vt500_parser.svg
+// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+// -- http://www.inwap.com/pdp10/ansicode.txt
+const (
+ // ECMA-48 Set Graphics Rendition
+ // Note:
+ // -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved
+ // -- Fonts could possibly be supported via SetCurrentConsoleFontEx
+ // -- Windows does not expose the per-window cursor (i.e., caret) blink times
+ ANSI_SGR_RESET = 0
+ ANSI_SGR_BOLD = 1
+ ANSI_SGR_DIM = 2
+ _ANSI_SGR_ITALIC = 3
+ ANSI_SGR_UNDERLINE = 4
+ _ANSI_SGR_BLINKSLOW = 5
+ _ANSI_SGR_BLINKFAST = 6
+ ANSI_SGR_REVERSE = 7
+ _ANSI_SGR_INVISIBLE = 8
+ _ANSI_SGR_LINETHROUGH = 9
+ _ANSI_SGR_FONT_00 = 10
+ _ANSI_SGR_FONT_01 = 11
+ _ANSI_SGR_FONT_02 = 12
+ _ANSI_SGR_FONT_03 = 13
+ _ANSI_SGR_FONT_04 = 14
+ _ANSI_SGR_FONT_05 = 15
+ _ANSI_SGR_FONT_06 = 16
+ _ANSI_SGR_FONT_07 = 17
+ _ANSI_SGR_FONT_08 = 18
+ _ANSI_SGR_FONT_09 = 19
+ _ANSI_SGR_FONT_10 = 20
+ _ANSI_SGR_DOUBLEUNDERLINE = 21
+ ANSI_SGR_BOLD_DIM_OFF = 22
+ _ANSI_SGR_ITALIC_OFF = 23
+ ANSI_SGR_UNDERLINE_OFF = 24
+ _ANSI_SGR_BLINK_OFF = 25
+ _ANSI_SGR_RESERVED_00 = 26
+ ANSI_SGR_REVERSE_OFF = 27
+ _ANSI_SGR_INVISIBLE_OFF = 28
+ _ANSI_SGR_LINETHROUGH_OFF = 29
+ ANSI_SGR_FOREGROUND_BLACK = 30
+ ANSI_SGR_FOREGROUND_RED = 31
+ ANSI_SGR_FOREGROUND_GREEN = 32
+ ANSI_SGR_FOREGROUND_YELLOW = 33
+ ANSI_SGR_FOREGROUND_BLUE = 34
+ ANSI_SGR_FOREGROUND_MAGENTA = 35
+ ANSI_SGR_FOREGROUND_CYAN = 36
+ ANSI_SGR_FOREGROUND_WHITE = 37
+ _ANSI_SGR_RESERVED_01 = 38
+ ANSI_SGR_FOREGROUND_DEFAULT = 39
+ ANSI_SGR_BACKGROUND_BLACK = 40
+ ANSI_SGR_BACKGROUND_RED = 41
+ ANSI_SGR_BACKGROUND_GREEN = 42
+ ANSI_SGR_BACKGROUND_YELLOW = 43
+ ANSI_SGR_BACKGROUND_BLUE = 44
+ ANSI_SGR_BACKGROUND_MAGENTA = 45
+ ANSI_SGR_BACKGROUND_CYAN = 46
+ ANSI_SGR_BACKGROUND_WHITE = 47
+ _ANSI_SGR_RESERVED_02 = 48
+ ANSI_SGR_BACKGROUND_DEFAULT = 49
+ // 50 - 65: Unsupported
+
+ ANSI_MAX_CMD_LENGTH = 4096
+
+ MAX_INPUT_EVENTS = 128
+ DEFAULT_WIDTH = 80
+ DEFAULT_HEIGHT = 24
+
+ ANSI_BEL = 0x07
+ ANSI_BACKSPACE = 0x08
+ ANSI_TAB = 0x09
+ ANSI_LINE_FEED = 0x0A
+ ANSI_VERTICAL_TAB = 0x0B
+ ANSI_FORM_FEED = 0x0C
+ ANSI_CARRIAGE_RETURN = 0x0D
+ ANSI_ESCAPE_PRIMARY = 0x1B
+ ANSI_ESCAPE_SECONDARY = 0x5B
+ ANSI_OSC_STRING_ENTRY = 0x5D
+ ANSI_COMMAND_FIRST = 0x40
+ ANSI_COMMAND_LAST = 0x7E
+ DCS_ENTRY = 0x90
+ CSI_ENTRY = 0x9B
+ OSC_STRING = 0x9D
+ ANSI_PARAMETER_SEP = ";"
+ ANSI_CMD_G0 = '('
+ ANSI_CMD_G1 = ')'
+ ANSI_CMD_G2 = '*'
+ ANSI_CMD_G3 = '+'
+ ANSI_CMD_DECPNM = '>'
+ ANSI_CMD_DECPAM = '='
+ ANSI_CMD_OSC = ']'
+ ANSI_CMD_STR_TERM = '\\'
+
+ KEY_CONTROL_PARAM_2 = ";2"
+ KEY_CONTROL_PARAM_3 = ";3"
+ KEY_CONTROL_PARAM_4 = ";4"
+ KEY_CONTROL_PARAM_5 = ";5"
+ KEY_CONTROL_PARAM_6 = ";6"
+ KEY_CONTROL_PARAM_7 = ";7"
+ KEY_CONTROL_PARAM_8 = ";8"
+ KEY_ESC_CSI = "\x1B["
+ KEY_ESC_N = "\x1BN"
+ KEY_ESC_O = "\x1BO"
+
+ FILL_CHARACTER = ' '
+)
+
+func getByteRange(start byte, end byte) []byte {
+ bytes := make([]byte, 0, 32)
+ for i := start; i <= end; i++ {
+ bytes = append(bytes, byte(i))
+ }
+
+ return bytes
+}
+
+var toGroundBytes = getToGroundBytes()
+var executors = getExecuteBytes()
+
+// SPACE 20+A0 hex Always and everywhere a blank space
+// Intermediate 20-2F hex !"#$%&'()*+,-./
+var intermeds = getByteRange(0x20, 0x2F)
+
+// Parameters 30-3F hex 0123456789:;<=>?
+// CSI Parameters 30-39, 3B hex 0123456789;
+var csiParams = getByteRange(0x30, 0x3F)
+
+var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
+
+// Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
+var upperCase = getByteRange(0x40, 0x5F)
+
+// Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
+var lowerCase = getByteRange(0x60, 0x7E)
+
+// Alphabetics 40-7E hex (all of upper and lower case)
+var alphabetics = append(upperCase, lowerCase...)
+
+var printables = getByteRange(0x20, 0x7F)
+
+var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
+var escapeToGroundBytes = getEscapeToGroundBytes()
+
+// See http://www.vt100.net/emu/vt500_parser.png for description of the complex
+// byte ranges below
+
+func getEscapeToGroundBytes() []byte {
+ escapeToGroundBytes := getByteRange(0x30, 0x4F)
+ escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
+ escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
+ escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
+ escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
+ escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
+ return escapeToGroundBytes
+}
+
+func getExecuteBytes() []byte {
+ executeBytes := getByteRange(0x00, 0x17)
+ executeBytes = append(executeBytes, 0x19)
+ executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
+ return executeBytes
+}
+
+func getToGroundBytes() []byte {
+ groundBytes := []byte{0x18}
+ groundBytes = append(groundBytes, 0x1A)
+ groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
+ groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
+ groundBytes = append(groundBytes, 0x99)
+ groundBytes = append(groundBytes, 0x9A)
+ groundBytes = append(groundBytes, 0x9C)
+ return groundBytes
+}
+
+// Delete 7F hex Always and everywhere ignored
+// C1 Control 80-9F hex 32 additional control characters
+// G1 Displayable A1-FE hex 94 additional displayable characters
+// Special A0+FF hex Same as SPACE and DELETE
diff --git a/vendor/github.com/Azure/go-ansiterm/context.go b/vendor/github.com/Azure/go-ansiterm/context.go
new file mode 100644
index 00000000..8d66e777
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/context.go
@@ -0,0 +1,7 @@
+package ansiterm
+
+type ansiContext struct {
+ currentChar byte
+ paramBuffer []byte
+ interBuffer []byte
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/csi_entry_state.go b/vendor/github.com/Azure/go-ansiterm/csi_entry_state.go
new file mode 100644
index 00000000..bcbe00d0
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/csi_entry_state.go
@@ -0,0 +1,49 @@
+package ansiterm
+
+type csiEntryState struct {
+ baseState
+}
+
+func (csiState csiEntryState) Handle(b byte) (s state, e error) {
+ csiState.parser.logf("CsiEntry::Handle %#x", b)
+
+ nextState, err := csiState.baseState.Handle(b)
+ if nextState != nil || err != nil {
+ return nextState, err
+ }
+
+ switch {
+ case sliceContains(alphabetics, b):
+ return csiState.parser.ground, nil
+ case sliceContains(csiCollectables, b):
+ return csiState.parser.csiParam, nil
+ case sliceContains(executors, b):
+ return csiState, csiState.parser.execute()
+ }
+
+ return csiState, nil
+}
+
+func (csiState csiEntryState) Transition(s state) error {
+ csiState.parser.logf("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
+ csiState.baseState.Transition(s)
+
+ switch s {
+ case csiState.parser.ground:
+ return csiState.parser.csiDispatch()
+ case csiState.parser.csiParam:
+ switch {
+ case sliceContains(csiParams, csiState.parser.context.currentChar):
+ csiState.parser.collectParam()
+ case sliceContains(intermeds, csiState.parser.context.currentChar):
+ csiState.parser.collectInter()
+ }
+ }
+
+ return nil
+}
+
+func (csiState csiEntryState) Enter() error {
+ csiState.parser.clear()
+ return nil
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/csi_param_state.go b/vendor/github.com/Azure/go-ansiterm/csi_param_state.go
new file mode 100644
index 00000000..7ed5e01c
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/csi_param_state.go
@@ -0,0 +1,38 @@
+package ansiterm
+
+type csiParamState struct {
+ baseState
+}
+
+func (csiState csiParamState) Handle(b byte) (s state, e error) {
+ csiState.parser.logf("CsiParam::Handle %#x", b)
+
+ nextState, err := csiState.baseState.Handle(b)
+ if nextState != nil || err != nil {
+ return nextState, err
+ }
+
+ switch {
+ case sliceContains(alphabetics, b):
+ return csiState.parser.ground, nil
+ case sliceContains(csiCollectables, b):
+ csiState.parser.collectParam()
+ return csiState, nil
+ case sliceContains(executors, b):
+ return csiState, csiState.parser.execute()
+ }
+
+ return csiState, nil
+}
+
+func (csiState csiParamState) Transition(s state) error {
+ csiState.parser.logf("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
+ csiState.baseState.Transition(s)
+
+ switch s {
+ case csiState.parser.ground:
+ return csiState.parser.csiDispatch()
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go b/vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go
new file mode 100644
index 00000000..1c719db9
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/escape_intermediate_state.go
@@ -0,0 +1,36 @@
+package ansiterm
+
+type escapeIntermediateState struct {
+ baseState
+}
+
+func (escState escapeIntermediateState) Handle(b byte) (s state, e error) {
+ escState.parser.logf("escapeIntermediateState::Handle %#x", b)
+ nextState, err := escState.baseState.Handle(b)
+ if nextState != nil || err != nil {
+ return nextState, err
+ }
+
+ switch {
+ case sliceContains(intermeds, b):
+ return escState, escState.parser.collectInter()
+ case sliceContains(executors, b):
+ return escState, escState.parser.execute()
+ case sliceContains(escapeIntermediateToGroundBytes, b):
+ return escState.parser.ground, nil
+ }
+
+ return escState, nil
+}
+
+func (escState escapeIntermediateState) Transition(s state) error {
+ escState.parser.logf("escapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
+ escState.baseState.Transition(s)
+
+ switch s {
+ case escState.parser.ground:
+ return escState.parser.escDispatch()
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/escape_state.go b/vendor/github.com/Azure/go-ansiterm/escape_state.go
new file mode 100644
index 00000000..6390abd2
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/escape_state.go
@@ -0,0 +1,47 @@
+package ansiterm
+
+type escapeState struct {
+ baseState
+}
+
+func (escState escapeState) Handle(b byte) (s state, e error) {
+ escState.parser.logf("escapeState::Handle %#x", b)
+ nextState, err := escState.baseState.Handle(b)
+ if nextState != nil || err != nil {
+ return nextState, err
+ }
+
+ switch {
+ case b == ANSI_ESCAPE_SECONDARY:
+ return escState.parser.csiEntry, nil
+ case b == ANSI_OSC_STRING_ENTRY:
+ return escState.parser.oscString, nil
+ case sliceContains(executors, b):
+ return escState, escState.parser.execute()
+ case sliceContains(escapeToGroundBytes, b):
+ return escState.parser.ground, nil
+ case sliceContains(intermeds, b):
+ return escState.parser.escapeIntermediate, nil
+ }
+
+ return escState, nil
+}
+
+func (escState escapeState) Transition(s state) error {
+ escState.parser.logf("Escape::Transition %s --> %s", escState.Name(), s.Name())
+ escState.baseState.Transition(s)
+
+ switch s {
+ case escState.parser.ground:
+ return escState.parser.escDispatch()
+ case escState.parser.escapeIntermediate:
+ return escState.parser.collectInter()
+ }
+
+ return nil
+}
+
+func (escState escapeState) Enter() error {
+ escState.parser.clear()
+ return nil
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/event_handler.go b/vendor/github.com/Azure/go-ansiterm/event_handler.go
new file mode 100644
index 00000000..98087b38
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/event_handler.go
@@ -0,0 +1,90 @@
+package ansiterm
+
+type AnsiEventHandler interface {
+ // Print
+ Print(b byte) error
+
+ // Execute C0 commands
+ Execute(b byte) error
+
+ // CUrsor Up
+ CUU(int) error
+
+ // CUrsor Down
+ CUD(int) error
+
+ // CUrsor Forward
+ CUF(int) error
+
+ // CUrsor Backward
+ CUB(int) error
+
+ // Cursor to Next Line
+ CNL(int) error
+
+ // Cursor to Previous Line
+ CPL(int) error
+
+ // Cursor Horizontal position Absolute
+ CHA(int) error
+
+ // Vertical line Position Absolute
+ VPA(int) error
+
+ // CUrsor Position
+ CUP(int, int) error
+
+ // Horizontal and Vertical Position (depends on PUM)
+ HVP(int, int) error
+
+ // Text Cursor Enable Mode
+ DECTCEM(bool) error
+
+ // Origin Mode
+ DECOM(bool) error
+
+ // 132 Column Mode
+ DECCOLM(bool) error
+
+ // Erase in Display
+ ED(int) error
+
+ // Erase in Line
+ EL(int) error
+
+ // Insert Line
+ IL(int) error
+
+ // Delete Line
+ DL(int) error
+
+ // Insert Character
+ ICH(int) error
+
+ // Delete Character
+ DCH(int) error
+
+ // Set Graphics Rendition
+ SGR([]int) error
+
+ // Pan Down
+ SU(int) error
+
+ // Pan Up
+ SD(int) error
+
+ // Device Attributes
+ DA([]string) error
+
+ // Set Top and Bottom Margins
+ DECSTBM(int, int) error
+
+ // Index
+ IND() error
+
+ // Reverse Index
+ RI() error
+
+ // Flush updates from previous commands
+ Flush() error
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/ground_state.go b/vendor/github.com/Azure/go-ansiterm/ground_state.go
new file mode 100644
index 00000000..52451e94
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/ground_state.go
@@ -0,0 +1,24 @@
+package ansiterm
+
+type groundState struct {
+ baseState
+}
+
+func (gs groundState) Handle(b byte) (s state, e error) {
+ gs.parser.context.currentChar = b
+
+ nextState, err := gs.baseState.Handle(b)
+ if nextState != nil || err != nil {
+ return nextState, err
+ }
+
+ switch {
+ case sliceContains(printables, b):
+ return gs, gs.parser.print()
+
+ case sliceContains(executors, b):
+ return gs, gs.parser.execute()
+ }
+
+ return gs, nil
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/osc_string_state.go b/vendor/github.com/Azure/go-ansiterm/osc_string_state.go
new file mode 100644
index 00000000..593b10ab
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/osc_string_state.go
@@ -0,0 +1,31 @@
+package ansiterm
+
+type oscStringState struct {
+ baseState
+}
+
+func (oscState oscStringState) Handle(b byte) (s state, e error) {
+ oscState.parser.logf("OscString::Handle %#x", b)
+ nextState, err := oscState.baseState.Handle(b)
+ if nextState != nil || err != nil {
+ return nextState, err
+ }
+
+ switch {
+ case isOscStringTerminator(b):
+ return oscState.parser.ground, nil
+ }
+
+ return oscState, nil
+}
+
+// See below for OSC string terminators for linux
+// http://man7.org/linux/man-pages/man4/console_codes.4.html
+func isOscStringTerminator(b byte) bool {
+
+ if b == ANSI_BEL || b == 0x5C {
+ return true
+ }
+
+ return false
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/parser.go b/vendor/github.com/Azure/go-ansiterm/parser.go
new file mode 100644
index 00000000..03cec7ad
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/parser.go
@@ -0,0 +1,151 @@
+package ansiterm
+
+import (
+ "errors"
+ "log"
+ "os"
+)
+
+type AnsiParser struct {
+ currState state
+ eventHandler AnsiEventHandler
+ context *ansiContext
+ csiEntry state
+ csiParam state
+ dcsEntry state
+ escape state
+ escapeIntermediate state
+ error state
+ ground state
+ oscString state
+ stateMap []state
+
+ logf func(string, ...interface{})
+}
+
+type Option func(*AnsiParser)
+
+func WithLogf(f func(string, ...interface{})) Option {
+ return func(ap *AnsiParser) {
+ ap.logf = f
+ }
+}
+
+func CreateParser(initialState string, evtHandler AnsiEventHandler, opts ...Option) *AnsiParser {
+ ap := &AnsiParser{
+ eventHandler: evtHandler,
+ context: &ansiContext{},
+ }
+ for _, o := range opts {
+ o(ap)
+ }
+
+ if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
+ logFile, _ := os.Create("ansiParser.log")
+ logger := log.New(logFile, "", log.LstdFlags)
+ if ap.logf != nil {
+ l := ap.logf
+ ap.logf = func(s string, v ...interface{}) {
+ l(s, v...)
+ logger.Printf(s, v...)
+ }
+ } else {
+ ap.logf = logger.Printf
+ }
+ }
+
+ if ap.logf == nil {
+ ap.logf = func(string, ...interface{}) {}
+ }
+
+ ap.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: ap}}
+ ap.csiParam = csiParamState{baseState{name: "CsiParam", parser: ap}}
+ ap.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: ap}}
+ ap.escape = escapeState{baseState{name: "Escape", parser: ap}}
+ ap.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: ap}}
+ ap.error = errorState{baseState{name: "Error", parser: ap}}
+ ap.ground = groundState{baseState{name: "Ground", parser: ap}}
+ ap.oscString = oscStringState{baseState{name: "OscString", parser: ap}}
+
+ ap.stateMap = []state{
+ ap.csiEntry,
+ ap.csiParam,
+ ap.dcsEntry,
+ ap.escape,
+ ap.escapeIntermediate,
+ ap.error,
+ ap.ground,
+ ap.oscString,
+ }
+
+ ap.currState = getState(initialState, ap.stateMap)
+
+ ap.logf("CreateParser: parser %p", ap)
+ return ap
+}
+
+func getState(name string, states []state) state {
+ for _, el := range states {
+ if el.Name() == name {
+ return el
+ }
+ }
+
+ return nil
+}
+
+func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
+ for i, b := range bytes {
+ if err := ap.handle(b); err != nil {
+ return i, err
+ }
+ }
+
+ return len(bytes), ap.eventHandler.Flush()
+}
+
+func (ap *AnsiParser) handle(b byte) error {
+ ap.context.currentChar = b
+ newState, err := ap.currState.Handle(b)
+ if err != nil {
+ return err
+ }
+
+ if newState == nil {
+ ap.logf("WARNING: newState is nil")
+ return errors.New("New state of 'nil' is invalid.")
+ }
+
+ if newState != ap.currState {
+ if err := ap.changeState(newState); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (ap *AnsiParser) changeState(newState state) error {
+ ap.logf("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
+
+ // Exit old state
+ if err := ap.currState.Exit(); err != nil {
+ ap.logf("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
+ return err
+ }
+
+ // Perform transition action
+ if err := ap.currState.Transition(newState); err != nil {
+ ap.logf("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
+ return err
+ }
+
+ // Enter new state
+ if err := newState.Enter(); err != nil {
+ ap.logf("Enter state '%s' failed with: '%v'", newState.Name(), err)
+ return err
+ }
+
+ ap.currState = newState
+ return nil
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go b/vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go
new file mode 100644
index 00000000..de0a1f9c
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/parser_action_helpers.go
@@ -0,0 +1,99 @@
+package ansiterm
+
+import (
+ "strconv"
+)
+
+func parseParams(bytes []byte) ([]string, error) {
+ paramBuff := make([]byte, 0, 0)
+ params := []string{}
+
+ for _, v := range bytes {
+ if v == ';' {
+ if len(paramBuff) > 0 {
+ // Completed parameter, append it to the list
+ s := string(paramBuff)
+ params = append(params, s)
+ paramBuff = make([]byte, 0, 0)
+ }
+ } else {
+ paramBuff = append(paramBuff, v)
+ }
+ }
+
+ // Last parameter may not be terminated with ';'
+ if len(paramBuff) > 0 {
+ s := string(paramBuff)
+ params = append(params, s)
+ }
+
+ return params, nil
+}
+
+func parseCmd(context ansiContext) (string, error) {
+ return string(context.currentChar), nil
+}
+
+func getInt(params []string, dflt int) int {
+ i := getInts(params, 1, dflt)[0]
+ return i
+}
+
+func getInts(params []string, minCount int, dflt int) []int {
+ ints := []int{}
+
+ for _, v := range params {
+ i, _ := strconv.Atoi(v)
+ // Zero is mapped to the default value in VT100.
+ if i == 0 {
+ i = dflt
+ }
+ ints = append(ints, i)
+ }
+
+ if len(ints) < minCount {
+ remaining := minCount - len(ints)
+ for i := 0; i < remaining; i++ {
+ ints = append(ints, dflt)
+ }
+ }
+
+ return ints
+}
+
+func (ap *AnsiParser) modeDispatch(param string, set bool) error {
+ switch param {
+ case "?3":
+ return ap.eventHandler.DECCOLM(set)
+ case "?6":
+ return ap.eventHandler.DECOM(set)
+ case "?25":
+ return ap.eventHandler.DECTCEM(set)
+ }
+ return nil
+}
+
+func (ap *AnsiParser) hDispatch(params []string) error {
+ if len(params) == 1 {
+ return ap.modeDispatch(params[0], true)
+ }
+
+ return nil
+}
+
+func (ap *AnsiParser) lDispatch(params []string) error {
+ if len(params) == 1 {
+ return ap.modeDispatch(params[0], false)
+ }
+
+ return nil
+}
+
+func getEraseParam(params []string) int {
+ param := getInt(params, 0)
+ if param < 0 || 3 < param {
+ param = 0
+ }
+
+ return param
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/parser_actions.go b/vendor/github.com/Azure/go-ansiterm/parser_actions.go
new file mode 100644
index 00000000..0bb5e51e
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/parser_actions.go
@@ -0,0 +1,119 @@
+package ansiterm
+
+func (ap *AnsiParser) collectParam() error {
+ currChar := ap.context.currentChar
+ ap.logf("collectParam %#x", currChar)
+ ap.context.paramBuffer = append(ap.context.paramBuffer, currChar)
+ return nil
+}
+
+func (ap *AnsiParser) collectInter() error {
+ currChar := ap.context.currentChar
+ ap.logf("collectInter %#x", currChar)
+ ap.context.paramBuffer = append(ap.context.interBuffer, currChar)
+ return nil
+}
+
+func (ap *AnsiParser) escDispatch() error {
+ cmd, _ := parseCmd(*ap.context)
+ intermeds := ap.context.interBuffer
+ ap.logf("escDispatch currentChar: %#x", ap.context.currentChar)
+ ap.logf("escDispatch: %v(%v)", cmd, intermeds)
+
+ switch cmd {
+ case "D": // IND
+ return ap.eventHandler.IND()
+ case "E": // NEL, equivalent to CRLF
+ err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN)
+ if err == nil {
+ err = ap.eventHandler.Execute(ANSI_LINE_FEED)
+ }
+ return err
+ case "M": // RI
+ return ap.eventHandler.RI()
+ }
+
+ return nil
+}
+
+func (ap *AnsiParser) csiDispatch() error {
+ cmd, _ := parseCmd(*ap.context)
+ params, _ := parseParams(ap.context.paramBuffer)
+ ap.logf("Parsed params: %v with length: %d", params, len(params))
+
+ ap.logf("csiDispatch: %v(%v)", cmd, params)
+
+ switch cmd {
+ case "@":
+ return ap.eventHandler.ICH(getInt(params, 1))
+ case "A":
+ return ap.eventHandler.CUU(getInt(params, 1))
+ case "B":
+ return ap.eventHandler.CUD(getInt(params, 1))
+ case "C":
+ return ap.eventHandler.CUF(getInt(params, 1))
+ case "D":
+ return ap.eventHandler.CUB(getInt(params, 1))
+ case "E":
+ return ap.eventHandler.CNL(getInt(params, 1))
+ case "F":
+ return ap.eventHandler.CPL(getInt(params, 1))
+ case "G":
+ return ap.eventHandler.CHA(getInt(params, 1))
+ case "H":
+ ints := getInts(params, 2, 1)
+ x, y := ints[0], ints[1]
+ return ap.eventHandler.CUP(x, y)
+ case "J":
+ param := getEraseParam(params)
+ return ap.eventHandler.ED(param)
+ case "K":
+ param := getEraseParam(params)
+ return ap.eventHandler.EL(param)
+ case "L":
+ return ap.eventHandler.IL(getInt(params, 1))
+ case "M":
+ return ap.eventHandler.DL(getInt(params, 1))
+ case "P":
+ return ap.eventHandler.DCH(getInt(params, 1))
+ case "S":
+ return ap.eventHandler.SU(getInt(params, 1))
+ case "T":
+ return ap.eventHandler.SD(getInt(params, 1))
+ case "c":
+ return ap.eventHandler.DA(params)
+ case "d":
+ return ap.eventHandler.VPA(getInt(params, 1))
+ case "f":
+ ints := getInts(params, 2, 1)
+ x, y := ints[0], ints[1]
+ return ap.eventHandler.HVP(x, y)
+ case "h":
+ return ap.hDispatch(params)
+ case "l":
+ return ap.lDispatch(params)
+ case "m":
+ return ap.eventHandler.SGR(getInts(params, 1, 0))
+ case "r":
+ ints := getInts(params, 2, 1)
+ top, bottom := ints[0], ints[1]
+ return ap.eventHandler.DECSTBM(top, bottom)
+ default:
+ ap.logf("ERROR: Unsupported CSI command: '%s', with full context: %v", cmd, ap.context)
+ return nil
+ }
+
+}
+
+func (ap *AnsiParser) print() error {
+ return ap.eventHandler.Print(ap.context.currentChar)
+}
+
+func (ap *AnsiParser) clear() error {
+ ap.context = &ansiContext{}
+ return nil
+}
+
+func (ap *AnsiParser) execute() error {
+ return ap.eventHandler.Execute(ap.context.currentChar)
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/states.go b/vendor/github.com/Azure/go-ansiterm/states.go
new file mode 100644
index 00000000..f2ea1fcd
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/states.go
@@ -0,0 +1,71 @@
+package ansiterm
+
+type stateID int
+
+type state interface {
+ Enter() error
+ Exit() error
+ Handle(byte) (state, error)
+ Name() string
+ Transition(state) error
+}
+
+type baseState struct {
+ name string
+ parser *AnsiParser
+}
+
+func (base baseState) Enter() error {
+ return nil
+}
+
+func (base baseState) Exit() error {
+ return nil
+}
+
+func (base baseState) Handle(b byte) (s state, e error) {
+
+ switch {
+ case b == CSI_ENTRY:
+ return base.parser.csiEntry, nil
+ case b == DCS_ENTRY:
+ return base.parser.dcsEntry, nil
+ case b == ANSI_ESCAPE_PRIMARY:
+ return base.parser.escape, nil
+ case b == OSC_STRING:
+ return base.parser.oscString, nil
+ case sliceContains(toGroundBytes, b):
+ return base.parser.ground, nil
+ }
+
+ return nil, nil
+}
+
+func (base baseState) Name() string {
+ return base.name
+}
+
+func (base baseState) Transition(s state) error {
+ if s == base.parser.ground {
+ execBytes := []byte{0x18}
+ execBytes = append(execBytes, 0x1A)
+ execBytes = append(execBytes, getByteRange(0x80, 0x8F)...)
+ execBytes = append(execBytes, getByteRange(0x91, 0x97)...)
+ execBytes = append(execBytes, 0x99)
+ execBytes = append(execBytes, 0x9A)
+
+ if sliceContains(execBytes, base.parser.context.currentChar) {
+ return base.parser.execute()
+ }
+ }
+
+ return nil
+}
+
+type dcsEntryState struct {
+ baseState
+}
+
+type errorState struct {
+ baseState
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/utilities.go b/vendor/github.com/Azure/go-ansiterm/utilities.go
new file mode 100644
index 00000000..39211449
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/utilities.go
@@ -0,0 +1,21 @@
+package ansiterm
+
+import (
+ "strconv"
+)
+
+func sliceContains(bytes []byte, b byte) bool {
+ for _, v := range bytes {
+ if v == b {
+ return true
+ }
+ }
+
+ return false
+}
+
+func convertBytesToInteger(bytes []byte) int {
+ s := string(bytes)
+ i, _ := strconv.Atoi(s)
+ return i
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/ansi.go b/vendor/github.com/Azure/go-ansiterm/winterm/ansi.go
new file mode 100644
index 00000000..5599082a
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/winterm/ansi.go
@@ -0,0 +1,196 @@
+// +build windows
+
+package winterm
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "syscall"
+
+ "github.com/Azure/go-ansiterm"
+ windows "golang.org/x/sys/windows"
+)
+
+// Windows keyboard constants
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx.
+const (
+ VK_PRIOR = 0x21 // PAGE UP key
+ VK_NEXT = 0x22 // PAGE DOWN key
+ VK_END = 0x23 // END key
+ VK_HOME = 0x24 // HOME key
+ VK_LEFT = 0x25 // LEFT ARROW key
+ VK_UP = 0x26 // UP ARROW key
+ VK_RIGHT = 0x27 // RIGHT ARROW key
+ VK_DOWN = 0x28 // DOWN ARROW key
+ VK_SELECT = 0x29 // SELECT key
+ VK_PRINT = 0x2A // PRINT key
+ VK_EXECUTE = 0x2B // EXECUTE key
+ VK_SNAPSHOT = 0x2C // PRINT SCREEN key
+ VK_INSERT = 0x2D // INS key
+ VK_DELETE = 0x2E // DEL key
+ VK_HELP = 0x2F // HELP key
+ VK_F1 = 0x70 // F1 key
+ VK_F2 = 0x71 // F2 key
+ VK_F3 = 0x72 // F3 key
+ VK_F4 = 0x73 // F4 key
+ VK_F5 = 0x74 // F5 key
+ VK_F6 = 0x75 // F6 key
+ VK_F7 = 0x76 // F7 key
+ VK_F8 = 0x77 // F8 key
+ VK_F9 = 0x78 // F9 key
+ VK_F10 = 0x79 // F10 key
+ VK_F11 = 0x7A // F11 key
+ VK_F12 = 0x7B // F12 key
+
+ RIGHT_ALT_PRESSED = 0x0001
+ LEFT_ALT_PRESSED = 0x0002
+ RIGHT_CTRL_PRESSED = 0x0004
+ LEFT_CTRL_PRESSED = 0x0008
+ SHIFT_PRESSED = 0x0010
+ NUMLOCK_ON = 0x0020
+ SCROLLLOCK_ON = 0x0040
+ CAPSLOCK_ON = 0x0080
+ ENHANCED_KEY = 0x0100
+)
+
+type ansiCommand struct {
+ CommandBytes []byte
+ Command string
+ Parameters []string
+ IsSpecial bool
+}
+
+func newAnsiCommand(command []byte) *ansiCommand {
+
+ if isCharacterSelectionCmdChar(command[1]) {
+ // Is Character Set Selection commands
+ return &ansiCommand{
+ CommandBytes: command,
+ Command: string(command),
+ IsSpecial: true,
+ }
+ }
+
+ // last char is command character
+ lastCharIndex := len(command) - 1
+
+ ac := &ansiCommand{
+ CommandBytes: command,
+ Command: string(command[lastCharIndex]),
+ IsSpecial: false,
+ }
+
+ // more than a single escape
+ if lastCharIndex != 0 {
+ start := 1
+ // skip if double char escape sequence
+ if command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY {
+ start++
+ }
+ // convert this to GetNextParam method
+ ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP)
+ }
+
+ return ac
+}
+
+func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 {
+ if index < 0 || index >= len(ac.Parameters) {
+ return defaultValue
+ }
+
+ param, err := strconv.ParseInt(ac.Parameters[index], 10, 16)
+ if err != nil {
+ return defaultValue
+ }
+
+ return int16(param)
+}
+
+func (ac *ansiCommand) String() string {
+ return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
+ bytesToHex(ac.CommandBytes),
+ ac.Command,
+ strings.Join(ac.Parameters, "\",\""))
+}
+
+// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands.
+// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html.
+func isAnsiCommandChar(b byte) bool {
+ switch {
+ case ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY:
+ return true
+ case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM:
+ // non-CSI escape sequence terminator
+ return true
+ case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.ANSI_BEL:
+ // String escape sequence terminator
+ return true
+ }
+ return false
+}
+
+func isXtermOscSequence(command []byte, current byte) bool {
+ return (len(command) >= 2 && command[0] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL)
+}
+
+func isCharacterSelectionCmdChar(b byte) bool {
+ return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.ANSI_CMD_G3)
+}
+
+// bytesToHex converts a slice of bytes to a human-readable string.
+func bytesToHex(b []byte) string {
+ hex := make([]string, len(b))
+ for i, ch := range b {
+ hex[i] = fmt.Sprintf("%X", ch)
+ }
+ return strings.Join(hex, "")
+}
+
+// ensureInRange adjusts the passed value, if necessary, to ensure it is within
+// the passed min / max range.
+func ensureInRange(n int16, min int16, max int16) int16 {
+ if n < min {
+ return min
+ } else if n > max {
+ return max
+ } else {
+ return n
+ }
+}
+
+func GetStdFile(nFile int) (*os.File, uintptr) {
+ var file *os.File
+
+ // syscall uses negative numbers
+ // windows package uses very big uint32
+ // Keep these switches split so we don't have to convert ints too much.
+ switch uint32(nFile) {
+ case windows.STD_INPUT_HANDLE:
+ file = os.Stdin
+ case windows.STD_OUTPUT_HANDLE:
+ file = os.Stdout
+ case windows.STD_ERROR_HANDLE:
+ file = os.Stderr
+ default:
+ switch nFile {
+ case syscall.STD_INPUT_HANDLE:
+ file = os.Stdin
+ case syscall.STD_OUTPUT_HANDLE:
+ file = os.Stdout
+ case syscall.STD_ERROR_HANDLE:
+ file = os.Stderr
+ default:
+ panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
+ }
+ }
+
+ fd, err := syscall.GetStdHandle(nFile)
+ if err != nil {
+ panic(fmt.Errorf("Invalid standard handle identifier: %v -- %v", nFile, err))
+ }
+
+ return file, uintptr(fd)
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/api.go b/vendor/github.com/Azure/go-ansiterm/winterm/api.go
new file mode 100644
index 00000000..6055e33b
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/winterm/api.go
@@ -0,0 +1,327 @@
+// +build windows
+
+package winterm
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+//===========================================================================================================
+// IMPORTANT NOTE:
+//
+// The methods below make extensive use of the "unsafe" package to obtain the required pointers.
+// Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack
+// variables) the pointers reference *before* the API completes.
+//
+// As a result, in those cases, the code must hint that the variables remain in active by invoking the
+// dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer
+// require unsafe pointers.
+//
+// If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform
+// the garbage collector the variables remain in use if:
+//
+// -- The value is not a pointer (e.g., int32, struct)
+// -- The value is not referenced by the method after passing the pointer to Windows
+//
+// See http://golang.org/doc/go1.3.
+//===========================================================================================================
+
+var (
+ kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
+
+ getConsoleCursorInfoProc = kernel32DLL.NewProc("GetConsoleCursorInfo")
+ setConsoleCursorInfoProc = kernel32DLL.NewProc("SetConsoleCursorInfo")
+ setConsoleCursorPositionProc = kernel32DLL.NewProc("SetConsoleCursorPosition")
+ setConsoleModeProc = kernel32DLL.NewProc("SetConsoleMode")
+ getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
+ setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
+ scrollConsoleScreenBufferProc = kernel32DLL.NewProc("ScrollConsoleScreenBufferA")
+ setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute")
+ setConsoleWindowInfoProc = kernel32DLL.NewProc("SetConsoleWindowInfo")
+ writeConsoleOutputProc = kernel32DLL.NewProc("WriteConsoleOutputW")
+ readConsoleInputProc = kernel32DLL.NewProc("ReadConsoleInputW")
+ waitForSingleObjectProc = kernel32DLL.NewProc("WaitForSingleObject")
+)
+
+// Windows Console constants
+const (
+ // Console modes
+ // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
+ ENABLE_PROCESSED_INPUT = 0x0001
+ ENABLE_LINE_INPUT = 0x0002
+ ENABLE_ECHO_INPUT = 0x0004
+ ENABLE_WINDOW_INPUT = 0x0008
+ ENABLE_MOUSE_INPUT = 0x0010
+ ENABLE_INSERT_MODE = 0x0020
+ ENABLE_QUICK_EDIT_MODE = 0x0040
+ ENABLE_EXTENDED_FLAGS = 0x0080
+ ENABLE_AUTO_POSITION = 0x0100
+ ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
+
+ ENABLE_PROCESSED_OUTPUT = 0x0001
+ ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+ DISABLE_NEWLINE_AUTO_RETURN = 0x0008
+ ENABLE_LVB_GRID_WORLDWIDE = 0x0010
+
+ // Character attributes
+ // Note:
+ // -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
+ // Clearing all foreground or background colors results in black; setting all creates white.
+ // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
+ FOREGROUND_BLUE uint16 = 0x0001
+ FOREGROUND_GREEN uint16 = 0x0002
+ FOREGROUND_RED uint16 = 0x0004
+ FOREGROUND_INTENSITY uint16 = 0x0008
+ FOREGROUND_MASK uint16 = 0x000F
+
+ BACKGROUND_BLUE uint16 = 0x0010
+ BACKGROUND_GREEN uint16 = 0x0020
+ BACKGROUND_RED uint16 = 0x0040
+ BACKGROUND_INTENSITY uint16 = 0x0080
+ BACKGROUND_MASK uint16 = 0x00F0
+
+ COMMON_LVB_MASK uint16 = 0xFF00
+ COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000
+ COMMON_LVB_UNDERSCORE uint16 = 0x8000
+
+ // Input event types
+ // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
+ KEY_EVENT = 0x0001
+ MOUSE_EVENT = 0x0002
+ WINDOW_BUFFER_SIZE_EVENT = 0x0004
+ MENU_EVENT = 0x0008
+ FOCUS_EVENT = 0x0010
+
+ // WaitForSingleObject return codes
+ WAIT_ABANDONED = 0x00000080
+ WAIT_FAILED = 0xFFFFFFFF
+ WAIT_SIGNALED = 0x0000000
+ WAIT_TIMEOUT = 0x00000102
+
+ // WaitForSingleObject wait duration
+ WAIT_INFINITE = 0xFFFFFFFF
+ WAIT_ONE_SECOND = 1000
+ WAIT_HALF_SECOND = 500
+ WAIT_QUARTER_SECOND = 250
+)
+
+// Windows API Console types
+// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD)
+// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment
+type (
+ CHAR_INFO struct {
+ UnicodeChar uint16
+ Attributes uint16
+ }
+
+ CONSOLE_CURSOR_INFO struct {
+ Size uint32
+ Visible int32
+ }
+
+ CONSOLE_SCREEN_BUFFER_INFO struct {
+ Size COORD
+ CursorPosition COORD
+ Attributes uint16
+ Window SMALL_RECT
+ MaximumWindowSize COORD
+ }
+
+ COORD struct {
+ X int16
+ Y int16
+ }
+
+ SMALL_RECT struct {
+ Left int16
+ Top int16
+ Right int16
+ Bottom int16
+ }
+
+ // INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
+ // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
+ INPUT_RECORD struct {
+ EventType uint16
+ KeyEvent KEY_EVENT_RECORD
+ }
+
+ KEY_EVENT_RECORD struct {
+ KeyDown int32
+ RepeatCount uint16
+ VirtualKeyCode uint16
+ VirtualScanCode uint16
+ UnicodeChar uint16
+ ControlKeyState uint32
+ }
+
+ WINDOW_BUFFER_SIZE struct {
+ Size COORD
+ }
+)
+
+// boolToBOOL converts a Go bool into a Windows int32.
+func boolToBOOL(f bool) int32 {
+ if f {
+ return int32(1)
+ } else {
+ return int32(0)
+ }
+}
+
+// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor.
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx.
+func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
+ r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
+ return checkError(r1, r2, err)
+}
+
+// SetConsoleCursorInfo sets the size and visiblity of the console cursor.
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx.
+func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
+ r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
+ return checkError(r1, r2, err)
+}
+
+// SetConsoleCursorPosition location of the console cursor.
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
+func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
+ r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
+ use(coord)
+ return checkError(r1, r2, err)
+}
+
+// GetConsoleMode gets the console mode for given file descriptor
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
+func GetConsoleMode(handle uintptr) (mode uint32, err error) {
+ err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
+ return mode, err
+}
+
+// SetConsoleMode sets the console mode for given file descriptor
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
+func SetConsoleMode(handle uintptr, mode uint32) error {
+ r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
+ use(mode)
+ return checkError(r1, r2, err)
+}
+
+// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
+func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
+ info := CONSOLE_SCREEN_BUFFER_INFO{}
+ err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
+ if err != nil {
+ return nil, err
+ }
+ return &info, nil
+}
+
+func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
+ r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
+ use(scrollRect)
+ use(clipRect)
+ use(destOrigin)
+ use(char)
+ return checkError(r1, r2, err)
+}
+
+// SetConsoleScreenBufferSize sets the size of the console screen buffer.
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
+func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
+ r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
+ use(coord)
+ return checkError(r1, r2, err)
+}
+
+// SetConsoleTextAttribute sets the attributes of characters written to the
+// console screen buffer by the WriteFile or WriteConsole function.
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
+func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
+ r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
+ use(attribute)
+ return checkError(r1, r2, err)
+}
+
+// SetConsoleWindowInfo sets the size and position of the console screen buffer's window.
+// Note that the size and location must be within and no larger than the backing console screen buffer.
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
+func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
+ r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
+ use(isAbsolute)
+ use(rect)
+ return checkError(r1, r2, err)
+}
+
+// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer.
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
+func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
+ r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
+ use(buffer)
+ use(bufferSize)
+ use(bufferCoord)
+ return checkError(r1, r2, err)
+}
+
+// ReadConsoleInput reads (and removes) data from the console input buffer.
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
+func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
+ r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
+ use(buffer)
+ return checkError(r1, r2, err)
+}
+
+// WaitForSingleObject waits for the passed handle to be signaled.
+// It returns true if the handle was signaled; false otherwise.
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
+func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
+ r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait)))
+ switch r1 {
+ case WAIT_ABANDONED, WAIT_TIMEOUT:
+ return false, nil
+ case WAIT_SIGNALED:
+ return true, nil
+ }
+ use(msWait)
+ return false, err
+}
+
+// String helpers
+func (info CONSOLE_SCREEN_BUFFER_INFO) String() string {
+ return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize)
+}
+
+func (coord COORD) String() string {
+ return fmt.Sprintf("%v,%v", coord.X, coord.Y)
+}
+
+func (rect SMALL_RECT) String() string {
+ return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom)
+}
+
+// checkError evaluates the results of a Windows API call and returns the error if it failed.
+func checkError(r1, r2 uintptr, err error) error {
+ // Windows APIs return non-zero to indicate success
+ if r1 != 0 {
+ return nil
+ }
+
+ // Return the error if provided, otherwise default to EINVAL
+ if err != nil {
+ return err
+ }
+ return syscall.EINVAL
+}
+
+// coordToPointer converts a COORD into a uintptr (by fooling the type system).
+func coordToPointer(c COORD) uintptr {
+ // Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass.
+ return uintptr(*((*uint32)(unsafe.Pointer(&c))))
+}
+
+// use is a no-op, but the compiler cannot see that it is.
+// Calling use(p) ensures that p is kept live until that point.
+func use(p interface{}) {}
diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go b/vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go
new file mode 100644
index 00000000..cbec8f72
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/winterm/attr_translation.go
@@ -0,0 +1,100 @@
+// +build windows
+
+package winterm
+
+import "github.com/Azure/go-ansiterm"
+
+const (
+ FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
+ BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
+)
+
+// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
+// request represented by the passed ANSI mode.
+func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) {
+ switch ansiMode {
+
+ // Mode styles
+ case ansiterm.ANSI_SGR_BOLD:
+ windowsMode = windowsMode | FOREGROUND_INTENSITY
+
+ case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF:
+ windowsMode &^= FOREGROUND_INTENSITY
+
+ case ansiterm.ANSI_SGR_UNDERLINE:
+ windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
+
+ case ansiterm.ANSI_SGR_REVERSE:
+ inverted = true
+
+ case ansiterm.ANSI_SGR_REVERSE_OFF:
+ inverted = false
+
+ case ansiterm.ANSI_SGR_UNDERLINE_OFF:
+ windowsMode &^= COMMON_LVB_UNDERSCORE
+
+ // Foreground colors
+ case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT:
+ windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK)
+
+ case ansiterm.ANSI_SGR_FOREGROUND_BLACK:
+ windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK)
+
+ case ansiterm.ANSI_SGR_FOREGROUND_RED:
+ windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED
+
+ case ansiterm.ANSI_SGR_FOREGROUND_GREEN:
+ windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN
+
+ case ansiterm.ANSI_SGR_FOREGROUND_YELLOW:
+ windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN
+
+ case ansiterm.ANSI_SGR_FOREGROUND_BLUE:
+ windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE
+
+ case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA:
+ windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE
+
+ case ansiterm.ANSI_SGR_FOREGROUND_CYAN:
+ windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE
+
+ case ansiterm.ANSI_SGR_FOREGROUND_WHITE:
+ windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
+
+ // Background colors
+ case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT:
+ // Black with no intensity
+ windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK)
+
+ case ansiterm.ANSI_SGR_BACKGROUND_BLACK:
+ windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK)
+
+ case ansiterm.ANSI_SGR_BACKGROUND_RED:
+ windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED
+
+ case ansiterm.ANSI_SGR_BACKGROUND_GREEN:
+ windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN
+
+ case ansiterm.ANSI_SGR_BACKGROUND_YELLOW:
+ windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN
+
+ case ansiterm.ANSI_SGR_BACKGROUND_BLUE:
+ windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE
+
+ case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA:
+ windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE
+
+ case ansiterm.ANSI_SGR_BACKGROUND_CYAN:
+ windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE
+
+ case ansiterm.ANSI_SGR_BACKGROUND_WHITE:
+ windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
+ }
+
+ return windowsMode, inverted
+}
+
+// invertAttributes inverts the foreground and background colors of a Windows attributes value
+func invertAttributes(windowsMode uint16) uint16 {
+ return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go b/vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go
new file mode 100644
index 00000000..3ee06ea7
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go
@@ -0,0 +1,101 @@
+// +build windows
+
+package winterm
+
+const (
+ horizontal = iota
+ vertical
+)
+
+func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
+ if h.originMode {
+ sr := h.effectiveSr(info.Window)
+ return SMALL_RECT{
+ Top: sr.top,
+ Bottom: sr.bottom,
+ Left: 0,
+ Right: info.Size.X - 1,
+ }
+ } else {
+ return SMALL_RECT{
+ Top: info.Window.Top,
+ Bottom: info.Window.Bottom,
+ Left: 0,
+ Right: info.Size.X - 1,
+ }
+ }
+}
+
+// setCursorPosition sets the cursor to the specified position, bounded to the screen size
+func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error {
+ position.X = ensureInRange(position.X, window.Left, window.Right)
+ position.Y = ensureInRange(position.Y, window.Top, window.Bottom)
+ err := SetConsoleCursorPosition(h.fd, position)
+ if err != nil {
+ return err
+ }
+ h.logf("Cursor position set: (%d, %d)", position.X, position.Y)
+ return err
+}
+
+func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error {
+ return h.moveCursor(vertical, param)
+}
+
+func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error {
+ return h.moveCursor(horizontal, param)
+}
+
+func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ position := info.CursorPosition
+ switch moveMode {
+ case horizontal:
+ position.X += int16(param)
+ case vertical:
+ position.Y += int16(param)
+ }
+
+ if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) moveCursorLine(param int) error {
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ position := info.CursorPosition
+ position.X = 0
+ position.Y += int16(param)
+
+ if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error {
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ position := info.CursorPosition
+ position.X = int16(param) - 1
+
+ if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go b/vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go
new file mode 100644
index 00000000..244b5fa2
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/winterm/erase_helpers.go
@@ -0,0 +1,84 @@
+// +build windows
+
+package winterm
+
+import "github.com/Azure/go-ansiterm"
+
+func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error {
+ // Ignore an invalid (negative area) request
+ if toCoord.Y < fromCoord.Y {
+ return nil
+ }
+
+ var err error
+
+ var coordStart = COORD{}
+ var coordEnd = COORD{}
+
+ xCurrent, yCurrent := fromCoord.X, fromCoord.Y
+ xEnd, yEnd := toCoord.X, toCoord.Y
+
+ // Clear any partial initial line
+ if xCurrent > 0 {
+ coordStart.X, coordStart.Y = xCurrent, yCurrent
+ coordEnd.X, coordEnd.Y = xEnd, yCurrent
+
+ err = h.clearRect(attributes, coordStart, coordEnd)
+ if err != nil {
+ return err
+ }
+
+ xCurrent = 0
+ yCurrent += 1
+ }
+
+ // Clear intervening rectangular section
+ if yCurrent < yEnd {
+ coordStart.X, coordStart.Y = xCurrent, yCurrent
+ coordEnd.X, coordEnd.Y = xEnd, yEnd-1
+
+ err = h.clearRect(attributes, coordStart, coordEnd)
+ if err != nil {
+ return err
+ }
+
+ xCurrent = 0
+ yCurrent = yEnd
+ }
+
+ // Clear remaining partial ending line
+ coordStart.X, coordStart.Y = xCurrent, yCurrent
+ coordEnd.X, coordEnd.Y = xEnd, yEnd
+
+ err = h.clearRect(attributes, coordStart, coordEnd)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error {
+ region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X}
+ width := toCoord.X - fromCoord.X + 1
+ height := toCoord.Y - fromCoord.Y + 1
+ size := uint32(width) * uint32(height)
+
+ if size <= 0 {
+ return nil
+ }
+
+ buffer := make([]CHAR_INFO, size)
+
+ char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes}
+ for i := 0; i < int(size); i++ {
+ buffer[i] = char
+ }
+
+ err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, ®ion)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go b/vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go
new file mode 100644
index 00000000..2d27fa1d
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/winterm/scroll_helper.go
@@ -0,0 +1,118 @@
+// +build windows
+
+package winterm
+
+// effectiveSr gets the current effective scroll region in buffer coordinates
+func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion {
+ top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom)
+ bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom)
+ if top >= bottom {
+ top = window.Top
+ bottom = window.Bottom
+ }
+ return scrollRegion{top: top, bottom: bottom}
+}
+
+func (h *windowsAnsiEventHandler) scrollUp(param int) error {
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ sr := h.effectiveSr(info.Window)
+ return h.scroll(param, sr, info)
+}
+
+func (h *windowsAnsiEventHandler) scrollDown(param int) error {
+ return h.scrollUp(-param)
+}
+
+func (h *windowsAnsiEventHandler) deleteLines(param int) error {
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ start := info.CursorPosition.Y
+ sr := h.effectiveSr(info.Window)
+ // Lines cannot be inserted or deleted outside the scrolling region.
+ if start >= sr.top && start <= sr.bottom {
+ sr.top = start
+ return h.scroll(param, sr, info)
+ } else {
+ return nil
+ }
+}
+
+func (h *windowsAnsiEventHandler) insertLines(param int) error {
+ return h.deleteLines(-param)
+}
+
+// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates.
+func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error {
+ h.logf("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom)
+ h.logf("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
+
+ // Copy from and clip to the scroll region (full buffer width)
+ scrollRect := SMALL_RECT{
+ Top: sr.top,
+ Bottom: sr.bottom,
+ Left: 0,
+ Right: info.Size.X - 1,
+ }
+
+ // Origin to which area should be copied
+ destOrigin := COORD{
+ X: 0,
+ Y: sr.top - int16(param),
+ }
+
+ char := CHAR_INFO{
+ UnicodeChar: ' ',
+ Attributes: h.attributes,
+ }
+
+ if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) deleteCharacters(param int) error {
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+ return h.scrollLine(param, info.CursorPosition, info)
+}
+
+func (h *windowsAnsiEventHandler) insertCharacters(param int) error {
+ return h.deleteCharacters(-param)
+}
+
+// scrollLine scrolls a line horizontally starting at the provided position by a number of columns.
+func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error {
+ // Copy from and clip to the scroll region (full buffer width)
+ scrollRect := SMALL_RECT{
+ Top: position.Y,
+ Bottom: position.Y,
+ Left: position.X,
+ Right: info.Size.X - 1,
+ }
+
+ // Origin to which area should be copied
+ destOrigin := COORD{
+ X: position.X - int16(columns),
+ Y: position.Y,
+ }
+
+ char := CHAR_INFO{
+ UnicodeChar: ' ',
+ Attributes: h.attributes,
+ }
+
+ if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/utilities.go b/vendor/github.com/Azure/go-ansiterm/winterm/utilities.go
new file mode 100644
index 00000000..afa7635d
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/winterm/utilities.go
@@ -0,0 +1,9 @@
+// +build windows
+
+package winterm
+
+// AddInRange increments a value by the passed quantity while ensuring the values
+// always remain within the supplied min / max range.
+func addInRange(n int16, increment int16, min int16, max int16) int16 {
+ return ensureInRange(n+increment, min, max)
+}
diff --git a/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go b/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go
new file mode 100644
index 00000000..2d40fb75
--- /dev/null
+++ b/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go
@@ -0,0 +1,743 @@
+// +build windows
+
+package winterm
+
+import (
+ "bytes"
+ "log"
+ "os"
+ "strconv"
+
+ "github.com/Azure/go-ansiterm"
+)
+
+type windowsAnsiEventHandler struct {
+ fd uintptr
+ file *os.File
+ infoReset *CONSOLE_SCREEN_BUFFER_INFO
+ sr scrollRegion
+ buffer bytes.Buffer
+ attributes uint16
+ inverted bool
+ wrapNext bool
+ drewMarginByte bool
+ originMode bool
+ marginByte byte
+ curInfo *CONSOLE_SCREEN_BUFFER_INFO
+ curPos COORD
+ logf func(string, ...interface{})
+}
+
+type Option func(*windowsAnsiEventHandler)
+
+func WithLogf(f func(string, ...interface{})) Option {
+ return func(w *windowsAnsiEventHandler) {
+ w.logf = f
+ }
+}
+
+func CreateWinEventHandler(fd uintptr, file *os.File, opts ...Option) ansiterm.AnsiEventHandler {
+ infoReset, err := GetConsoleScreenBufferInfo(fd)
+ if err != nil {
+ return nil
+ }
+
+ h := &windowsAnsiEventHandler{
+ fd: fd,
+ file: file,
+ infoReset: infoReset,
+ attributes: infoReset.Attributes,
+ }
+ for _, o := range opts {
+ o(h)
+ }
+
+ if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
+ logFile, _ := os.Create("winEventHandler.log")
+ logger := log.New(logFile, "", log.LstdFlags)
+ if h.logf != nil {
+ l := h.logf
+ h.logf = func(s string, v ...interface{}) {
+ l(s, v...)
+ logger.Printf(s, v...)
+ }
+ } else {
+ h.logf = logger.Printf
+ }
+ }
+
+ if h.logf == nil {
+ h.logf = func(string, ...interface{}) {}
+ }
+
+ return h
+}
+
+type scrollRegion struct {
+ top int16
+ bottom int16
+}
+
+// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the
+// current cursor position and scroll region settings, in which case it returns
+// true. If no special handling is necessary, then it does nothing and returns
+// false.
+//
+// In the false case, the caller should ensure that a carriage return
+// and line feed are inserted or that the text is otherwise wrapped.
+func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
+ if h.wrapNext {
+ if err := h.Flush(); err != nil {
+ return false, err
+ }
+ h.clearWrap()
+ }
+ pos, info, err := h.getCurrentInfo()
+ if err != nil {
+ return false, err
+ }
+ sr := h.effectiveSr(info.Window)
+ if pos.Y == sr.bottom {
+ // Scrolling is necessary. Let Windows automatically scroll if the scrolling region
+ // is the full window.
+ if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
+ if includeCR {
+ pos.X = 0
+ h.updatePos(pos)
+ }
+ return false, nil
+ }
+
+ // A custom scroll region is active. Scroll the window manually to simulate
+ // the LF.
+ if err := h.Flush(); err != nil {
+ return false, err
+ }
+ h.logf("Simulating LF inside scroll region")
+ if err := h.scrollUp(1); err != nil {
+ return false, err
+ }
+ if includeCR {
+ pos.X = 0
+ if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
+ return false, err
+ }
+ }
+ return true, nil
+
+ } else if pos.Y < info.Window.Bottom {
+ // Let Windows handle the LF.
+ pos.Y++
+ if includeCR {
+ pos.X = 0
+ }
+ h.updatePos(pos)
+ return false, nil
+ } else {
+ // The cursor is at the bottom of the screen but outside the scroll
+ // region. Skip the LF.
+ h.logf("Simulating LF outside scroll region")
+ if includeCR {
+ if err := h.Flush(); err != nil {
+ return false, err
+ }
+ pos.X = 0
+ if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
+ return false, err
+ }
+ }
+ return true, nil
+ }
+}
+
+// executeLF executes a LF without a CR.
+func (h *windowsAnsiEventHandler) executeLF() error {
+ handled, err := h.simulateLF(false)
+ if err != nil {
+ return err
+ }
+ if !handled {
+ // Windows LF will reset the cursor column position. Write the LF
+ // and restore the cursor position.
+ pos, _, err := h.getCurrentInfo()
+ if err != nil {
+ return err
+ }
+ h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
+ if pos.X != 0 {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("Resetting cursor position for LF without CR")
+ if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) Print(b byte) error {
+ if h.wrapNext {
+ h.buffer.WriteByte(h.marginByte)
+ h.clearWrap()
+ if _, err := h.simulateLF(true); err != nil {
+ return err
+ }
+ }
+ pos, info, err := h.getCurrentInfo()
+ if err != nil {
+ return err
+ }
+ if pos.X == info.Size.X-1 {
+ h.wrapNext = true
+ h.marginByte = b
+ } else {
+ pos.X++
+ h.updatePos(pos)
+ h.buffer.WriteByte(b)
+ }
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) Execute(b byte) error {
+ switch b {
+ case ansiterm.ANSI_TAB:
+ h.logf("Execute(TAB)")
+ // Move to the next tab stop, but preserve auto-wrap if already set.
+ if !h.wrapNext {
+ pos, info, err := h.getCurrentInfo()
+ if err != nil {
+ return err
+ }
+ pos.X = (pos.X + 8) - pos.X%8
+ if pos.X >= info.Size.X {
+ pos.X = info.Size.X - 1
+ }
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
+ return err
+ }
+ }
+ return nil
+
+ case ansiterm.ANSI_BEL:
+ h.buffer.WriteByte(ansiterm.ANSI_BEL)
+ return nil
+
+ case ansiterm.ANSI_BACKSPACE:
+ if h.wrapNext {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.clearWrap()
+ }
+ pos, _, err := h.getCurrentInfo()
+ if err != nil {
+ return err
+ }
+ if pos.X > 0 {
+ pos.X--
+ h.updatePos(pos)
+ h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE)
+ }
+ return nil
+
+ case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
+ // Treat as true LF.
+ return h.executeLF()
+
+ case ansiterm.ANSI_LINE_FEED:
+ // Simulate a CR and LF for now since there is no way in go-ansiterm
+ // to tell if the LF should include CR (and more things break when it's
+ // missing than when it's incorrectly added).
+ handled, err := h.simulateLF(true)
+ if handled || err != nil {
+ return err
+ }
+ return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
+
+ case ansiterm.ANSI_CARRIAGE_RETURN:
+ if h.wrapNext {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.clearWrap()
+ }
+ pos, _, err := h.getCurrentInfo()
+ if err != nil {
+ return err
+ }
+ if pos.X != 0 {
+ pos.X = 0
+ h.updatePos(pos)
+ h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN)
+ }
+ return nil
+
+ default:
+ return nil
+ }
+}
+
+func (h *windowsAnsiEventHandler) CUU(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("CUU: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+ return h.moveCursorVertical(-param)
+}
+
+func (h *windowsAnsiEventHandler) CUD(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("CUD: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+ return h.moveCursorVertical(param)
+}
+
+func (h *windowsAnsiEventHandler) CUF(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("CUF: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+ return h.moveCursorHorizontal(param)
+}
+
+func (h *windowsAnsiEventHandler) CUB(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("CUB: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+ return h.moveCursorHorizontal(-param)
+}
+
+func (h *windowsAnsiEventHandler) CNL(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("CNL: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+ return h.moveCursorLine(param)
+}
+
+func (h *windowsAnsiEventHandler) CPL(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("CPL: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+ return h.moveCursorLine(-param)
+}
+
+func (h *windowsAnsiEventHandler) CHA(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("CHA: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+ return h.moveCursorColumn(param)
+}
+
+func (h *windowsAnsiEventHandler) VPA(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("VPA: [[%d]]", param)
+ h.clearWrap()
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+ window := h.getCursorWindow(info)
+ position := info.CursorPosition
+ position.Y = window.Top + int16(param) - 1
+ return h.setCursorPosition(position, window)
+}
+
+func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("CUP: [[%d %d]]", row, col)
+ h.clearWrap()
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ window := h.getCursorWindow(info)
+ position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
+ return h.setCursorPosition(position, window)
+}
+
+func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("HVP: [[%d %d]]", row, col)
+ h.clearWrap()
+ return h.CUP(row, col)
+}
+
+func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
+ h.clearWrap()
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) DECOM(enable bool) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("DECOM: [%v]", []string{strconv.FormatBool(enable)})
+ h.clearWrap()
+ h.originMode = enable
+ return h.CUP(1, 1)
+}
+
+func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
+ h.clearWrap()
+ if err := h.ED(2); err != nil {
+ return err
+ }
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+ targetWidth := int16(80)
+ if use132 {
+ targetWidth = 132
+ }
+ if info.Size.X < targetWidth {
+ if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
+ h.logf("set buffer failed: %v", err)
+ return err
+ }
+ }
+ window := info.Window
+ window.Left = 0
+ window.Right = targetWidth - 1
+ if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
+ h.logf("set window failed: %v", err)
+ return err
+ }
+ if info.Size.X > targetWidth {
+ if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
+ h.logf("set buffer failed: %v", err)
+ return err
+ }
+ }
+ return SetConsoleCursorPosition(h.fd, COORD{0, 0})
+}
+
+func (h *windowsAnsiEventHandler) ED(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("ED: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+
+ // [J -- Erases from the cursor to the end of the screen, including the cursor position.
+ // [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
+ // [2J -- Erases the complete display. The cursor does not move.
+ // Notes:
+ // -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
+
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ var start COORD
+ var end COORD
+
+ switch param {
+ case 0:
+ start = info.CursorPosition
+ end = COORD{info.Size.X - 1, info.Size.Y - 1}
+
+ case 1:
+ start = COORD{0, 0}
+ end = info.CursorPosition
+
+ case 2:
+ start = COORD{0, 0}
+ end = COORD{info.Size.X - 1, info.Size.Y - 1}
+ }
+
+ err = h.clearRange(h.attributes, start, end)
+ if err != nil {
+ return err
+ }
+
+ // If the whole buffer was cleared, move the window to the top while preserving
+ // the window-relative cursor position.
+ if param == 2 {
+ pos := info.CursorPosition
+ window := info.Window
+ pos.Y -= window.Top
+ window.Bottom -= window.Top
+ window.Top = 0
+ if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
+ return err
+ }
+ if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) EL(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("EL: [%v]", strconv.Itoa(param))
+ h.clearWrap()
+
+ // [K -- Erases from the cursor to the end of the line, including the cursor position.
+ // [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
+ // [2K -- Erases the complete line.
+
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ var start COORD
+ var end COORD
+
+ switch param {
+ case 0:
+ start = info.CursorPosition
+ end = COORD{info.Size.X, info.CursorPosition.Y}
+
+ case 1:
+ start = COORD{0, info.CursorPosition.Y}
+ end = info.CursorPosition
+
+ case 2:
+ start = COORD{0, info.CursorPosition.Y}
+ end = COORD{info.Size.X, info.CursorPosition.Y}
+ }
+
+ err = h.clearRange(h.attributes, start, end)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) IL(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("IL: [%v]", strconv.Itoa(param))
+ h.clearWrap()
+ return h.insertLines(param)
+}
+
+func (h *windowsAnsiEventHandler) DL(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("DL: [%v]", strconv.Itoa(param))
+ h.clearWrap()
+ return h.deleteLines(param)
+}
+
+func (h *windowsAnsiEventHandler) ICH(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("ICH: [%v]", strconv.Itoa(param))
+ h.clearWrap()
+ return h.insertCharacters(param)
+}
+
+func (h *windowsAnsiEventHandler) DCH(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("DCH: [%v]", strconv.Itoa(param))
+ h.clearWrap()
+ return h.deleteCharacters(param)
+}
+
+func (h *windowsAnsiEventHandler) SGR(params []int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ strings := []string{}
+ for _, v := range params {
+ strings = append(strings, strconv.Itoa(v))
+ }
+
+ h.logf("SGR: [%v]", strings)
+
+ if len(params) <= 0 {
+ h.attributes = h.infoReset.Attributes
+ h.inverted = false
+ } else {
+ for _, attr := range params {
+
+ if attr == ansiterm.ANSI_SGR_RESET {
+ h.attributes = h.infoReset.Attributes
+ h.inverted = false
+ continue
+ }
+
+ h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
+ }
+ }
+
+ attributes := h.attributes
+ if h.inverted {
+ attributes = invertAttributes(attributes)
+ }
+ err := SetConsoleTextAttribute(h.fd, attributes)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) SU(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("SU: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+ return h.scrollUp(param)
+}
+
+func (h *windowsAnsiEventHandler) SD(param int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("SD: [%v]", []string{strconv.Itoa(param)})
+ h.clearWrap()
+ return h.scrollDown(param)
+}
+
+func (h *windowsAnsiEventHandler) DA(params []string) error {
+ h.logf("DA: [%v]", params)
+ // DA cannot be implemented because it must send data on the VT100 input stream,
+ // which is not available to go-ansiterm.
+ return nil
+}
+
+func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("DECSTBM: [%d, %d]", top, bottom)
+
+ // Windows is 0 indexed, Linux is 1 indexed
+ h.sr.top = int16(top - 1)
+ h.sr.bottom = int16(bottom - 1)
+
+ // This command also moves the cursor to the origin.
+ h.clearWrap()
+ return h.CUP(1, 1)
+}
+
+func (h *windowsAnsiEventHandler) RI() error {
+ if err := h.Flush(); err != nil {
+ return err
+ }
+ h.logf("RI: []")
+ h.clearWrap()
+
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ sr := h.effectiveSr(info.Window)
+ if info.CursorPosition.Y == sr.top {
+ return h.scrollDown(1)
+ }
+
+ return h.moveCursorVertical(-1)
+}
+
+func (h *windowsAnsiEventHandler) IND() error {
+ h.logf("IND: []")
+ return h.executeLF()
+}
+
+func (h *windowsAnsiEventHandler) Flush() error {
+ h.curInfo = nil
+ if h.buffer.Len() > 0 {
+ h.logf("Flush: [%s]", h.buffer.Bytes())
+ if _, err := h.buffer.WriteTo(h.file); err != nil {
+ return err
+ }
+ }
+
+ if h.wrapNext && !h.drewMarginByte {
+ h.logf("Flush: drawing margin byte '%c'", h.marginByte)
+
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return err
+ }
+
+ charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
+ size := COORD{1, 1}
+ position := COORD{0, 0}
+ region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
+ if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil {
+ return err
+ }
+ h.drewMarginByte = true
+ }
+ return nil
+}
+
+// cacheConsoleInfo ensures that the current console screen information has been queried
+// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos.
+func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
+ if h.curInfo == nil {
+ info, err := GetConsoleScreenBufferInfo(h.fd)
+ if err != nil {
+ return COORD{}, nil, err
+ }
+ h.curInfo = info
+ h.curPos = info.CursorPosition
+ }
+ return h.curPos, h.curInfo, nil
+}
+
+func (h *windowsAnsiEventHandler) updatePos(pos COORD) {
+ if h.curInfo == nil {
+ panic("failed to call getCurrentInfo before calling updatePos")
+ }
+ h.curPos = pos
+}
+
+// clearWrap clears the state where the cursor is in the margin
+// waiting for the next character before wrapping the line. This must
+// be done before most operations that act on the cursor.
+func (h *windowsAnsiEventHandler) clearWrap() {
+ h.wrapNext = false
+ h.drewMarginByte = false
+}
diff --git a/vendor/github.com/blacknon/go-sshlib/README.md b/vendor/github.com/blacknon/go-sshlib/README.md
index 95bdaa08..8dafcad5 100644
--- a/vendor/github.com/blacknon/go-sshlib/README.md
+++ b/vendor/github.com/blacknon/go-sshlib/README.md
@@ -6,6 +6,7 @@ go-sshlib
## About
A library to handle ssh easily with Golang.It can do multiple proxy, x11 forwarding, etc.
+Supported on Linux, macOS and Windows.
* This program refactors the processing performed by lssh(https://github.com/blacknon/lssh) so that it can be treated as a library.
@@ -19,7 +20,7 @@ A library to handle ssh easily with Golang.It can do multiple proxy, x11 forward
## Example
- // Copyright (c) 2019 Blacknon. All rights reserved.
+ // Copyright (c) 2022 Blacknon. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@@ -84,4 +85,3 @@ A library to handle ssh easily with Golang.It can do multiple proxy, x11 forward
// Start ssh shell
con.Shell(session)
}
-
diff --git a/vendor/github.com/blacknon/go-sshlib/cmd.go b/vendor/github.com/blacknon/go-sshlib/cmd.go
index 307d0593..3f1fac00 100644
--- a/vendor/github.com/blacknon/go-sshlib/cmd.go
+++ b/vendor/github.com/blacknon/go-sshlib/cmd.go
@@ -35,7 +35,8 @@ func (c *Connect) Command(command string) (err error) {
w, _ := c.Session.StdinPipe()
go io.Copy(w, c.Stdin)
} else {
- c.Session.Stdin = os.Stdin
+ stdin := GetStdin()
+ c.Session.Stdin = stdin
}
if c.Stdout != nil {
diff --git a/vendor/github.com/blacknon/go-sshlib/connect.go b/vendor/github.com/blacknon/go-sshlib/connect.go
index 4b8ae9b8..fac121e9 100644
--- a/vendor/github.com/blacknon/go-sshlib/connect.go
+++ b/vendor/github.com/blacknon/go-sshlib/connect.go
@@ -46,6 +46,31 @@ type Connect struct {
// Forward ssh agent flag.
ForwardAgent bool
+ // CheckKnownHosts if true, check knownhosts.
+ CheckKnownHosts bool
+
+ // OverwriteKnownHosts if true, if the knownhost is different, check whether to overwrite.
+ OverwriteKnownHosts bool
+
+ // KnownHostsFiles is list of knownhosts files path.
+ KnownHostsFiles []string
+
+ // TextAskWriteKnownHosts defines a confirmation message when writing a knownhost.
+ // We are using Go's template engine and have the following variables available.
+ // - Address ... ssh server hostname
+ // - RemoteAddr ... ssh server address
+ // - Fingerprint ... ssh PublicKey fingerprint
+ TextAskWriteKnownHosts string
+
+ // TextAskOverwriteKnownHosts defines a confirmation message when over-writing a knownhost.
+ // We are using Go's template engine and have the following variables available.
+ // - Address ... ssh server hostname
+ // - RemoteAddr ... ssh server address
+ // - OldKeyText ... old ssh PublicKey text.
+ // ex: /home/user/.ssh/known_hosts:17: ecdsa-sha2-nistp256 AAAAE2VjZHN...bJklasnFtkFSDyOjTFSv2g=
+ // - NewFingerprint ... new ssh PublicKey fingerprint
+ TextAskOverwriteKnownHosts string
+
// ssh-agent interface.
// agent.Agent or agent.ExtendedAgent
Agent AgentInterface
@@ -77,10 +102,19 @@ func (c *Connect) CreateClient(host, port, user string, authMethods []ssh.AuthMe
// Create new ssh.ClientConfig{}
config := &ssh.ClientConfig{
- User: user,
- Auth: authMethods,
- HostKeyCallback: ssh.InsecureIgnoreHostKey(),
- Timeout: time.Duration(timeout) * time.Second,
+ User: user,
+ Auth: authMethods,
+ Timeout: time.Duration(timeout) * time.Second,
+ }
+
+ if c.CheckKnownHosts {
+ if len(c.KnownHostsFiles) == 0 {
+ // append default files
+ c.KnownHostsFiles = append(c.KnownHostsFiles, "~/.ssh/known_hosts")
+ }
+ config.HostKeyCallback = c.verifyAndAppendNew
+ } else {
+ config.HostKeyCallback = ssh.InsecureIgnoreHostKey()
}
// check Dialer
@@ -172,14 +206,18 @@ func RequestTty(session *ssh.Session) (err error) {
}
// Get terminal window size
- fd := int(os.Stdin.Fd())
+ fd := int(os.Stdout.Fd())
width, hight, err := terminal.GetSize(fd)
if err != nil {
return
}
- // TODO(blacknon): 環境変数から取得する方式だと、Windowsでうまく動作するか不明なので確認して対処する
+ // Get env `TERM`
term := os.Getenv("TERM")
+ if len(term) == 0 {
+ term = "xterm"
+ }
+
if err = session.RequestPty(term, hight, width, modes); err != nil {
session.Close()
return
diff --git a/vendor/github.com/blacknon/go-sshlib/forward.go b/vendor/github.com/blacknon/go-sshlib/forward.go
index bc86b9ca..c32b41a8 100644
--- a/vendor/github.com/blacknon/go-sshlib/forward.go
+++ b/vendor/github.com/blacknon/go-sshlib/forward.go
@@ -2,8 +2,6 @@
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
-// TODO(blacknon): Dynamic Port forwardingと同じような感じでhttp proxyを生やす関数を追加
-
package sshlib
import (
@@ -339,10 +337,30 @@ func (c *Connect) TCPDynamicForward(address, port string) (err error) {
return
}
-// TODO(blacknon):
-// OpenSsh独自の実装っぽいので、追加はちょっと厳しいかも?
-// とりあえず調べてみる。
-//
// TCPReverseDynamicForward reverse forwarding tcp data.
// Like Openssh Reverse Dynamic forward (`ssh -R `).
-// func (c *Connect) TCPReverseDynamicForward(address, port string) (err error) {}
+func (c *Connect) TCPReverseDynamicForward(address, port string) (err error) {
+ // Create Socks5 config
+ conf := &socks5.Config{
+ Dial: func(ctx context.Context, n, addr string) (net.Conn, error) {
+ return net.Dial(n, addr)
+ },
+ Resolver: socks5Resolver{},
+ }
+
+ // create listner
+ listner, err := c.Client.Listen("tcp", net.JoinHostPort(address, port))
+ if err != nil {
+ return
+ }
+
+ // Create Socks5 server
+ s, err := socks5.New(conf)
+ if err != nil {
+ return
+ }
+
+ // Listen
+ err = s.Serve(listner)
+ return
+}
diff --git a/vendor/github.com/blacknon/go-sshlib/knownhosts.go b/vendor/github.com/blacknon/go-sshlib/knownhosts.go
new file mode 100644
index 00000000..cf7fff65
--- /dev/null
+++ b/vendor/github.com/blacknon/go-sshlib/knownhosts.go
@@ -0,0 +1,270 @@
+// Copyright (c) 2021 Blacknon. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+
+package sshlib
+
+import (
+ "bufio"
+ "fmt"
+ "net"
+ "os"
+ "os/signal"
+ "strings"
+ "syscall"
+ "text/template"
+
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/knownhosts"
+)
+
+type WriteInventory struct {
+ Address string
+ RemoteAddr string
+ Fingerprint string
+}
+
+type OverwriteInventory struct {
+ Address string
+ RemoteAddr string
+ Fingerprint string
+ OldKeyText string
+}
+
+// verifyAndAppendNew checks knownhosts from the files stored in c.KnownHostsFiles.
+// If there is a problem with the known hosts check, it returns an error and the check content.
+// If is no problem, error returns Nil.
+//
+// 【参考】: https://github.com/tatsushid/minssh/blob/57eae8c5bcf5d94639891f3267f05251f05face4/pkg/minssh/minssh.go#L190-L237
+func (c *Connect) verifyAndAppendNew(hostname string, remote net.Addr, key ssh.PublicKey) (err error) {
+ // set TextAskWriteKnownHosts default text
+ if len(c.TextAskWriteKnownHosts) == 0 {
+ c.TextAskWriteKnownHosts += "The authenticity of host '{{.Address}} ({{.RemoteAddr}})' can't be established.\n"
+ c.TextAskWriteKnownHosts += "RSA key fingerprint is {{.Fingerprint}}\n"
+ c.TextAskWriteKnownHosts += "Are you sure you want to continue connecting (yes/no)?"
+ }
+
+ // set TextAskOverwriteKnownHosts default text
+ if len(c.TextAskOverwriteKnownHosts) == 0 {
+ c.TextAskOverwriteKnownHosts += "The authenticity of host '{{.Address}} ({{.RemoteAddr}})' can't be established.\n"
+ c.TextAskOverwriteKnownHosts += "Old key: {{.OldKeyText}}\n"
+ c.TextAskOverwriteKnownHosts += "Are you sure you want to overwrite {{.Fingerprint}}, continue connecting (yes/no)?"
+ }
+
+ // check count KnownHostsFiles
+ if len(c.KnownHostsFiles) == 0 {
+ return fmt.Errorf("there is no knownhosts file")
+ }
+ knownHostsFiles := c.KnownHostsFiles
+
+ // abspath
+ for i, file := range knownHostsFiles {
+ file = getAbsPath(file)
+ knownHostsFiles[i] = file
+ }
+
+ // get hostKeyCallback
+ hostKeyCallback, err := knownhosts.New(knownHostsFiles...)
+ if err != nil {
+ return
+ }
+
+ // check hostkey
+ err = hostKeyCallback(hostname, remote, key)
+ if err == nil {
+ return nil
+ }
+
+ // var
+ filepath := knownHostsFiles[0]
+ var line int
+
+ // check error
+ keyErr, ok := err.(*knownhosts.KeyError)
+ if !ok || len(keyErr.Want) > 0 {
+ for _, w := range keyErr.Want {
+ oldkey := w.String()
+ line = w.Line
+
+ if answer, err := askOverwriteKnownHostKey(c.TextAskOverwriteKnownHosts, hostname, remote, key, oldkey); err != nil || !answer {
+ msg := "host key verification failed"
+ if err != nil {
+ msg += ": " + err.Error()
+ }
+ return fmt.Errorf(msg)
+ }
+ }
+ } else {
+ if answer, err := askAddingUnknownHostKey(c.TextAskWriteKnownHosts, hostname, remote, key); err != nil || !answer {
+ msg := "host key verification failed"
+ if err != nil {
+ msg += ": " + err.Error()
+ }
+ return fmt.Errorf(msg)
+ }
+ line = 0
+ }
+
+ err = writeKnownHostsKey(filepath, line, hostname, remote, key)
+
+ return nil
+}
+
+// askAddingUnknownHostKey
+// 【参考】: https://github.com/tatsushid/minssh/blob/57eae8c5bcf5d94639891f3267f05251f05face4/pkg/minssh/minssh.go#L93-L128
+func askAddingUnknownHostKey(text string, address string, remote net.Addr, key ssh.PublicKey) (bool, error) {
+ // set template variable
+ sweaters := WriteInventory{address, remote.String(), ssh.FingerprintSHA256(key)}
+
+ // set template
+ tmpl, err := template.New("test").Parse(text)
+ if err != nil {
+ return false, err
+ }
+
+ //
+ stopC := make(chan struct{})
+ defer func() {
+ close(stopC)
+ }()
+
+ go func() {
+ sigC := make(chan os.Signal, 1)
+ signal.Notify(sigC, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
+ select {
+ case <-sigC:
+ os.Exit(1)
+ case <-stopC:
+ }
+ }()
+
+ err = tmpl.Execute(os.Stdout, sweaters)
+ if err != nil {
+ return false, err
+ }
+
+ b := bufio.NewReader(os.Stdin)
+ for {
+ answer, err := b.ReadString('\n')
+ if err != nil {
+ return false, fmt.Errorf("failed to read answer: %s", err)
+ }
+ answer = string(strings.ToLower(strings.TrimSpace(answer)))
+ if answer == "yes" {
+ return true, nil
+ } else if answer == "no" {
+ return false, nil
+ }
+ fmt.Print("Please type 'yes' or 'no': ")
+ }
+}
+
+// askOverwriteKnownHostKey
+// 【参考】: https://github.com/tatsushid/minssh/blob/57eae8c5bcf5d94639891f3267f05251f05face4/pkg/minssh/minssh.go#L93-L128
+func askOverwriteKnownHostKey(text string, address string, remote net.Addr, key ssh.PublicKey, oldkey string) (bool, error) {
+ // set template variable
+ sweaters := OverwriteInventory{address, remote.String(), ssh.FingerprintSHA256(key), oldkey}
+
+ // set template
+ tmpl, err := template.New("test").Parse(text)
+ if err != nil {
+ return false, err
+ }
+
+ //
+ stopC := make(chan struct{})
+ defer func() {
+ close(stopC)
+ }()
+
+ go func() {
+ sigC := make(chan os.Signal, 1)
+ signal.Notify(sigC, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
+ select {
+ case <-sigC:
+ os.Exit(1)
+ case <-stopC:
+ }
+ }()
+
+ err = tmpl.Execute(os.Stdout, sweaters)
+ if err != nil {
+ return false, err
+ }
+
+ b := bufio.NewReader(os.Stdin)
+ for {
+ answer, err := b.ReadString('\n')
+ if err != nil {
+ return false, fmt.Errorf("failed to read answer: %s", err)
+ }
+ answer = string(strings.ToLower(strings.TrimSpace(answer)))
+ if answer == "yes" {
+ return true, nil
+ } else if answer == "no" {
+ return false, nil
+ }
+ fmt.Print("Please type 'yes' or 'no': ")
+ }
+}
+
+func writeKnownHostsKey(filepath string, linenum int, hostname string, remote net.Addr, key ssh.PublicKey) (err error) {
+ //
+ var addrs []string
+ if remote.String() == hostname {
+ addrs = []string{hostname}
+ } else {
+ addrs = []string{hostname, remote.String()}
+ }
+
+ // set string
+ entry := knownhosts.Line(addrs, key)
+ if linenum == 0 {
+ // open file
+ f, err := os.OpenFile(filepath, os.O_WRONLY|os.O_APPEND, 0600)
+ if err != nil {
+ return fmt.Errorf("failed to add new host key: %s", err)
+ }
+ defer f.Close()
+
+ if _, err = f.WriteString(entry + "\n"); err != nil {
+ return fmt.Errorf("failed to add new host key: %s", err)
+ }
+ } else {
+ // open file
+ fr, err := os.Open(filepath)
+ if err != nil {
+ return fmt.Errorf("failed to add new host key: %s", err)
+ }
+ defer fr.Close()
+
+ fw, err := os.OpenFile(filepath, os.O_WRONLY, 0600)
+ if err != nil {
+ return fmt.Errorf("failed to add new host key: %s", err)
+ }
+ defer fw.Close()
+
+ scanner := bufio.NewScanner(fr)
+ writer := bufio.NewWriter(fw)
+ var line string
+
+ count := 1
+ for scanner.Scan() {
+ line = scanner.Text()
+
+ if count == linenum {
+ line = entry
+ }
+
+ writer.WriteString(line + "\n")
+ count += 1
+ }
+
+ err = writer.Flush()
+ if err != nil {
+ fmt.Println(err)
+ }
+ }
+
+ return
+}
diff --git a/vendor/github.com/blacknon/go-sshlib/shell.go b/vendor/github.com/blacknon/go-sshlib/shell.go
index 003ee871..9fa61f9e 100644
--- a/vendor/github.com/blacknon/go-sshlib/shell.go
+++ b/vendor/github.com/blacknon/go-sshlib/shell.go
@@ -2,8 +2,6 @@
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
-// TODO(blacknon): Windowsでもshellを使えるようにする(どうやってやるのかは不明)
-
package sshlib
import (
@@ -86,44 +84,6 @@ func (c *Connect) CmdShell(session *ssh.Session, command string) (err error) {
return
}
-func (c *Connect) setupShell(session *ssh.Session) (err error) {
- // set FD
- session.Stdin = os.Stdin
- session.Stdout = os.Stdout
- session.Stderr = os.Stderr
-
- // Logging
- if c.logging {
- err = c.logger(session)
- if err != nil {
- log.Println(err)
- }
- }
- err = nil
-
- // Request tty
- err = RequestTty(session)
- if err != nil {
- return err
- }
-
- // x11 forwarding
- if c.ForwardX11 {
- err = c.X11Forward(session)
- if err != nil {
- log.Println(err)
- }
- }
- err = nil
-
- // ssh agent forwarding
- if c.ForwardAgent {
- c.ForwardSshAgent(session)
- }
-
- return
-}
-
// SetLog set up terminal log logging.
// This only happens in Connect.Shell().
func (c *Connect) SetLog(path string, timestamp bool) {
@@ -140,7 +100,6 @@ func (c *Connect) SetLogWithRemoveAnsiCode(path string, timestamp bool) {
}
// logger is logging terminal log to c.logFile
-// TODO(blacknon): Writerを利用した処理方法に変更する(v0.1.1)
func (c *Connect) logger(session *ssh.Session) (err error) {
logfile, err := os.OpenFile(c.logFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
@@ -194,3 +153,42 @@ func (c *Connect) logger(session *ssh.Session) (err error) {
return err
}
+
+func (c *Connect) setupShell(session *ssh.Session) (err error) {
+ // set FD
+ stdin := GetStdin()
+ session.Stdin = stdin
+ session.Stdout = os.Stdout
+ session.Stderr = os.Stderr
+
+ // Logging
+ if c.logging {
+ err = c.logger(session)
+ if err != nil {
+ log.Println(err)
+ }
+ }
+ err = nil
+
+ // Request tty
+ err = RequestTty(session)
+ if err != nil {
+ return err
+ }
+
+ // x11 forwarding
+ if c.ForwardX11 {
+ err = c.X11Forward(session)
+ if err != nil {
+ log.Println(err)
+ }
+ }
+ err = nil
+
+ // ssh agent forwarding
+ if c.ForwardAgent {
+ c.ForwardSshAgent(session)
+ }
+
+ return
+}
diff --git a/vendor/github.com/blacknon/go-sshlib/stdin_unix.go b/vendor/github.com/blacknon/go-sshlib/stdin_unix.go
new file mode 100644
index 00000000..a957df7a
--- /dev/null
+++ b/vendor/github.com/blacknon/go-sshlib/stdin_unix.go
@@ -0,0 +1,16 @@
+// Copyright (c) 2021 Blacknon. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+//go:build !windows && !plan9 && !nacl
+// +build !windows,!plan9,!nacl
+
+package sshlib
+
+import (
+ "io"
+ "os"
+)
+
+func GetStdin() io.ReadCloser {
+ return os.Stdin
+}
diff --git a/vendor/github.com/blacknon/go-sshlib/stdin_windows.go b/vendor/github.com/blacknon/go-sshlib/stdin_windows.go
new file mode 100644
index 00000000..a70c7269
--- /dev/null
+++ b/vendor/github.com/blacknon/go-sshlib/stdin_windows.go
@@ -0,0 +1,21 @@
+// Copyright (c) 2021 Blacknon. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+//go:build windows
+// +build windows
+
+package sshlib
+
+import (
+ "io"
+
+ windowsconsole "github.com/moby/term/windows"
+ "golang.org/x/sys/windows"
+)
+
+func GetStdin() io.ReadCloser {
+ h := uint32(windows.STD_INPUT_HANDLE)
+ stdin := windowsconsole.NewAnsiReader(int(h))
+
+ return stdin
+}
diff --git a/vendor/github.com/c-bata/go-prompt/completer/file.go b/vendor/github.com/c-bata/go-prompt/completer/file.go
deleted file mode 100644
index 59f2d8de..00000000
--- a/vendor/github.com/c-bata/go-prompt/completer/file.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package completer
-
-import (
- "io/ioutil"
- "os"
- "os/user"
- "path/filepath"
- "runtime"
-
- prompt "github.com/c-bata/go-prompt"
- "github.com/c-bata/go-prompt/internal/debug"
-)
-
-var (
- // FilePathCompletionSeparator holds separate characters.
- FilePathCompletionSeparator = string([]byte{' ', os.PathSeparator})
-)
-
-// FilePathCompleter is a completer for your local file system.
-// Please caution that you need to set OptionCompletionWordSeparator(completer.FilePathCompletionSeparator)
-// when you use this completer.
-type FilePathCompleter struct {
- Filter func(fi os.FileInfo) bool
- IgnoreCase bool
- fileListCache map[string][]prompt.Suggest
-}
-
-func cleanFilePath(path string) (dir, base string, err error) {
- if path == "" {
- return ".", "", nil
- }
-
- var endsWithSeparator bool
- if len(path) >= 1 && path[len(path)-1] == os.PathSeparator {
- endsWithSeparator = true
- }
-
- if runtime.GOOS != "windows" && len(path) >= 2 && path[0:2] == "~/" {
- me, err := user.Current()
- if err != nil {
- return "", "", err
- }
- path = filepath.Join(me.HomeDir, path[1:])
- }
- path = filepath.Clean(os.ExpandEnv(path))
- dir = filepath.Dir(path)
- base = filepath.Base(path)
-
- if endsWithSeparator {
- dir = path + string(os.PathSeparator) // Append slash(in POSIX) if path ends with slash.
- base = "" // Set empty string if path ends with separator.
- }
- return dir, base, nil
-}
-
-// Complete returns suggestions from your local file system.
-func (c *FilePathCompleter) Complete(d prompt.Document) []prompt.Suggest {
- if c.fileListCache == nil {
- c.fileListCache = make(map[string][]prompt.Suggest, 4)
- }
-
- path := d.GetWordBeforeCursor()
- dir, base, err := cleanFilePath(path)
- if err != nil {
- debug.Log("completer: cannot get current user:" + err.Error())
- return nil
- }
-
- if cached, ok := c.fileListCache[dir]; ok {
- return prompt.FilterHasPrefix(cached, base, c.IgnoreCase)
- }
-
- files, err := ioutil.ReadDir(dir)
- if err != nil && os.IsNotExist(err) {
- return nil
- } else if err != nil {
- debug.Log("completer: cannot read directory items:" + err.Error())
- return nil
- }
-
- suggests := make([]prompt.Suggest, 0, len(files))
- for _, f := range files {
- if c.Filter != nil && !c.Filter(f) {
- continue
- }
- suggests = append(suggests, prompt.Suggest{Text: f.Name()})
- }
- c.fileListCache[dir] = suggests
- return prompt.FilterHasPrefix(suggests, base, c.IgnoreCase)
-}
diff --git a/vendor/github.com/kballard/go-shellquote/LICENSE b/vendor/github.com/kballard/go-shellquote/LICENSE
new file mode 100644
index 00000000..a6d77312
--- /dev/null
+++ b/vendor/github.com/kballard/go-shellquote/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2014 Kevin Ballard
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the "Software"),
+to deal in the Software without restriction, including without limitation
+the rights to use, copy, modify, merge, publish, distribute, sublicense,
+and/or sell copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/github.com/kballard/go-shellquote/README b/vendor/github.com/kballard/go-shellquote/README
new file mode 100644
index 00000000..4d34e87a
--- /dev/null
+++ b/vendor/github.com/kballard/go-shellquote/README
@@ -0,0 +1,36 @@
+PACKAGE
+
+package shellquote
+ import "github.com/kballard/go-shellquote"
+
+ Shellquote provides utilities for joining/splitting strings using sh's
+ word-splitting rules.
+
+VARIABLES
+
+var (
+ UnterminatedSingleQuoteError = errors.New("Unterminated single-quoted string")
+ UnterminatedDoubleQuoteError = errors.New("Unterminated double-quoted string")
+ UnterminatedEscapeError = errors.New("Unterminated backslash-escape")
+)
+
+
+FUNCTIONS
+
+func Join(args ...string) string
+ Join quotes each argument and joins them with a space. If passed to
+ /bin/sh, the resulting string will be split back into the original
+ arguments.
+
+func Split(input string) (words []string, err error)
+ Split splits a string according to /bin/sh's word-splitting rules. It
+ supports backslash-escapes, single-quotes, and double-quotes. Notably it
+ does not support the $'' style of quoting. It also doesn't attempt to
+ perform any other sort of expansion, including brace expansion, shell
+ expansion, or pathname expansion.
+
+ If the given input has an unterminated quoted string or ends in a
+ backslash-escape, one of UnterminatedSingleQuoteError,
+ UnterminatedDoubleQuoteError, or UnterminatedEscapeError is returned.
+
+
diff --git a/vendor/github.com/kballard/go-shellquote/doc.go b/vendor/github.com/kballard/go-shellquote/doc.go
new file mode 100644
index 00000000..9445fa4a
--- /dev/null
+++ b/vendor/github.com/kballard/go-shellquote/doc.go
@@ -0,0 +1,3 @@
+// Shellquote provides utilities for joining/splitting strings using sh's
+// word-splitting rules.
+package shellquote
diff --git a/vendor/github.com/kballard/go-shellquote/quote.go b/vendor/github.com/kballard/go-shellquote/quote.go
new file mode 100644
index 00000000..72a8cb38
--- /dev/null
+++ b/vendor/github.com/kballard/go-shellquote/quote.go
@@ -0,0 +1,102 @@
+package shellquote
+
+import (
+ "bytes"
+ "strings"
+ "unicode/utf8"
+)
+
+// Join quotes each argument and joins them with a space.
+// If passed to /bin/sh, the resulting string will be split back into the
+// original arguments.
+func Join(args ...string) string {
+ var buf bytes.Buffer
+ for i, arg := range args {
+ if i != 0 {
+ buf.WriteByte(' ')
+ }
+ quote(arg, &buf)
+ }
+ return buf.String()
+}
+
+const (
+ specialChars = "\\'\"`${[|&;<>()*?!"
+ extraSpecialChars = " \t\n"
+ prefixChars = "~"
+)
+
+func quote(word string, buf *bytes.Buffer) {
+ // We want to try to produce a "nice" output. As such, we will
+ // backslash-escape most characters, but if we encounter a space, or if we
+ // encounter an extra-special char (which doesn't work with
+ // backslash-escaping) we switch over to quoting the whole word. We do this
+ // with a space because it's typically easier for people to read multi-word
+ // arguments when quoted with a space rather than with ugly backslashes
+ // everywhere.
+ origLen := buf.Len()
+
+ if len(word) == 0 {
+ // oops, no content
+ buf.WriteString("''")
+ return
+ }
+
+ cur, prev := word, word
+ atStart := true
+ for len(cur) > 0 {
+ c, l := utf8.DecodeRuneInString(cur)
+ cur = cur[l:]
+ if strings.ContainsRune(specialChars, c) || (atStart && strings.ContainsRune(prefixChars, c)) {
+ // copy the non-special chars up to this point
+ if len(cur) < len(prev) {
+ buf.WriteString(prev[0 : len(prev)-len(cur)-l])
+ }
+ buf.WriteByte('\\')
+ buf.WriteRune(c)
+ prev = cur
+ } else if strings.ContainsRune(extraSpecialChars, c) {
+ // start over in quote mode
+ buf.Truncate(origLen)
+ goto quote
+ }
+ atStart = false
+ }
+ if len(prev) > 0 {
+ buf.WriteString(prev)
+ }
+ return
+
+quote:
+ // quote mode
+ // Use single-quotes, but if we find a single-quote in the word, we need
+ // to terminate the string, emit an escaped quote, and start the string up
+ // again
+ inQuote := false
+ for len(word) > 0 {
+ i := strings.IndexRune(word, '\'')
+ if i == -1 {
+ break
+ }
+ if i > 0 {
+ if !inQuote {
+ buf.WriteByte('\'')
+ inQuote = true
+ }
+ buf.WriteString(word[0:i])
+ }
+ word = word[i+1:]
+ if inQuote {
+ buf.WriteByte('\'')
+ inQuote = false
+ }
+ buf.WriteString("\\'")
+ }
+ if len(word) > 0 {
+ if !inQuote {
+ buf.WriteByte('\'')
+ }
+ buf.WriteString(word)
+ buf.WriteByte('\'')
+ }
+}
diff --git a/vendor/github.com/kballard/go-shellquote/unquote.go b/vendor/github.com/kballard/go-shellquote/unquote.go
new file mode 100644
index 00000000..b1b13da9
--- /dev/null
+++ b/vendor/github.com/kballard/go-shellquote/unquote.go
@@ -0,0 +1,156 @@
+package shellquote
+
+import (
+ "bytes"
+ "errors"
+ "strings"
+ "unicode/utf8"
+)
+
+var (
+ UnterminatedSingleQuoteError = errors.New("Unterminated single-quoted string")
+ UnterminatedDoubleQuoteError = errors.New("Unterminated double-quoted string")
+ UnterminatedEscapeError = errors.New("Unterminated backslash-escape")
+)
+
+var (
+ splitChars = " \n\t"
+ singleChar = '\''
+ doubleChar = '"'
+ escapeChar = '\\'
+ doubleEscapeChars = "$`\"\n\\"
+)
+
+// Split splits a string according to /bin/sh's word-splitting rules. It
+// supports backslash-escapes, single-quotes, and double-quotes. Notably it does
+// not support the $'' style of quoting. It also doesn't attempt to perform any
+// other sort of expansion, including brace expansion, shell expansion, or
+// pathname expansion.
+//
+// If the given input has an unterminated quoted string or ends in a
+// backslash-escape, one of UnterminatedSingleQuoteError,
+// UnterminatedDoubleQuoteError, or UnterminatedEscapeError is returned.
+func Split(input string) (words []string, err error) {
+ var buf bytes.Buffer
+ words = make([]string, 0)
+
+ for len(input) > 0 {
+ // skip any splitChars at the start
+ c, l := utf8.DecodeRuneInString(input)
+ if strings.ContainsRune(splitChars, c) {
+ input = input[l:]
+ continue
+ } else if c == escapeChar {
+ // Look ahead for escaped newline so we can skip over it
+ next := input[l:]
+ if len(next) == 0 {
+ err = UnterminatedEscapeError
+ return
+ }
+ c2, l2 := utf8.DecodeRuneInString(next)
+ if c2 == '\n' {
+ input = next[l2:]
+ continue
+ }
+ }
+
+ var word string
+ word, input, err = splitWord(input, &buf)
+ if err != nil {
+ return
+ }
+ words = append(words, word)
+ }
+ return
+}
+
+func splitWord(input string, buf *bytes.Buffer) (word string, remainder string, err error) {
+ buf.Reset()
+
+raw:
+ {
+ cur := input
+ for len(cur) > 0 {
+ c, l := utf8.DecodeRuneInString(cur)
+ cur = cur[l:]
+ if c == singleChar {
+ buf.WriteString(input[0 : len(input)-len(cur)-l])
+ input = cur
+ goto single
+ } else if c == doubleChar {
+ buf.WriteString(input[0 : len(input)-len(cur)-l])
+ input = cur
+ goto double
+ } else if c == escapeChar {
+ buf.WriteString(input[0 : len(input)-len(cur)-l])
+ input = cur
+ goto escape
+ } else if strings.ContainsRune(splitChars, c) {
+ buf.WriteString(input[0 : len(input)-len(cur)-l])
+ return buf.String(), cur, nil
+ }
+ }
+ if len(input) > 0 {
+ buf.WriteString(input)
+ input = ""
+ }
+ goto done
+ }
+
+escape:
+ {
+ if len(input) == 0 {
+ return "", "", UnterminatedEscapeError
+ }
+ c, l := utf8.DecodeRuneInString(input)
+ if c == '\n' {
+ // a backslash-escaped newline is elided from the output entirely
+ } else {
+ buf.WriteString(input[:l])
+ }
+ input = input[l:]
+ }
+ goto raw
+
+single:
+ {
+ i := strings.IndexRune(input, singleChar)
+ if i == -1 {
+ return "", "", UnterminatedSingleQuoteError
+ }
+ buf.WriteString(input[0:i])
+ input = input[i+1:]
+ goto raw
+ }
+
+double:
+ {
+ cur := input
+ for len(cur) > 0 {
+ c, l := utf8.DecodeRuneInString(cur)
+ cur = cur[l:]
+ if c == doubleChar {
+ buf.WriteString(input[0 : len(input)-len(cur)-l])
+ input = cur
+ goto raw
+ } else if c == escapeChar {
+ // bash only supports certain escapes in double-quoted strings
+ c2, l2 := utf8.DecodeRuneInString(cur)
+ cur = cur[l2:]
+ if strings.ContainsRune(doubleEscapeChars, c2) {
+ buf.WriteString(input[0 : len(input)-len(cur)-l-l2])
+ if c2 == '\n' {
+ // newline is special, skip the backslash entirely
+ } else {
+ buf.WriteRune(c2)
+ }
+ input = cur
+ }
+ }
+ }
+ return "", "", UnterminatedDoubleQuoteError
+ }
+
+done:
+ return buf.String(), input, nil
+}
diff --git a/vendor/gopkg.in/yaml.v2/LICENSE b/vendor/github.com/moby/term/LICENSE
similarity index 93%
rename from vendor/gopkg.in/yaml.v2/LICENSE
rename to vendor/github.com/moby/term/LICENSE
index 8dada3ed..6d8d58fb 100644
--- a/vendor/gopkg.in/yaml.v2/LICENSE
+++ b/vendor/github.com/moby/term/LICENSE
@@ -1,6 +1,7 @@
+
Apache License
Version 2.0, January 2004
- http://www.apache.org/licenses/
+ https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
@@ -175,24 +176,13 @@
END OF TERMS AND CONDITIONS
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "{}"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright {yyyy} {name of copyright owner}
+ Copyright 2013-2018 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
+ https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/vendor/github.com/moby/term/windows/ansi_reader.go b/vendor/github.com/moby/term/windows/ansi_reader.go
new file mode 100644
index 00000000..15525152
--- /dev/null
+++ b/vendor/github.com/moby/term/windows/ansi_reader.go
@@ -0,0 +1,252 @@
+// +build windows
+
+package windowsconsole
+
+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 {
+ 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 {
+ 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:]
+ }
+
+ return copiedLength, nil
+ }
+
+ // Read and translate key events
+ events, err := readInputEvents(ar, len(p))
+ if err != nil {
+ return 0, err
+ } else if len(events) == 0 {
+ return 0, nil
+ }
+
+ keyBytes := translateKeyEvents(events, []byte(escapeSequence))
+
+ // Save excess bytes and right-size keyBytes
+ if len(keyBytes) > len(p) {
+ ar.buffer = keyBytes[len(p):]
+ keyBytes = keyBytes[:len(p)]
+ } else if len(keyBytes) == 0 {
+ return 0, nil
+ }
+
+ copiedLength := copy(p, keyBytes)
+ if copiedLength != len(keyBytes) {
+ return 0, errors.New("unexpected copy length encountered")
+ }
+
+ return copiedLength, nil
+}
+
+// readInputEvents polls until at least one event is available.
+func readInputEvents(ar *ansiReader, 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
+ }
+
+ // Wait for and read input events
+ events := make([]winterm.INPUT_RECORD, countRecords)
+ nEvents := uint32(0)
+ eventsExist, err := winterm.WaitForSingleObject(ar.fd, winterm.WAIT_INFINITE)
+ if err != nil {
+ return nil, err
+ }
+
+ if eventsExist {
+ err = winterm.ReadConsoleInput(ar.fd, events, &nEvents)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ // Return a slice restricted to the number of returned records
+ 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
+ // -D Signals the end of input from the keyboard; also exits current shell.
+ // -H Deletes the first character to the left of the cursor. Also called the ERASE key.
+ // -Q Restarts printing after it has been stopped with -s.
+ // -S Suspends printing on the screen (does not stop the program).
+ // -U Deletes all characters on the current line. Also called the KILL key.
+ // -E Quits current command and creates a core
+
+ }
+
+ // +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 ""
+}
diff --git a/vendor/github.com/moby/term/windows/ansi_writer.go b/vendor/github.com/moby/term/windows/ansi_writer.go
new file mode 100644
index 00000000..ccb5ef07
--- /dev/null
+++ b/vendor/github.com/moby/term/windows/ansi_writer.go
@@ -0,0 +1,56 @@
+// +build windows
+
+package windowsconsole
+
+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 {
+ file, fd := winterm.GetStdFile(nFile)
+ info, err := winterm.GetConsoleScreenBufferInfo(fd)
+ if err != nil {
+ return nil
+ }
+
+ parser := ansiterm.CreateParser("Ground", winterm.CreateWinEventHandler(fd, file))
+
+ return &ansiWriter{
+ file: file,
+ fd: fd,
+ infoReset: info,
+ command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH),
+ escapeSequence: []byte(ansiterm.KEY_ESC_CSI),
+ parser: parser,
+ }
+}
+
+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
+ }
+
+ return aw.parser.Parse(p)
+}
diff --git a/vendor/github.com/moby/term/windows/console.go b/vendor/github.com/moby/term/windows/console.go
new file mode 100644
index 00000000..993694dd
--- /dev/null
+++ b/vendor/github.com/moby/term/windows/console.go
@@ -0,0 +1,39 @@
+// +build windows
+
+package windowsconsole
+
+import (
+ "os"
+
+ "golang.org/x/sys/windows"
+)
+
+// 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.
+// Deprecated: use golang.org/x/sys/windows.GetConsoleMode() or golang.org/x/term.IsTerminal()
+var IsConsole = isConsole
+
+func isConsole(fd uintptr) bool {
+ var mode uint32
+ err := windows.GetConsoleMode(windows.Handle(fd), &mode)
+ return err == nil
+}
diff --git a/vendor/github.com/moby/term/windows/doc.go b/vendor/github.com/moby/term/windows/doc.go
new file mode 100644
index 00000000..54265fff
--- /dev/null
+++ b/vendor/github.com/moby/term/windows/doc.go
@@ -0,0 +1,5 @@
+// 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
diff --git a/vendor/github.com/nsf/termbox-go/README.md b/vendor/github.com/nsf/termbox-go/README.md
index 734aab73..72b5a292 100644
--- a/vendor/github.com/nsf/termbox-go/README.md
+++ b/vendor/github.com/nsf/termbox-go/README.md
@@ -1,17 +1,18 @@
-[![GoDoc](https://godoc.org/github.com/nsf/termbox-go?status.svg)](http://godoc.org/github.com/nsf/termbox-go)
+[![Go Reference](https://pkg.go.dev/badge/github.com/nsf/termbox-go.svg)](https://pkg.go.dev/github.com/nsf/termbox-go)
+![Build](https://github.com/nsf/termbox-go/actions/workflows/test-ubuntu.yml/badge.svg)
## IMPORTANT
-This library is somewhat not maintained anymore. But I'm glad that it did what I wanted the most. It moved people away from "ncurses" mindset and these days we see both re-implementations of termbox API in various languages and even possibly better libs with similar API design. If you're looking for a Go lib that provides terminal-based user interface facilities, I've heard that https://github.com/gdamore/tcell is good (never used it myself). Also for more complicated interfaces and/or computer games I recommend you to consider using HTML-based UI. Having said that, termbox still somewhat works. In fact I'm writing this line of text right now in godit (which is a text editor written using termbox-go). So, be aware. Good luck and have a nice day.
+This library is somewhat not maintained anymore. But I'm glad that it did what I wanted the most. It moved people away from "ncurses" mindset and these days we see both re-implementations of termbox API in various languages and even possibly better libs with similar API design. If you're looking for a Go lib that provides terminal-based user interface facilities, I've heard that [gdamore/tcell](https://github.com/gdamore/tcell) is good (never used it myself). Also for more complicated interfaces and/or computer games I recommend you to consider using HTML-based UI. Having said that, termbox still somewhat works. In fact I'm writing this line of text right now in godit (which is a text editor written using termbox-go). So, be aware. Good luck and have a nice day.
## Termbox
-Termbox is a library that provides a minimalistic API which allows the programmer to write text-based user interfaces. The library is crossplatform and has both terminal-based implementations on *nix operating systems and a winapi console based implementation for windows operating systems. The basic idea is an abstraction of the greatest common subset of features available on all major terminals and other terminal-like APIs in a minimalistic fashion. Small API means it is easy to implement, test, maintain and learn it, that's what makes the termbox a distinct library in its area.
+Termbox is a library that provides a minimalistic API which allows the programmer to write text-based user interfaces. The library is crossplatform and has both terminal-based implementations on \*nix operating systems and a winapi console based implementation for windows operating systems. The basic idea is an abstraction of the greatest common subset of features available on all major terminals and other terminal-like APIs in a minimalistic fashion. Small API means it is easy to implement, test, maintain and learn it, that's what makes the termbox a distinct library in its area.
### Installation
Install and update this go package with `go get -u github.com/nsf/termbox-go`
### Examples
-For examples of what can be done take a look at demos in the _demos directory. You can try them with go run: `go run _demos/keyboard.go`
+For examples of what can be done take a look at various examples in the `_demos` directory. You can try them with go run: `go run _demos/keyboard.go`
There are also some interesting projects using termbox-go:
- [godit](https://github.com/nsf/godit) is an emacsish lightweight text editor written using termbox.
@@ -21,7 +22,6 @@ There are also some interesting projects using termbox-go:
- [httopd](https://github.com/verdverm/httopd) is top for httpd logs.
- [mop](https://github.com/mop-tracker/mop) is stock market tracker for hackers.
- [termui](https://github.com/gizak/termui) is a terminal dashboard.
- - [termdash](https://github.com/mum4k/termdash) is a terminal dashboard.
- [termloop](https://github.com/JoelOtter/termloop) is a terminal game engine.
- [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) is a XTerm 256 color chart.
- [gocui](https://github.com/jroimartin/gocui) is a minimalist Go library aimed at creating console user interfaces.
@@ -29,21 +29,18 @@ There are also some interesting projects using termbox-go:
- [pxl](https://github.com/ichinaski/pxl) displays images in the terminal.
- [snake-game](https://github.com/DyegoCosta/snake-game) is an implementation of the Snake game.
- [gone](https://github.com/guillaumebreton/gone) is a CLI pomodoro® timer.
- - [Spoof.go](https://github.com/sabey/spoofgo) controllable movement spoofing from the cli
- - [lf](https://github.com/gokcehan/lf) is a terminal file manager
+ - [Spoof.go](https://github.com/sabey/spoofgo) controllable movement spoofing from the cli.
- [rat](https://github.com/ericfreese/rat) lets you compose shell commands to build terminal applications.
- [httplab](https://github.com/gchaincl/httplab) An interactive web server.
- - [tetris](https://github.com/MichaelS11/tetris) Go Tetris with AI option
- [wot](https://github.com/kyu-suke/wot) Wait time during command is completed.
- - [2048-go](https://github.com/1984weed/2048-go) is 2048 in Go
+ - [2048-go](https://github.com/1984weed/2048-go) is 2048 in Go.
- [jv](https://github.com/maxzender/jv) helps you view JSON on the command-line.
- [pinger](https://github.com/hirose31/pinger) helps you to monitor numerous hosts using ICMP ECHO_REQUEST.
- - [vixl44](https://github.com/sebashwa/vixl44) lets you create pixel art inside your terminal using vim movements
- - [zterm](https://github.com/varunrau/zterm) is a typing game inspired by http://zty.pe/
+ - [vixl44](https://github.com/sebashwa/vixl44) lets you create pixel art inside your terminal using vim movements.
+ - [zterm](https://github.com/varunrau/zterm) is a typing game inspired by http://zty.pe/.
- [gotypist](https://github.com/pb-/gotypist) is a fun touch-typing tutor following Steve Yegge's method.
- [cointop](https://github.com/miguelmota/cointop) is an interactive terminal based UI application for tracking cryptocurrencies.
- [pexpo](https://github.com/nnao45/pexpo) is a terminal sending ping tool written in Go.
- [jid](https://github.com/simeji/jid) is an interactive JSON drill down tool using filtering queries like jq.
-
-### API reference
-[godoc.org/github.com/nsf/termbox-go](http://godoc.org/github.com/nsf/termbox-go)
+ - [nonograminGo](https://github.com/N0RM4L15T/nonograminGo) is a nonogram (aka. picross) in Go.
+ - [tower-of-go](https://github.com/kjirou/tower-of-go) is a tiny maze game that runs on the terminal.
diff --git a/vendor/github.com/nsf/termbox-go/api.go b/vendor/github.com/nsf/termbox-go/api.go
index 92e3a44c..52f6a43c 100644
--- a/vendor/github.com/nsf/termbox-go/api.go
+++ b/vendor/github.com/nsf/termbox-go/api.go
@@ -2,13 +2,16 @@
package termbox
-import "github.com/mattn/go-runewidth"
-import "fmt"
-import "os"
-import "os/signal"
-import "syscall"
-import "runtime"
-import "time"
+import (
+ "fmt"
+ "os"
+ "os/signal"
+ "runtime"
+ "syscall"
+ "time"
+
+ "github.com/mattn/go-runewidth"
+)
// public API
@@ -22,9 +25,13 @@ import "time"
// }
// defer termbox.Close()
func Init() error {
+ if IsInit {
+ return nil
+ }
+
var err error
- if runtime.GOOS == "openbsd" {
+ if runtime.GOOS == "openbsd" || runtime.GOOS == "freebsd" {
out, err = os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return err
@@ -127,6 +134,10 @@ func Interrupt() {
// Finalizes termbox library, should be called after successful initialization
// when termbox's functionality isn't required anymore.
func Close() {
+ if !IsInit {
+ return
+ }
+
quit <- 1
out.WriteString(funcs[t_show_cursor])
out.WriteString(funcs[t_sgr0])
@@ -242,6 +253,50 @@ func SetCell(x, y int, ch rune, fg, bg Attribute) {
back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
}
+// Returns the specified cell from the internal back buffer.
+func GetCell(x, y int) Cell {
+ return back_buffer.cells[y*back_buffer.width+x]
+}
+
+// Changes cell's character (rune) in the internal back buffer at the
+// specified position.
+func SetChar(x, y int, ch rune) {
+ if x < 0 || x >= back_buffer.width {
+ return
+ }
+ if y < 0 || y >= back_buffer.height {
+ return
+ }
+
+ back_buffer.cells[y*back_buffer.width+x].Ch = ch
+}
+
+// Changes cell's foreground attributes in the internal back buffer at
+// the specified position.
+func SetFg(x, y int, fg Attribute) {
+ if x < 0 || x >= back_buffer.width {
+ return
+ }
+ if y < 0 || y >= back_buffer.height {
+ return
+ }
+
+ back_buffer.cells[y*back_buffer.width+x].Fg = fg
+}
+
+// Changes cell's background attributes in the internal back buffer at
+// the specified position.
+func SetBg(x, y int, bg Attribute) {
+ if x < 0 || x >= back_buffer.width {
+ return
+ }
+ if y < 0 || y >= back_buffer.height {
+ return
+ }
+
+ back_buffer.cells[y*back_buffer.width+x].Bg = bg
+}
+
// Returns a slice into the termbox's back buffer. You can get its dimensions
// using 'Size' function. The slice remains valid as long as no 'Clear' or
// 'Flush' function calls were made after call to this function.
diff --git a/vendor/github.com/nsf/termbox-go/api_common.go b/vendor/github.com/nsf/termbox-go/api_common.go
index 5ca1371a..20ff6a7d 100644
--- a/vendor/github.com/nsf/termbox-go/api_common.go
+++ b/vendor/github.com/nsf/termbox-go/api_common.go
@@ -9,7 +9,7 @@ type (
EventType uint8
Modifier uint8
Key uint16
- Attribute uint16
+ Attribute uint64
)
// This type represents a termbox event. The 'Mod', 'Key' and 'Ch' fields are
@@ -142,6 +142,14 @@ const (
ColorMagenta
ColorCyan
ColorWhite
+ ColorDarkGray
+ ColorLightRed
+ ColorLightGreen
+ ColorLightYellow
+ ColorLightBlue
+ ColorLightMagenta
+ ColorLightCyan
+ ColorLightGray
)
// Cell attributes, it is possible to use multiple attributes by combining them
@@ -154,8 +162,13 @@ const (
// them with caution and test your code on various terminals.
const (
AttrBold Attribute = 1 << (iota + 9)
+ AttrBlink
+ AttrHidden
+ AttrDim
AttrUnderline
+ AttrCursive
AttrReverse
+ max_attr
)
// Input mode. See SetInputMode function.
@@ -173,6 +186,7 @@ const (
Output256
Output216
OutputGrayscale
+ OutputRGB
)
// Event type. See Event.Type field.
@@ -185,3 +199,30 @@ const (
EventRaw
EventNone
)
+
+// AttributeToRGB converts an Attribute to the underlying rgb triplet.
+// This is only useful if termbox is in Full RGB mode and the specified
+// attribute is also an attribute with r, g, b specified
+func AttributeToRGB(attr Attribute) (uint8, uint8, uint8) {
+ var color uint64 = uint64(attr) / uint64(max_attr)
+ // Have to right-shift with the highest attribute bit.
+ // For this, we divide by max_attr
+ var b uint8 = uint8(color % 256)
+ var g uint8 = uint8(color >> 8 % 256)
+ var r uint8 = uint8(color >> 16 % 256)
+ return r, g, b
+}
+
+// RGBToAttribute is used to convert an rgb triplet into a termbox attribute.
+// This attribute can only be applied when termbox is in Full RGB mode,
+// otherwise it'll be ignored and no color will be drawn.
+// R, G, B have to be in the range of 0 and 255.
+func RGBToAttribute(r uint8, g uint8, b uint8) Attribute {
+ var color uint64 = uint64(b)
+ color += uint64(g) << 8
+ color += uint64(r) << 16
+ color += 1 << 25
+ color = color * uint64(max_attr)
+ // Left-shift back to the place where rgb is stored.
+ return Attribute(color)
+}
diff --git a/vendor/github.com/nsf/termbox-go/api_windows.go b/vendor/github.com/nsf/termbox-go/api_windows.go
index 5c1e388a..f517f10b 100644
--- a/vendor/github.com/nsf/termbox-go/api_windows.go
+++ b/vendor/github.com/nsf/termbox-go/api_windows.go
@@ -2,6 +2,8 @@ package termbox
import (
"syscall"
+
+ "github.com/mattn/go-runewidth"
)
// public API
@@ -109,18 +111,37 @@ func Interrupt() {
interrupt_comm <- struct{}{}
}
+// https://docs.microsoft.com/en-us/windows/console/char-info-str
+const (
+ common_lvb_leading_byte = 0x0100
+ common_lvb_trailing_byte = 0x0200
+)
+
// Synchronizes the internal back buffer with the terminal.
func Flush() error {
update_size_maybe()
prepare_diff_messages()
for _, diff := range diffbuf {
+ chars := []char_info{}
+ for _, char := range diff.chars {
+ if runewidth.RuneWidth(rune(char.char)) > 1 {
+ char.attr |= common_lvb_leading_byte
+ chars = append(chars, char)
+ chars = append(chars, char_info{
+ char: char.char,
+ attr: char.attr | common_lvb_trailing_byte,
+ })
+ } else {
+ chars = append(chars, char)
+ }
+ }
r := small_rect{
left: 0,
top: diff.pos,
right: term_size.x - 1,
bottom: diff.pos + diff.lines - 1,
}
- write_console_output(out, diff.chars, r)
+ write_console_output(out, chars, r)
}
if !is_cursor_hidden(cursor_x, cursor_y) {
move_cursor(cursor_x, cursor_y)
@@ -162,6 +183,50 @@ func SetCell(x, y int, ch rune, fg, bg Attribute) {
back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
}
+// Returns the specified cell from the internal back buffer.
+func GetCell(x, y int) Cell {
+ return back_buffer.cells[y*back_buffer.width+x]
+}
+
+// Changes cell's character (rune) in the internal back buffer at the
+// specified position.
+func SetChar(x, y int, ch rune) {
+ if x < 0 || x >= back_buffer.width {
+ return
+ }
+ if y < 0 || y >= back_buffer.height {
+ return
+ }
+
+ back_buffer.cells[y*back_buffer.width+x].Ch = ch
+}
+
+// Changes cell's foreground attributes in the internal back buffer at
+// the specified position.
+func SetFg(x, y int, fg Attribute) {
+ if x < 0 || x >= back_buffer.width {
+ return
+ }
+ if y < 0 || y >= back_buffer.height {
+ return
+ }
+
+ back_buffer.cells[y*back_buffer.width+x].Fg = fg
+}
+
+// Changes cell's background attributes in the internal back buffer at
+// the specified position.
+func SetBg(x, y int, bg Attribute) {
+ if x < 0 || x >= back_buffer.width {
+ return
+ }
+ if y < 0 || y >= back_buffer.height {
+ return
+ }
+
+ back_buffer.cells[y*back_buffer.width+x].Bg = bg
+}
+
// Returns a slice into the termbox's back buffer. You can get its dimensions
// using 'Size' function. The slice remains valid as long as no 'Clear' or
// 'Flush' function calls were made after call to this function.
diff --git a/vendor/github.com/nsf/termbox-go/collect_terminfo.py b/vendor/github.com/nsf/termbox-go/collect_terminfo.py
index 5e50975e..613a467f 100644
--- a/vendor/github.com/nsf/termbox-go/collect_terminfo.py
+++ b/vendor/github.com/nsf/termbox-go/collect_terminfo.py
@@ -69,7 +69,10 @@ def w(s):
def iter_pairs(iterable):
iterable = iter(iterable)
while True:
- yield (next(iterable), next(iterable))
+ try:
+ yield (next(iterable), next(iterable))
+ except StopIteration:
+ return
def do_term(term, nick):
w("// %s\n" % term)
diff --git a/vendor/github.com/nsf/termbox-go/termbox.go b/vendor/github.com/nsf/termbox-go/termbox.go
index fbe4c3de..cb3d3783 100644
--- a/vendor/github.com/nsf/termbox-go/termbox.go
+++ b/vendor/github.com/nsf/termbox-go/termbox.go
@@ -14,6 +14,11 @@ import "io"
// private API
const (
+ // for future contributors: after adding something here,
+ // you have to add the corresponding index in a terminfo
+ // file to `terminfo.go#ti_funcs`. The values can be taken
+ // from (ncurses) `term.h`. The builtin terminfo at terminfo_builtin.go
+ // also needs adjusting with the new values.
t_enter_ca = iota
t_exit_ca
t_show_cursor
@@ -22,7 +27,10 @@ const (
t_sgr0
t_underline
t_bold
+ t_hidden
t_blink
+ t_dim
+ t_cursive
t_reverse
t_enter_keypad
t_exit_keypad
@@ -102,10 +110,19 @@ func write_sgr_fg(a Attribute) {
outbuf.WriteString("\033[38;5;")
outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
outbuf.WriteString("m")
+ case OutputRGB:
+ r, g, b := AttributeToRGB(a)
+ outbuf.WriteString(escapeRGB(true, r, g, b))
default:
- outbuf.WriteString("\033[3")
- outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
- outbuf.WriteString("m")
+ if a < ColorDarkGray {
+ outbuf.WriteString("\033[3")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(a-ColorBlack), 10))
+ outbuf.WriteString("m")
+ } else {
+ outbuf.WriteString("\033[9")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(a-ColorDarkGray), 10))
+ outbuf.WriteString("m")
+ }
}
}
@@ -115,10 +132,19 @@ func write_sgr_bg(a Attribute) {
outbuf.WriteString("\033[48;5;")
outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
outbuf.WriteString("m")
+ case OutputRGB:
+ r, g, b := AttributeToRGB(a)
+ outbuf.WriteString(escapeRGB(false, r, g, b))
default:
- outbuf.WriteString("\033[4")
- outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
- outbuf.WriteString("m")
+ if a < ColorDarkGray {
+ outbuf.WriteString("\033[4")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(a-ColorBlack), 10))
+ outbuf.WriteString("m")
+ } else {
+ outbuf.WriteString("\033[10")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(a-ColorDarkGray), 10))
+ outbuf.WriteString("m")
+ }
}
}
@@ -131,13 +157,45 @@ func write_sgr(fg, bg Attribute) {
outbuf.WriteString("\033[48;5;")
outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
outbuf.WriteString("m")
+ case OutputRGB:
+ r, g, b := AttributeToRGB(fg)
+ outbuf.WriteString(escapeRGB(true, r, g, b))
+ r, g, b = AttributeToRGB(bg)
+ outbuf.WriteString(escapeRGB(false, r, g, b))
default:
- outbuf.WriteString("\033[3")
- outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
- outbuf.WriteString(";4")
- outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
- outbuf.WriteString("m")
+ if fg < ColorDarkGray {
+ outbuf.WriteString("\033[3")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-ColorBlack), 10))
+ outbuf.WriteString(";")
+ } else {
+ outbuf.WriteString("\033[9")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-ColorDarkGray), 10))
+ outbuf.WriteString(";")
+ }
+ if bg < ColorDarkGray {
+ outbuf.WriteString("4")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-ColorBlack), 10))
+ outbuf.WriteString("m")
+ } else {
+ outbuf.WriteString("10")
+ outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-ColorDarkGray), 10))
+ outbuf.WriteString("m")
+ }
+ }
+}
+
+func escapeRGB(fg bool, r uint8, g uint8, b uint8) string {
+ var escape string = "\033["
+ if fg {
+ escape += "38"
+ } else {
+ escape += "48"
}
+ escape += ";2;"
+ escape += strconv.FormatUint(uint64(r), 10) + ";"
+ escape += strconv.FormatUint(uint64(g), 10) + ";"
+ escape += strconv.FormatUint(uint64(b), 10) + "m"
+ return escape
}
type winsize struct {
@@ -197,9 +255,12 @@ func send_attr(fg, bg Attribute) {
if bgcol != ColorDefault {
bgcol = grayscale[bgcol]
}
+ case OutputRGB:
+ fgcol = fg
+ bgcol = bg
default:
- fgcol = fg & 0x0F
- bgcol = bg & 0x0F
+ fgcol = fg & 0xFF
+ bgcol = bg & 0xFF
}
if fgcol != ColorDefault {
@@ -215,12 +276,24 @@ func send_attr(fg, bg Attribute) {
if fg&AttrBold != 0 {
outbuf.WriteString(funcs[t_bold])
}
- if bg&AttrBold != 0 {
+ /*if bg&AttrBold != 0 {
+ outbuf.WriteString(funcs[t_blink])
+ }*/
+ if fg&AttrBlink != 0 {
outbuf.WriteString(funcs[t_blink])
}
if fg&AttrUnderline != 0 {
outbuf.WriteString(funcs[t_underline])
}
+ if fg&AttrCursive != 0 {
+ outbuf.WriteString(funcs[t_cursive])
+ }
+ if fg&AttrHidden != 0 {
+ outbuf.WriteString(funcs[t_hidden])
+ }
+ if fg&AttrDim != 0 {
+ outbuf.WriteString(funcs[t_dim])
+ }
if fg&AttrReverse|bg&AttrReverse != 0 {
outbuf.WriteString(funcs[t_reverse])
}
diff --git a/vendor/github.com/nsf/termbox-go/termbox_windows.go b/vendor/github.com/nsf/termbox-go/termbox_windows.go
index 22e0f9ea..d46eb043 100644
--- a/vendor/github.com/nsf/termbox-go/termbox_windows.go
+++ b/vendor/github.com/nsf/termbox-go/termbox_windows.go
@@ -771,7 +771,11 @@ func key_event_record_to_event(r *key_event_record) (Event, bool) {
case vk_tab:
e.Key = KeyTab
case vk_enter:
- e.Key = KeyEnter
+ if ctrlpressed {
+ e.Key = KeyCtrlJ
+ } else {
+ e.Key = KeyEnter
+ }
case vk_esc:
switch {
case input_mode&InputEsc != 0:
diff --git a/vendor/github.com/nsf/termbox-go/terminfo.go b/vendor/github.com/nsf/termbox-go/terminfo.go
index ab2e7a19..e1d59975 100644
--- a/vendor/github.com/nsf/termbox-go/terminfo.go
+++ b/vendor/github.com/nsf/termbox-go/terminfo.go
@@ -222,7 +222,21 @@ func ti_read_string(rd *bytes.Reader, str_off, table int16) (string, error) {
// "Maps" the function constants from termbox.go to the number of the respective
// string capability in the terminfo file. Taken from (ncurses) term.h.
var ti_funcs = []int16{
- 28, 40, 16, 13, 5, 39, 36, 27, 26, 34, 89, 88,
+ 28, // enter ca
+ 40, // exit ca
+ 16, // show cursor
+ 13, // hide cursor
+ 5, // clear screen
+ 39, // sgr0
+ 36, // underline
+ 27, // bold
+ 32, // hidden
+ 26, // blink
+ 30, // dim
+ 311, // cursive
+ 34, // reverse
+ 89, // enter keypad ("keypad_xmit")
+ 88, // exit keypad ("keypad_local")
}
// Same as above for the special keys.
diff --git a/vendor/github.com/nsf/termbox-go/terminfo_builtin.go b/vendor/github.com/nsf/termbox-go/terminfo_builtin.go
index a9486606..da0674a7 100644
--- a/vendor/github.com/nsf/termbox-go/terminfo_builtin.go
+++ b/vendor/github.com/nsf/termbox-go/terminfo_builtin.go
@@ -7,7 +7,23 @@ var eterm_keys = []string{
"\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
}
var eterm_funcs = []string{
- "\x1b7\x1b[?47h", "\x1b[2J\x1b[?47l\x1b8", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "", "", "", "",
+ t_enter_ca: "\x1b7\x1b[?47h",
+ t_exit_ca: "\x1b[2J\x1b[?47l\x1b8",
+ t_show_cursor: "\x1b[?25h",
+ t_hide_cursor: "\x1b[?25l",
+ t_clear_screen: "\x1b[H\x1b[2J",
+ t_sgr0: "\x1b[m\x0f",
+ t_underline: "\x1b[4m",
+ t_bold: "\x1b[1m",
+ t_hidden: "",
+ t_blink: "\x1b[5m",
+ t_dim: "",
+ t_cursive: "",
+ t_reverse: "\x1b[7m",
+ t_enter_keypad: "",
+ t_exit_keypad: "",
+ t_enter_mouse: "",
+ t_exit_mouse: "",
}
// screen
@@ -15,7 +31,23 @@ var screen_keys = []string{
"\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1bOA", "\x1bOB", "\x1bOD", "\x1bOC",
}
var screen_funcs = []string{
- "\x1b[?1049h", "\x1b[?1049l", "\x1b[34h\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b[?1h\x1b=", "\x1b[?1l\x1b>", ti_mouse_enter, ti_mouse_leave,
+ t_enter_ca: "\x1b[?1049h",
+ t_exit_ca: "\x1b[?1049l",
+ t_show_cursor: "\x1b[34h\x1b[?25h",
+ t_hide_cursor: "\x1b[?25l",
+ t_clear_screen: "\x1b[H\x1b[J",
+ t_sgr0: "\x1b[m\x0f",
+ t_underline: "\x1b[4m",
+ t_bold: "\x1b[1m",
+ t_hidden: "",
+ t_blink: "\x1b[5m",
+ t_dim: "",
+ t_cursive: "",
+ t_reverse: "\x1b[7m",
+ t_enter_keypad: "\x1b[?1h\x1b=",
+ t_exit_keypad: "\x1b[?1l\x1b>",
+ t_enter_mouse: ti_mouse_enter,
+ t_exit_mouse: ti_mouse_leave,
}
// xterm
@@ -23,7 +55,23 @@ var xterm_keys = []string{
"\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1bOH", "\x1bOF", "\x1b[5~", "\x1b[6~", "\x1bOA", "\x1bOB", "\x1bOD", "\x1bOC",
}
var xterm_funcs = []string{
- "\x1b[?1049h", "\x1b[?1049l", "\x1b[?12l\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b(B\x1b[m", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b[?1h\x1b=", "\x1b[?1l\x1b>", ti_mouse_enter, ti_mouse_leave,
+ t_enter_ca: "\x1b[?1049h",
+ t_exit_ca: "\x1b[?1049l",
+ t_show_cursor: "\x1b[?12l\x1b[?25h",
+ t_hide_cursor: "\x1b[?25l",
+ t_clear_screen: "\x1b[H\x1b[2J",
+ t_sgr0: "\x1b(B\x1b[m",
+ t_underline: "\x1b[4m",
+ t_bold: "\x1b[1m",
+ t_hidden: "",
+ t_blink: "\x1b[5m",
+ t_dim: "",
+ t_cursive: "",
+ t_reverse: "\x1b[7m",
+ t_enter_keypad: "\x1b[?1h\x1b=",
+ t_exit_keypad: "\x1b[?1l\x1b>",
+ t_enter_mouse: ti_mouse_enter,
+ t_exit_mouse: ti_mouse_leave,
}
// rxvt-unicode
@@ -31,7 +79,23 @@ var rxvt_unicode_keys = []string{
"\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
}
var rxvt_unicode_funcs = []string{
- "\x1b[?1049h", "\x1b[r\x1b[?1049l", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x1b(B", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b=", "\x1b>", ti_mouse_enter, ti_mouse_leave,
+ t_enter_ca: "\x1b[?1049h",
+ t_exit_ca: "\x1b[r\x1b[?1049l",
+ t_show_cursor: "\x1b[?25h",
+ t_hide_cursor: "\x1b[?25l",
+ t_clear_screen: "\x1b[H\x1b[2J",
+ t_sgr0: "\x1b[m\x1b(B",
+ t_underline: "\x1b[4m",
+ t_bold: "\x1b[1m",
+ t_hidden: "",
+ t_blink: "\x1b[5m",
+ t_dim: "",
+ t_cursive: "",
+ t_reverse: "\x1b[7m",
+ t_enter_keypad: "\x1b=",
+ t_exit_keypad: "\x1b>",
+ t_enter_mouse: ti_mouse_enter,
+ t_exit_mouse: ti_mouse_leave,
}
// linux
@@ -39,7 +103,23 @@ var linux_keys = []string{
"\x1b[[A", "\x1b[[B", "\x1b[[C", "\x1b[[D", "\x1b[[E", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
}
var linux_funcs = []string{
- "", "", "\x1b[?25h\x1b[?0c", "\x1b[?25l\x1b[?1c", "\x1b[H\x1b[J", "\x1b[0;10m", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "", "", "", "",
+ t_enter_ca: "",
+ t_exit_ca: "",
+ t_show_cursor: "\x1b[?25h\x1b[?0c",
+ t_hide_cursor: "\x1b[?25l\x1b[?1c",
+ t_clear_screen: "\x1b[H\x1b[J",
+ t_sgr0: "\x1b[0;10m",
+ t_underline: "\x1b[4m",
+ t_bold: "\x1b[1m",
+ t_hidden: "",
+ t_blink: "\x1b[5m",
+ t_dim: "",
+ t_cursive: "",
+ t_reverse: "\x1b[7m",
+ t_enter_keypad: "",
+ t_exit_keypad: "",
+ t_enter_mouse: "",
+ t_exit_mouse: "",
}
// rxvt-256color
@@ -47,7 +127,23 @@ var rxvt_256color_keys = []string{
"\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
}
var rxvt_256color_funcs = []string{
- "\x1b7\x1b[?47h", "\x1b[2J\x1b[?47l\x1b8", "\x1b[?25h", "\x1b[?25l", "\x1b[H\x1b[2J", "\x1b[m\x0f", "\x1b[4m", "\x1b[1m", "\x1b[5m", "\x1b[7m", "\x1b=", "\x1b>", ti_mouse_enter, ti_mouse_leave,
+ t_enter_ca: "\x1b7\x1b[?47h",
+ t_exit_ca: "\x1b[2J\x1b[?47l\x1b8",
+ t_show_cursor: "\x1b[?25h",
+ t_hide_cursor: "\x1b[?25l",
+ t_clear_screen: "\x1b[H\x1b[2J",
+ t_sgr0: "\x1b[m\x0f",
+ t_underline: "\x1b[4m",
+ t_bold: "\x1b[1m",
+ t_hidden: "",
+ t_blink: "\x1b[5m",
+ t_dim: "",
+ t_cursive: "",
+ t_reverse: "\x1b[7m",
+ t_enter_keypad: "\x1b=",
+ t_exit_keypad: "\x1b>",
+ t_enter_mouse: ti_mouse_enter,
+ t_exit_mouse: ti_mouse_leave,
}
var terms = []struct {
diff --git a/vendor/github.com/pkg/sftp/.gitignore b/vendor/github.com/pkg/sftp/.gitignore
index e1ec837c..caf2dca2 100644
--- a/vendor/github.com/pkg/sftp/.gitignore
+++ b/vendor/github.com/pkg/sftp/.gitignore
@@ -5,3 +5,6 @@ server_standalone/server_standalone
examples/*/id_rsa
examples/*/id_rsa.pub
+
+memprofile.out
+memprofile.svg
diff --git a/vendor/github.com/pkg/sftp/.travis.yml b/vendor/github.com/pkg/sftp/.travis.yml
deleted file mode 100644
index 3c1e05e5..00000000
--- a/vendor/github.com/pkg/sftp/.travis.yml
+++ /dev/null
@@ -1,40 +0,0 @@
-language: go
-go_import_path: github.com/pkg/sftp
-
-# current and previous stable releases, plus tip
-# remember to exclude previous and tip for macs below
-go:
- - 1.11.x
- - 1.12.x
- - tip
-
-os:
- - linux
- - osx
-
-env:
- global:
- - GO111MODULE=on
-
-matrix:
- exclude:
- - os: osx
- go: 1.10.x
- - os: osx
- go: tip
-
-addons:
- ssh_known_hosts:
- - bitbucket.org
-
-install:
- - go get -t -v ./...
- - ssh-keygen -t rsa -q -P "" -f $HOME/.ssh/id_rsa
-
-script:
- - go test -integration -v ./...
- - go test -testserver -v ./...
- - go test -integration -testserver -v ./...
- - go test -race -integration -v ./...
- - go test -race -testserver -v ./...
- - go test -race -integration -testserver -v ./...
diff --git a/vendor/github.com/pkg/sftp/Makefile b/vendor/github.com/pkg/sftp/Makefile
new file mode 100644
index 00000000..4d3a0079
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/Makefile
@@ -0,0 +1,27 @@
+.PHONY: integration integration_w_race benchmark
+
+integration:
+ go test -integration -v ./...
+ go test -testserver -v ./...
+ go test -integration -testserver -v ./...
+ go test -integration -allocator -v ./...
+ go test -testserver -allocator -v ./...
+ go test -integration -testserver -allocator -v ./...
+
+integration_w_race:
+ go test -race -integration -v ./...
+ go test -race -testserver -v ./...
+ go test -race -integration -testserver -v ./...
+ go test -race -integration -allocator -v ./...
+ go test -race -testserver -allocator -v ./...
+ go test -race -integration -allocator -testserver -v ./...
+
+COUNT ?= 1
+BENCHMARK_PATTERN ?= "."
+
+benchmark:
+ go test -integration -run=NONE -bench=$(BENCHMARK_PATTERN) -benchmem -count=$(COUNT)
+
+benchmark_w_memprofile:
+ go test -integration -run=NONE -bench=$(BENCHMARK_PATTERN) -benchmem -count=$(COUNT) -memprofile memprofile.out
+ go tool pprof -svg -output=memprofile.svg memprofile.out
diff --git a/vendor/github.com/pkg/sftp/README.md b/vendor/github.com/pkg/sftp/README.md
index 1fb700c4..5e78cd39 100644
--- a/vendor/github.com/pkg/sftp/README.md
+++ b/vendor/github.com/pkg/sftp/README.md
@@ -5,25 +5,25 @@ The `sftp` package provides support for file system operations on remote ssh
servers using the SFTP subsystem. It also implements an SFTP server for serving
files from the filesystem.
-[![UNIX Build Status](https://travis-ci.org/pkg/sftp.svg?branch=master)](https://travis-ci.org/pkg/sftp) [![GoDoc](http://godoc.org/github.com/pkg/sftp?status.svg)](http://godoc.org/github.com/pkg/sftp)
+![CI Status](https://github.com/pkg/sftp/workflows/CI/badge.svg?branch=master&event=push) [![Go Reference](https://pkg.go.dev/badge/github.com/pkg/sftp.svg)](https://pkg.go.dev/github.com/pkg/sftp)
usage and examples
------------------
-See [godoc.org/github.com/pkg/sftp](http://godoc.org/github.com/pkg/sftp) for
+See [https://pkg.go.dev/github.com/pkg/sftp](https://pkg.go.dev/github.com/pkg/sftp) for
examples and usage.
The basic operation of the package mirrors the facilities of the
[os](http://golang.org/pkg/os) package.
The Walker interface for directory traversal is heavily inspired by Keith
-Rarick's [fs](http://godoc.org/github.com/kr/fs) package.
+Rarick's [fs](https://pkg.go.dev/github.com/kr/fs) package.
roadmap
-------
- * There is way too much duplication in the Client methods. If there was an
- unmarshal(interface{}) method this would reduce a heap of the duplication.
+* There is way too much duplication in the Client methods. If there was an
+ unmarshal(interface{}) method this would reduce a heap of the duplication.
contributing
------------
diff --git a/vendor/github.com/pkg/sftp/allocator.go b/vendor/github.com/pkg/sftp/allocator.go
new file mode 100644
index 00000000..3e67e543
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/allocator.go
@@ -0,0 +1,96 @@
+package sftp
+
+import (
+ "sync"
+)
+
+type allocator struct {
+ sync.Mutex
+ available [][]byte
+ // map key is the request order
+ used map[uint32][][]byte
+}
+
+func newAllocator() *allocator {
+ return &allocator{
+ // micro optimization: initialize available pages with an initial capacity
+ available: make([][]byte, 0, SftpServerWorkerCount*2),
+ used: make(map[uint32][][]byte),
+ }
+}
+
+// GetPage returns a previously allocated and unused []byte or create a new one.
+// The slice have a fixed size = maxMsgLength, this value is suitable for both
+// receiving new packets and reading the files to serve
+func (a *allocator) GetPage(requestOrderID uint32) []byte {
+ a.Lock()
+ defer a.Unlock()
+
+ var result []byte
+
+ // get an available page and remove it from the available ones.
+ if len(a.available) > 0 {
+ truncLength := len(a.available) - 1
+ result = a.available[truncLength]
+
+ a.available[truncLength] = nil // clear out the internal pointer
+ a.available = a.available[:truncLength] // truncate the slice
+ }
+
+ // no preallocated slice found, just allocate a new one
+ if result == nil {
+ result = make([]byte, maxMsgLength)
+ }
+
+ // put result in used pages
+ a.used[requestOrderID] = append(a.used[requestOrderID], result)
+
+ return result
+}
+
+// ReleasePages marks unused all pages in use for the given requestID
+func (a *allocator) ReleasePages(requestOrderID uint32) {
+ a.Lock()
+ defer a.Unlock()
+
+ if used := a.used[requestOrderID]; len(used) > 0 {
+ a.available = append(a.available, used...)
+ }
+ delete(a.used, requestOrderID)
+}
+
+// Free removes all the used and available pages.
+// Call this method when the allocator is not needed anymore
+func (a *allocator) Free() {
+ a.Lock()
+ defer a.Unlock()
+
+ a.available = nil
+ a.used = make(map[uint32][][]byte)
+}
+
+func (a *allocator) countUsedPages() int {
+ a.Lock()
+ defer a.Unlock()
+
+ num := 0
+ for _, p := range a.used {
+ num += len(p)
+ }
+ return num
+}
+
+func (a *allocator) countAvailablePages() int {
+ a.Lock()
+ defer a.Unlock()
+
+ return len(a.available)
+}
+
+func (a *allocator) isRequestOrderIDUsed(requestOrderID uint32) bool {
+ a.Lock()
+ defer a.Unlock()
+
+ _, ok := a.used[requestOrderID]
+ return ok
+}
diff --git a/vendor/github.com/pkg/sftp/attrs.go b/vendor/github.com/pkg/sftp/attrs.go
index 335bcc28..2bb2d576 100644
--- a/vendor/github.com/pkg/sftp/attrs.go
+++ b/vendor/github.com/pkg/sftp/attrs.go
@@ -5,43 +5,42 @@ package sftp
import (
"os"
- "syscall"
"time"
)
const (
- ssh_FILEXFER_ATTR_SIZE = 0x00000001
- ssh_FILEXFER_ATTR_UIDGID = 0x00000002
- ssh_FILEXFER_ATTR_PERMISSIONS = 0x00000004
- ssh_FILEXFER_ATTR_ACMODTIME = 0x00000008
- ssh_FILEXFER_ATTR_EXTENDED = 0x80000000
+ sshFileXferAttrSize = 0x00000001
+ sshFileXferAttrUIDGID = 0x00000002
+ sshFileXferAttrPermissions = 0x00000004
+ sshFileXferAttrACmodTime = 0x00000008
+ sshFileXferAttrExtended = 0x80000000
+
+ sshFileXferAttrAll = sshFileXferAttrSize | sshFileXferAttrUIDGID | sshFileXferAttrPermissions |
+ sshFileXferAttrACmodTime | sshFileXferAttrExtended
)
// fileInfo is an artificial type designed to satisfy os.FileInfo.
type fileInfo struct {
- name string
- size int64
- mode os.FileMode
- mtime time.Time
- sys interface{}
+ name string
+ stat *FileStat
}
// Name returns the base name of the file.
func (fi *fileInfo) Name() string { return fi.name }
// Size returns the length in bytes for regular files; system-dependent for others.
-func (fi *fileInfo) Size() int64 { return fi.size }
+func (fi *fileInfo) Size() int64 { return int64(fi.stat.Size) }
// Mode returns file mode bits.
-func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
+func (fi *fileInfo) Mode() os.FileMode { return toFileMode(fi.stat.Mode) }
// ModTime returns the last modification time of the file.
-func (fi *fileInfo) ModTime() time.Time { return fi.mtime }
+func (fi *fileInfo) ModTime() time.Time { return time.Unix(int64(fi.stat.Mtime), 0) }
// IsDir returns true if the file is a directory.
func (fi *fileInfo) IsDir() bool { return fi.Mode().IsDir() }
-func (fi *fileInfo) Sys() interface{} { return fi.sys }
+func (fi *fileInfo) Sys() interface{} { return fi.stat }
// FileStat holds the original unmarshalled values from a call to READDIR or
// *STAT. It is exported for the purposes of accessing the raw values via
@@ -63,25 +62,21 @@ type StatExtended struct {
ExtData string
}
-func fileInfoFromStat(st *FileStat, name string) os.FileInfo {
- fs := &fileInfo{
- name: name,
- size: int64(st.Size),
- mode: toFileMode(st.Mode),
- mtime: time.Unix(int64(st.Mtime), 0),
- sys: st,
+func fileInfoFromStat(stat *FileStat, name string) os.FileInfo {
+ return &fileInfo{
+ name: name,
+ stat: stat,
}
- return fs
}
-func fileStatFromInfo(fi os.FileInfo) (uint32, FileStat) {
+func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) {
mtime := fi.ModTime().Unix()
atime := mtime
- var flags uint32 = ssh_FILEXFER_ATTR_SIZE |
- ssh_FILEXFER_ATTR_PERMISSIONS |
- ssh_FILEXFER_ATTR_ACMODTIME
+ var flags uint32 = sshFileXferAttrSize |
+ sshFileXferAttrPermissions |
+ sshFileXferAttrACmodTime
- fileStat := FileStat{
+ fileStat := &FileStat{
Size: uint64(fi.Size()),
Mode: fromFileMode(fi.Mode()),
Mtime: uint32(mtime),
@@ -89,155 +84,7 @@ func fileStatFromInfo(fi os.FileInfo) (uint32, FileStat) {
}
// os specific file stat decoding
- fileStatFromInfoOs(fi, &flags, &fileStat)
+ fileStatFromInfoOs(fi, &flags, fileStat)
return flags, fileStat
}
-
-func unmarshalAttrs(b []byte) (*FileStat, []byte) {
- flags, b := unmarshalUint32(b)
- return getFileStat(flags, b)
-}
-
-func getFileStat(flags uint32, b []byte) (*FileStat, []byte) {
- var fs FileStat
- if flags&ssh_FILEXFER_ATTR_SIZE == ssh_FILEXFER_ATTR_SIZE {
- fs.Size, b = unmarshalUint64(b)
- }
- if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
- fs.UID, b = unmarshalUint32(b)
- }
- if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
- fs.GID, b = unmarshalUint32(b)
- }
- if flags&ssh_FILEXFER_ATTR_PERMISSIONS == ssh_FILEXFER_ATTR_PERMISSIONS {
- fs.Mode, b = unmarshalUint32(b)
- }
- if flags&ssh_FILEXFER_ATTR_ACMODTIME == ssh_FILEXFER_ATTR_ACMODTIME {
- fs.Atime, b = unmarshalUint32(b)
- fs.Mtime, b = unmarshalUint32(b)
- }
- if flags&ssh_FILEXFER_ATTR_EXTENDED == ssh_FILEXFER_ATTR_EXTENDED {
- var count uint32
- count, b = unmarshalUint32(b)
- ext := make([]StatExtended, count)
- for i := uint32(0); i < count; i++ {
- var typ string
- var data string
- typ, b = unmarshalString(b)
- data, b = unmarshalString(b)
- ext[i] = StatExtended{typ, data}
- }
- fs.Extended = ext
- }
- return &fs, b
-}
-
-func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
- // attributes variable struct, and also variable per protocol version
- // spec version 3 attributes:
- // uint32 flags
- // uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
- // uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID
- // uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID
- // uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
- // uint32 atime present only if flag SSH_FILEXFER_ACMODTIME
- // uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME
- // uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
- // string extended_type
- // string extended_data
- // ... more extended data (extended_type - extended_data pairs),
- // so that number of pairs equals extended_count
-
- flags, fileStat := fileStatFromInfo(fi)
-
- b = marshalUint32(b, flags)
- if flags&ssh_FILEXFER_ATTR_SIZE != 0 {
- b = marshalUint64(b, fileStat.Size)
- }
- if flags&ssh_FILEXFER_ATTR_UIDGID != 0 {
- b = marshalUint32(b, fileStat.UID)
- b = marshalUint32(b, fileStat.GID)
- }
- if flags&ssh_FILEXFER_ATTR_PERMISSIONS != 0 {
- b = marshalUint32(b, fileStat.Mode)
- }
- if flags&ssh_FILEXFER_ATTR_ACMODTIME != 0 {
- b = marshalUint32(b, fileStat.Atime)
- b = marshalUint32(b, fileStat.Mtime)
- }
-
- return b
-}
-
-// toFileMode converts sftp filemode bits to the os.FileMode specification
-func toFileMode(mode uint32) os.FileMode {
- var fm = os.FileMode(mode & 0777)
- switch mode & syscall.S_IFMT {
- case syscall.S_IFBLK:
- fm |= os.ModeDevice
- case syscall.S_IFCHR:
- fm |= os.ModeDevice | os.ModeCharDevice
- case syscall.S_IFDIR:
- fm |= os.ModeDir
- case syscall.S_IFIFO:
- fm |= os.ModeNamedPipe
- case syscall.S_IFLNK:
- fm |= os.ModeSymlink
- case syscall.S_IFREG:
- // nothing to do
- case syscall.S_IFSOCK:
- fm |= os.ModeSocket
- }
- if mode&syscall.S_ISGID != 0 {
- fm |= os.ModeSetgid
- }
- if mode&syscall.S_ISUID != 0 {
- fm |= os.ModeSetuid
- }
- if mode&syscall.S_ISVTX != 0 {
- fm |= os.ModeSticky
- }
- return fm
-}
-
-// fromFileMode converts from the os.FileMode specification to sftp filemode bits
-func fromFileMode(mode os.FileMode) uint32 {
- ret := uint32(0)
-
- if mode&os.ModeDevice != 0 {
- if mode&os.ModeCharDevice != 0 {
- ret |= syscall.S_IFCHR
- } else {
- ret |= syscall.S_IFBLK
- }
- }
- if mode&os.ModeDir != 0 {
- ret |= syscall.S_IFDIR
- }
- if mode&os.ModeSymlink != 0 {
- ret |= syscall.S_IFLNK
- }
- if mode&os.ModeNamedPipe != 0 {
- ret |= syscall.S_IFIFO
- }
- if mode&os.ModeSetgid != 0 {
- ret |= syscall.S_ISGID
- }
- if mode&os.ModeSetuid != 0 {
- ret |= syscall.S_ISUID
- }
- if mode&os.ModeSticky != 0 {
- ret |= syscall.S_ISVTX
- }
- if mode&os.ModeSocket != 0 {
- ret |= syscall.S_IFSOCK
- }
-
- if mode&os.ModeType == 0 {
- ret |= syscall.S_IFREG
- }
- ret |= uint32(mode & os.ModePerm)
-
- return ret
-}
diff --git a/vendor/github.com/pkg/sftp/attrs_stubs.go b/vendor/github.com/pkg/sftp/attrs_stubs.go
index 81cf3eac..c01f3367 100644
--- a/vendor/github.com/pkg/sftp/attrs_stubs.go
+++ b/vendor/github.com/pkg/sftp/attrs_stubs.go
@@ -1,4 +1,4 @@
-// +build !cgo,!plan9 windows android
+// +build plan9 windows android
package sftp
diff --git a/vendor/github.com/pkg/sftp/attrs_unix.go b/vendor/github.com/pkg/sftp/attrs_unix.go
index 95e7922e..d1f44524 100644
--- a/vendor/github.com/pkg/sftp/attrs_unix.go
+++ b/vendor/github.com/pkg/sftp/attrs_unix.go
@@ -1,5 +1,4 @@
-// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix
-// +build cgo
+// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix js
package sftp
@@ -10,7 +9,7 @@ import (
func fileStatFromInfoOs(fi os.FileInfo, flags *uint32, fileStat *FileStat) {
if statt, ok := fi.Sys().(*syscall.Stat_t); ok {
- *flags |= ssh_FILEXFER_ATTR_UIDGID
+ *flags |= sshFileXferAttrUIDGID
fileStat.UID = statt.Uid
fileStat.GID = statt.Gid
}
diff --git a/vendor/github.com/pkg/sftp/client.go b/vendor/github.com/pkg/sftp/client.go
index 11a81b5e..ce62286f 100644
--- a/vendor/github.com/pkg/sftp/client.go
+++ b/vendor/github.com/pkg/sftp/client.go
@@ -3,24 +3,32 @@ package sftp
import (
"bytes"
"encoding/binary"
+ "errors"
"io"
+ "math"
"os"
"path"
+ "sync"
"sync/atomic"
"syscall"
"time"
"github.com/kr/fs"
- "github.com/pkg/errors"
"golang.org/x/crypto/ssh"
)
-// InternalInconsistency indicates the packets sent and the data queued to be
-// written to the file don't match up. It is an unusual error and usually is
-// caused by bad behavior server side or connection issues. The error is
-// limited in scope to the call where it happened, the client object is still
-// OK to use as long as the connection is still open.
-var InternalInconsistency = errors.New("internal inconsistency")
+var (
+ // ErrInternalInconsistency indicates the packets sent and the data queued to be
+ // written to the file don't match up. It is an unusual error and usually is
+ // caused by bad behavior server side or connection issues. The error is
+ // limited in scope to the call where it happened, the client object is still
+ // OK to use as long as the connection is still open.
+ ErrInternalInconsistency = errors.New("internal inconsistency")
+ // InternalInconsistency alias for ErrInternalInconsistency.
+ //
+ // Deprecated: please use ErrInternalInconsistency
+ InternalInconsistency = ErrInternalInconsistency
+)
// A ClientOption is a function which applies configuration to a Client.
type ClientOption func(*Client) error
@@ -35,10 +43,10 @@ type ClientOption func(*Client) error
func MaxPacketChecked(size int) ClientOption {
return func(c *Client) error {
if size < 1 {
- return errors.Errorf("size must be greater or equal to 1")
+ return errors.New("size must be greater or equal to 1")
}
if size > 32768 {
- return errors.Errorf("sizes larger than 32KB might not work with all servers")
+ return errors.New("sizes larger than 32KB might not work with all servers")
}
c.maxPacket = size
return nil
@@ -57,7 +65,7 @@ func MaxPacketChecked(size int) ClientOption {
func MaxPacketUnchecked(size int) ClientOption {
return func(c *Client) error {
if size < 1 {
- return errors.Errorf("size must be greater or equal to 1")
+ return errors.New("size must be greater or equal to 1")
}
c.maxPacket = size
return nil
@@ -82,13 +90,91 @@ func MaxPacket(size int) ClientOption {
func MaxConcurrentRequestsPerFile(n int) ClientOption {
return func(c *Client) error {
if n < 1 {
- return errors.Errorf("n must be greater or equal to 1")
+ return errors.New("n must be greater or equal to 1")
}
c.maxConcurrentRequests = n
return nil
}
}
+// UseConcurrentWrites allows the Client to perform concurrent Writes.
+//
+// Using concurrency while doing writes, requires special consideration.
+// A write to a later offset in a file after an error,
+// could end up with a file length longer than what was successfully written.
+//
+// When using this option, if you receive an error during `io.Copy` or `io.WriteTo`,
+// you may need to `Truncate` the target Writer to avoid “holes” in the data written.
+func UseConcurrentWrites(value bool) ClientOption {
+ return func(c *Client) error {
+ c.useConcurrentWrites = value
+ return nil
+ }
+}
+
+// UseConcurrentReads allows the Client to perform concurrent Reads.
+//
+// Concurrent reads are generally safe to use and not using them will degrade
+// performance, so this option is enabled by default.
+//
+// When enabled, WriteTo will use Stat/Fstat to get the file size and determines
+// how many concurrent workers to use.
+// Some "read once" servers will delete the file if they receive a stat call on an
+// open file and then the download will fail.
+// Disabling concurrent reads you will be able to download files from these servers.
+// If concurrent reads are disabled, the UseFstat option is ignored.
+func UseConcurrentReads(value bool) ClientOption {
+ return func(c *Client) error {
+ c.disableConcurrentReads = !value
+ return nil
+ }
+}
+
+// UseFstat sets whether to use Fstat or Stat when File.WriteTo is called
+// (usually when copying files).
+// Some servers limit the amount of open files and calling Stat after opening
+// the file will throw an error From the server. Setting this flag will call
+// Fstat instead of Stat which is suppose to be called on an open file handle.
+//
+// It has been found that that with IBM Sterling SFTP servers which have
+// "extractability" level set to 1 which means only 1 file can be opened at
+// any given time.
+//
+// If the server you are working with still has an issue with both Stat and
+// Fstat calls you can always open a file and read it until the end.
+//
+// Another reason to read the file until its end and Fstat doesn't work is
+// that in some servers, reading a full file will automatically delete the
+// file as some of these mainframes map the file to a message in a queue.
+// Once the file has been read it will get deleted.
+func UseFstat(value bool) ClientOption {
+ return func(c *Client) error {
+ c.useFstat = value
+ return nil
+ }
+}
+
+// Client represents an SFTP session on a *ssh.ClientConn SSH connection.
+// Multiple Clients can be active on a single SSH connection, and a Client
+// may be called concurrently from multiple Goroutines.
+//
+// Client implements the github.com/kr/fs.FileSystem interface.
+type Client struct {
+ clientConn
+
+ ext map[string]string // Extensions (name -> data).
+
+ maxPacket int // max packet size read or written.
+ maxConcurrentRequests int
+ nextid uint32
+
+ // write concurrency is… error prone.
+ // Default behavior should be to not use it.
+ useConcurrentWrites bool
+ useFstat bool
+ disableConcurrentReads bool
+}
+
// NewClient creates a new SFTP client on conn, using zero or more option
// functions.
func NewClient(conn *ssh.Client, opts ...ClientOption) (*Client, error) {
@@ -124,13 +210,20 @@ func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Clie
inflight: make(map[uint32]chan<- result),
closed: make(chan struct{}),
},
+
+ ext: make(map[string]string),
+
maxPacket: 1 << 15,
maxConcurrentRequests: 64,
}
- if err := sftp.applyOptions(opts...); err != nil {
- wr.Close()
- return nil, err
+
+ for _, opt := range opts {
+ if err := opt(sftp); err != nil {
+ wr.Close()
+ return nil, err
+ }
}
+
if err := sftp.sendInit(); err != nil {
wr.Close()
return nil, err
@@ -139,28 +232,21 @@ func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Clie
wr.Close()
return nil, err
}
+
sftp.clientConn.wg.Add(1)
go sftp.loop()
- return sftp, nil
-}
-
-// Client represents an SFTP session on a *ssh.ClientConn SSH connection.
-// Multiple Clients can be active on a single SSH connection, and a Client
-// may be called concurrently from multiple Goroutines.
-//
-// Client implements the github.com/kr/fs.FileSystem interface.
-type Client struct {
- clientConn
- maxPacket int // max packet size read or written.
- nextid uint32
- maxConcurrentRequests int
+ return sftp, nil
}
// Create creates the named file 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 you need more
// control over the flags/mode used to open the file see client.OpenFile.
+//
+// Note that some SFTP servers (eg. AWS Transfer) do not support opening files
+// read/write at the same time. For those services you will need to use
+// `client.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC)`.
func (c *Client) Create(path string) (*File, error) {
return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC))
}
@@ -168,7 +254,7 @@ func (c *Client) Create(path string) (*File, error) {
const sftpProtocolVersion = 3 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
func (c *Client) sendInit() error {
- return c.clientConn.conn.sendPacket(sshFxInitPacket{
+ return c.clientConn.conn.sendPacket(&sshFxInitPacket{
Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
})
}
@@ -179,22 +265,43 @@ func (c *Client) nextID() uint32 {
}
func (c *Client) recvVersion() error {
- typ, data, err := c.recvPacket()
+ typ, data, err := c.recvPacket(0)
if err != nil {
return err
}
- if typ != ssh_FXP_VERSION {
- return &unexpectedPacketErr{ssh_FXP_VERSION, typ}
+ if typ != sshFxpVersion {
+ return &unexpectedPacketErr{sshFxpVersion, typ}
}
- version, _ := unmarshalUint32(data)
+ version, data, err := unmarshalUint32Safe(data)
+ if err != nil {
+ return err
+ }
if version != sftpProtocolVersion {
return &unexpectedVersionErr{sftpProtocolVersion, version}
}
+ for len(data) > 0 {
+ var ext extensionPair
+ ext, data, err = unmarshalExtensionPair(data)
+ if err != nil {
+ return err
+ }
+ c.ext[ext.Name] = ext.Data
+ }
+
return nil
}
+// HasExtension checks whether the server supports a named extension.
+//
+// The first return value is the extension data reported by the server
+// (typically a version number).
+func (c *Client) HasExtension(name string) (string, bool) {
+ data, ok := c.ext[name]
+ return data, ok
+}
+
// Walk returns a new Walker rooted at root.
func (c *Client) Walk(root string) *fs.Walker {
return fs.WalkFS(root, c)
@@ -212,7 +319,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
var done = false
for !done {
id := c.nextID()
- typ, data, err1 := c.sendPacket(sshFxpReaddirPacket{
+ typ, data, err1 := c.sendPacket(nil, &sshFxpReaddirPacket{
ID: id,
Handle: handle,
})
@@ -222,7 +329,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
break
}
switch typ {
- case ssh_FXP_NAME:
+ case sshFxpName:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIDErr{id, sid}
@@ -239,7 +346,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
}
attrs = append(attrs, fileInfoFromStat(attr, path.Base(filename)))
}
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
// TODO(dfc) scope warning!
err = normaliseError(unmarshalStatus(id, data))
done = true
@@ -255,7 +362,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
func (c *Client) opendir(path string) (string, error) {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpOpendirPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpOpendirPacket{
ID: id,
Path: path,
})
@@ -263,14 +370,14 @@ func (c *Client) opendir(path string) (string, error) {
return "", err
}
switch typ {
- case ssh_FXP_HANDLE:
+ case sshFxpHandle:
sid, data := unmarshalUint32(data)
if sid != id {
return "", &unexpectedIDErr{id, sid}
}
handle, _ := unmarshalString(data)
return handle, nil
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return "", normaliseError(unmarshalStatus(id, data))
default:
return "", unimplementedPacketErr(typ)
@@ -280,34 +387,18 @@ func (c *Client) opendir(path string) (string, error) {
// Stat returns a FileInfo structure describing the file specified by path 'p'.
// If 'p' is a symbolic link, the returned FileInfo structure describes the referent file.
func (c *Client) Stat(p string) (os.FileInfo, error) {
- id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpStatPacket{
- ID: id,
- Path: p,
- })
+ fs, err := c.stat(p)
if err != nil {
return nil, err
}
- switch typ {
- case ssh_FXP_ATTRS:
- sid, data := unmarshalUint32(data)
- if sid != id {
- return nil, &unexpectedIDErr{id, sid}
- }
- attr, _ := unmarshalAttrs(data)
- return fileInfoFromStat(attr, path.Base(p)), nil
- case ssh_FXP_STATUS:
- return nil, normaliseError(unmarshalStatus(id, data))
- default:
- return nil, unimplementedPacketErr(typ)
- }
+ return fileInfoFromStat(fs, path.Base(p)), nil
}
// Lstat returns a FileInfo structure describing the file specified by path 'p'.
// If 'p' is a symbolic link, the returned FileInfo structure describes the symbolic link.
func (c *Client) Lstat(p string) (os.FileInfo, error) {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpLstatPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpLstatPacket{
ID: id,
Path: p,
})
@@ -315,14 +406,14 @@ func (c *Client) Lstat(p string) (os.FileInfo, error) {
return nil, err
}
switch typ {
- case ssh_FXP_ATTRS:
+ case sshFxpAttrs:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIDErr{id, sid}
}
attr, _ := unmarshalAttrs(data)
return fileInfoFromStat(attr, path.Base(p)), nil
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return nil, normaliseError(unmarshalStatus(id, data))
default:
return nil, unimplementedPacketErr(typ)
@@ -332,7 +423,7 @@ func (c *Client) Lstat(p string) (os.FileInfo, error) {
// ReadLink reads the target of a symbolic link.
func (c *Client) ReadLink(p string) (string, error) {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpReadlinkPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpReadlinkPacket{
ID: id,
Path: p,
})
@@ -340,7 +431,7 @@ func (c *Client) ReadLink(p string) (string, error) {
return "", err
}
switch typ {
- case ssh_FXP_NAME:
+ case sshFxpName:
sid, data := unmarshalUint32(data)
if sid != id {
return "", &unexpectedIDErr{id, sid}
@@ -351,17 +442,36 @@ func (c *Client) ReadLink(p string) (string, error) {
}
filename, _ := unmarshalString(data) // ignore dummy attributes
return filename, nil
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return "", normaliseError(unmarshalStatus(id, data))
default:
return "", unimplementedPacketErr(typ)
}
}
+// Link creates a hard link at 'newname', pointing at the same inode as 'oldname'
+func (c *Client) Link(oldname, newname string) error {
+ id := c.nextID()
+ typ, data, err := c.sendPacket(nil, &sshFxpHardlinkPacket{
+ ID: id,
+ Oldpath: oldname,
+ Newpath: newname,
+ })
+ if err != nil {
+ return err
+ }
+ switch typ {
+ case sshFxpStatus:
+ return normaliseError(unmarshalStatus(id, data))
+ default:
+ return unimplementedPacketErr(typ)
+ }
+}
+
// Symlink creates a symbolic link at 'newname', pointing at target 'oldname'
func (c *Client) Symlink(oldname, newname string) error {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpSymlinkPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpSymlinkPacket{
ID: id,
Linkpath: newname,
Targetpath: oldname,
@@ -370,7 +480,26 @@ func (c *Client) Symlink(oldname, newname string) error {
return err
}
switch typ {
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
+ return normaliseError(unmarshalStatus(id, data))
+ default:
+ return unimplementedPacketErr(typ)
+ }
+}
+
+func (c *Client) setfstat(handle string, flags uint32, attrs interface{}) error {
+ id := c.nextID()
+ typ, data, err := c.sendPacket(nil, &sshFxpFsetstatPacket{
+ ID: id,
+ Handle: handle,
+ Flags: flags,
+ Attrs: attrs,
+ })
+ if err != nil {
+ return err
+ }
+ switch typ {
+ case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -380,7 +509,7 @@ func (c *Client) Symlink(oldname, newname string) error {
// setstat is a convience wrapper to allow for changing of various parts of the file descriptor.
func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpSetstatPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpSetstatPacket{
ID: id,
Path: path,
Flags: flags,
@@ -390,7 +519,7 @@ func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {
return err
}
switch typ {
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -404,7 +533,7 @@ func (c *Client) Chtimes(path string, atime time.Time, mtime time.Time) error {
Mtime uint32
}
attrs := times{uint32(atime.Unix()), uint32(mtime.Unix())}
- return c.setstat(path, ssh_FILEXFER_ATTR_ACMODTIME, attrs)
+ return c.setstat(path, sshFileXferAttrACmodTime, attrs)
}
// Chown changes the user and group owners of the named file.
@@ -414,12 +543,16 @@ func (c *Client) Chown(path string, uid, gid int) error {
GID uint32
}
attrs := owner{uint32(uid), uint32(gid)}
- return c.setstat(path, ssh_FILEXFER_ATTR_UIDGID, attrs)
+ return c.setstat(path, sshFileXferAttrUIDGID, attrs)
}
// Chmod changes the permissions of the named file.
+//
+// Chmod does not apply a umask, because even retrieving the umask is not
+// possible in a portable way without causing a race condition. Callers
+// should mask off umask bits, if desired.
func (c *Client) Chmod(path string, mode os.FileMode) error {
- return c.setstat(path, ssh_FILEXFER_ATTR_PERMISSIONS, uint32(mode))
+ return c.setstat(path, sshFileXferAttrPermissions, toChmodPerm(mode))
}
// Truncate sets the size of the named file. Although it may be safely assumed
@@ -427,7 +560,7 @@ func (c *Client) Chmod(path string, mode os.FileMode) error {
// the SFTP protocol does not specify what behavior the server should do when setting
// size greater than the current size.
func (c *Client) Truncate(path string, size int64) error {
- return c.setstat(path, ssh_FILEXFER_ATTR_SIZE, uint64(size))
+ return c.setstat(path, sshFileXferAttrSize, uint64(size))
}
// Open opens the named file for reading. If successful, methods on the
@@ -446,7 +579,7 @@ func (c *Client) OpenFile(path string, f int) (*File, error) {
func (c *Client) open(path string, pflags uint32) (*File, error) {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpOpenPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpOpenPacket{
ID: id,
Path: path,
Pflags: pflags,
@@ -455,14 +588,14 @@ func (c *Client) open(path string, pflags uint32) (*File, error) {
return nil, err
}
switch typ {
- case ssh_FXP_HANDLE:
+ case sshFxpHandle:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIDErr{id, sid}
}
handle, _ := unmarshalString(data)
return &File{c: c, path: path, handle: handle}, nil
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return nil, normaliseError(unmarshalStatus(id, data))
default:
return nil, unimplementedPacketErr(typ)
@@ -474,7 +607,7 @@ func (c *Client) open(path string, pflags uint32) (*File, error) {
// immediately after this request has been sent.
func (c *Client) close(handle string) error {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpClosePacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpClosePacket{
ID: id,
Handle: handle,
})
@@ -482,16 +615,40 @@ func (c *Client) close(handle string) error {
return err
}
switch typ {
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
}
}
+func (c *Client) stat(path string) (*FileStat, error) {
+ id := c.nextID()
+ typ, data, err := c.sendPacket(nil, &sshFxpStatPacket{
+ ID: id,
+ Path: path,
+ })
+ if err != nil {
+ return nil, err
+ }
+ switch typ {
+ case sshFxpAttrs:
+ sid, data := unmarshalUint32(data)
+ if sid != id {
+ return nil, &unexpectedIDErr{id, sid}
+ }
+ attr, _ := unmarshalAttrs(data)
+ return attr, nil
+ case sshFxpStatus:
+ return nil, normaliseError(unmarshalStatus(id, data))
+ default:
+ return nil, unimplementedPacketErr(typ)
+ }
+}
+
func (c *Client) fstat(handle string) (*FileStat, error) {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpFstatPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpFstatPacket{
ID: id,
Handle: handle,
})
@@ -499,14 +656,14 @@ func (c *Client) fstat(handle string) (*FileStat, error) {
return nil, err
}
switch typ {
- case ssh_FXP_ATTRS:
+ case sshFxpAttrs:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIDErr{id, sid}
}
attr, _ := unmarshalAttrs(data)
return attr, nil
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return nil, normaliseError(unmarshalStatus(id, data))
default:
return nil, unimplementedPacketErr(typ)
@@ -520,7 +677,7 @@ func (c *Client) fstat(handle string) (*FileStat, error) {
func (c *Client) StatVFS(path string) (*StatVFS, error) {
// send the StatVFS packet to the server
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpStatvfsPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpStatvfsPacket{
ID: id,
Path: path,
})
@@ -530,7 +687,7 @@ func (c *Client) StatVFS(path string) (*StatVFS, error) {
switch typ {
// server responded with valid data
- case ssh_FXP_EXTENDED_REPLY:
+ case sshFxpExtendedReply:
var response StatVFS
err = binary.Read(bytes.NewReader(data), binary.BigEndian, &response)
if err != nil {
@@ -540,8 +697,8 @@ func (c *Client) StatVFS(path string) (*StatVFS, error) {
return &response, nil
// the resquest failed
- case ssh_FXP_STATUS:
- return nil, errors.New(fxp(ssh_FXP_STATUS).String())
+ case sshFxpStatus:
+ return nil, normaliseError(unmarshalStatus(id, data))
default:
return nil, unimplementedPacketErr(typ)
@@ -558,20 +715,24 @@ func (c *Client) Join(elem ...string) string { return path.Join(elem...) }
// is not empty.
func (c *Client) Remove(path string) error {
err := c.removeFile(path)
+ // some servers, *cough* osx *cough*, return EPERM, not ENODIR.
+ // serv-u returns ssh_FX_FILE_IS_A_DIRECTORY
+ // EPERM is converted to os.ErrPermission so it is not a StatusError
if err, ok := err.(*StatusError); ok {
switch err.Code {
- // some servers, *cough* osx *cough*, return EPERM, not ENODIR.
- // serv-u returns ssh_FX_FILE_IS_A_DIRECTORY
- case ssh_FX_PERMISSION_DENIED, ssh_FX_FAILURE, ssh_FX_FILE_IS_A_DIRECTORY:
+ case sshFxFailure, sshFxFileIsADirectory:
return c.RemoveDirectory(path)
}
}
+ if os.IsPermission(err) {
+ return c.RemoveDirectory(path)
+ }
return err
}
func (c *Client) removeFile(path string) error {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpRemovePacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpRemovePacket{
ID: id,
Filename: path,
})
@@ -579,7 +740,7 @@ func (c *Client) removeFile(path string) error {
return err
}
switch typ {
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -589,7 +750,7 @@ func (c *Client) removeFile(path string) error {
// RemoveDirectory removes a directory path.
func (c *Client) RemoveDirectory(path string) error {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpRmdirPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpRmdirPacket{
ID: id,
Path: path,
})
@@ -597,7 +758,7 @@ func (c *Client) RemoveDirectory(path string) error {
return err
}
switch typ {
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -607,7 +768,7 @@ func (c *Client) RemoveDirectory(path string) error {
// Rename renames a file.
func (c *Client) Rename(oldname, newname string) error {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpRenamePacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpRenamePacket{
ID: id,
Oldpath: oldname,
Newpath: newname,
@@ -616,7 +777,7 @@ func (c *Client) Rename(oldname, newname string) error {
return err
}
switch typ {
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -627,7 +788,7 @@ func (c *Client) Rename(oldname, newname string) error {
// which will replace newname if it already exists.
func (c *Client) PosixRename(oldname, newname string) error {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpPosixRenamePacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpPosixRenamePacket{
ID: id,
Oldpath: oldname,
Newpath: newname,
@@ -636,16 +797,20 @@ func (c *Client) PosixRename(oldname, newname string) error {
return err
}
switch typ {
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
}
}
-func (c *Client) realpath(path string) (string, error) {
+// RealPath can be used to have the server canonicalize any given path name to an absolute path.
+//
+// This is useful for converting path names containing ".." components,
+// or relative pathnames without a leading slash into absolute paths.
+func (c *Client) RealPath(path string) (string, error) {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpRealpathPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpRealpathPacket{
ID: id,
Path: path,
})
@@ -653,7 +818,7 @@ func (c *Client) realpath(path string) (string, error) {
return "", err
}
switch typ {
- case ssh_FXP_NAME:
+ case sshFxpName:
sid, data := unmarshalUint32(data)
if sid != id {
return "", &unexpectedIDErr{id, sid}
@@ -664,7 +829,7 @@ func (c *Client) realpath(path string) (string, error) {
}
filename, _ := unmarshalString(data) // ignore attributes
return filename, nil
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return "", normaliseError(unmarshalStatus(id, data))
default:
return "", unimplementedPacketErr(typ)
@@ -674,7 +839,7 @@ func (c *Client) realpath(path string) (string, error) {
// Getwd returns the current working directory of the server. Operations
// involving relative paths will be based at this location.
func (c *Client) Getwd() (string, error) {
- return c.realpath(".")
+ return c.RealPath(".")
}
// Mkdir creates the specified directory. An error will be returned if a file or
@@ -682,7 +847,7 @@ func (c *Client) Getwd() (string, error) {
// parent folder does not exist (the method cannot create complete paths).
func (c *Client) Mkdir(path string) error {
id := c.nextID()
- typ, data, err := c.sendPacket(sshFxpMkdirPacket{
+ typ, data, err := c.sendPacket(nil, &sshFxpMkdirPacket{
ID: id,
Path: path,
})
@@ -690,7 +855,7 @@ func (c *Client) Mkdir(path string) error {
return err
}
switch typ {
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -714,12 +879,12 @@ func (c *Client) MkdirAll(path string) error {
// 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.
+ for i > 0 && path[i-1] == '/' { // Skip trailing path separator.
i--
}
j := i
- for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.
+ for j > 0 && path[j-1] != '/' { // Scan backward over element.
j--
}
@@ -745,23 +910,14 @@ func (c *Client) MkdirAll(path string) error {
return nil
}
-// applyOptions applies options functions to the Client.
-// If an error is encountered, option processing ceases.
-func (c *Client) applyOptions(opts ...ClientOption) error {
- for _, f := range opts {
- if err := f(c); err != nil {
- return err
- }
- }
- return nil
-}
-
// File represents a remote file.
type File struct {
c *Client
path string
handle string
- offset uint64 // current offset within remote file
+
+ mu sync.Mutex
+ offset int64 // current offset within remote file
}
// Close closes the File, rendering it unusable for I/O. It returns an
@@ -785,234 +941,460 @@ func (f *File) Name() string {
// than calling Read multiple times. io.Copy will do this
// automatically.
func (f *File) Read(b []byte) (int, error) {
- // Split the read into multiple maxPacket sized concurrent reads
- // bounded by maxConcurrentRequests. This allows reads with a suitably
- // large buffer to transfer data at a much faster rate due to
- // overlapping round trip times.
- inFlight := 0
- desiredInFlight := 1
- offset := f.offset
- // maxConcurrentRequests buffer to deal with broadcastErr() floods
- // also must have a buffer of max value of (desiredInFlight - inFlight)
- ch := make(chan result, f.c.maxConcurrentRequests+1)
- type inflightRead struct {
- b []byte
- offset uint64
- }
- reqs := map[uint32]inflightRead{}
- type offsetErr struct {
- offset uint64
- err error
- }
- var firstErr offsetErr
-
- sendReq := func(b []byte, offset uint64) {
- reqID := f.c.nextID()
- f.c.dispatchRequest(ch, sshFxpReadPacket{
- ID: reqID,
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ n, err := f.ReadAt(b, f.offset)
+ f.offset += int64(n)
+ return n, err
+}
+
+// readChunkAt attempts to read the whole entire length of the buffer from the file starting at the offset.
+// It will continue progressively reading into the buffer until it fills the whole buffer, or an error occurs.
+func (f *File) readChunkAt(ch chan result, b []byte, off int64) (n int, err error) {
+ for err == nil && n < len(b) {
+ id := f.c.nextID()
+ typ, data, err := f.c.sendPacket(ch, &sshFxpReadPacket{
+ ID: id,
Handle: f.handle,
- Offset: offset,
- Len: uint32(len(b)),
+ Offset: uint64(off) + uint64(n),
+ Len: uint32(len(b) - n),
})
- inFlight++
- reqs[reqID] = inflightRead{b: b, offset: offset}
- }
-
- var read int
- for len(b) > 0 || inFlight > 0 {
- for inFlight < desiredInFlight && len(b) > 0 && firstErr.err == nil {
- l := min(len(b), f.c.maxPacket)
- rb := b[:l]
- sendReq(rb, offset)
- offset += uint64(l)
- b = b[l:]
+ if err != nil {
+ return n, err
}
- if inFlight == 0 {
- break
+ switch typ {
+ case sshFxpStatus:
+ return n, normaliseError(unmarshalStatus(id, data))
+
+ case sshFxpData:
+ sid, data := unmarshalUint32(data)
+ if id != sid {
+ return n, &unexpectedIDErr{id, sid}
+ }
+
+ l, data := unmarshalUint32(data)
+ n += copy(b[n:], data[:l])
+
+ default:
+ return n, unimplementedPacketErr(typ)
}
- res := <-ch
- inFlight--
- if res.err != nil {
- firstErr = offsetErr{offset: 0, err: res.err}
- continue
+ }
+
+ return
+}
+
+func (f *File) readAtSequential(b []byte, off int64) (read int, err error) {
+ for read < len(b) {
+ rb := b[read:]
+ if len(rb) > f.c.maxPacket {
+ rb = rb[:f.c.maxPacket]
}
- reqID, data := unmarshalUint32(res.data)
- req, ok := reqs[reqID]
- if !ok {
- firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
- continue
+ n, err := f.readChunkAt(nil, rb, off+int64(read))
+ if n < 0 {
+ panic("sftp.File: returned negative count from readChunkAt")
}
- delete(reqs, reqID)
- switch res.typ {
- case ssh_FXP_STATUS:
- if firstErr.err == nil || req.offset < firstErr.offset {
- firstErr = offsetErr{
- offset: req.offset,
- err: normaliseError(unmarshalStatus(reqID, res.data)),
- }
- }
- case ssh_FXP_DATA:
- l, data := unmarshalUint32(data)
- n := copy(req.b, data[:l])
+ if n > 0 {
read += n
- if n < len(req.b) {
- sendReq(req.b[l:], req.offset+uint64(l))
+ }
+ if err != nil {
+ if errors.Is(err, io.EOF) {
+ return read, nil // return nil explicitly.
}
- if desiredInFlight < f.c.maxConcurrentRequests {
- desiredInFlight++
+ return read, err
+ }
+ }
+ return read, nil
+}
+
+// ReadAt reads up to len(b) byte from the File at a given offset `off`. It returns
+// the number of bytes read and an error, if any. ReadAt follows io.ReaderAt semantics,
+// so the file offset is not altered during the read.
+func (f *File) ReadAt(b []byte, off int64) (int, error) {
+ if len(b) <= f.c.maxPacket {
+ // This should be able to be serviced with 1/2 requests.
+ // So, just do it directly.
+ return f.readChunkAt(nil, b, off)
+ }
+
+ if f.c.disableConcurrentReads {
+ return f.readAtSequential(b, off)
+ }
+
+ // Split the read into multiple maxPacket-sized concurrent reads bounded by maxConcurrentRequests.
+ // This allows writes with a suitably large buffer to transfer data at a much faster rate
+ // by overlapping round trip times.
+
+ cancel := make(chan struct{})
+
+ concurrency := len(b)/f.c.maxPacket + 1
+ if concurrency > f.c.maxConcurrentRequests || concurrency < 1 {
+ concurrency = f.c.maxConcurrentRequests
+ }
+
+ resPool := newResChanPool(concurrency)
+
+ type work struct {
+ id uint32
+ res chan result
+
+ b []byte
+ off int64
+ }
+ workCh := make(chan work)
+
+ // Slice: cut up the Read into any number of buffers of length <= f.c.maxPacket, and at appropriate offsets.
+ go func() {
+ defer close(workCh)
+
+ b := b
+ offset := off
+ chunkSize := f.c.maxPacket
+
+ for len(b) > 0 {
+ rb := b
+ if len(rb) > chunkSize {
+ rb = rb[:chunkSize]
+ }
+
+ id := f.c.nextID()
+ res := resPool.Get()
+
+ f.c.dispatchRequest(res, &sshFxpReadPacket{
+ ID: id,
+ Handle: f.handle,
+ Offset: uint64(offset),
+ Len: uint32(chunkSize),
+ })
+
+ select {
+ case workCh <- work{id, res, rb, offset}:
+ case <-cancel:
+ return
}
+
+ offset += int64(len(rb))
+ b = b[len(rb):]
+ }
+ }()
+
+ type rErr struct {
+ off int64
+ err error
+ }
+ errCh := make(chan rErr)
+
+ var wg sync.WaitGroup
+ wg.Add(concurrency)
+ for i := 0; i < concurrency; i++ {
+ // Map_i: each worker gets work, and then performs the Read into its buffer from its respective offset.
+ go func() {
+ defer wg.Done()
+
+ for packet := range workCh {
+ var n int
+
+ s := <-packet.res
+ resPool.Put(packet.res)
+
+ err := s.err
+ if err == nil {
+ switch s.typ {
+ case sshFxpStatus:
+ err = normaliseError(unmarshalStatus(packet.id, s.data))
+
+ case sshFxpData:
+ sid, data := unmarshalUint32(s.data)
+ if packet.id != sid {
+ err = &unexpectedIDErr{packet.id, sid}
+
+ } else {
+ l, data := unmarshalUint32(data)
+ n = copy(packet.b, data[:l])
+
+ // For normal disk files, it is guaranteed that this will read
+ // the specified number of bytes, or up to end of file.
+ // This implies, if we have a short read, that means EOF.
+ if n < len(packet.b) {
+ err = io.EOF
+ }
+ }
+
+ default:
+ err = unimplementedPacketErr(s.typ)
+ }
+ }
+
+ if err != nil {
+ // return the offset as the start + how much we read before the error.
+ errCh <- rErr{packet.off + int64(n), err}
+ return
+ }
+ }
+ }()
+ }
+
+ // Wait for long tail, before closing results.
+ go func() {
+ wg.Wait()
+ close(errCh)
+ }()
+
+ // Reduce: collect all the results into a relevant return: the earliest offset to return an error.
+ firstErr := rErr{math.MaxInt64, nil}
+ for rErr := range errCh {
+ if rErr.off <= firstErr.off {
+ firstErr = rErr
+ }
+
+ select {
+ case <-cancel:
default:
- firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
+ // stop any more work from being distributed. (Just in case.)
+ close(cancel)
}
}
- // If the error is anything other than EOF, then there
- // may be gaps in the data copied to the buffer so it's
- // best to return 0 so the caller can't make any
- // incorrect assumptions about the state of the buffer.
- if firstErr.err != nil && firstErr.err != io.EOF {
- read = 0
+
+ if firstErr.err != nil {
+ // firstErr.err != nil if and only if firstErr.off > our starting offset.
+ return int(firstErr.off - off), firstErr.err
+ }
+
+ // As per spec for io.ReaderAt, we return nil error if and only if we read everything.
+ return len(b), nil
+}
+
+// writeToSequential implements WriteTo, but works sequentially with no parallelism.
+func (f *File) writeToSequential(w io.Writer) (written int64, err error) {
+ b := make([]byte, f.c.maxPacket)
+ ch := make(chan result, 1) // reusable channel
+
+ for {
+ n, err := f.readChunkAt(ch, b, f.offset)
+ if n < 0 {
+ panic("sftp.File: returned negative count from readChunkAt")
+ }
+
+ if n > 0 {
+ f.offset += int64(n)
+
+ m, err2 := w.Write(b[:n])
+ written += int64(m)
+
+ if err == nil {
+ err = err2
+ }
+ }
+
+ if err != nil {
+ if err == io.EOF {
+ return written, nil // return nil explicitly.
+ }
+
+ return written, err
+ }
}
- f.offset += uint64(read)
- return read, firstErr.err
}
-// WriteTo writes the file to w. The return value is the number of bytes
-// written. Any error encountered during the write is also returned.
+// WriteTo writes the file to the given Writer.
+// The return value is the number of bytes written.
+// Any error encountered during the write is also returned.
//
-// This method is preferred over calling Read multiple times to
-// maximise throughput for transferring the entire file (especially
-// over high latency links).
-func (f *File) WriteTo(w io.Writer) (int64, error) {
- fi, err := f.c.Stat(f.path)
+// This method is preferred over calling Read multiple times
+// to maximise throughput for transferring the entire file,
+// especially over high latency links.
+func (f *File) WriteTo(w io.Writer) (written int64, err error) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ if f.c.disableConcurrentReads {
+ return f.writeToSequential(w)
+ }
+
+ // For concurrency, we want to guess how many concurrent workers we should use.
+ var fileStat *FileStat
+ if f.c.useFstat {
+ fileStat, err = f.c.fstat(f.handle)
+ } else {
+ fileStat, err = f.c.stat(f.path)
+ }
if err != nil {
return 0, err
}
- inFlight := 0
- desiredInFlight := 1
- offset := f.offset
- writeOffset := offset
- fileSize := uint64(fi.Size())
- // see comment on same line in Read() above
- ch := make(chan result, f.c.maxConcurrentRequests+1)
- type inflightRead struct {
- b []byte
- offset uint64
- }
- reqs := map[uint32]inflightRead{}
- pendingWrites := map[uint64][]byte{}
- type offsetErr struct {
- offset uint64
- err error
- }
- var firstErr offsetErr
-
- sendReq := func(b []byte, offset uint64) {
- reqID := f.c.nextID()
- f.c.dispatchRequest(ch, sshFxpReadPacket{
- ID: reqID,
- Handle: f.handle,
- Offset: offset,
- Len: uint32(len(b)),
- })
- inFlight++
- reqs[reqID] = inflightRead{b: b, offset: offset}
- }
-
- var copied int64
- for firstErr.err == nil || inFlight > 0 {
- if firstErr.err == nil {
- for inFlight+len(pendingWrites) < desiredInFlight {
- b := make([]byte, f.c.maxPacket)
- sendReq(b, offset)
- offset += uint64(f.c.maxPacket)
- if offset > fileSize {
- desiredInFlight = 1
- }
+
+ fileSize := fileStat.Size
+ if fileSize <= uint64(f.c.maxPacket) || !isRegular(fileStat.Mode) {
+ // only regular files are guaranteed to return (full read) xor (partial read, next error)
+ return f.writeToSequential(w)
+ }
+
+ concurrency64 := fileSize/uint64(f.c.maxPacket) + 1 // a bad guess, but better than no guess
+ if concurrency64 > uint64(f.c.maxConcurrentRequests) || concurrency64 < 1 {
+ concurrency64 = uint64(f.c.maxConcurrentRequests)
+ }
+ // Now that concurrency64 is saturated to an int value, we know this assignment cannot possibly overflow.
+ concurrency := int(concurrency64)
+
+ chunkSize := f.c.maxPacket
+ pool := newBufPool(concurrency, chunkSize)
+ resPool := newResChanPool(concurrency)
+
+ cancel := make(chan struct{})
+ var wg sync.WaitGroup
+ defer func() {
+ // Once the writing Reduce phase has ended, all the feed work needs to unconditionally stop.
+ close(cancel)
+
+ // We want to wait until all outstanding goroutines with an `f` or `f.c` reference have completed.
+ // Just to be sure we don’t orphan any goroutines any hanging references.
+ wg.Wait()
+ }()
+
+ type writeWork struct {
+ b []byte
+ off int64
+ err error
+
+ next chan writeWork
+ }
+ writeCh := make(chan writeWork)
+
+ type readWork struct {
+ id uint32
+ res chan result
+ off int64
+
+ cur, next chan writeWork
+ }
+ readCh := make(chan readWork)
+
+ // Slice: hand out chunks of work on demand, with a `cur` and `next` channel built-in for sequencing.
+ go func() {
+ defer close(readCh)
+
+ off := f.offset
+
+ cur := writeCh
+ for {
+ id := f.c.nextID()
+ res := resPool.Get()
+
+ next := make(chan writeWork)
+ readWork := readWork{
+ id: id,
+ res: res,
+ off: off,
+
+ cur: cur,
+ next: next,
}
- }
- if inFlight == 0 {
- if firstErr.err == nil && len(pendingWrites) > 0 {
- return copied, InternalInconsistency
+ f.c.dispatchRequest(res, &sshFxpReadPacket{
+ ID: id,
+ Handle: f.handle,
+ Offset: uint64(off),
+ Len: uint32(chunkSize),
+ })
+
+ select {
+ case readCh <- readWork:
+ case <-cancel:
+ return
}
- break
- }
- res := <-ch
- inFlight--
- if res.err != nil {
- firstErr = offsetErr{offset: 0, err: res.err}
- continue
- }
- reqID, data := unmarshalUint32(res.data)
- req, ok := reqs[reqID]
- if !ok {
- firstErr = offsetErr{offset: 0, err: errors.Errorf("sid: %v not found", reqID)}
- continue
+
+ off += int64(chunkSize)
+ cur = next
}
- delete(reqs, reqID)
- switch res.typ {
- case ssh_FXP_STATUS:
- if firstErr.err == nil || req.offset < firstErr.offset {
- firstErr = offsetErr{offset: req.offset, err: normaliseError(unmarshalStatus(reqID, res.data))}
- }
- case ssh_FXP_DATA:
- l, data := unmarshalUint32(data)
- if req.offset == writeOffset {
- nbytes, err := w.Write(data)
- copied += int64(nbytes)
- if err != nil {
- // We will never receive another DATA with offset==writeOffset, so
- // the loop will drain inFlight and then exit.
- firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: err}
- break
+ }()
+
+ wg.Add(concurrency)
+ for i := 0; i < concurrency; i++ {
+ // Map_i: each worker gets readWork, and does the Read into a buffer at the given offset.
+ go func() {
+ defer wg.Done()
+
+ for readWork := range readCh {
+ var b []byte
+ var n int
+
+ s := <-readWork.res
+ resPool.Put(readWork.res)
+
+ err := s.err
+ if err == nil {
+ switch s.typ {
+ case sshFxpStatus:
+ err = normaliseError(unmarshalStatus(readWork.id, s.data))
+
+ case sshFxpData:
+ sid, data := unmarshalUint32(s.data)
+ if readWork.id != sid {
+ err = &unexpectedIDErr{readWork.id, sid}
+
+ } else {
+ l, data := unmarshalUint32(data)
+ b = pool.Get()[:l]
+ n = copy(b, data[:l])
+ b = b[:n]
+ }
+
+ default:
+ err = unimplementedPacketErr(s.typ)
+ }
}
- if nbytes < int(l) {
- firstErr = offsetErr{offset: req.offset + uint64(nbytes), err: io.ErrShortWrite}
- break
+
+ writeWork := writeWork{
+ b: b,
+ off: readWork.off,
+ err: err,
+
+ next: readWork.next,
}
- switch {
- case offset > fileSize:
- desiredInFlight = 1
- case desiredInFlight < f.c.maxConcurrentRequests:
- desiredInFlight++
+
+ select {
+ case readWork.cur <- writeWork:
+ case <-cancel:
+ return
}
- writeOffset += uint64(nbytes)
- for {
- pendingData, ok := pendingWrites[writeOffset]
- if !ok {
- break
- }
- // Give go a chance to free the memory.
- delete(pendingWrites, writeOffset)
- nbytes, err := w.Write(pendingData)
- // Do not move writeOffset on error so subsequent iterations won't trigger
- // any writes.
- if err != nil {
- firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: err}
- break
- }
- if nbytes < len(pendingData) {
- firstErr = offsetErr{offset: writeOffset + uint64(nbytes), err: io.ErrShortWrite}
- break
- }
- writeOffset += uint64(nbytes)
+
+ if err != nil {
+ return
}
- } else {
- // Don't write the data yet because
- // this response came in out of order
- // and we need to wait for responses
- // for earlier segments of the file.
- pendingWrites[req.offset] = data
}
- default:
- firstErr = offsetErr{offset: 0, err: unimplementedPacketErr(res.typ)}
- }
+ }()
}
- if firstErr.err != io.EOF {
- return copied, firstErr.err
+
+ // Reduce: serialize the results from the reads into sequential writes.
+ cur := writeCh
+ for {
+ packet, ok := <-cur
+ if !ok {
+ return written, errors.New("sftp.File.WriteTo: unexpectedly closed channel")
+ }
+
+ // Because writes are serialized, this will always be the last successfully read byte.
+ f.offset = packet.off + int64(len(packet.b))
+
+ if len(packet.b) > 0 {
+ n, err := w.Write(packet.b)
+ written += int64(n)
+ if err != nil {
+ return written, err
+ }
+ }
+
+ if packet.err != nil {
+ if packet.err == io.EOF {
+ return written, nil
+ }
+
+ return written, packet.err
+ }
+
+ pool.Put(packet.b)
+ cur = packet.next
}
- return copied, nil
}
// Stat returns the FileInfo structure describing file. If there is an
@@ -1034,157 +1416,410 @@ func (f *File) Stat() (os.FileInfo, error) {
// than calling Write multiple times. io.Copy will do this
// automatically.
func (f *File) Write(b []byte) (int, error) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ n, err := f.WriteAt(b, f.offset)
+ f.offset += int64(n)
+ return n, err
+}
+
+func (f *File) writeChunkAt(ch chan result, b []byte, off int64) (int, error) {
+ typ, data, err := f.c.sendPacket(ch, &sshFxpWritePacket{
+ ID: f.c.nextID(),
+ Handle: f.handle,
+ Offset: uint64(off),
+ Length: uint32(len(b)),
+ Data: b,
+ })
+ if err != nil {
+ return 0, err
+ }
+
+ switch typ {
+ case sshFxpStatus:
+ id, _ := unmarshalUint32(data)
+ err := normaliseError(unmarshalStatus(id, data))
+ if err != nil {
+ return 0, err
+ }
+
+ default:
+ return 0, unimplementedPacketErr(typ)
+ }
+
+ return len(b), nil
+}
+
+// writeAtConcurrent implements WriterAt, but works concurrently rather than sequentially.
+func (f *File) writeAtConcurrent(b []byte, off int64) (int, error) {
// Split the write into multiple maxPacket sized concurrent writes
// bounded by maxConcurrentRequests. This allows writes with a suitably
// large buffer to transfer data at a much faster rate due to
// overlapping round trip times.
- inFlight := 0
- desiredInFlight := 1
- offset := f.offset
- // see comment on same line in Read() above
- ch := make(chan result, f.c.maxConcurrentRequests+1)
- var firstErr error
- written := len(b)
- for len(b) > 0 || inFlight > 0 {
- for inFlight < desiredInFlight && len(b) > 0 && firstErr == nil {
- l := min(len(b), f.c.maxPacket)
- rb := b[:l]
- f.c.dispatchRequest(ch, sshFxpWritePacket{
- ID: f.c.nextID(),
- Handle: f.handle,
- Offset: offset,
- Length: uint32(len(rb)),
- Data: rb,
- })
- inFlight++
- offset += uint64(l)
- b = b[l:]
+
+ cancel := make(chan struct{})
+
+ type work struct {
+ b []byte
+ off int64
+ }
+ workCh := make(chan work)
+
+ // Slice: cut up the Read into any number of buffers of length <= f.c.maxPacket, and at appropriate offsets.
+ go func() {
+ defer close(workCh)
+
+ var read int
+ chunkSize := f.c.maxPacket
+
+ for read < len(b) {
+ wb := b[read:]
+ if len(wb) > chunkSize {
+ wb = wb[:chunkSize]
+ }
+
+ select {
+ case workCh <- work{wb, off + int64(read)}:
+ case <-cancel:
+ return
+ }
+
+ read += len(wb)
}
+ }()
- if inFlight == 0 {
- break
+ type wErr struct {
+ off int64
+ err error
+ }
+ errCh := make(chan wErr)
+
+ concurrency := len(b)/f.c.maxPacket + 1
+ if concurrency > f.c.maxConcurrentRequests || concurrency < 1 {
+ concurrency = f.c.maxConcurrentRequests
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(concurrency)
+ for i := 0; i < concurrency; i++ {
+ // Map_i: each worker gets work, and does the Write from each buffer to its respective offset.
+ go func() {
+ defer wg.Done()
+
+ ch := make(chan result, 1) // reusable channel per mapper.
+
+ for packet := range workCh {
+ n, err := f.writeChunkAt(ch, packet.b, packet.off)
+ if err != nil {
+ // return the offset as the start + how much we wrote before the error.
+ errCh <- wErr{packet.off + int64(n), err}
+ }
+ }
+ }()
+ }
+
+ // Wait for long tail, before closing results.
+ go func() {
+ wg.Wait()
+ close(errCh)
+ }()
+
+ // Reduce: collect all the results into a relevant return: the earliest offset to return an error.
+ firstErr := wErr{math.MaxInt64, nil}
+ for wErr := range errCh {
+ if wErr.off <= firstErr.off {
+ firstErr = wErr
}
- res := <-ch
- inFlight--
- if res.err != nil {
- firstErr = res.err
- continue
+
+ select {
+ case <-cancel:
+ default:
+ // stop any more work from being distributed. (Just in case.)
+ close(cancel)
}
- switch res.typ {
- case ssh_FXP_STATUS:
- id, _ := unmarshalUint32(res.data)
- err := normaliseError(unmarshalStatus(id, res.data))
- if err != nil && firstErr == nil {
- firstErr = err
- break
+ }
+
+ if firstErr.err != nil {
+ // firstErr.err != nil if and only if firstErr.off >= our starting offset.
+ return int(firstErr.off - off), firstErr.err
+ }
+
+ return len(b), nil
+}
+
+// WriteAt writes up to len(b) byte to the File at a given offset `off`. It returns
+// the number of bytes written and an error, if any. WriteAt follows io.WriterAt semantics,
+// so the file offset is not altered during the write.
+func (f *File) WriteAt(b []byte, off int64) (written int, err error) {
+ if len(b) <= f.c.maxPacket {
+ // We can do this in one write.
+ return f.writeChunkAt(nil, b, off)
+ }
+
+ if f.c.useConcurrentWrites {
+ return f.writeAtConcurrent(b, off)
+ }
+
+ ch := make(chan result, 1) // reusable channel
+
+ chunkSize := f.c.maxPacket
+
+ for written < len(b) {
+ wb := b[written:]
+ if len(wb) > chunkSize {
+ wb = wb[:chunkSize]
+ }
+
+ n, err := f.writeChunkAt(ch, wb, off+int64(written))
+ if n > 0 {
+ written += n
+ }
+
+ if err != nil {
+ return written, err
+ }
+ }
+
+ return len(b), nil
+}
+
+// ReadFromWithConcurrency implements ReaderFrom,
+// but uses the given concurrency to issue multiple requests at the same time.
+//
+// Giving a concurrency of less than one will default to the Client’s max concurrency.
+//
+// Otherwise, the given concurrency will be capped by the Client's max concurrency.
+func (f *File) ReadFromWithConcurrency(r io.Reader, concurrency int) (read int64, err error) {
+ // Split the write into multiple maxPacket sized concurrent writes.
+ // This allows writes with a suitably large reader
+ // to transfer data at a much faster rate due to overlapping round trip times.
+
+ cancel := make(chan struct{})
+
+ type work struct {
+ b []byte
+ n int
+ off int64
+ }
+ workCh := make(chan work)
+
+ type rwErr struct {
+ off int64
+ err error
+ }
+ errCh := make(chan rwErr)
+
+ if concurrency > f.c.maxConcurrentRequests || concurrency < 1 {
+ concurrency = f.c.maxConcurrentRequests
+ }
+
+ pool := newBufPool(concurrency, f.c.maxPacket)
+
+ // Slice: cut up the Read into any number of buffers of length <= f.c.maxPacket, and at appropriate offsets.
+ go func() {
+ defer close(workCh)
+
+ off := f.offset
+
+ for {
+ b := pool.Get()
+
+ n, err := r.Read(b)
+ if n > 0 {
+ read += int64(n)
+
+ select {
+ case workCh <- work{b, n, off}:
+ // We need the pool.Put(b) to put the whole slice, not just trunced.
+ case <-cancel:
+ return
+ }
+
+ off += int64(n)
}
- if desiredInFlight < f.c.maxConcurrentRequests {
- desiredInFlight++
+
+ if err != nil {
+ if err != io.EOF {
+ errCh <- rwErr{off, err}
+ }
+ return
+ }
+ }
+ }()
+
+ var wg sync.WaitGroup
+ wg.Add(concurrency)
+ for i := 0; i < concurrency; i++ {
+ // Map_i: each worker gets work, and does the Write from each buffer to its respective offset.
+ go func() {
+ defer wg.Done()
+
+ ch := make(chan result, 1) // reusable channel per mapper.
+
+ for packet := range workCh {
+ n, err := f.writeChunkAt(ch, packet.b[:packet.n], packet.off)
+ if err != nil {
+ // return the offset as the start + how much we wrote before the error.
+ errCh <- rwErr{packet.off + int64(n), err}
+ }
+ pool.Put(packet.b)
}
+ }()
+ }
+
+ // Wait for long tail, before closing results.
+ go func() {
+ wg.Wait()
+ close(errCh)
+ }()
+
+ // Reduce: Collect all the results into a relevant return: the earliest offset to return an error.
+ firstErr := rwErr{math.MaxInt64, nil}
+ for rwErr := range errCh {
+ if rwErr.off <= firstErr.off {
+ firstErr = rwErr
+ }
+
+ select {
+ case <-cancel:
default:
- firstErr = unimplementedPacketErr(res.typ)
+ // stop any more work from being distributed.
+ close(cancel)
}
}
- // If error is non-nil, then there may be gaps in the data written to
- // the file so it's best to return 0 so the caller can't make any
- // incorrect assumptions about the state of the file.
- if firstErr != nil {
- written = 0
+
+ if firstErr.err != nil {
+ // firstErr.err != nil if and only if firstErr.off is a valid offset.
+ //
+ // firstErr.off will then be the lesser of:
+ // * the offset of the first error from writing,
+ // * the last successfully read offset.
+ //
+ // This could be less than the last successfully written offset,
+ // which is the whole reason for the UseConcurrentWrites() ClientOption.
+ //
+ // Callers are responsible for truncating any SFTP files to a safe length.
+ f.offset = firstErr.off
+
+ // ReadFrom is defined to return the read bytes, regardless of any writer errors.
+ return read, firstErr.err
}
- f.offset += uint64(written)
- return written, firstErr
+
+ f.offset += read
+ return read, nil
}
// ReadFrom reads data from r until EOF and writes it to the file. The return
// value is the number of bytes read. Any error except io.EOF encountered
// during the read is also returned.
//
-// This method is preferred over calling Write multiple times to
-// maximise throughput for transferring the entire file (especially
-// over high latency links).
+// This method is preferred over calling Write multiple times
+// to maximise throughput for transferring the entire file,
+// especially over high-latency links.
func (f *File) ReadFrom(r io.Reader) (int64, error) {
- inFlight := 0
- desiredInFlight := 1
- offset := f.offset
- // see comment on same line in Read() above
- ch := make(chan result, f.c.maxConcurrentRequests+1)
- var firstErr error
- read := int64(0)
- b := make([]byte, f.c.maxPacket)
- for inFlight > 0 || firstErr == nil {
- for inFlight < desiredInFlight && firstErr == nil {
- n, err := r.Read(b)
- if err != nil {
- firstErr = err
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ if f.c.useConcurrentWrites {
+ var remain int64
+ switch r := r.(type) {
+ case interface{ Len() int }:
+ remain = int64(r.Len())
+
+ case interface{ Size() int64 }:
+ remain = r.Size()
+
+ case *io.LimitedReader:
+ remain = r.N
+
+ case interface{ Stat() (os.FileInfo, error) }:
+ info, err := r.Stat()
+ if err == nil {
+ remain = info.Size()
}
- f.c.dispatchRequest(ch, sshFxpWritePacket{
- ID: f.c.nextID(),
- Handle: f.handle,
- Offset: offset,
- Length: uint32(n),
- Data: b[:n],
- })
- inFlight++
- offset += uint64(n)
- read += int64(n)
}
- if inFlight == 0 {
- break
+ if remain < 0 {
+ // We can strongly assert that we want default max concurrency here.
+ return f.ReadFromWithConcurrency(r, f.c.maxConcurrentRequests)
+ }
+
+ if remain > int64(f.c.maxPacket) {
+ // Otherwise, only use concurrency, if it would be at least two packets.
+
+ // This is the best reasonable guess we can make.
+ concurrency64 := remain/int64(f.c.maxPacket) + 1
+
+ // We need to cap this value to an `int` size value to avoid overflow on 32-bit machines.
+ // So, we may as well pre-cap it to `f.c.maxConcurrentRequests`.
+ if concurrency64 > int64(f.c.maxConcurrentRequests) {
+ concurrency64 = int64(f.c.maxConcurrentRequests)
+ }
+
+ return f.ReadFromWithConcurrency(r, int(concurrency64))
}
- res := <-ch
- inFlight--
- if res.err != nil {
- firstErr = res.err
- continue
+ }
+
+ ch := make(chan result, 1) // reusable channel
+
+ b := make([]byte, f.c.maxPacket)
+
+ var read int64
+ for {
+ n, err := r.Read(b)
+ if n < 0 {
+ panic("sftp.File: reader returned negative count from Read")
}
- switch res.typ {
- case ssh_FXP_STATUS:
- id, _ := unmarshalUint32(res.data)
- err := normaliseError(unmarshalStatus(id, res.data))
- if err != nil && firstErr == nil {
- firstErr = err
- break
+
+ if n > 0 {
+ read += int64(n)
+
+ m, err2 := f.writeChunkAt(ch, b[:n], f.offset)
+ f.offset += int64(m)
+
+ if err == nil {
+ err = err2
}
- if desiredInFlight < f.c.maxConcurrentRequests {
- desiredInFlight++
+ }
+
+ if err != nil {
+ if err == io.EOF {
+ return read, nil // return nil explicitly.
}
- default:
- firstErr = unimplementedPacketErr(res.typ)
+
+ return read, err
}
}
- if firstErr == io.EOF {
- firstErr = nil
- }
- // If error is non-nil, then there may be gaps in the data written to
- // the file so it's best to return 0 so the caller can't make any
- // incorrect assumptions about the state of the file.
- if firstErr != nil {
- read = 0
- }
- f.offset += uint64(read)
- return read, firstErr
}
// Seek implements io.Seeker by setting the client offset for the next Read or
// Write. It returns the next offset read. Seeking before or after the end of
// the file is undefined. Seeking relative to the end calls Stat.
func (f *File) Seek(offset int64, whence int) (int64, error) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
switch whence {
case io.SeekStart:
- f.offset = uint64(offset)
case io.SeekCurrent:
- f.offset = uint64(int64(f.offset) + offset)
+ offset += f.offset
case io.SeekEnd:
fi, err := f.Stat()
if err != nil {
- return int64(f.offset), err
+ return f.offset, err
}
- f.offset = uint64(fi.Size() + offset)
+ offset += fi.Size()
default:
- return int64(f.offset), unimplementedSeekWhence(whence)
+ return f.offset, unimplementedSeekWhence(whence)
}
- return int64(f.offset), nil
+
+ if offset < 0 {
+ return f.offset, os.ErrInvalid
+ }
+
+ f.offset = offset
+ return f.offset, nil
}
// Chown changes the uid/gid of the current file.
@@ -1193,23 +1828,39 @@ func (f *File) Chown(uid, gid int) error {
}
// Chmod changes the permissions of the current file.
+//
+// See Client.Chmod for details.
func (f *File) Chmod(mode os.FileMode) error {
- return f.c.Chmod(f.path, mode)
+ return f.c.setfstat(f.handle, sshFileXferAttrPermissions, toChmodPerm(mode))
+}
+
+// Sync requests a flush of the contents of a File to stable storage.
+//
+// Sync requires the server to support the fsync@openssh.com extension.
+func (f *File) Sync() error {
+ id := f.c.nextID()
+ typ, data, err := f.c.sendPacket(nil, &sshFxpFsyncPacket{
+ ID: id,
+ Handle: f.handle,
+ })
+
+ switch {
+ case err != nil:
+ return err
+ case typ == sshFxpStatus:
+ return normaliseError(unmarshalStatus(id, data))
+ default:
+ return &unexpectedPacketErr{want: sshFxpStatus, got: typ}
+ }
}
// Truncate sets the size of the current file. Although it may be safely assumed
// that if the size is less than its current size it will be truncated to fit,
// the SFTP protocol does not specify what behavior the server should do when setting
// size greater than the current size.
+// We send a SSH_FXP_FSETSTAT here since we have a file handle
func (f *File) Truncate(size int64) error {
- return f.c.Truncate(f.path, size)
-}
-
-func min(a, b int) int {
- if a > b {
- return b
- }
- return a
+ return f.c.setfstat(f.handle, sshFileXferAttrSize, uint64(size))
}
// normaliseError normalises an error into a more standard form that can be
@@ -1218,11 +1869,13 @@ func normaliseError(err error) error {
switch err := err.(type) {
case *StatusError:
switch err.Code {
- case ssh_FX_EOF:
+ case sshFxEOF:
return io.EOF
- case ssh_FX_NO_SUCH_FILE:
+ case sshFxNoSuchFile:
return os.ErrNotExist
- case ssh_FX_OK:
+ case sshFxPermissionDenied:
+ return os.ErrPermission
+ case sshFxOk:
return nil
default:
return err
@@ -1232,52 +1885,52 @@ func normaliseError(err error) error {
}
}
-func unmarshalStatus(id uint32, data []byte) error {
- sid, data := unmarshalUint32(data)
- if sid != id {
- return &unexpectedIDErr{id, sid}
- }
- code, data := unmarshalUint32(data)
- msg, data, _ := unmarshalStringSafe(data)
- lang, _, _ := unmarshalStringSafe(data)
- return &StatusError{
- Code: code,
- msg: msg,
- lang: lang,
- }
-}
-
-func marshalStatus(b []byte, err StatusError) []byte {
- b = marshalUint32(b, err.Code)
- b = marshalString(b, err.msg)
- b = marshalString(b, err.lang)
- return b
-}
-
// flags converts the flags passed to OpenFile into ssh flags.
// Unsupported flags are ignored.
func flags(f int) uint32 {
var out uint32
switch f & os.O_WRONLY {
case os.O_WRONLY:
- out |= ssh_FXF_WRITE
+ out |= sshFxfWrite
case os.O_RDONLY:
- out |= ssh_FXF_READ
+ out |= sshFxfRead
}
if f&os.O_RDWR == os.O_RDWR {
- out |= ssh_FXF_READ | ssh_FXF_WRITE
+ out |= sshFxfRead | sshFxfWrite
}
if f&os.O_APPEND == os.O_APPEND {
- out |= ssh_FXF_APPEND
+ out |= sshFxfAppend
}
if f&os.O_CREATE == os.O_CREATE {
- out |= ssh_FXF_CREAT
+ out |= sshFxfCreat
}
if f&os.O_TRUNC == os.O_TRUNC {
- out |= ssh_FXF_TRUNC
+ out |= sshFxfTrunc
}
if f&os.O_EXCL == os.O_EXCL {
- out |= ssh_FXF_EXCL
+ out |= sshFxfExcl
}
return out
}
+
+// toChmodPerm converts Go permission bits to POSIX permission bits.
+//
+// This differs from fromFileMode in that we preserve the POSIX versions of
+// setuid, setgid and sticky in m, because we've historically supported those
+// bits, and we mask off any non-permission bits.
+func toChmodPerm(m os.FileMode) (perm uint32) {
+ const mask = os.ModePerm | s_ISUID | s_ISGID | s_ISVTX
+ perm = uint32(m & mask)
+
+ if m&os.ModeSetuid != 0 {
+ perm |= s_ISUID
+ }
+ if m&os.ModeSetgid != 0 {
+ perm |= s_ISGID
+ }
+ if m&os.ModeSticky != 0 {
+ perm |= s_ISVTX
+ }
+
+ return perm
+}
diff --git a/vendor/github.com/pkg/sftp/conn.go b/vendor/github.com/pkg/sftp/conn.go
index 62e585e8..7d951423 100644
--- a/vendor/github.com/pkg/sftp/conn.go
+++ b/vendor/github.com/pkg/sftp/conn.go
@@ -2,10 +2,9 @@ package sftp
import (
"encoding"
+ "fmt"
"io"
"sync"
-
- "github.com/pkg/errors"
)
// conn implements a bidirectional channel on which client and server
@@ -13,27 +12,34 @@ import (
type conn struct {
io.Reader
io.WriteCloser
+ // this is the same allocator used in packet manager
+ alloc *allocator
sync.Mutex // used to serialise writes to sendPacket
- // sendPacketTest is needed to replicate packet issues in testing
- sendPacketTest func(w io.Writer, m encoding.BinaryMarshaler) error
}
-func (c *conn) recvPacket() (uint8, []byte, error) {
- return recvPacket(c)
+// the orderID is used in server mode if the allocator is enabled.
+// For the client mode just pass 0
+func (c *conn) recvPacket(orderID uint32) (uint8, []byte, error) {
+ return recvPacket(c, c.alloc, orderID)
}
func (c *conn) sendPacket(m encoding.BinaryMarshaler) error {
c.Lock()
defer c.Unlock()
- if c.sendPacketTest != nil {
- return c.sendPacketTest(c, m)
- }
+
return sendPacket(c, m)
}
+func (c *conn) Close() error {
+ c.Lock()
+ defer c.Unlock()
+ return c.WriteCloser.Close()
+}
+
type clientConn struct {
conn
- wg sync.WaitGroup
+ wg sync.WaitGroup
+
sync.Mutex // protects inflight
inflight map[uint32]chan<- result // outstanding requests
@@ -66,31 +72,56 @@ func (c *clientConn) loop() {
// recv continuously reads from the server and forwards responses to the
// appropriate channel.
func (c *clientConn) recv() error {
- defer func() {
- c.conn.Lock()
- c.conn.Close()
- c.conn.Unlock()
- }()
+ defer c.conn.Close()
+
for {
- typ, data, err := c.recvPacket()
+ typ, data, err := c.recvPacket(0)
+ if err != nil {
+ return err
+ }
+ sid, _, err := unmarshalUint32Safe(data)
if err != nil {
return err
}
- sid, _ := unmarshalUint32(data)
- c.Lock()
- ch, ok := c.inflight[sid]
- delete(c.inflight, sid)
- c.Unlock()
+
+ ch, ok := c.getChannel(sid)
if !ok {
// This is an unexpected occurrence. Send the error
// back to all listeners so that they terminate
// gracefully.
- return errors.Errorf("sid: %v not fond", sid)
+ return fmt.Errorf("sid not found: %d", sid)
}
+
ch <- result{typ: typ, data: data}
}
}
+func (c *clientConn) putChannel(ch chan<- result, sid uint32) bool {
+ c.Lock()
+ defer c.Unlock()
+
+ select {
+ case <-c.closed:
+ // already closed with broadcastErr, return error on chan.
+ ch <- result{err: ErrSSHFxConnectionLost}
+ return false
+ default:
+ }
+
+ c.inflight[sid] = ch
+ return true
+}
+
+func (c *clientConn) getChannel(sid uint32) (chan<- result, bool) {
+ c.Lock()
+ defer c.Unlock()
+
+ ch, ok := c.inflight[sid]
+ delete(c.inflight, sid)
+
+ return ch, ok
+}
+
// result captures the result of receiving the a packet from the server
type result struct {
typ byte
@@ -103,36 +134,48 @@ type idmarshaler interface {
encoding.BinaryMarshaler
}
-func (c *clientConn) sendPacket(p idmarshaler) (byte, []byte, error) {
- ch := make(chan result, 2)
+func (c *clientConn) sendPacket(ch chan result, p idmarshaler) (byte, []byte, error) {
+ if cap(ch) < 1 {
+ ch = make(chan result, 1)
+ }
+
c.dispatchRequest(ch, p)
s := <-ch
return s.typ, s.data, s.err
}
+// dispatchRequest should ideally only be called by race-detection tests outside of this file,
+// where you have to ensure two packets are in flight sequentially after each other.
func (c *clientConn) dispatchRequest(ch chan<- result, p idmarshaler) {
- c.Lock()
- c.inflight[p.id()] = ch
- c.Unlock()
+ sid := p.id()
+
+ if !c.putChannel(ch, sid) {
+ // already closed.
+ return
+ }
+
if err := c.conn.sendPacket(p); err != nil {
- c.Lock()
- delete(c.inflight, p.id())
- c.Unlock()
- ch <- result{err: err}
+ if ch, ok := c.getChannel(sid); ok {
+ ch <- result{err: err}
+ }
}
}
// broadcastErr sends an error to all goroutines waiting for a response.
func (c *clientConn) broadcastErr(err error) {
c.Lock()
- listeners := make([]chan<- result, 0, len(c.inflight))
- for _, ch := range c.inflight {
- listeners = append(listeners, ch)
- }
- c.Unlock()
- for _, ch := range listeners {
- ch <- result{err: err}
+ defer c.Unlock()
+
+ bcastRes := result{err: ErrSSHFxConnectionLost}
+ for sid, ch := range c.inflight {
+ ch <- bcastRes
+
+ // Replace the chan in inflight,
+ // we have hijacked this chan,
+ // and this guarantees always-only-once sending.
+ c.inflight[sid] = make(chan<- result, 1)
}
+
c.err = err
close(c.closed)
}
@@ -141,6 +184,6 @@ type serverConn struct {
conn
}
-func (s *serverConn) sendError(p ider, err error) error {
- return s.sendPacket(statusFromError(p, err))
+func (s *serverConn) sendError(id uint32, err error) error {
+ return s.sendPacket(statusFromError(id, err))
}
diff --git a/vendor/github.com/pkg/sftp/fuzz.go b/vendor/github.com/pkg/sftp/fuzz.go
new file mode 100644
index 00000000..169aebc2
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/fuzz.go
@@ -0,0 +1,22 @@
+// +build gofuzz
+
+package sftp
+
+import "bytes"
+
+type sinkfuzz struct{}
+
+func (*sinkfuzz) Close() error { return nil }
+func (*sinkfuzz) Write(p []byte) (int, error) { return len(p), nil }
+
+var devnull = &sinkfuzz{}
+
+// To run: go-fuzz-build && go-fuzz
+func Fuzz(data []byte) int {
+ c, err := NewClientPipe(bytes.NewReader(data), devnull)
+ if err != nil {
+ return 0
+ }
+ c.Close()
+ return 1
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/attrs.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/attrs.go
new file mode 100644
index 00000000..1d4bb799
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/attrs.go
@@ -0,0 +1,326 @@
+package filexfer
+
+// Attributes related flags.
+const (
+ AttrSize = 1 << iota // SSH_FILEXFER_ATTR_SIZE
+ AttrUIDGID // SSH_FILEXFER_ATTR_UIDGID
+ AttrPermissions // SSH_FILEXFER_ATTR_PERMISSIONS
+ AttrACModTime // SSH_FILEXFER_ACMODTIME
+
+ AttrExtended = 1 << 31 // SSH_FILEXFER_ATTR_EXTENDED
+)
+
+// Attributes defines the file attributes type defined in draft-ietf-secsh-filexfer-02
+//
+// Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5
+type Attributes struct {
+ Flags uint32
+
+ // AttrSize
+ Size uint64
+
+ // AttrUIDGID
+ UID uint32
+ GID uint32
+
+ // AttrPermissions
+ Permissions FileMode
+
+ // AttrACmodTime
+ ATime uint32
+ MTime uint32
+
+ // AttrExtended
+ ExtendedAttributes []ExtendedAttribute
+}
+
+// GetSize returns the Size field and a bool that is true if and only if the value is valid/defined.
+func (a *Attributes) GetSize() (size uint64, ok bool) {
+ return a.Size, a.Flags&AttrSize != 0
+}
+
+// SetSize is a convenience function that sets the Size field,
+// and marks the field as valid/defined in Flags.
+func (a *Attributes) SetSize(size uint64) {
+ a.Flags |= AttrSize
+ a.Size = size
+}
+
+// GetUIDGID returns the UID and GID fields and a bool that is true if and only if the values are valid/defined.
+func (a *Attributes) GetUIDGID() (uid, gid uint32, ok bool) {
+ return a.UID, a.GID, a.Flags&AttrUIDGID != 0
+}
+
+// SetUIDGID is a convenience function that sets the UID and GID fields,
+// and marks the fields as valid/defined in Flags.
+func (a *Attributes) SetUIDGID(uid, gid uint32) {
+ a.Flags |= AttrUIDGID
+ a.UID = uid
+ a.GID = gid
+}
+
+// GetPermissions returns the Permissions field and a bool that is true if and only if the value is valid/defined.
+func (a *Attributes) GetPermissions() (perms FileMode, ok bool) {
+ return a.Permissions, a.Flags&AttrPermissions != 0
+}
+
+// SetPermissions is a convenience function that sets the Permissions field,
+// and marks the field as valid/defined in Flags.
+func (a *Attributes) SetPermissions(perms FileMode) {
+ a.Flags |= AttrPermissions
+ a.Permissions = perms
+}
+
+// GetACModTime returns the ATime and MTime fields and a bool that is true if and only if the values are valid/defined.
+func (a *Attributes) GetACModTime() (atime, mtime uint32, ok bool) {
+ return a.ATime, a.MTime, a.Flags&AttrACModTime != 0
+ return a.ATime, a.MTime, a.Flags&AttrACModTime != 0
+}
+
+// SetACModTime is a convenience function that sets the ATime and MTime fields,
+// and marks the fields as valid/defined in Flags.
+func (a *Attributes) SetACModTime(atime, mtime uint32) {
+ a.Flags |= AttrACModTime
+ a.ATime = atime
+ a.MTime = mtime
+}
+
+// Len returns the number of bytes a would marshal into.
+func (a *Attributes) Len() int {
+ length := 4
+
+ if a.Flags&AttrSize != 0 {
+ length += 8
+ }
+
+ if a.Flags&AttrUIDGID != 0 {
+ length += 4 + 4
+ }
+
+ if a.Flags&AttrPermissions != 0 {
+ length += 4
+ }
+
+ if a.Flags&AttrACModTime != 0 {
+ length += 4 + 4
+ }
+
+ if a.Flags&AttrExtended != 0 {
+ length += 4
+
+ for _, ext := range a.ExtendedAttributes {
+ length += ext.Len()
+ }
+ }
+
+ return length
+}
+
+// MarshalInto marshals e onto the end of the given Buffer.
+func (a *Attributes) MarshalInto(b *Buffer) {
+ b.AppendUint32(a.Flags)
+
+ if a.Flags&AttrSize != 0 {
+ b.AppendUint64(a.Size)
+ }
+
+ if a.Flags&AttrUIDGID != 0 {
+ b.AppendUint32(a.UID)
+ b.AppendUint32(a.GID)
+ }
+
+ if a.Flags&AttrPermissions != 0 {
+ b.AppendUint32(uint32(a.Permissions))
+ }
+
+ if a.Flags&AttrACModTime != 0 {
+ b.AppendUint32(a.ATime)
+ b.AppendUint32(a.MTime)
+ }
+
+ if a.Flags&AttrExtended != 0 {
+ b.AppendUint32(uint32(len(a.ExtendedAttributes)))
+
+ for _, ext := range a.ExtendedAttributes {
+ ext.MarshalInto(b)
+ }
+ }
+}
+
+// MarshalBinary returns a as the binary encoding of a.
+func (a *Attributes) MarshalBinary() ([]byte, error) {
+ buf := NewBuffer(make([]byte, 0, a.Len()))
+ a.MarshalInto(buf)
+ return buf.Bytes(), nil
+}
+
+// UnmarshalFrom unmarshals an Attributes from the given Buffer into e.
+//
+// NOTE: The values of fields not covered in the a.Flags are explicitly undefined.
+func (a *Attributes) UnmarshalFrom(b *Buffer) (err error) {
+ flags, err := b.ConsumeUint32()
+ if err != nil {
+ return err
+ }
+
+ return a.XXX_UnmarshalByFlags(flags, b)
+}
+
+// XXX_UnmarshalByFlags uses the pre-existing a.Flags field to determine which fields to decode.
+// DO NOT USE THIS: it is an anti-corruption function to implement existing internal usage in pkg/sftp.
+// This function is not a part of any compatibility promise.
+func (a *Attributes) XXX_UnmarshalByFlags(flags uint32, b *Buffer) (err error) {
+ a.Flags = flags
+
+ // Short-circuit dummy attributes.
+ if a.Flags == 0 {
+ return nil
+ }
+
+ if a.Flags&AttrSize != 0 {
+ if a.Size, err = b.ConsumeUint64(); err != nil {
+ return err
+ }
+ }
+
+ if a.Flags&AttrUIDGID != 0 {
+ if a.UID, err = b.ConsumeUint32(); err != nil {
+ return err
+ }
+
+ if a.GID, err = b.ConsumeUint32(); err != nil {
+ return err
+ }
+ }
+
+ if a.Flags&AttrPermissions != 0 {
+ m, err := b.ConsumeUint32()
+ if err != nil {
+ return err
+ }
+
+ a.Permissions = FileMode(m)
+ }
+
+ if a.Flags&AttrACModTime != 0 {
+ if a.ATime, err = b.ConsumeUint32(); err != nil {
+ return err
+ }
+
+ if a.MTime, err = b.ConsumeUint32(); err != nil {
+ return err
+ }
+ }
+
+ if a.Flags&AttrExtended != 0 {
+ count, err := b.ConsumeUint32()
+ if err != nil {
+ return err
+ }
+
+ a.ExtendedAttributes = make([]ExtendedAttribute, count)
+ for i := range a.ExtendedAttributes {
+ a.ExtendedAttributes[i].UnmarshalFrom(b)
+ }
+ }
+
+ return nil
+}
+
+// UnmarshalBinary decodes the binary encoding of Attributes into e.
+func (a *Attributes) UnmarshalBinary(data []byte) error {
+ return a.UnmarshalFrom(NewBuffer(data))
+}
+
+// ExtendedAttribute defines the extended file attribute type defined in draft-ietf-secsh-filexfer-02
+//
+// Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5
+type ExtendedAttribute struct {
+ Type string
+ Data string
+}
+
+// Len returns the number of bytes e would marshal into.
+func (e *ExtendedAttribute) Len() int {
+ return 4 + len(e.Type) + 4 + len(e.Data)
+}
+
+// MarshalInto marshals e onto the end of the given Buffer.
+func (e *ExtendedAttribute) MarshalInto(b *Buffer) {
+ b.AppendString(e.Type)
+ b.AppendString(e.Data)
+}
+
+// MarshalBinary returns e as the binary encoding of e.
+func (e *ExtendedAttribute) MarshalBinary() ([]byte, error) {
+ buf := NewBuffer(make([]byte, 0, e.Len()))
+ e.MarshalInto(buf)
+ return buf.Bytes(), nil
+}
+
+// UnmarshalFrom unmarshals an ExtendedAattribute from the given Buffer into e.
+func (e *ExtendedAttribute) UnmarshalFrom(b *Buffer) (err error) {
+ if e.Type, err = b.ConsumeString(); err != nil {
+ return err
+ }
+
+ if e.Data, err = b.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// UnmarshalBinary decodes the binary encoding of ExtendedAttribute into e.
+func (e *ExtendedAttribute) UnmarshalBinary(data []byte) error {
+ return e.UnmarshalFrom(NewBuffer(data))
+}
+
+// NameEntry implements the SSH_FXP_NAME repeated data type from draft-ietf-secsh-filexfer-02
+//
+// This type is incompatible with versions 4 or higher.
+type NameEntry struct {
+ Filename string
+ Longname string
+ Attrs Attributes
+}
+
+// Len returns the number of bytes e would marshal into.
+func (e *NameEntry) Len() int {
+ return 4 + len(e.Filename) + 4 + len(e.Longname) + e.Attrs.Len()
+}
+
+// MarshalInto marshals e onto the end of the given Buffer.
+func (e *NameEntry) MarshalInto(b *Buffer) {
+ b.AppendString(e.Filename)
+ b.AppendString(e.Longname)
+
+ e.Attrs.MarshalInto(b)
+}
+
+// MarshalBinary returns e as the binary encoding of e.
+func (e *NameEntry) MarshalBinary() ([]byte, error) {
+ buf := NewBuffer(make([]byte, 0, e.Len()))
+ e.MarshalInto(buf)
+ return buf.Bytes(), nil
+}
+
+// UnmarshalFrom unmarshals an NameEntry from the given Buffer into e.
+//
+// NOTE: The values of fields not covered in the a.Flags are explicitly undefined.
+func (e *NameEntry) UnmarshalFrom(b *Buffer) (err error) {
+ if e.Filename, err = b.ConsumeString(); err != nil {
+ return err
+ }
+
+ if e.Longname, err = b.ConsumeString(); err != nil {
+ return err
+ }
+
+ return e.Attrs.UnmarshalFrom(b)
+}
+
+// UnmarshalBinary decodes the binary encoding of NameEntry into e.
+func (e *NameEntry) UnmarshalBinary(data []byte) error {
+ return e.UnmarshalFrom(NewBuffer(data))
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/buffer.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/buffer.go
new file mode 100644
index 00000000..a6086036
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/buffer.go
@@ -0,0 +1,293 @@
+package filexfer
+
+import (
+ "encoding/binary"
+ "errors"
+)
+
+// Various encoding errors.
+var (
+ ErrShortPacket = errors.New("packet too short")
+ ErrLongPacket = errors.New("packet too long")
+)
+
+// Buffer wraps up the various encoding details of the SSH format.
+//
+// Data types are encoded as per section 4 from https://tools.ietf.org/html/draft-ietf-secsh-architecture-09#page-8
+type Buffer struct {
+ b []byte
+ off int
+}
+
+// NewBuffer creates and initializes a new buffer using buf as its initial contents.
+// The new buffer takes ownership of buf, and the caller should not use buf after this call.
+//
+// In most cases, new(Buffer) (or just declaring a Buffer variable) is sufficient to initialize a Buffer.
+func NewBuffer(buf []byte) *Buffer {
+ return &Buffer{
+ b: buf,
+ }
+}
+
+// NewMarshalBuffer creates a new Buffer ready to start marshaling a Packet into.
+// It preallocates enough space for uint32(length), uint8(type), uint32(request-id) and size more bytes.
+func NewMarshalBuffer(size int) *Buffer {
+ return NewBuffer(make([]byte, 4+1+4+size))
+}
+
+// Bytes returns a slice of length b.Len() holding the unconsumed bytes in the Buffer.
+// The slice is valid for use only until the next buffer modification
+// (that is, only until the next call to an Append or Consume method).
+func (b *Buffer) Bytes() []byte {
+ return b.b[b.off:]
+}
+
+// Len returns the number of unconsumed bytes in the buffer.
+func (b *Buffer) Len() int { return len(b.b) - b.off }
+
+// Cap returns the capacity of the buffer’s underlying byte slice,
+// that is, the total space allocated for the buffer’s data.
+func (b *Buffer) Cap() int { return cap(b.b) }
+
+// Reset resets the buffer to be empty, but it retains the underlying storage for use by future Appends.
+func (b *Buffer) Reset() {
+ b.b = b.b[:0]
+ b.off = 0
+}
+
+// StartPacket resets and initializes the buffer to be ready to start marshaling a packet into.
+// It truncates the buffer, reserves space for uint32(length), then appends the given packetType and requestID.
+func (b *Buffer) StartPacket(packetType PacketType, requestID uint32) {
+ b.b, b.off = append(b.b[:0], make([]byte, 4)...), 0
+
+ b.AppendUint8(uint8(packetType))
+ b.AppendUint32(requestID)
+}
+
+// Packet finalizes the packet started from StartPacket.
+// It is expected that this will end the ownership of the underlying byte-slice,
+// and so the returned byte-slices may be reused the same as any other byte-slice,
+// the caller should not use this buffer after this call.
+//
+// It writes the packet body length into the first four bytes of the buffer in network byte order (big endian).
+// The packet body length is the length of this buffer less the 4-byte length itself, plus the length of payload.
+//
+// It is assumed that no Consume methods have been called on this buffer,
+// and so it returns the whole underlying slice.
+func (b *Buffer) Packet(payload []byte) (header, payloadPassThru []byte, err error) {
+ b.PutLength(len(b.b) - 4 + len(payload))
+
+ return b.b, payload, nil
+}
+
+// ConsumeUint8 consumes a single byte from the buffer.
+// If the buffer does not have enough data, it will return ErrShortPacket.
+func (b *Buffer) ConsumeUint8() (uint8, error) {
+ if b.Len() < 1 {
+ return 0, ErrShortPacket
+ }
+
+ var v uint8
+ v, b.off = b.b[b.off], b.off+1
+ return v, nil
+}
+
+// AppendUint8 appends a single byte into the buffer.
+func (b *Buffer) AppendUint8(v uint8) {
+ b.b = append(b.b, v)
+}
+
+// ConsumeBool consumes a single byte from the buffer, and returns true if that byte is non-zero.
+// If the buffer does not have enough data, it will return ErrShortPacket.
+func (b *Buffer) ConsumeBool() (bool, error) {
+ v, err := b.ConsumeUint8()
+ if err != nil {
+ return false, err
+ }
+
+ return v != 0, nil
+}
+
+// AppendBool appends a single bool into the buffer.
+// It encodes it as a single byte, with false as 0, and true as 1.
+func (b *Buffer) AppendBool(v bool) {
+ if v {
+ b.AppendUint8(1)
+ } else {
+ b.AppendUint8(0)
+ }
+}
+
+// ConsumeUint16 consumes a single uint16 from the buffer, in network byte order (big-endian).
+// If the buffer does not have enough data, it will return ErrShortPacket.
+func (b *Buffer) ConsumeUint16() (uint16, error) {
+ if b.Len() < 2 {
+ return 0, ErrShortPacket
+ }
+
+ v := binary.BigEndian.Uint16(b.b[b.off:])
+ b.off += 2
+ return v, nil
+}
+
+// AppendUint16 appends single uint16 into the buffer, in network byte order (big-endian).
+func (b *Buffer) AppendUint16(v uint16) {
+ b.b = append(b.b,
+ byte(v>>8),
+ byte(v>>0),
+ )
+}
+
+// unmarshalUint32 is used internally to read the packet length.
+// It is unsafe, and so not exported.
+// Even within this package, its use should be avoided.
+func unmarshalUint32(b []byte) uint32 {
+ return binary.BigEndian.Uint32(b[:4])
+}
+
+// ConsumeUint32 consumes a single uint32 from the buffer, in network byte order (big-endian).
+// If the buffer does not have enough data, it will return ErrShortPacket.
+func (b *Buffer) ConsumeUint32() (uint32, error) {
+ if b.Len() < 4 {
+ return 0, ErrShortPacket
+ }
+
+ v := binary.BigEndian.Uint32(b.b[b.off:])
+ b.off += 4
+ return v, nil
+}
+
+// AppendUint32 appends a single uint32 into the buffer, in network byte order (big-endian).
+func (b *Buffer) AppendUint32(v uint32) {
+ b.b = append(b.b,
+ byte(v>>24),
+ byte(v>>16),
+ byte(v>>8),
+ byte(v>>0),
+ )
+}
+
+// ConsumeUint64 consumes a single uint64 from the buffer, in network byte order (big-endian).
+// If the buffer does not have enough data, it will return ErrShortPacket.
+func (b *Buffer) ConsumeUint64() (uint64, error) {
+ if b.Len() < 8 {
+ return 0, ErrShortPacket
+ }
+
+ v := binary.BigEndian.Uint64(b.b[b.off:])
+ b.off += 8
+ return v, nil
+}
+
+// AppendUint64 appends a single uint64 into the buffer, in network byte order (big-endian).
+func (b *Buffer) AppendUint64(v uint64) {
+ b.b = append(b.b,
+ byte(v>>56),
+ byte(v>>48),
+ byte(v>>40),
+ byte(v>>32),
+ byte(v>>24),
+ byte(v>>16),
+ byte(v>>8),
+ byte(v>>0),
+ )
+}
+
+// ConsumeInt64 consumes a single int64 from the buffer, in network byte order (big-endian) with two’s complement.
+// If the buffer does not have enough data, it will return ErrShortPacket.
+func (b *Buffer) ConsumeInt64() (int64, error) {
+ u, err := b.ConsumeUint64()
+ if err != nil {
+ return 0, err
+ }
+
+ return int64(u), err
+}
+
+// AppendInt64 appends a single int64 into the buffer, in network byte order (big-endian) with two’s complement.
+func (b *Buffer) AppendInt64(v int64) {
+ b.AppendUint64(uint64(v))
+}
+
+// ConsumeByteSlice consumes a single string of raw binary data from the buffer.
+// A string is a uint32 length, followed by that number of raw bytes.
+// If the buffer does not have enough data, or defines a length larger than available, it will return ErrShortPacket.
+//
+// The returned slice aliases the buffer contents, and is valid only as long as the buffer is not reused
+// (that is, only until the next call to Reset, PutLength, StartPacket, or UnmarshalBinary).
+//
+// In no case will any Consume calls return overlapping slice aliases,
+// and Append calls are guaranteed to not disturb this slice alias.
+func (b *Buffer) ConsumeByteSlice() ([]byte, error) {
+ length, err := b.ConsumeUint32()
+ if err != nil {
+ return nil, err
+ }
+
+ if b.Len() < int(length) {
+ return nil, ErrShortPacket
+ }
+
+ v := b.b[b.off:]
+ if len(v) > int(length) {
+ v = v[:length:length]
+ }
+ b.off += int(length)
+ return v, nil
+}
+
+// AppendByteSlice appends a single string of raw binary data into the buffer.
+// A string is a uint32 length, followed by that number of raw bytes.
+func (b *Buffer) AppendByteSlice(v []byte) {
+ b.AppendUint32(uint32(len(v)))
+ b.b = append(b.b, v...)
+}
+
+// ConsumeString consumes a single string of binary data from the buffer.
+// A string is a uint32 length, followed by that number of raw bytes.
+// If the buffer does not have enough data, or defines a length larger than available, it will return ErrShortPacket.
+//
+// NOTE: Go implicitly assumes that strings contain UTF-8 encoded data.
+// All caveats on using arbitrary binary data in Go strings applies.
+func (b *Buffer) ConsumeString() (string, error) {
+ v, err := b.ConsumeByteSlice()
+ if err != nil {
+ return "", err
+ }
+
+ return string(v), nil
+}
+
+// AppendString appends a single string of binary data into the buffer.
+// A string is a uint32 length, followed by that number of raw bytes.
+func (b *Buffer) AppendString(v string) {
+ b.AppendByteSlice([]byte(v))
+}
+
+// PutLength writes the given size into the first four bytes of the buffer in network byte order (big endian).
+func (b *Buffer) PutLength(size int) {
+ if len(b.b) < 4 {
+ b.b = append(b.b, make([]byte, 4-len(b.b))...)
+ }
+
+ binary.BigEndian.PutUint32(b.b, uint32(size))
+}
+
+// MarshalBinary returns a clone of the full internal buffer.
+func (b *Buffer) MarshalBinary() ([]byte, error) {
+ clone := make([]byte, len(b.b))
+ n := copy(clone, b.b)
+ return clone[:n], nil
+}
+
+// UnmarshalBinary sets the internal buffer of b to be a clone of data, and zeros the internal offset.
+func (b *Buffer) UnmarshalBinary(data []byte) error {
+ if grow := len(data) - len(b.b); grow > 0 {
+ b.b = append(b.b, make([]byte, grow)...)
+ }
+
+ n := copy(b.b, data)
+ b.b = b.b[:n]
+ b.off = 0
+ return nil
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extended_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extended_packets.go
new file mode 100644
index 00000000..6b7b2cef
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extended_packets.go
@@ -0,0 +1,142 @@
+package filexfer
+
+import (
+ "encoding"
+ "sync"
+)
+
+// ExtendedData aliases the untyped interface composition of encoding.BinaryMarshaler and encoding.BinaryUnmarshaler.
+type ExtendedData = interface {
+ encoding.BinaryMarshaler
+ encoding.BinaryUnmarshaler
+}
+
+// ExtendedDataConstructor defines a function that returns a new(ArbitraryExtendedPacket).
+type ExtendedDataConstructor func() ExtendedData
+
+var extendedPacketTypes = struct {
+ mu sync.RWMutex
+ constructors map[string]ExtendedDataConstructor
+}{
+ constructors: make(map[string]ExtendedDataConstructor),
+}
+
+// RegisterExtendedPacketType defines a specific ExtendedDataConstructor for the given extension string.
+func RegisterExtendedPacketType(extension string, constructor ExtendedDataConstructor) {
+ extendedPacketTypes.mu.Lock()
+ defer extendedPacketTypes.mu.Unlock()
+
+ if _, exist := extendedPacketTypes.constructors[extension]; exist {
+ panic("encoding/ssh/filexfer: multiple registration of extended packet type " + extension)
+ }
+
+ extendedPacketTypes.constructors[extension] = constructor
+}
+
+func newExtendedPacket(extension string) ExtendedData {
+ extendedPacketTypes.mu.RLock()
+ defer extendedPacketTypes.mu.RUnlock()
+
+ if f := extendedPacketTypes.constructors[extension]; f != nil {
+ return f()
+ }
+
+ return new(Buffer)
+}
+
+// ExtendedPacket defines the SSH_FXP_CLOSE packet.
+type ExtendedPacket struct {
+ ExtendedRequest string
+
+ Data ExtendedData
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *ExtendedPacket) Type() PacketType {
+ return PacketTypeExtended
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+//
+// The Data is marshaled into binary, and returned as the payload.
+func (p *ExtendedPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.ExtendedRequest) // string(extended-request)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeExtended, reqid)
+ buf.AppendString(p.ExtendedRequest)
+
+ if p.Data != nil {
+ payload, err = p.Data.MarshalBinary()
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+//
+// If p.Data is nil, and the extension has been registered, a new type will be made from the registration.
+// If the extension has not been registered, then a new Buffer will be allocated.
+// Then the request-specific-data will be unmarshaled from the rest of the buffer.
+func (p *ExtendedPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.ExtendedRequest, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ if p.Data == nil {
+ p.Data = newExtendedPacket(p.ExtendedRequest)
+ }
+
+ return p.Data.UnmarshalBinary(buf.Bytes())
+}
+
+// ExtendedReplyPacket defines the SSH_FXP_CLOSE packet.
+type ExtendedReplyPacket struct {
+ Data ExtendedData
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *ExtendedReplyPacket) Type() PacketType {
+ return PacketTypeExtendedReply
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+//
+// The Data is marshaled into binary, and returned as the payload.
+func (p *ExtendedReplyPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ buf = NewMarshalBuffer(0)
+ }
+
+ buf.StartPacket(PacketTypeExtendedReply, reqid)
+
+ if p.Data != nil {
+ payload, err = p.Data.MarshalBinary()
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+//
+// If p.Data is nil, and there is request-specific-data,
+// then the request-specific-data will be wrapped in a Buffer and assigned to p.Data.
+func (p *ExtendedReplyPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Data == nil {
+ p.Data = new(Buffer)
+ }
+
+ return p.Data.UnmarshalBinary(buf.Bytes())
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extensions.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extensions.go
new file mode 100644
index 00000000..11c0b99c
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/extensions.go
@@ -0,0 +1,46 @@
+package filexfer
+
+// ExtensionPair defines the extension-pair type defined in draft-ietf-secsh-filexfer-13.
+// This type is backwards-compatible with how draft-ietf-secsh-filexfer-02 defines extensions.
+//
+// Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-4.2
+type ExtensionPair struct {
+ Name string
+ Data string
+}
+
+// Len returns the number of bytes e would marshal into.
+func (e *ExtensionPair) Len() int {
+ return 4 + len(e.Name) + 4 + len(e.Data)
+}
+
+// MarshalInto marshals e onto the end of the given Buffer.
+func (e *ExtensionPair) MarshalInto(buf *Buffer) {
+ buf.AppendString(e.Name)
+ buf.AppendString(e.Data)
+}
+
+// MarshalBinary returns e as the binary encoding of e.
+func (e *ExtensionPair) MarshalBinary() ([]byte, error) {
+ buf := NewBuffer(make([]byte, 0, e.Len()))
+ e.MarshalInto(buf)
+ return buf.Bytes(), nil
+}
+
+// UnmarshalFrom unmarshals an ExtensionPair from the given Buffer into e.
+func (e *ExtensionPair) UnmarshalFrom(buf *Buffer) (err error) {
+ if e.Name, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ if e.Data, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// UnmarshalBinary decodes the binary encoding of ExtensionPair into e.
+func (e *ExtensionPair) UnmarshalBinary(data []byte) error {
+ return e.UnmarshalFrom(NewBuffer(data))
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/filexfer.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/filexfer.go
new file mode 100644
index 00000000..1e5abf74
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/filexfer.go
@@ -0,0 +1,54 @@
+// Package filexfer implements the wire encoding for secsh-filexfer as described in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
+package filexfer
+
+// PacketMarshaller narrowly defines packets that will only be transmitted.
+//
+// ExtendedPacket types will often only implement this interface,
+// since decoding the whole packet body of an ExtendedPacket can only be done dependent on the ExtendedRequest field.
+type PacketMarshaller interface {
+ // MarshalPacket is the primary intended way to encode a packet.
+ // The request-id for the packet is set from reqid.
+ //
+ // An optional buffer may be given in b.
+ // If the buffer has a minimum capacity, it shall be truncated and used to marshal the header into.
+ // The minimum capacity for the packet must be a constant expression, and should be at least 9.
+ //
+ // It shall return the main body of the encoded packet in header,
+ // and may optionally return an additional payload to be written immediately after the header.
+ //
+ // It shall encode in the first 4-bytes of the header the proper length of the rest of the header+payload.
+ MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error)
+}
+
+// Packet defines the behavior of a full generic SFTP packet.
+//
+// InitPacket, and VersionPacket are not generic SFTP packets, and instead implement (Un)MarshalBinary.
+//
+// ExtendedPacket types should not iplement this interface,
+// since decoding the whole packet body of an ExtendedPacket can only be done dependent on the ExtendedRequest field.
+type Packet interface {
+ PacketMarshaller
+
+ // Type returns the SSH_FXP_xy value associated with the specific packet.
+ Type() PacketType
+
+ // UnmarshalPacketBody decodes a packet body from the given Buffer.
+ // It is assumed that the common header values of the length, type and request-id have already been consumed.
+ //
+ // Implementations should not alias the given Buffer,
+ // instead they can consider prepopulating an internal buffer as a hint,
+ // and copying into that buffer if it has sufficient length.
+ UnmarshalPacketBody(buf *Buffer) error
+}
+
+// ComposePacket converts returns from MarshalPacket into an equivalent call to MarshalBinary.
+func ComposePacket(header, payload []byte, err error) ([]byte, error) {
+ return append(header, payload...), err
+}
+
+// Default length values,
+// Defined in draft-ietf-secsh-filexfer-02 section 3.
+const (
+ DefaultMaxPacketLength = 34000
+ DefaultMaxDataLength = 32768
+)
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fx.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fx.go
new file mode 100644
index 00000000..48f86986
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fx.go
@@ -0,0 +1,147 @@
+package filexfer
+
+import (
+ "fmt"
+)
+
+// Status defines the SFTP error codes used in SSH_FXP_STATUS response packets.
+type Status uint32
+
+// Defines the various SSH_FX_* values.
+const (
+ // see draft-ietf-secsh-filexfer-02
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7
+ StatusOK = Status(iota)
+ StatusEOF
+ StatusNoSuchFile
+ StatusPermissionDenied
+ StatusFailure
+ StatusBadMessage
+ StatusNoConnection
+ StatusConnectionLost
+ StatusOPUnsupported
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-03#section-7
+ StatusV4InvalidHandle
+ StatusV4NoSuchPath
+ StatusV4FileAlreadyExists
+ StatusV4WriteProtect
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-7
+ StatusV4NoMedia
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-7
+ StatusV5NoSpaceOnFilesystem
+ StatusV5QuotaExceeded
+ StatusV5UnknownPrincipal
+ StatusV5LockConflict
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-06#section-8
+ StatusV6DirNotEmpty
+ StatusV6NotADirectory
+ StatusV6InvalidFilename
+ StatusV6LinkLoop
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-07#section-8
+ StatusV6CannotDelete
+ StatusV6InvalidParameter
+ StatusV6FileIsADirectory
+ StatusV6ByteRangeLockConflict
+ StatusV6ByteRangeLockRefused
+ StatusV6DeletePending
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-08#section-8.1
+ StatusV6FileCorrupt
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-10#section-9.1
+ StatusV6OwnerInvalid
+ StatusV6GroupInvalid
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1
+ StatusV6NoMatchingByteRangeLock
+)
+
+func (s Status) Error() string {
+ return s.String()
+}
+
+// Is returns true if the target is the same Status code,
+// or target is a StatusPacket with the same Status code.
+func (s Status) Is(target error) bool {
+ if target, ok := target.(*StatusPacket); ok {
+ return target.StatusCode == s
+ }
+
+ return s == target
+}
+
+func (s Status) String() string {
+ switch s {
+ case StatusOK:
+ return "SSH_FX_OK"
+ case StatusEOF:
+ return "SSH_FX_EOF"
+ case StatusNoSuchFile:
+ return "SSH_FX_NO_SUCH_FILE"
+ case StatusPermissionDenied:
+ return "SSH_FX_PERMISSION_DENIED"
+ case StatusFailure:
+ return "SSH_FX_FAILURE"
+ case StatusBadMessage:
+ return "SSH_FX_BAD_MESSAGE"
+ case StatusNoConnection:
+ return "SSH_FX_NO_CONNECTION"
+ case StatusConnectionLost:
+ return "SSH_FX_CONNECTION_LOST"
+ case StatusOPUnsupported:
+ return "SSH_FX_OP_UNSUPPORTED"
+ case StatusV4InvalidHandle:
+ return "SSH_FX_INVALID_HANDLE"
+ case StatusV4NoSuchPath:
+ return "SSH_FX_NO_SUCH_PATH"
+ case StatusV4FileAlreadyExists:
+ return "SSH_FX_FILE_ALREADY_EXISTS"
+ case StatusV4WriteProtect:
+ return "SSH_FX_WRITE_PROTECT"
+ case StatusV4NoMedia:
+ return "SSH_FX_NO_MEDIA"
+ case StatusV5NoSpaceOnFilesystem:
+ return "SSH_FX_NO_SPACE_ON_FILESYSTEM"
+ case StatusV5QuotaExceeded:
+ return "SSH_FX_QUOTA_EXCEEDED"
+ case StatusV5UnknownPrincipal:
+ return "SSH_FX_UNKNOWN_PRINCIPAL"
+ case StatusV5LockConflict:
+ return "SSH_FX_LOCK_CONFLICT"
+ case StatusV6DirNotEmpty:
+ return "SSH_FX_DIR_NOT_EMPTY"
+ case StatusV6NotADirectory:
+ return "SSH_FX_NOT_A_DIRECTORY"
+ case StatusV6InvalidFilename:
+ return "SSH_FX_INVALID_FILENAME"
+ case StatusV6LinkLoop:
+ return "SSH_FX_LINK_LOOP"
+ case StatusV6CannotDelete:
+ return "SSH_FX_CANNOT_DELETE"
+ case StatusV6InvalidParameter:
+ return "SSH_FX_INVALID_PARAMETER"
+ case StatusV6FileIsADirectory:
+ return "SSH_FX_FILE_IS_A_DIRECTORY"
+ case StatusV6ByteRangeLockConflict:
+ return "SSH_FX_BYTE_RANGE_LOCK_CONFLICT"
+ case StatusV6ByteRangeLockRefused:
+ return "SSH_FX_BYTE_RANGE_LOCK_REFUSED"
+ case StatusV6DeletePending:
+ return "SSH_FX_DELETE_PENDING"
+ case StatusV6FileCorrupt:
+ return "SSH_FX_FILE_CORRUPT"
+ case StatusV6OwnerInvalid:
+ return "SSH_FX_OWNER_INVALID"
+ case StatusV6GroupInvalid:
+ return "SSH_FX_GROUP_INVALID"
+ case StatusV6NoMatchingByteRangeLock:
+ return "SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK"
+ default:
+ return fmt.Sprintf("SSH_FX_UNKNOWN(%d)", s)
+ }
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fxp.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fxp.go
new file mode 100644
index 00000000..15caf6d2
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/fxp.go
@@ -0,0 +1,124 @@
+package filexfer
+
+import (
+ "fmt"
+)
+
+// PacketType defines the various SFTP packet types.
+type PacketType uint8
+
+// Request packet types.
+const (
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
+ PacketTypeInit = PacketType(iota + 1)
+ PacketTypeVersion
+ PacketTypeOpen
+ PacketTypeClose
+ PacketTypeRead
+ PacketTypeWrite
+ PacketTypeLStat
+ PacketTypeFStat
+ PacketTypeSetstat
+ PacketTypeFSetstat
+ PacketTypeOpenDir
+ PacketTypeReadDir
+ PacketTypeRemove
+ PacketTypeMkdir
+ PacketTypeRmdir
+ PacketTypeRealPath
+ PacketTypeStat
+ PacketTypeRename
+ PacketTypeReadLink
+ PacketTypeSymlink
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-07#section-3.3
+ PacketTypeV6Link
+
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-08#section-3.3
+ PacketTypeV6Block
+ PacketTypeV6Unblock
+)
+
+// Response packet types.
+const (
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
+ PacketTypeStatus = PacketType(iota + 101)
+ PacketTypeHandle
+ PacketTypeData
+ PacketTypeName
+ PacketTypeAttrs
+)
+
+// Extended packet types.
+const (
+ // https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
+ PacketTypeExtended = PacketType(iota + 200)
+ PacketTypeExtendedReply
+)
+
+func (f PacketType) String() string {
+ switch f {
+ case PacketTypeInit:
+ return "SSH_FXP_INIT"
+ case PacketTypeVersion:
+ return "SSH_FXP_VERSION"
+ case PacketTypeOpen:
+ return "SSH_FXP_OPEN"
+ case PacketTypeClose:
+ return "SSH_FXP_CLOSE"
+ case PacketTypeRead:
+ return "SSH_FXP_READ"
+ case PacketTypeWrite:
+ return "SSH_FXP_WRITE"
+ case PacketTypeLStat:
+ return "SSH_FXP_LSTAT"
+ case PacketTypeFStat:
+ return "SSH_FXP_FSTAT"
+ case PacketTypeSetstat:
+ return "SSH_FXP_SETSTAT"
+ case PacketTypeFSetstat:
+ return "SSH_FXP_FSETSTAT"
+ case PacketTypeOpenDir:
+ return "SSH_FXP_OPENDIR"
+ case PacketTypeReadDir:
+ return "SSH_FXP_READDIR"
+ case PacketTypeRemove:
+ return "SSH_FXP_REMOVE"
+ case PacketTypeMkdir:
+ return "SSH_FXP_MKDIR"
+ case PacketTypeRmdir:
+ return "SSH_FXP_RMDIR"
+ case PacketTypeRealPath:
+ return "SSH_FXP_REALPATH"
+ case PacketTypeStat:
+ return "SSH_FXP_STAT"
+ case PacketTypeRename:
+ return "SSH_FXP_RENAME"
+ case PacketTypeReadLink:
+ return "SSH_FXP_READLINK"
+ case PacketTypeSymlink:
+ return "SSH_FXP_SYMLINK"
+ case PacketTypeV6Link:
+ return "SSH_FXP_LINK"
+ case PacketTypeV6Block:
+ return "SSH_FXP_BLOCK"
+ case PacketTypeV6Unblock:
+ return "SSH_FXP_UNBLOCK"
+ case PacketTypeStatus:
+ return "SSH_FXP_STATUS"
+ case PacketTypeHandle:
+ return "SSH_FXP_HANDLE"
+ case PacketTypeData:
+ return "SSH_FXP_DATA"
+ case PacketTypeName:
+ return "SSH_FXP_NAME"
+ case PacketTypeAttrs:
+ return "SSH_FXP_ATTRS"
+ case PacketTypeExtended:
+ return "SSH_FXP_EXTENDED"
+ case PacketTypeExtendedReply:
+ return "SSH_FXP_EXTENDED_REPLY"
+ default:
+ return fmt.Sprintf("SSH_FXP_UNKNOWN(%d)", f)
+ }
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/handle_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/handle_packets.go
new file mode 100644
index 00000000..a1427712
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/handle_packets.go
@@ -0,0 +1,249 @@
+package filexfer
+
+// ClosePacket defines the SSH_FXP_CLOSE packet.
+type ClosePacket struct {
+ Handle string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *ClosePacket) Type() PacketType {
+ return PacketTypeClose
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *ClosePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Handle) // string(handle)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeClose, reqid)
+ buf.AppendString(p.Handle)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *ClosePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Handle, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ReadPacket defines the SSH_FXP_READ packet.
+type ReadPacket struct {
+ Handle string
+ Offset uint64
+ Len uint32
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *ReadPacket) Type() PacketType {
+ return PacketTypeRead
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *ReadPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ // string(handle) + uint64(offset) + uint32(len)
+ size := 4 + len(p.Handle) + 8 + 4
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeRead, reqid)
+ buf.AppendString(p.Handle)
+ buf.AppendUint64(p.Offset)
+ buf.AppendUint32(p.Len)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *ReadPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Handle, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ if p.Offset, err = buf.ConsumeUint64(); err != nil {
+ return err
+ }
+
+ if p.Len, err = buf.ConsumeUint32(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// WritePacket defines the SSH_FXP_WRITE packet.
+type WritePacket struct {
+ Handle string
+ Offset uint64
+ Data []byte
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *WritePacket) Type() PacketType {
+ return PacketTypeWrite
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *WritePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ // string(handle) + uint64(offset) + uint32(len(data)); data content in payload
+ size := 4 + len(p.Handle) + 8 + 4
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeWrite, reqid)
+ buf.AppendString(p.Handle)
+ buf.AppendUint64(p.Offset)
+ buf.AppendUint32(uint32(len(p.Data)))
+
+ return buf.Packet(p.Data)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+//
+// If p.Data is already populated, and of sufficient length to hold the data,
+// then this will copy the data into that byte slice.
+//
+// If p.Data has a length insufficient to hold the data,
+// then this will make a new slice of sufficient length, and copy the data into that.
+//
+// This means this _does not_ alias any of the data buffer that is passed in.
+func (p *WritePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Handle, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ if p.Offset, err = buf.ConsumeUint64(); err != nil {
+ return err
+ }
+
+ data, err := buf.ConsumeByteSlice()
+ if err != nil {
+ return err
+ }
+
+ if len(p.Data) < len(data) {
+ p.Data = make([]byte, len(data))
+ }
+
+ n := copy(p.Data, data)
+ p.Data = p.Data[:n]
+ return nil
+}
+
+// FStatPacket defines the SSH_FXP_FSTAT packet.
+type FStatPacket struct {
+ Handle string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *FStatPacket) Type() PacketType {
+ return PacketTypeFStat
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *FStatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Handle) // string(handle)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeFStat, reqid)
+ buf.AppendString(p.Handle)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *FStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Handle, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// FSetstatPacket defines the SSH_FXP_FSETSTAT packet.
+type FSetstatPacket struct {
+ Handle string
+ Attrs Attributes
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *FSetstatPacket) Type() PacketType {
+ return PacketTypeFSetstat
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *FSetstatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Handle) + p.Attrs.Len() // string(handle) + ATTRS(attrs)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeFSetstat, reqid)
+ buf.AppendString(p.Handle)
+
+ p.Attrs.MarshalInto(buf)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *FSetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Handle, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return p.Attrs.UnmarshalFrom(buf)
+}
+
+// ReadDirPacket defines the SSH_FXP_READDIR packet.
+type ReadDirPacket struct {
+ Handle string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *ReadDirPacket) Type() PacketType {
+ return PacketTypeReadDir
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *ReadDirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Handle) // string(handle)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeReadDir, reqid)
+ buf.AppendString(p.Handle)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *ReadDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Handle, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/init_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/init_packets.go
new file mode 100644
index 00000000..b0bc6f50
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/init_packets.go
@@ -0,0 +1,99 @@
+package filexfer
+
+// InitPacket defines the SSH_FXP_INIT packet.
+type InitPacket struct {
+ Version uint32
+ Extensions []*ExtensionPair
+}
+
+// MarshalBinary returns p as the binary encoding of p.
+func (p *InitPacket) MarshalBinary() ([]byte, error) {
+ size := 1 + 4 // byte(type) + uint32(version)
+
+ for _, ext := range p.Extensions {
+ size += ext.Len()
+ }
+
+ b := NewBuffer(make([]byte, 4, 4+size))
+ b.AppendUint8(uint8(PacketTypeInit))
+ b.AppendUint32(p.Version)
+
+ for _, ext := range p.Extensions {
+ ext.MarshalInto(b)
+ }
+
+ b.PutLength(size)
+
+ return b.Bytes(), nil
+}
+
+// UnmarshalBinary unmarshals a full raw packet out of the given data.
+// It is assumed that the uint32(length) has already been consumed to receive the data.
+// It is also assumed that the uint8(type) has already been consumed to which packet to unmarshal into.
+func (p *InitPacket) UnmarshalBinary(data []byte) (err error) {
+ buf := NewBuffer(data)
+
+ if p.Version, err = buf.ConsumeUint32(); err != nil {
+ return err
+ }
+
+ for buf.Len() > 0 {
+ var ext ExtensionPair
+ if err := ext.UnmarshalFrom(buf); err != nil {
+ return err
+ }
+
+ p.Extensions = append(p.Extensions, &ext)
+ }
+
+ return nil
+}
+
+// VersionPacket defines the SSH_FXP_VERSION packet.
+type VersionPacket struct {
+ Version uint32
+ Extensions []*ExtensionPair
+}
+
+// MarshalBinary returns p as the binary encoding of p.
+func (p *VersionPacket) MarshalBinary() ([]byte, error) {
+ size := 1 + 4 // byte(type) + uint32(version)
+
+ for _, ext := range p.Extensions {
+ size += ext.Len()
+ }
+
+ b := NewBuffer(make([]byte, 4, 4+size))
+ b.AppendUint8(uint8(PacketTypeVersion))
+ b.AppendUint32(p.Version)
+
+ for _, ext := range p.Extensions {
+ ext.MarshalInto(b)
+ }
+
+ b.PutLength(size)
+
+ return b.Bytes(), nil
+}
+
+// UnmarshalBinary unmarshals a full raw packet out of the given data.
+// It is assumed that the uint32(length) has already been consumed to receive the data.
+// It is also assumed that the uint8(type) has already been consumed to which packet to unmarshal into.
+func (p *VersionPacket) UnmarshalBinary(data []byte) (err error) {
+ buf := NewBuffer(data)
+
+ if p.Version, err = buf.ConsumeUint32(); err != nil {
+ return err
+ }
+
+ for buf.Len() > 0 {
+ var ext ExtensionPair
+ if err := ext.UnmarshalFrom(buf); err != nil {
+ return err
+ }
+
+ p.Extensions = append(p.Extensions, &ext)
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/open_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/open_packets.go
new file mode 100644
index 00000000..13587114
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/open_packets.go
@@ -0,0 +1,89 @@
+package filexfer
+
+// SSH_FXF_* flags.
+const (
+ FlagRead = 1 << iota // SSH_FXF_READ
+ FlagWrite // SSH_FXF_WRITE
+ FlagAppend // SSH_FXF_APPEND
+ FlagCreate // SSH_FXF_CREAT
+ FlagTruncate // SSH_FXF_TRUNC
+ FlagExclusive // SSH_FXF_EXCL
+)
+
+// OpenPacket defines the SSH_FXP_OPEN packet.
+type OpenPacket struct {
+ Filename string
+ PFlags uint32
+ Attrs Attributes
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *OpenPacket) Type() PacketType {
+ return PacketTypeOpen
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *OpenPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ // string(filename) + uint32(pflags) + ATTRS(attrs)
+ size := 4 + len(p.Filename) + 4 + p.Attrs.Len()
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeOpen, reqid)
+ buf.AppendString(p.Filename)
+ buf.AppendUint32(p.PFlags)
+
+ p.Attrs.MarshalInto(buf)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *OpenPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Filename, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ if p.PFlags, err = buf.ConsumeUint32(); err != nil {
+ return err
+ }
+
+ return p.Attrs.UnmarshalFrom(buf)
+}
+
+// OpenDirPacket defines the SSH_FXP_OPENDIR packet.
+type OpenDirPacket struct {
+ Path string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *OpenDirPacket) Type() PacketType {
+ return PacketTypeOpenDir
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *OpenDirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Path) // string(path)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeOpenDir, reqid)
+ buf.AppendString(p.Path)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *OpenDirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Path, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/packets.go
new file mode 100644
index 00000000..3f24e9c2
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/packets.go
@@ -0,0 +1,323 @@
+package filexfer
+
+import (
+ "errors"
+ "fmt"
+ "io"
+)
+
+// smallBufferSize is an initial allocation minimal capacity.
+const smallBufferSize = 64
+
+func newPacketFromType(typ PacketType) (Packet, error) {
+ switch typ {
+ case PacketTypeOpen:
+ return new(OpenPacket), nil
+ case PacketTypeClose:
+ return new(ClosePacket), nil
+ case PacketTypeRead:
+ return new(ReadPacket), nil
+ case PacketTypeWrite:
+ return new(WritePacket), nil
+ case PacketTypeLStat:
+ return new(LStatPacket), nil
+ case PacketTypeFStat:
+ return new(FStatPacket), nil
+ case PacketTypeSetstat:
+ return new(SetstatPacket), nil
+ case PacketTypeFSetstat:
+ return new(FSetstatPacket), nil
+ case PacketTypeOpenDir:
+ return new(OpenDirPacket), nil
+ case PacketTypeReadDir:
+ return new(ReadDirPacket), nil
+ case PacketTypeRemove:
+ return new(RemovePacket), nil
+ case PacketTypeMkdir:
+ return new(MkdirPacket), nil
+ case PacketTypeRmdir:
+ return new(RmdirPacket), nil
+ case PacketTypeRealPath:
+ return new(RealPathPacket), nil
+ case PacketTypeStat:
+ return new(StatPacket), nil
+ case PacketTypeRename:
+ return new(RenamePacket), nil
+ case PacketTypeReadLink:
+ return new(ReadLinkPacket), nil
+ case PacketTypeSymlink:
+ return new(SymlinkPacket), nil
+ case PacketTypeExtended:
+ return new(ExtendedPacket), nil
+ default:
+ return nil, fmt.Errorf("unexpected request packet type: %v", typ)
+ }
+}
+
+// RawPacket implements the general packet format from draft-ietf-secsh-filexfer-02
+//
+// RawPacket is intended for use in clients receiving responses,
+// where a response will be expected to be of a limited number of types,
+// and unmarshaling unknown/unexpected response packets is unnecessary.
+//
+// For servers expecting to receive arbitrary request packet types,
+// use RequestPacket.
+//
+// Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
+type RawPacket struct {
+ PacketType PacketType
+ RequestID uint32
+
+ Data Buffer
+}
+
+// Type returns the Type field defining the SSH_FXP_xy type for this packet.
+func (p *RawPacket) Type() PacketType {
+ return p.PacketType
+}
+
+// Reset clears the pointers and reference-semantic variables of RawPacket,
+// releasing underlying resources, and making them and the RawPacket suitable to be reused,
+// so long as no other references have been kept.
+func (p *RawPacket) Reset() {
+ p.Data = Buffer{}
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+//
+// The internal p.RequestID is overridden by the reqid argument.
+func (p *RawPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ buf = NewMarshalBuffer(0)
+ }
+
+ buf.StartPacket(p.PacketType, reqid)
+
+ return buf.Packet(p.Data.Bytes())
+}
+
+// MarshalBinary returns p as the binary encoding of p.
+//
+// This is a convenience implementation primarily intended for tests,
+// because it is inefficient with allocations.
+func (p *RawPacket) MarshalBinary() ([]byte, error) {
+ return ComposePacket(p.MarshalPacket(p.RequestID, nil))
+}
+
+// UnmarshalFrom decodes a RawPacket from the given Buffer into p.
+//
+// The Data field will alias the passed in Buffer,
+// so the buffer passed in should not be reused before RawPacket.Reset().
+func (p *RawPacket) UnmarshalFrom(buf *Buffer) error {
+ typ, err := buf.ConsumeUint8()
+ if err != nil {
+ return err
+ }
+
+ p.PacketType = PacketType(typ)
+
+ if p.RequestID, err = buf.ConsumeUint32(); err != nil {
+ return err
+ }
+
+ p.Data = *buf
+ return nil
+}
+
+// UnmarshalBinary decodes a full raw packet out of the given data.
+// It is assumed that the uint32(length) has already been consumed to receive the data.
+//
+// This is a convenience implementation primarily intended for tests,
+// because this must clone the given data byte slice,
+// as Data is not allowed to alias any part of the data byte slice.
+func (p *RawPacket) UnmarshalBinary(data []byte) error {
+ clone := make([]byte, len(data))
+ n := copy(clone, data)
+ return p.UnmarshalFrom(NewBuffer(clone[:n]))
+}
+
+// readPacket reads a uint32 length-prefixed binary data packet from r.
+// using the given byte slice as a backing array.
+//
+// If the packet length read from r is bigger than maxPacketLength,
+// or greater than math.MaxInt32 on a 32-bit implementation,
+// then a `ErrLongPacket` error will be returned.
+//
+// If the given byte slice is insufficient to hold the packet,
+// then it will be extended to fill the packet size.
+func readPacket(r io.Reader, b []byte, maxPacketLength uint32) ([]byte, error) {
+ if cap(b) < 4 {
+ // We will need allocate our own buffer just for reading the packet length.
+
+ // However, we don’t really want to allocate an extremely narrow buffer (4-bytes),
+ // and cause unnecessary allocation churn from both length reads and small packet reads,
+ // so we use smallBufferSize from the bytes package as a reasonable guess.
+
+ // But if callers really do want to force narrow throw-away allocation of every packet body,
+ // they can do so with a buffer of capacity 4.
+ b = make([]byte, smallBufferSize)
+ }
+
+ if _, err := io.ReadFull(r, b[:4]); err != nil {
+ return nil, err
+ }
+
+ length := unmarshalUint32(b)
+ if int(length) < 5 {
+ // Must have at least uint8(type) and uint32(request-id)
+
+ if int(length) < 0 {
+ // Only possible when strconv.IntSize == 32,
+ // the packet length is longer than math.MaxInt32,
+ // and thus longer than any possible slice.
+ return nil, ErrLongPacket
+ }
+
+ return nil, ErrShortPacket
+ }
+ if length > maxPacketLength {
+ return nil, ErrLongPacket
+ }
+
+ if int(length) > cap(b) {
+ // We know int(length) must be positive, because of tests above.
+ b = make([]byte, length)
+ }
+
+ n, err := io.ReadFull(r, b[:length])
+ return b[:n], err
+}
+
+// ReadFrom provides a simple functional packet reader,
+// using the given byte slice as a backing array.
+//
+// To protect against potential denial of service attacks,
+// if the read packet length is longer than maxPacketLength,
+// then no packet data will be read, and ErrLongPacket will be returned.
+// (On 32-bit int architectures, all packets >= 2^31 in length
+// will return ErrLongPacket regardless of maxPacketLength.)
+//
+// If the read packet length is longer than cap(b),
+// then a throw-away slice will allocated to meet the exact packet length.
+// This can be used to limit the length of reused buffers,
+// while still allowing reception of occasional large packets.
+//
+// The Data field may alias the passed in byte slice,
+// so the byte slice passed in should not be reused before RawPacket.Reset().
+func (p *RawPacket) ReadFrom(r io.Reader, b []byte, maxPacketLength uint32) error {
+ b, err := readPacket(r, b, maxPacketLength)
+ if err != nil {
+ return err
+ }
+
+ return p.UnmarshalFrom(NewBuffer(b))
+}
+
+// RequestPacket implements the general packet format from draft-ietf-secsh-filexfer-02
+// but also automatically decode/encodes valid request packets (2 < type < 100 || type == 200).
+//
+// RequestPacket is intended for use in servers receiving requests,
+// where any arbitrary request may be received, and so decoding them automatically
+// is useful.
+//
+// For clients expecting to receive specific response packet types,
+// where automatic unmarshaling of the packet body does not make sense,
+// use RawPacket.
+//
+// Defined in https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-3
+type RequestPacket struct {
+ RequestID uint32
+
+ Request Packet
+}
+
+// Type returns the SSH_FXP_xy value associated with the underlying packet.
+func (p *RequestPacket) Type() PacketType {
+ return p.Request.Type()
+}
+
+// Reset clears the pointers and reference-semantic variables in RequestPacket,
+// releasing underlying resources, and making them and the RequestPacket suitable to be reused,
+// so long as no other references have been kept.
+func (p *RequestPacket) Reset() {
+ p.Request = nil
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+//
+// The internal p.RequestID is overridden by the reqid argument.
+func (p *RequestPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ if p.Request == nil {
+ return nil, nil, errors.New("empty request packet")
+ }
+
+ return p.Request.MarshalPacket(reqid, b)
+}
+
+// MarshalBinary returns p as the binary encoding of p.
+//
+// This is a convenience implementation primarily intended for tests,
+// because it is inefficient with allocations.
+func (p *RequestPacket) MarshalBinary() ([]byte, error) {
+ return ComposePacket(p.MarshalPacket(p.RequestID, nil))
+}
+
+// UnmarshalFrom decodes a RequestPacket from the given Buffer into p.
+//
+// The Request field may alias the passed in Buffer, (e.g. SSH_FXP_WRITE),
+// so the buffer passed in should not be reused before RequestPacket.Reset().
+func (p *RequestPacket) UnmarshalFrom(buf *Buffer) error {
+ typ, err := buf.ConsumeUint8()
+ if err != nil {
+ return err
+ }
+
+ p.Request, err = newPacketFromType(PacketType(typ))
+ if err != nil {
+ return err
+ }
+
+ if p.RequestID, err = buf.ConsumeUint32(); err != nil {
+ return err
+ }
+
+ return p.Request.UnmarshalPacketBody(buf)
+}
+
+// UnmarshalBinary decodes a full request packet out of the given data.
+// It is assumed that the uint32(length) has already been consumed to receive the data.
+//
+// This is a convenience implementation primarily intended for tests,
+// because this must clone the given data byte slice,
+// as Request is not allowed to alias any part of the data byte slice.
+func (p *RequestPacket) UnmarshalBinary(data []byte) error {
+ clone := make([]byte, len(data))
+ n := copy(clone, data)
+ return p.UnmarshalFrom(NewBuffer(clone[:n]))
+}
+
+// ReadFrom provides a simple functional packet reader,
+// using the given byte slice as a backing array.
+//
+// To protect against potential denial of service attacks,
+// if the read packet length is longer than maxPacketLength,
+// then no packet data will be read, and ErrLongPacket will be returned.
+// (On 32-bit int architectures, all packets >= 2^31 in length
+// will return ErrLongPacket regardless of maxPacketLength.)
+//
+// If the read packet length is longer than cap(b),
+// then a throw-away slice will allocated to meet the exact packet length.
+// This can be used to limit the length of reused buffers,
+// while still allowing reception of occasional large packets.
+//
+// The Request field may alias the passed in byte slice,
+// so the byte slice passed in should not be reused before RawPacket.Reset().
+func (p *RequestPacket) ReadFrom(r io.Reader, b []byte, maxPacketLength uint32) error {
+ b, err := readPacket(r, b, maxPacketLength)
+ if err != nil {
+ return err
+ }
+
+ return p.UnmarshalFrom(NewBuffer(b))
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/path_packets.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/path_packets.go
new file mode 100644
index 00000000..e6f692d9
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/path_packets.go
@@ -0,0 +1,368 @@
+package filexfer
+
+// LStatPacket defines the SSH_FXP_LSTAT packet.
+type LStatPacket struct {
+ Path string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *LStatPacket) Type() PacketType {
+ return PacketTypeLStat
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *LStatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Path) // string(path)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeLStat, reqid)
+ buf.AppendString(p.Path)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *LStatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Path, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// SetstatPacket defines the SSH_FXP_SETSTAT packet.
+type SetstatPacket struct {
+ Path string
+ Attrs Attributes
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *SetstatPacket) Type() PacketType {
+ return PacketTypeSetstat
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *SetstatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Path) + p.Attrs.Len() // string(path) + ATTRS(attrs)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeSetstat, reqid)
+ buf.AppendString(p.Path)
+
+ p.Attrs.MarshalInto(buf)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *SetstatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Path, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return p.Attrs.UnmarshalFrom(buf)
+}
+
+// RemovePacket defines the SSH_FXP_REMOVE packet.
+type RemovePacket struct {
+ Path string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *RemovePacket) Type() PacketType {
+ return PacketTypeRemove
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *RemovePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Path) // string(path)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeRemove, reqid)
+ buf.AppendString(p.Path)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *RemovePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Path, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// MkdirPacket defines the SSH_FXP_MKDIR packet.
+type MkdirPacket struct {
+ Path string
+ Attrs Attributes
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *MkdirPacket) Type() PacketType {
+ return PacketTypeMkdir
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *MkdirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Path) + p.Attrs.Len() // string(path) + ATTRS(attrs)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeMkdir, reqid)
+ buf.AppendString(p.Path)
+
+ p.Attrs.MarshalInto(buf)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *MkdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Path, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return p.Attrs.UnmarshalFrom(buf)
+}
+
+// RmdirPacket defines the SSH_FXP_RMDIR packet.
+type RmdirPacket struct {
+ Path string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *RmdirPacket) Type() PacketType {
+ return PacketTypeRmdir
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *RmdirPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Path) // string(path)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeRmdir, reqid)
+ buf.AppendString(p.Path)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *RmdirPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Path, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// RealPathPacket defines the SSH_FXP_REALPATH packet.
+type RealPathPacket struct {
+ Path string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *RealPathPacket) Type() PacketType {
+ return PacketTypeRealPath
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *RealPathPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Path) // string(path)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeRealPath, reqid)
+ buf.AppendString(p.Path)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *RealPathPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Path, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// StatPacket defines the SSH_FXP_STAT packet.
+type StatPacket struct {
+ Path string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *StatPacket) Type() PacketType {
+ return PacketTypeStat
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *StatPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Path) // string(path)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeStat, reqid)
+ buf.AppendString(p.Path)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *StatPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Path, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// RenamePacket defines the SSH_FXP_RENAME packet.
+type RenamePacket struct {
+ OldPath string
+ NewPath string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *RenamePacket) Type() PacketType {
+ return PacketTypeRename
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *RenamePacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ // string(oldpath) + string(newpath)
+ size := 4 + len(p.OldPath) + 4 + len(p.NewPath)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeRename, reqid)
+ buf.AppendString(p.OldPath)
+ buf.AppendString(p.NewPath)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *RenamePacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.OldPath, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ if p.NewPath, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ReadLinkPacket defines the SSH_FXP_READLINK packet.
+type ReadLinkPacket struct {
+ Path string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *ReadLinkPacket) Type() PacketType {
+ return PacketTypeReadLink
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *ReadLinkPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ size := 4 + len(p.Path) // string(path)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeReadLink, reqid)
+ buf.AppendString(p.Path)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *ReadLinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ if p.Path, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// SymlinkPacket defines the SSH_FXP_SYMLINK packet.
+//
+// The order of the arguments to the SSH_FXP_SYMLINK method was inadvertently reversed.
+// Unfortunately, the reversal was not noticed until the server was widely deployed.
+// Covered in Section 3.1 of https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
+type SymlinkPacket struct {
+ LinkPath string
+ TargetPath string
+}
+
+// Type returns the SSH_FXP_xy value associated with this packet type.
+func (p *SymlinkPacket) Type() PacketType {
+ return PacketTypeSymlink
+}
+
+// MarshalPacket returns p as a two-part binary encoding of p.
+func (p *SymlinkPacket) MarshalPacket(reqid uint32, b []byte) (header, payload []byte, err error) {
+ buf := NewBuffer(b)
+ if buf.Cap() < 9 {
+ // string(targetpath) + string(linkpath)
+ size := 4 + len(p.TargetPath) + 4 + len(p.LinkPath)
+ buf = NewMarshalBuffer(size)
+ }
+
+ buf.StartPacket(PacketTypeSymlink, reqid)
+
+ // Arguments were inadvertently reversed.
+ buf.AppendString(p.TargetPath)
+ buf.AppendString(p.LinkPath)
+
+ return buf.Packet(payload)
+}
+
+// UnmarshalPacketBody unmarshals the packet body from the given Buffer.
+// It is assumed that the uint32(request-id) has already been consumed.
+func (p *SymlinkPacket) UnmarshalPacketBody(buf *Buffer) (err error) {
+ // Arguments were inadvertently reversed.
+ if p.TargetPath, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ if p.LinkPath, err = buf.ConsumeString(); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/permissions.go b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/permissions.go
new file mode 100644
index 00000000..2fe63d59
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/internal/encoding/ssh/filexfer/permissions.go
@@ -0,0 +1,114 @@
+package filexfer
+
+// FileMode represents a file’s mode and permission bits.
+// The bits are defined according to POSIX standards,
+// and may not apply to the OS being built for.
+type FileMode uint32
+
+// Permission flags, defined here to avoid potential inconsistencies in individual OS implementations.
+const (
+ ModePerm FileMode = 0o0777 // S_IRWXU | S_IRWXG | S_IRWXO
+ ModeUserRead FileMode = 0o0400 // S_IRUSR
+ ModeUserWrite FileMode = 0o0200 // S_IWUSR
+ ModeUserExec FileMode = 0o0100 // S_IXUSR
+ ModeGroupRead FileMode = 0o0040 // S_IRGRP
+ ModeGroupWrite FileMode = 0o0020 // S_IWGRP
+ ModeGroupExec FileMode = 0o0010 // S_IXGRP
+ ModeOtherRead FileMode = 0o0004 // S_IROTH
+ ModeOtherWrite FileMode = 0o0002 // S_IWOTH
+ ModeOtherExec FileMode = 0o0001 // S_IXOTH
+
+ ModeSetUID FileMode = 0o4000 // S_ISUID
+ ModeSetGID FileMode = 0o2000 // S_ISGID
+ ModeSticky FileMode = 0o1000 // S_ISVTX
+
+ ModeType FileMode = 0xF000 // S_IFMT
+ ModeNamedPipe FileMode = 0x1000 // S_IFIFO
+ ModeCharDevice FileMode = 0x2000 // S_IFCHR
+ ModeDir FileMode = 0x4000 // S_IFDIR
+ ModeDevice FileMode = 0x6000 // S_IFBLK
+ ModeRegular FileMode = 0x8000 // S_IFREG
+ ModeSymlink FileMode = 0xA000 // S_IFLNK
+ ModeSocket FileMode = 0xC000 // S_IFSOCK
+)
+
+// IsDir reports whether m describes a directory.
+// That is, it tests for m.Type() == ModeDir.
+func (m FileMode) IsDir() bool {
+ return (m & ModeType) == ModeDir
+}
+
+// IsRegular reports whether m describes a regular file.
+// That is, it tests for m.Type() == ModeRegular
+func (m FileMode) IsRegular() bool {
+ return (m & ModeType) == ModeRegular
+}
+
+// Perm returns the POSIX permission bits in m (m & ModePerm).
+func (m FileMode) Perm() FileMode {
+ return (m & ModePerm)
+}
+
+// Type returns the type bits in m (m & ModeType).
+func (m FileMode) Type() FileMode {
+ return (m & ModeType)
+}
+
+// String returns a `-rwxrwxrwx` style string representing the `ls -l` POSIX permissions string.
+func (m FileMode) String() string {
+ var buf [10]byte
+
+ switch m.Type() {
+ case ModeRegular:
+ buf[0] = '-'
+ case ModeDir:
+ buf[0] = 'd'
+ case ModeSymlink:
+ buf[0] = 'l'
+ case ModeDevice:
+ buf[0] = 'b'
+ case ModeCharDevice:
+ buf[0] = 'c'
+ case ModeNamedPipe:
+ buf[0] = 'p'
+ case ModeSocket:
+ buf[0] = 's'
+ default:
+ buf[0] = '?'
+ }
+
+ const rwx = "rwxrwxrwx"
+ for i, c := range rwx {
+ if m&(1< 0 && pattern[0] == '*' {
- pattern = pattern[1:]
- star = true
- }
- inrange := false
- var i int
-Scan:
- for i = 0; i < len(pattern); i++ {
- switch pattern[i] {
- case '\\':
-
- // error check handled in matchChunk: bad pattern.
- if i+1 < len(pattern) {
- i++
- }
- case '[':
- inrange = true
- case ']':
- inrange = false
- case '*':
- if !inrange {
- break Scan
- }
- }
- }
- return star, pattern[0:i], pattern[i:]
+ return c == '/'
}
-// matchChunk checks whether chunk matches the beginning of s.
-// If so, it returns the remainder of s (after the match).
-// Chunk is all single-character operators: literals, char classes, and ?.
-func matchChunk(chunk, s string) (rest string, ok bool, err error) {
- for len(chunk) > 0 {
- if len(s) == 0 {
- return
- }
- switch chunk[0] {
- case '[':
- // character class
- r, n := utf8.DecodeRuneInString(s)
- s = s[n:]
- chunk = chunk[1:]
- // We can't end right after '[', we're expecting at least
- // a closing bracket and possibly a caret.
- if len(chunk) == 0 {
- err = ErrBadPattern
- return
- }
- // possibly negated
- negated := chunk[0] == '^'
- if negated {
- chunk = chunk[1:]
- }
- // parse all ranges
- match := false
- nrange := 0
- for {
- if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
- chunk = chunk[1:]
- break
- }
- var lo, hi rune
- if lo, chunk, err = getEsc(chunk); err != nil {
- return
- }
- hi = lo
- if chunk[0] == '-' {
- if hi, chunk, err = getEsc(chunk[1:]); err != nil {
- return
- }
- }
- if lo <= r && r <= hi {
- match = true
- }
- nrange++
- }
- if match == negated {
- return
- }
-
- case '?':
- if isPathSeparator(s[0]) {
- return
- }
- _, n := utf8.DecodeRuneInString(s)
- s = s[n:]
- chunk = chunk[1:]
-
- case '\\':
- chunk = chunk[1:]
- if len(chunk) == 0 {
- err = ErrBadPattern
- return
- }
- fallthrough
-
- default:
- if chunk[0] != s[0] {
- return
- }
- s = s[1:]
- chunk = chunk[1:]
- }
- }
- return s, true, nil
-}
-
-// getEsc gets a possibly-escaped character from chunk, for a character class.
-func getEsc(chunk string) (r rune, nchunk string, err error) {
- if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
- err = ErrBadPattern
- return
- }
- if chunk[0] == '\\' {
- chunk = chunk[1:]
- if len(chunk) == 0 {
- err = ErrBadPattern
- return
- }
- }
- r, n := utf8.DecodeRuneInString(chunk)
- if r == utf8.RuneError && n == 1 {
- err = ErrBadPattern
- }
- nchunk = chunk[n:]
- if len(nchunk) == 0 {
- err = ErrBadPattern
- }
- return
-}
-
-// Split splits path immediately following the final Separator,
+// Split splits the path p immediately following the final slash,
// separating it into a directory and file name component.
-// If there is no Separator in path, Split returns an empty dir
-// and file set to path.
-// The returned values have the property that path = dir+file.
-func Split(path string) (dir, file string) {
- i := len(path) - 1
- for i >= 0 && !isPathSeparator(path[i]) {
- i--
- }
- return path[:i+1], path[i+1:]
+//
+// This is an alias for path.Split from the standard library,
+// offered so that callers need not import the path package.
+// For details, see https://golang.org/pkg/path/#Split.
+func Split(p string) (dir, file string) {
+ return path.Split(p)
}
// Glob returns the names of all files matching pattern or nil
// if there is no matching file. The syntax of patterns is the same
// as in Match. The pattern may describe hierarchical names such as
-// /usr/*/bin/ed (assuming the Separator is '/').
+// /usr/*/bin/ed.
//
// Glob ignores file system errors such as I/O errors reading directories.
// The only possible returned error is ErrBadPattern, when pattern
@@ -241,8 +82,7 @@ func cleanGlobPath(path string) string {
switch path {
case "":
return "."
- case string(separator):
- // do nothing to the path
+ case "/":
return path
default:
return path[0 : len(path)-1] // chop off trailing separator
@@ -280,9 +120,12 @@ func (c *Client) glob(dir, pattern string, matches []string) (m []string, e erro
return
}
-// Join joins any number of path elements into a single path, adding
-// a Separator if necessary.
-// all empty strings are ignored.
+// Join joins any number of path elements into a single path, separating
+// them with slashes.
+//
+// This is an alias for path.Join from the standard library,
+// offered so that callers need not import the path package.
+// For details, see https://golang.org/pkg/path/#Join.
func Join(elem ...string) string {
return path.Join(elem...)
}
@@ -290,6 +133,5 @@ func Join(elem ...string) string {
// hasMeta reports whether path contains any of the magic characters
// recognized by Match.
func hasMeta(path string) bool {
- // TODO(niemeyer): Should other magic characters be added here?
- return strings.ContainsAny(path, "*?[")
+ return strings.ContainsAny(path, "\\*?[")
}
diff --git a/vendor/github.com/pkg/sftp/packet-manager.go b/vendor/github.com/pkg/sftp/packet-manager.go
index 2f3be10a..c740c4c8 100644
--- a/vendor/github.com/pkg/sftp/packet-manager.go
+++ b/vendor/github.com/pkg/sftp/packet-manager.go
@@ -18,6 +18,8 @@ type packetManager struct {
sender packetSender // connection object
working *sync.WaitGroup
packetCount uint32
+ // it is not nil if the allocator is enabled
+ alloc *allocator
}
type packetSender interface {
@@ -39,21 +41,29 @@ func newPktMgr(sender packetSender) *packetManager {
}
//// packet ordering
-func (s *packetManager) newOrderId() uint32 {
+func (s *packetManager) newOrderID() uint32 {
s.packetCount++
return s.packetCount
}
+// returns the next orderID without incrementing it.
+// This is used before receiving a new packet, with the allocator enabled, to associate
+// the slice allocated for the received packet with the orderID that will be used to mark
+// the allocated slices for reuse once the request is served
+func (s *packetManager) getNextOrderID() uint32 {
+ return s.packetCount + 1
+}
+
type orderedRequest struct {
requestPacket
orderid uint32
}
func (s *packetManager) newOrderedRequest(p requestPacket) orderedRequest {
- return orderedRequest{requestPacket: p, orderid: s.newOrderId()}
+ return orderedRequest{requestPacket: p, orderid: s.newOrderID()}
}
-func (p orderedRequest) orderId() uint32 { return p.orderid }
-func (p orderedRequest) setOrderId(oid uint32) { p.orderid = oid }
+func (p orderedRequest) orderID() uint32 { return p.orderid }
+func (p orderedRequest) setOrderID(oid uint32) { p.orderid = oid }
type orderedResponse struct {
responsePacket
@@ -64,18 +74,18 @@ func (s *packetManager) newOrderedResponse(p responsePacket, id uint32,
) orderedResponse {
return orderedResponse{responsePacket: p, orderid: id}
}
-func (p orderedResponse) orderId() uint32 { return p.orderid }
-func (p orderedResponse) setOrderId(oid uint32) { p.orderid = oid }
+func (p orderedResponse) orderID() uint32 { return p.orderid }
+func (p orderedResponse) setOrderID(oid uint32) { p.orderid = oid }
type orderedPacket interface {
id() uint32
- orderId() uint32
+ orderID() uint32
}
type orderedPackets []orderedPacket
func (o orderedPackets) Sort() {
sort.Slice(o, func(i, j int) bool {
- return o[i].orderId() < o[j].orderId()
+ return o[i].orderID() < o[j].orderID()
})
}
@@ -104,7 +114,6 @@ func (s *packetManager) close() {
// maximizing throughput of file transfers.
func (s *packetManager) workerChan(runWorker func(chan orderedRequest),
) chan orderedRequest {
-
// multiple workers for faster read/writes
rwChan := make(chan orderedRequest, SftpServerWorkerCount)
for i := 0; i < SftpServerWorkerCount; i++ {
@@ -145,11 +154,11 @@ func (s *packetManager) controller() {
for {
select {
case pkt := <-s.requests:
- debug("incoming id (oid): %v (%v)", pkt.id(), pkt.orderId())
+ debug("incoming id (oid): %v (%v)", pkt.id(), pkt.orderID())
s.incoming = append(s.incoming, pkt)
s.incoming.Sort()
case pkt := <-s.responses:
- debug("outgoing id (oid): %v (%v)", pkt.id(), pkt.orderId())
+ debug("outgoing id (oid): %v (%v)", pkt.id(), pkt.orderID())
s.outgoing = append(s.outgoing, pkt)
s.outgoing.Sort()
case <-s.fini:
@@ -171,9 +180,13 @@ func (s *packetManager) maybeSendPackets() {
in := s.incoming[0]
// debug("incoming: %v", ids(s.incoming))
// debug("outgoing: %v", ids(s.outgoing))
- if in.orderId() == out.orderId() {
+ if in.orderID() == out.orderID() {
debug("Sending packet: %v", out.id())
s.sender.sendPacket(out.(encoding.BinaryMarshaler))
+ if s.alloc != nil {
+ // mark for reuse the slices allocated for this request
+ s.alloc.ReleasePages(in.orderID())
+ }
// pop off heads
copy(s.incoming, s.incoming[1:]) // shift left
s.incoming[len(s.incoming)-1] = nil // clear last
diff --git a/vendor/github.com/pkg/sftp/packet-typing.go b/vendor/github.com/pkg/sftp/packet-typing.go
index bcff9bf3..f4f90529 100644
--- a/vendor/github.com/pkg/sftp/packet-typing.go
+++ b/vendor/github.com/pkg/sftp/packet-typing.go
@@ -2,8 +2,7 @@ package sftp
import (
"encoding"
-
- "github.com/pkg/errors"
+ "fmt"
)
// all incoming packets
@@ -34,96 +33,98 @@ type notReadOnly interface {
//// define types by adding methods
// hasPath
-func (p sshFxpLstatPacket) getPath() string { return p.Path }
-func (p sshFxpStatPacket) getPath() string { return p.Path }
-func (p sshFxpRmdirPacket) getPath() string { return p.Path }
-func (p sshFxpReadlinkPacket) getPath() string { return p.Path }
-func (p sshFxpRealpathPacket) getPath() string { return p.Path }
-func (p sshFxpMkdirPacket) getPath() string { return p.Path }
-func (p sshFxpSetstatPacket) getPath() string { return p.Path }
-func (p sshFxpStatvfsPacket) getPath() string { return p.Path }
-func (p sshFxpRemovePacket) getPath() string { return p.Filename }
-func (p sshFxpRenamePacket) getPath() string { return p.Oldpath }
-func (p sshFxpSymlinkPacket) getPath() string { return p.Targetpath }
-func (p sshFxpOpendirPacket) getPath() string { return p.Path }
-func (p sshFxpOpenPacket) getPath() string { return p.Path }
+func (p *sshFxpLstatPacket) getPath() string { return p.Path }
+func (p *sshFxpStatPacket) getPath() string { return p.Path }
+func (p *sshFxpRmdirPacket) getPath() string { return p.Path }
+func (p *sshFxpReadlinkPacket) getPath() string { return p.Path }
+func (p *sshFxpRealpathPacket) getPath() string { return p.Path }
+func (p *sshFxpMkdirPacket) getPath() string { return p.Path }
+func (p *sshFxpSetstatPacket) getPath() string { return p.Path }
+func (p *sshFxpStatvfsPacket) getPath() string { return p.Path }
+func (p *sshFxpRemovePacket) getPath() string { return p.Filename }
+func (p *sshFxpRenamePacket) getPath() string { return p.Oldpath }
+func (p *sshFxpSymlinkPacket) getPath() string { return p.Targetpath }
+func (p *sshFxpOpendirPacket) getPath() string { return p.Path }
+func (p *sshFxpOpenPacket) getPath() string { return p.Path }
-func (p sshFxpExtendedPacketPosixRename) getPath() string { return p.Oldpath }
+func (p *sshFxpExtendedPacketPosixRename) getPath() string { return p.Oldpath }
+func (p *sshFxpExtendedPacketHardlink) getPath() string { return p.Oldpath }
-// hasHandle
-func (p sshFxpFstatPacket) getHandle() string { return p.Handle }
-func (p sshFxpFsetstatPacket) getHandle() string { return p.Handle }
-func (p sshFxpReadPacket) getHandle() string { return p.Handle }
-func (p sshFxpWritePacket) getHandle() string { return p.Handle }
-func (p sshFxpReaddirPacket) getHandle() string { return p.Handle }
-func (p sshFxpClosePacket) getHandle() string { return p.Handle }
+// getHandle
+func (p *sshFxpFstatPacket) getHandle() string { return p.Handle }
+func (p *sshFxpFsetstatPacket) getHandle() string { return p.Handle }
+func (p *sshFxpReadPacket) getHandle() string { return p.Handle }
+func (p *sshFxpWritePacket) getHandle() string { return p.Handle }
+func (p *sshFxpReaddirPacket) getHandle() string { return p.Handle }
+func (p *sshFxpClosePacket) getHandle() string { return p.Handle }
// notReadOnly
-func (p sshFxpWritePacket) notReadOnly() {}
-func (p sshFxpSetstatPacket) notReadOnly() {}
-func (p sshFxpFsetstatPacket) notReadOnly() {}
-func (p sshFxpRemovePacket) notReadOnly() {}
-func (p sshFxpMkdirPacket) notReadOnly() {}
-func (p sshFxpRmdirPacket) notReadOnly() {}
-func (p sshFxpRenamePacket) notReadOnly() {}
-func (p sshFxpSymlinkPacket) notReadOnly() {}
-func (p sshFxpExtendedPacketPosixRename) notReadOnly() {}
+func (p *sshFxpWritePacket) notReadOnly() {}
+func (p *sshFxpSetstatPacket) notReadOnly() {}
+func (p *sshFxpFsetstatPacket) notReadOnly() {}
+func (p *sshFxpRemovePacket) notReadOnly() {}
+func (p *sshFxpMkdirPacket) notReadOnly() {}
+func (p *sshFxpRmdirPacket) notReadOnly() {}
+func (p *sshFxpRenamePacket) notReadOnly() {}
+func (p *sshFxpSymlinkPacket) notReadOnly() {}
+func (p *sshFxpExtendedPacketPosixRename) notReadOnly() {}
+func (p *sshFxpExtendedPacketHardlink) notReadOnly() {}
// some packets with ID are missing id()
-func (p sshFxpDataPacket) id() uint32 { return p.ID }
-func (p sshFxpStatusPacket) id() uint32 { return p.ID }
-func (p sshFxpStatResponse) id() uint32 { return p.ID }
-func (p sshFxpNamePacket) id() uint32 { return p.ID }
-func (p sshFxpHandlePacket) id() uint32 { return p.ID }
-func (p StatVFS) id() uint32 { return p.ID }
-func (p sshFxVersionPacket) id() uint32 { return 0 }
+func (p *sshFxpDataPacket) id() uint32 { return p.ID }
+func (p *sshFxpStatusPacket) id() uint32 { return p.ID }
+func (p *sshFxpStatResponse) id() uint32 { return p.ID }
+func (p *sshFxpNamePacket) id() uint32 { return p.ID }
+func (p *sshFxpHandlePacket) id() uint32 { return p.ID }
+func (p *StatVFS) id() uint32 { return p.ID }
+func (p *sshFxVersionPacket) id() uint32 { return 0 }
// take raw incoming packet data and build packet objects
func makePacket(p rxPacket) (requestPacket, error) {
var pkt requestPacket
switch p.pktType {
- case ssh_FXP_INIT:
+ case sshFxpInit:
pkt = &sshFxInitPacket{}
- case ssh_FXP_LSTAT:
+ case sshFxpLstat:
pkt = &sshFxpLstatPacket{}
- case ssh_FXP_OPEN:
+ case sshFxpOpen:
pkt = &sshFxpOpenPacket{}
- case ssh_FXP_CLOSE:
+ case sshFxpClose:
pkt = &sshFxpClosePacket{}
- case ssh_FXP_READ:
+ case sshFxpRead:
pkt = &sshFxpReadPacket{}
- case ssh_FXP_WRITE:
+ case sshFxpWrite:
pkt = &sshFxpWritePacket{}
- case ssh_FXP_FSTAT:
+ case sshFxpFstat:
pkt = &sshFxpFstatPacket{}
- case ssh_FXP_SETSTAT:
+ case sshFxpSetstat:
pkt = &sshFxpSetstatPacket{}
- case ssh_FXP_FSETSTAT:
+ case sshFxpFsetstat:
pkt = &sshFxpFsetstatPacket{}
- case ssh_FXP_OPENDIR:
+ case sshFxpOpendir:
pkt = &sshFxpOpendirPacket{}
- case ssh_FXP_READDIR:
+ case sshFxpReaddir:
pkt = &sshFxpReaddirPacket{}
- case ssh_FXP_REMOVE:
+ case sshFxpRemove:
pkt = &sshFxpRemovePacket{}
- case ssh_FXP_MKDIR:
+ case sshFxpMkdir:
pkt = &sshFxpMkdirPacket{}
- case ssh_FXP_RMDIR:
+ case sshFxpRmdir:
pkt = &sshFxpRmdirPacket{}
- case ssh_FXP_REALPATH:
+ case sshFxpRealpath:
pkt = &sshFxpRealpathPacket{}
- case ssh_FXP_STAT:
+ case sshFxpStat:
pkt = &sshFxpStatPacket{}
- case ssh_FXP_RENAME:
+ case sshFxpRename:
pkt = &sshFxpRenamePacket{}
- case ssh_FXP_READLINK:
+ case sshFxpReadlink:
pkt = &sshFxpReadlinkPacket{}
- case ssh_FXP_SYMLINK:
+ case sshFxpSymlink:
pkt = &sshFxpSymlinkPacket{}
- case ssh_FXP_EXTENDED:
+ case sshFxpExtended:
pkt = &sshFxpExtendedPacket{}
default:
- return nil, errors.Errorf("unhandled packet type: %s", p.pktType)
+ return nil, fmt.Errorf("unhandled packet type: %s", p.pktType)
}
if err := pkt.UnmarshalBinary(p.pktBytes); err != nil {
// Return partially unpacked packet to allow callers to return
diff --git a/vendor/github.com/pkg/sftp/packet.go b/vendor/github.com/pkg/sftp/packet.go
index c5c32fc2..50ca069d 100644
--- a/vendor/github.com/pkg/sftp/packet.go
+++ b/vendor/github.com/pkg/sftp/packet.go
@@ -4,20 +4,21 @@ import (
"bytes"
"encoding"
"encoding/binary"
+ "errors"
"fmt"
"io"
"os"
"reflect"
-
- "github.com/pkg/errors"
)
var (
+ errLongPacket = errors.New("packet too long")
errShortPacket = errors.New("packet too short")
errUnknownExtendedPacket = errors.New("unknown extended packet")
)
const (
+ maxMsgLength = 256 * 1024
debugDumpTxPacket = false
debugDumpRxPacket = false
debugDumpTxPacketBytes = false
@@ -36,6 +37,50 @@ func marshalString(b []byte, v string) []byte {
return append(marshalUint32(b, uint32(len(v))), v...)
}
+func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
+ // attributes variable struct, and also variable per protocol version
+ // spec version 3 attributes:
+ // uint32 flags
+ // uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE
+ // uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID
+ // uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID
+ // uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
+ // uint32 atime present only if flag SSH_FILEXFER_ACMODTIME
+ // uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME
+ // uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
+ // string extended_type
+ // string extended_data
+ // ... more extended data (extended_type - extended_data pairs),
+ // so that number of pairs equals extended_count
+
+ flags, fileStat := fileStatFromInfo(fi)
+
+ b = marshalUint32(b, flags)
+ if flags&sshFileXferAttrSize != 0 {
+ b = marshalUint64(b, fileStat.Size)
+ }
+ if flags&sshFileXferAttrUIDGID != 0 {
+ b = marshalUint32(b, fileStat.UID)
+ b = marshalUint32(b, fileStat.GID)
+ }
+ if flags&sshFileXferAttrPermissions != 0 {
+ b = marshalUint32(b, fileStat.Mode)
+ }
+ if flags&sshFileXferAttrACmodTime != 0 {
+ b = marshalUint32(b, fileStat.Atime)
+ b = marshalUint32(b, fileStat.Mtime)
+ }
+
+ return b
+}
+
+func marshalStatus(b []byte, err StatusError) []byte {
+ b = marshalUint32(b, err.Code)
+ b = marshalString(b, err.msg)
+ b = marshalString(b, err.lang)
+ return b
+}
+
func marshal(b []byte, v interface{}) []byte {
if v == nil {
return b
@@ -55,12 +100,12 @@ func marshal(b []byte, v interface{}) []byte {
switch d := reflect.ValueOf(v); d.Kind() {
case reflect.Struct:
for i, n := 0, d.NumField(); i < n; i++ {
- b = append(marshal(b, d.Field(i).Interface()))
+ b = marshal(b, d.Field(i).Interface())
}
return b
case reflect.Slice:
for i, n := 0, d.Len(); i < n; i++ {
- b = append(marshal(b, d.Index(i).Interface()))
+ b = marshal(b, d.Index(i).Interface())
}
return b
default:
@@ -114,46 +159,137 @@ func unmarshalStringSafe(b []byte) (string, []byte, error) {
return string(b[:n]), b[n:], nil
}
+func unmarshalAttrs(b []byte) (*FileStat, []byte) {
+ flags, b := unmarshalUint32(b)
+ return unmarshalFileStat(flags, b)
+}
+
+func unmarshalFileStat(flags uint32, b []byte) (*FileStat, []byte) {
+ var fs FileStat
+ if flags&sshFileXferAttrSize == sshFileXferAttrSize {
+ fs.Size, b, _ = unmarshalUint64Safe(b)
+ }
+ if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
+ fs.UID, b, _ = unmarshalUint32Safe(b)
+ }
+ if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
+ fs.GID, b, _ = unmarshalUint32Safe(b)
+ }
+ if flags&sshFileXferAttrPermissions == sshFileXferAttrPermissions {
+ fs.Mode, b, _ = unmarshalUint32Safe(b)
+ }
+ if flags&sshFileXferAttrACmodTime == sshFileXferAttrACmodTime {
+ fs.Atime, b, _ = unmarshalUint32Safe(b)
+ fs.Mtime, b, _ = unmarshalUint32Safe(b)
+ }
+ if flags&sshFileXferAttrExtended == sshFileXferAttrExtended {
+ var count uint32
+ count, b, _ = unmarshalUint32Safe(b)
+ ext := make([]StatExtended, count)
+ for i := uint32(0); i < count; i++ {
+ var typ string
+ var data string
+ typ, b, _ = unmarshalStringSafe(b)
+ data, b, _ = unmarshalStringSafe(b)
+ ext[i] = StatExtended{
+ ExtType: typ,
+ ExtData: data,
+ }
+ }
+ fs.Extended = ext
+ }
+ return &fs, b
+}
+
+func unmarshalStatus(id uint32, data []byte) error {
+ sid, data := unmarshalUint32(data)
+ if sid != id {
+ return &unexpectedIDErr{id, sid}
+ }
+ code, data := unmarshalUint32(data)
+ msg, data, _ := unmarshalStringSafe(data)
+ lang, _, _ := unmarshalStringSafe(data)
+ return &StatusError{
+ Code: code,
+ msg: msg,
+ lang: lang,
+ }
+}
+
+type packetMarshaler interface {
+ marshalPacket() (header, payload []byte, err error)
+}
+
+func marshalPacket(m encoding.BinaryMarshaler) (header, payload []byte, err error) {
+ if m, ok := m.(packetMarshaler); ok {
+ return m.marshalPacket()
+ }
+
+ header, err = m.MarshalBinary()
+ return
+}
+
// sendPacket marshals p according to RFC 4234.
func sendPacket(w io.Writer, m encoding.BinaryMarshaler) error {
- bb, err := m.MarshalBinary()
+ header, payload, err := marshalPacket(m)
if err != nil {
- return errors.Errorf("binary marshaller failed: %v", err)
+ return fmt.Errorf("binary marshaller failed: %w", err)
}
+
+ length := len(header) + len(payload) - 4 // subtract the uint32(length) from the start
if debugDumpTxPacketBytes {
- debug("send packet: %s %d bytes %x", fxp(bb[0]), len(bb), bb[1:])
+ debug("send packet: %s %d bytes %x%x", fxp(header[4]), length, header[5:], payload)
} else if debugDumpTxPacket {
- debug("send packet: %s %d bytes", fxp(bb[0]), len(bb))
+ debug("send packet: %s %d bytes", fxp(header[4]), length)
}
- // Slide packet down 4 bytes to make room for length header.
- packet := append(bb, make([]byte, 4)...) // optimistically assume bb has capacity
- copy(packet[4:], bb)
- binary.BigEndian.PutUint32(packet[:4], uint32(len(bb)))
- _, err = w.Write(packet)
- if err != nil {
- return errors.Errorf("failed to send packet: %v", err)
+ binary.BigEndian.PutUint32(header[:4], uint32(length))
+
+ if _, err := w.Write(header); err != nil {
+ return fmt.Errorf("failed to send packet: %w", err)
+ }
+
+ if len(payload) > 0 {
+ if _, err := w.Write(payload); err != nil {
+ return fmt.Errorf("failed to send packet payload: %w", err)
+ }
}
+
return nil
}
-func recvPacket(r io.Reader) (uint8, []byte, error) {
- var b = []byte{0, 0, 0, 0}
- if _, err := io.ReadFull(r, b); err != nil {
+func recvPacket(r io.Reader, alloc *allocator, orderID uint32) (uint8, []byte, error) {
+ var b []byte
+ if alloc != nil {
+ b = alloc.GetPage(orderID)
+ } else {
+ b = make([]byte, 4)
+ }
+ if _, err := io.ReadFull(r, b[:4]); err != nil {
return 0, nil, err
}
- l, _ := unmarshalUint32(b)
- b = make([]byte, l)
- if _, err := io.ReadFull(r, b); err != nil {
- debug("recv packet %d bytes: err %v", l, err)
+ length, _ := unmarshalUint32(b)
+ if length > maxMsgLength {
+ debug("recv packet %d bytes too long", length)
+ return 0, nil, errLongPacket
+ }
+ if length == 0 {
+ debug("recv packet of 0 bytes too short")
+ return 0, nil, errShortPacket
+ }
+ if alloc == nil {
+ b = make([]byte, length)
+ }
+ if _, err := io.ReadFull(r, b[:length]); err != nil {
+ debug("recv packet %d bytes: err %v", length, err)
return 0, nil, err
}
if debugDumpRxPacketBytes {
- debug("recv packet: %s %d bytes %x", fxp(b[0]), l, b[1:])
+ debug("recv packet: %s %d bytes %x", fxp(b[0]), length, b[1:length])
} else if debugDumpRxPacket {
- debug("recv packet: %s %d bytes", fxp(b[0]), l)
+ debug("recv packet: %s %d bytes", fxp(b[0]), length)
}
- return b[0], b[1:], nil
+ return b[0], b[1:length], nil
}
type extensionPair struct {
@@ -182,19 +318,21 @@ type sshFxInitPacket struct {
Extensions []extensionPair
}
-func (p sshFxInitPacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 // byte + uint32
+func (p *sshFxInitPacket) MarshalBinary() ([]byte, error) {
+ l := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(version)
for _, e := range p.Extensions {
l += 4 + len(e.Name) + 4 + len(e.Data)
}
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_INIT)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpInit)
b = marshalUint32(b, p.Version)
+
for _, e := range p.Extensions {
b = marshalString(b, e.Name)
b = marshalString(b, e.Data)
}
+
return b, nil
}
@@ -216,35 +354,40 @@ func (p *sshFxInitPacket) UnmarshalBinary(b []byte) error {
type sshFxVersionPacket struct {
Version uint32
- Extensions []struct {
- Name, Data string
- }
+ Extensions []sshExtensionPair
+}
+
+type sshExtensionPair struct {
+ Name, Data string
}
-func (p sshFxVersionPacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 // byte + uint32
+func (p *sshFxVersionPacket) MarshalBinary() ([]byte, error) {
+ l := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(version)
for _, e := range p.Extensions {
l += 4 + len(e.Name) + 4 + len(e.Data)
}
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_VERSION)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpVersion)
b = marshalUint32(b, p.Version)
+
for _, e := range p.Extensions {
b = marshalString(b, e.Name)
b = marshalString(b, e.Data)
}
+
return b, nil
}
-func marshalIDString(packetType byte, id uint32, str string) ([]byte, error) {
- l := 1 + 4 + // type(byte) + uint32
+func marshalIDStringPacket(packetType byte, id uint32, str string) ([]byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(str)
- b := make([]byte, 0, l)
+ b := make([]byte, 4, l)
b = append(b, packetType)
b = marshalUint32(b, id)
b = marshalString(b, str)
+
return b, nil
}
@@ -263,10 +406,10 @@ type sshFxpReaddirPacket struct {
Handle string
}
-func (p sshFxpReaddirPacket) id() uint32 { return p.ID }
+func (p *sshFxpReaddirPacket) id() uint32 { return p.ID }
-func (p sshFxpReaddirPacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_READDIR, p.ID, p.Handle)
+func (p *sshFxpReaddirPacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpReaddir, p.ID, p.Handle)
}
func (p *sshFxpReaddirPacket) UnmarshalBinary(b []byte) error {
@@ -278,10 +421,10 @@ type sshFxpOpendirPacket struct {
Path string
}
-func (p sshFxpOpendirPacket) id() uint32 { return p.ID }
+func (p *sshFxpOpendirPacket) id() uint32 { return p.ID }
-func (p sshFxpOpendirPacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_OPENDIR, p.ID, p.Path)
+func (p *sshFxpOpendirPacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpOpendir, p.ID, p.Path)
}
func (p *sshFxpOpendirPacket) UnmarshalBinary(b []byte) error {
@@ -293,10 +436,10 @@ type sshFxpLstatPacket struct {
Path string
}
-func (p sshFxpLstatPacket) id() uint32 { return p.ID }
+func (p *sshFxpLstatPacket) id() uint32 { return p.ID }
-func (p sshFxpLstatPacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_LSTAT, p.ID, p.Path)
+func (p *sshFxpLstatPacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpLstat, p.ID, p.Path)
}
func (p *sshFxpLstatPacket) UnmarshalBinary(b []byte) error {
@@ -308,10 +451,10 @@ type sshFxpStatPacket struct {
Path string
}
-func (p sshFxpStatPacket) id() uint32 { return p.ID }
+func (p *sshFxpStatPacket) id() uint32 { return p.ID }
-func (p sshFxpStatPacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_STAT, p.ID, p.Path)
+func (p *sshFxpStatPacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpStat, p.ID, p.Path)
}
func (p *sshFxpStatPacket) UnmarshalBinary(b []byte) error {
@@ -323,10 +466,10 @@ type sshFxpFstatPacket struct {
Handle string
}
-func (p sshFxpFstatPacket) id() uint32 { return p.ID }
+func (p *sshFxpFstatPacket) id() uint32 { return p.ID }
-func (p sshFxpFstatPacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_FSTAT, p.ID, p.Handle)
+func (p *sshFxpFstatPacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpFstat, p.ID, p.Handle)
}
func (p *sshFxpFstatPacket) UnmarshalBinary(b []byte) error {
@@ -338,10 +481,10 @@ type sshFxpClosePacket struct {
Handle string
}
-func (p sshFxpClosePacket) id() uint32 { return p.ID }
+func (p *sshFxpClosePacket) id() uint32 { return p.ID }
-func (p sshFxpClosePacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_CLOSE, p.ID, p.Handle)
+func (p *sshFxpClosePacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpClose, p.ID, p.Handle)
}
func (p *sshFxpClosePacket) UnmarshalBinary(b []byte) error {
@@ -353,10 +496,10 @@ type sshFxpRemovePacket struct {
Filename string
}
-func (p sshFxpRemovePacket) id() uint32 { return p.ID }
+func (p *sshFxpRemovePacket) id() uint32 { return p.ID }
-func (p sshFxpRemovePacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_REMOVE, p.ID, p.Filename)
+func (p *sshFxpRemovePacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpRemove, p.ID, p.Filename)
}
func (p *sshFxpRemovePacket) UnmarshalBinary(b []byte) error {
@@ -368,10 +511,10 @@ type sshFxpRmdirPacket struct {
Path string
}
-func (p sshFxpRmdirPacket) id() uint32 { return p.ID }
+func (p *sshFxpRmdirPacket) id() uint32 { return p.ID }
-func (p sshFxpRmdirPacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_RMDIR, p.ID, p.Path)
+func (p *sshFxpRmdirPacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpRmdir, p.ID, p.Path)
}
func (p *sshFxpRmdirPacket) UnmarshalBinary(b []byte) error {
@@ -384,18 +527,19 @@ type sshFxpSymlinkPacket struct {
Linkpath string
}
-func (p sshFxpSymlinkPacket) id() uint32 { return p.ID }
+func (p *sshFxpSymlinkPacket) id() uint32 { return p.ID }
-func (p sshFxpSymlinkPacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 + // type(byte) + uint32
+func (p *sshFxpSymlinkPacket) MarshalBinary() ([]byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(p.Targetpath) +
4 + len(p.Linkpath)
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_SYMLINK)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpSymlink)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Targetpath)
b = marshalString(b, p.Linkpath)
+
return b, nil
}
@@ -411,15 +555,40 @@ func (p *sshFxpSymlinkPacket) UnmarshalBinary(b []byte) error {
return nil
}
+type sshFxpHardlinkPacket struct {
+ ID uint32
+ Oldpath string
+ Newpath string
+}
+
+func (p *sshFxpHardlinkPacket) id() uint32 { return p.ID }
+
+func (p *sshFxpHardlinkPacket) MarshalBinary() ([]byte, error) {
+ const ext = "hardlink@openssh.com"
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
+ 4 + len(ext) +
+ 4 + len(p.Oldpath) +
+ 4 + len(p.Newpath)
+
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpExtended)
+ b = marshalUint32(b, p.ID)
+ b = marshalString(b, ext)
+ b = marshalString(b, p.Oldpath)
+ b = marshalString(b, p.Newpath)
+
+ return b, nil
+}
+
type sshFxpReadlinkPacket struct {
ID uint32
Path string
}
-func (p sshFxpReadlinkPacket) id() uint32 { return p.ID }
+func (p *sshFxpReadlinkPacket) id() uint32 { return p.ID }
-func (p sshFxpReadlinkPacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_READLINK, p.ID, p.Path)
+func (p *sshFxpReadlinkPacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpReadlink, p.ID, p.Path)
}
func (p *sshFxpReadlinkPacket) UnmarshalBinary(b []byte) error {
@@ -431,10 +600,10 @@ type sshFxpRealpathPacket struct {
Path string
}
-func (p sshFxpRealpathPacket) id() uint32 { return p.ID }
+func (p *sshFxpRealpathPacket) id() uint32 { return p.ID }
-func (p sshFxpRealpathPacket) MarshalBinary() ([]byte, error) {
- return marshalIDString(ssh_FXP_REALPATH, p.ID, p.Path)
+func (p *sshFxpRealpathPacket) MarshalBinary() ([]byte, error) {
+ return marshalIDStringPacket(sshFxpRealpath, p.ID, p.Path)
}
func (p *sshFxpRealpathPacket) UnmarshalBinary(b []byte) error {
@@ -447,8 +616,8 @@ type sshFxpNameAttr struct {
Attrs []interface{}
}
-func (p sshFxpNameAttr) MarshalBinary() ([]byte, error) {
- b := []byte{}
+func (p *sshFxpNameAttr) MarshalBinary() ([]byte, error) {
+ var b []byte
b = marshalString(b, p.Name)
b = marshalString(b, p.LongName)
for _, attr := range p.Attrs {
@@ -459,23 +628,34 @@ func (p sshFxpNameAttr) MarshalBinary() ([]byte, error) {
type sshFxpNamePacket struct {
ID uint32
- NameAttrs []sshFxpNameAttr
+ NameAttrs []*sshFxpNameAttr
}
-func (p sshFxpNamePacket) MarshalBinary() ([]byte, error) {
- b := []byte{}
- b = append(b, ssh_FXP_NAME)
+func (p *sshFxpNamePacket) marshalPacket() ([]byte, []byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
+ 4
+
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpName)
b = marshalUint32(b, p.ID)
b = marshalUint32(b, uint32(len(p.NameAttrs)))
+
+ var payload []byte
for _, na := range p.NameAttrs {
ab, err := na.MarshalBinary()
if err != nil {
- return nil, err
+ return nil, nil, err
}
- b = append(b, ab...)
+ payload = append(payload, ab...)
}
- return b, nil
+
+ return b, payload, nil
+}
+
+func (p *sshFxpNamePacket) MarshalBinary() ([]byte, error) {
+ header, payload, err := p.marshalPacket()
+ return append(header, payload...), err
}
type sshFxpOpenPacket struct {
@@ -485,19 +665,20 @@ type sshFxpOpenPacket struct {
Flags uint32 // ignored
}
-func (p sshFxpOpenPacket) id() uint32 { return p.ID }
+func (p *sshFxpOpenPacket) id() uint32 { return p.ID }
-func (p sshFxpOpenPacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 +
+func (p *sshFxpOpenPacket) MarshalBinary() ([]byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(p.Path) +
4 + 4
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_OPEN)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpOpen)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Path)
b = marshalUint32(b, p.Pflags)
b = marshalUint32(b, p.Flags)
+
return b, nil
}
@@ -517,24 +698,25 @@ func (p *sshFxpOpenPacket) UnmarshalBinary(b []byte) error {
type sshFxpReadPacket struct {
ID uint32
- Handle string
- Offset uint64
Len uint32
+ Offset uint64
+ Handle string
}
-func (p sshFxpReadPacket) id() uint32 { return p.ID }
+func (p *sshFxpReadPacket) id() uint32 { return p.ID }
-func (p sshFxpReadPacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 + // type(byte) + uint32
+func (p *sshFxpReadPacket) MarshalBinary() ([]byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(p.Handle) +
8 + 4 // uint64 + uint32
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_READ)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpRead)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Handle)
b = marshalUint64(b, p.Offset)
b = marshalUint32(b, p.Len)
+
return b, nil
}
@@ -552,24 +734,45 @@ func (p *sshFxpReadPacket) UnmarshalBinary(b []byte) error {
return nil
}
+// We need allocate bigger slices with extra capacity to avoid a re-allocation in sshFxpDataPacket.MarshalBinary
+// So, we need: uint32(length) + byte(type) + uint32(id) + uint32(data_length)
+const dataHeaderLen = 4 + 1 + 4 + 4
+
+func (p *sshFxpReadPacket) getDataSlice(alloc *allocator, orderID uint32) []byte {
+ dataLen := p.Len
+ if dataLen > maxTxPacket {
+ dataLen = maxTxPacket
+ }
+
+ if alloc != nil {
+ // GetPage returns a slice with capacity = maxMsgLength this is enough to avoid new allocations in
+ // sshFxpDataPacket.MarshalBinary
+ return alloc.GetPage(orderID)[:dataLen]
+ }
+
+ // allocate with extra space for the header
+ return make([]byte, dataLen, dataLen+dataHeaderLen)
+}
+
type sshFxpRenamePacket struct {
ID uint32
Oldpath string
Newpath string
}
-func (p sshFxpRenamePacket) id() uint32 { return p.ID }
+func (p *sshFxpRenamePacket) id() uint32 { return p.ID }
-func (p sshFxpRenamePacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 + // type(byte) + uint32
+func (p *sshFxpRenamePacket) MarshalBinary() ([]byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(p.Oldpath) +
4 + len(p.Newpath)
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_RENAME)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpRename)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Oldpath)
b = marshalString(b, p.Newpath)
+
return b, nil
}
@@ -591,48 +794,54 @@ type sshFxpPosixRenamePacket struct {
Newpath string
}
-func (p sshFxpPosixRenamePacket) id() uint32 { return p.ID }
+func (p *sshFxpPosixRenamePacket) id() uint32 { return p.ID }
-func (p sshFxpPosixRenamePacket) MarshalBinary() ([]byte, error) {
+func (p *sshFxpPosixRenamePacket) MarshalBinary() ([]byte, error) {
const ext = "posix-rename@openssh.com"
- l := 1 + 4 + // type(byte) + uint32
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(ext) +
4 + len(p.Oldpath) +
4 + len(p.Newpath)
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_EXTENDED)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpExtended)
b = marshalUint32(b, p.ID)
b = marshalString(b, ext)
b = marshalString(b, p.Oldpath)
b = marshalString(b, p.Newpath)
+
return b, nil
}
type sshFxpWritePacket struct {
ID uint32
- Handle string
- Offset uint64
Length uint32
+ Offset uint64
+ Handle string
Data []byte
}
-func (p sshFxpWritePacket) id() uint32 { return p.ID }
+func (p *sshFxpWritePacket) id() uint32 { return p.ID }
-func (p sshFxpWritePacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 + // type(byte) + uint32
+func (p *sshFxpWritePacket) marshalPacket() ([]byte, []byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(p.Handle) +
- 8 + 4 + // uint64 + uint32
- len(p.Data)
+ 8 + // uint64
+ 4
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_WRITE)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpWrite)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Handle)
b = marshalUint64(b, p.Offset)
b = marshalUint32(b, p.Length)
- b = append(b, p.Data...)
- return b, nil
+
+ return b, p.Data, nil
+}
+
+func (p *sshFxpWritePacket) MarshalBinary() ([]byte, error) {
+ header, payload, err := p.marshalPacket()
+ return append(header, payload...), err
}
func (p *sshFxpWritePacket) UnmarshalBinary(b []byte) error {
@@ -649,28 +858,29 @@ func (p *sshFxpWritePacket) UnmarshalBinary(b []byte) error {
return errShortPacket
}
- p.Data = append([]byte{}, b[:p.Length]...)
+ p.Data = b[:p.Length]
return nil
}
type sshFxpMkdirPacket struct {
ID uint32
- Path string
Flags uint32 // ignored
+ Path string
}
-func (p sshFxpMkdirPacket) id() uint32 { return p.ID }
+func (p *sshFxpMkdirPacket) id() uint32 { return p.ID }
-func (p sshFxpMkdirPacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 + // type(byte) + uint32
+func (p *sshFxpMkdirPacket) MarshalBinary() ([]byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(p.Path) +
4 // uint32
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_MKDIR)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpMkdir)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Path)
b = marshalUint32(b, p.Flags)
+
return b, nil
}
@@ -688,47 +898,61 @@ func (p *sshFxpMkdirPacket) UnmarshalBinary(b []byte) error {
type sshFxpSetstatPacket struct {
ID uint32
- Path string
Flags uint32
+ Path string
Attrs interface{}
}
type sshFxpFsetstatPacket struct {
ID uint32
- Handle string
Flags uint32
+ Handle string
Attrs interface{}
}
-func (p sshFxpSetstatPacket) id() uint32 { return p.ID }
-func (p sshFxpFsetstatPacket) id() uint32 { return p.ID }
+func (p *sshFxpSetstatPacket) id() uint32 { return p.ID }
+func (p *sshFxpFsetstatPacket) id() uint32 { return p.ID }
-func (p sshFxpSetstatPacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 + // type(byte) + uint32
+func (p *sshFxpSetstatPacket) marshalPacket() ([]byte, []byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(p.Path) +
- 4 // uint32 + uint64
+ 4 // uint32
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_SETSTAT)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpSetstat)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Path)
b = marshalUint32(b, p.Flags)
- b = marshal(b, p.Attrs)
- return b, nil
+
+ payload := marshal(nil, p.Attrs)
+
+ return b, payload, nil
}
-func (p sshFxpFsetstatPacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 + // type(byte) + uint32
+func (p *sshFxpSetstatPacket) MarshalBinary() ([]byte, error) {
+ header, payload, err := p.marshalPacket()
+ return append(header, payload...), err
+}
+
+func (p *sshFxpFsetstatPacket) marshalPacket() ([]byte, []byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
4 + len(p.Handle) +
- 4 // uint32 + uint64
+ 4 // uint32
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_FSETSTAT)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpFsetstat)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Handle)
b = marshalUint32(b, p.Flags)
- b = marshal(b, p.Attrs)
- return b, nil
+
+ payload := marshal(nil, p.Attrs)
+
+ return b, payload, nil
+}
+
+func (p *sshFxpFsetstatPacket) MarshalBinary() ([]byte, error) {
+ header, payload, err := p.marshalPacket()
+ return append(header, payload...), err
}
func (p *sshFxpSetstatPacket) UnmarshalBinary(b []byte) error {
@@ -762,10 +986,15 @@ type sshFxpHandlePacket struct {
Handle string
}
-func (p sshFxpHandlePacket) MarshalBinary() ([]byte, error) {
- b := []byte{ssh_FXP_HANDLE}
+func (p *sshFxpHandlePacket) MarshalBinary() ([]byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
+ 4 + len(p.Handle)
+
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpHandle)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Handle)
+
return b, nil
}
@@ -774,10 +1003,17 @@ type sshFxpStatusPacket struct {
StatusError
}
-func (p sshFxpStatusPacket) MarshalBinary() ([]byte, error) {
- b := []byte{ssh_FXP_STATUS}
+func (p *sshFxpStatusPacket) MarshalBinary() ([]byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
+ 4 +
+ 4 + len(p.StatusError.msg) +
+ 4 + len(p.StatusError.lang)
+
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpStatus)
b = marshalUint32(b, p.ID)
b = marshalStatus(b, p.StatusError)
+
return b, nil
}
@@ -787,11 +1023,30 @@ type sshFxpDataPacket struct {
Data []byte
}
-func (p sshFxpDataPacket) MarshalBinary() ([]byte, error) {
- b := []byte{ssh_FXP_DATA}
+func (p *sshFxpDataPacket) marshalPacket() ([]byte, []byte, error) {
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
+ 4
+
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpData)
b = marshalUint32(b, p.ID)
b = marshalUint32(b, p.Length)
- b = append(b, p.Data[:p.Length]...)
+
+ return b, p.Data, nil
+}
+
+// MarshalBinary encodes the receiver into a binary form and returns the result.
+// To avoid a new allocation the Data slice must have a capacity >= Length + 9
+//
+// This is hand-coded rather than just append(header, payload...),
+// in order to try and reuse the r.Data backing store in the packet.
+func (p *sshFxpDataPacket) MarshalBinary() ([]byte, error) {
+ b := append(p.Data, make([]byte, dataHeaderLen)...)
+ copy(b[dataHeaderLen:], p.Data[:p.Length])
+ // b[0:4] will be overwritten with the length in sendPacket
+ b[4] = sshFxpData
+ binary.BigEndian.PutUint32(b[5:9], p.ID)
+ binary.BigEndian.PutUint32(b[9:13], p.Length)
return b, nil
}
@@ -805,8 +1060,7 @@ func (p *sshFxpDataPacket) UnmarshalBinary(b []byte) error {
return errShortPacket
}
- p.Data = make([]byte, p.Length)
- copy(p.Data, b)
+ p.Data = b[:p.Length]
return nil
}
@@ -815,18 +1069,20 @@ type sshFxpStatvfsPacket struct {
Path string
}
-func (p sshFxpStatvfsPacket) id() uint32 { return p.ID }
+func (p *sshFxpStatvfsPacket) id() uint32 { return p.ID }
-func (p sshFxpStatvfsPacket) MarshalBinary() ([]byte, error) {
- l := 1 + 4 + // type(byte) + uint32
- len(p.Path) +
- len("statvfs@openssh.com")
+func (p *sshFxpStatvfsPacket) MarshalBinary() ([]byte, error) {
+ const ext = "statvfs@openssh.com"
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
+ 4 + len(ext) +
+ 4 + len(p.Path)
- b := make([]byte, 0, l)
- b = append(b, ssh_FXP_EXTENDED)
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpExtended)
b = marshalUint32(b, p.ID)
- b = marshalString(b, "statvfs@openssh.com")
+ b = marshalString(b, ext)
b = marshalString(b, p.Path)
+
return b, nil
}
@@ -856,12 +1112,42 @@ func (p *StatVFS) FreeSpace() uint64 {
return p.Frsize * p.Bfree
}
-// Convert to ssh_FXP_EXTENDED_REPLY packet binary format
-func (p *StatVFS) MarshalBinary() ([]byte, error) {
+// marshalPacket converts to ssh_FXP_EXTENDED_REPLY packet binary format
+func (p *StatVFS) marshalPacket() ([]byte, []byte, error) {
+ header := []byte{0, 0, 0, 0, sshFxpExtendedReply}
+
var buf bytes.Buffer
- buf.Write([]byte{ssh_FXP_EXTENDED_REPLY})
err := binary.Write(&buf, binary.BigEndian, p)
- return buf.Bytes(), err
+
+ return header, buf.Bytes(), err
+}
+
+// MarshalBinary encodes the StatVFS as an SSH_FXP_EXTENDED_REPLY packet.
+func (p *StatVFS) MarshalBinary() ([]byte, error) {
+ header, payload, err := p.marshalPacket()
+ return append(header, payload...), err
+}
+
+type sshFxpFsyncPacket struct {
+ ID uint32
+ Handle string
+}
+
+func (p *sshFxpFsyncPacket) id() uint32 { return p.ID }
+
+func (p *sshFxpFsyncPacket) MarshalBinary() ([]byte, error) {
+ const ext = "fsync@openssh.com"
+ l := 4 + 1 + 4 + // uint32(length) + byte(type) + uint32(id)
+ 4 + len(ext) +
+ 4 + len(p.Handle)
+
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpExtended)
+ b = marshalUint32(b, p.ID)
+ b = marshalString(b, ext)
+ b = marshalString(b, p.Handle)
+
+ return b, nil
}
type sshFxpExtendedPacket struct {
@@ -873,17 +1159,17 @@ type sshFxpExtendedPacket struct {
}
}
-func (p sshFxpExtendedPacket) id() uint32 { return p.ID }
-func (p sshFxpExtendedPacket) readonly() bool {
+func (p *sshFxpExtendedPacket) id() uint32 { return p.ID }
+func (p *sshFxpExtendedPacket) readonly() bool {
if p.SpecificPacket == nil {
return true
}
return p.SpecificPacket.readonly()
}
-func (p sshFxpExtendedPacket) respond(svr *Server) responsePacket {
+func (p *sshFxpExtendedPacket) respond(svr *Server) responsePacket {
if p.SpecificPacket == nil {
- return statusFromError(p, nil)
+ return statusFromError(p.ID, nil)
}
return p.SpecificPacket.respond(svr)
}
@@ -903,8 +1189,10 @@ func (p *sshFxpExtendedPacket) UnmarshalBinary(b []byte) error {
p.SpecificPacket = &sshFxpExtendedPacketStatVFS{}
case "posix-rename@openssh.com":
p.SpecificPacket = &sshFxpExtendedPacketPosixRename{}
+ case "hardlink@openssh.com":
+ p.SpecificPacket = &sshFxpExtendedPacketHardlink{}
default:
- return errors.Wrapf(errUnknownExtendedPacket, "packet type %v", p.SpecificPacket)
+ return fmt.Errorf("packet type %v: %w", p.SpecificPacket, errUnknownExtendedPacket)
}
return p.SpecificPacket.UnmarshalBinary(bOrig)
@@ -916,8 +1204,8 @@ type sshFxpExtendedPacketStatVFS struct {
Path string
}
-func (p sshFxpExtendedPacketStatVFS) id() uint32 { return p.ID }
-func (p sshFxpExtendedPacketStatVFS) readonly() bool { return true }
+func (p *sshFxpExtendedPacketStatVFS) id() uint32 { return p.ID }
+func (p *sshFxpExtendedPacketStatVFS) readonly() bool { return true }
func (p *sshFxpExtendedPacketStatVFS) UnmarshalBinary(b []byte) error {
var err error
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
@@ -937,8 +1225,8 @@ type sshFxpExtendedPacketPosixRename struct {
Newpath string
}
-func (p sshFxpExtendedPacketPosixRename) id() uint32 { return p.ID }
-func (p sshFxpExtendedPacketPosixRename) readonly() bool { return false }
+func (p *sshFxpExtendedPacketPosixRename) id() uint32 { return p.ID }
+func (p *sshFxpExtendedPacketPosixRename) readonly() bool { return false }
func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error {
var err error
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
@@ -953,7 +1241,36 @@ func (p *sshFxpExtendedPacketPosixRename) UnmarshalBinary(b []byte) error {
return nil
}
-func (p sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket {
+func (p *sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket {
err := os.Rename(p.Oldpath, p.Newpath)
- return statusFromError(p, err)
+ return statusFromError(p.ID, err)
+}
+
+type sshFxpExtendedPacketHardlink struct {
+ ID uint32
+ ExtendedRequest string
+ Oldpath string
+ Newpath string
+}
+
+// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
+func (p *sshFxpExtendedPacketHardlink) id() uint32 { return p.ID }
+func (p *sshFxpExtendedPacketHardlink) readonly() bool { return true }
+func (p *sshFxpExtendedPacketHardlink) UnmarshalBinary(b []byte) error {
+ var err error
+ if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
+ return err
+ } else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil {
+ return err
+ } else if p.Oldpath, b, err = unmarshalStringSafe(b); err != nil {
+ return err
+ } else if p.Newpath, _, err = unmarshalStringSafe(b); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (p *sshFxpExtendedPacketHardlink) respond(s *Server) responsePacket {
+ err := os.Link(p.Oldpath, p.Newpath)
+ return statusFromError(p.ID, err)
}
diff --git a/vendor/github.com/pkg/sftp/pool.go b/vendor/github.com/pkg/sftp/pool.go
new file mode 100644
index 00000000..36126290
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/pool.go
@@ -0,0 +1,79 @@
+package sftp
+
+// bufPool provides a pool of byte-slices to be reused in various parts of the package.
+// It is safe to use concurrently through a pointer.
+type bufPool struct {
+ ch chan []byte
+ blen int
+}
+
+func newBufPool(depth, bufLen int) *bufPool {
+ return &bufPool{
+ ch: make(chan []byte, depth),
+ blen: bufLen,
+ }
+}
+
+func (p *bufPool) Get() []byte {
+ if p.blen <= 0 {
+ panic("bufPool: new buffer creation length must be greater than zero")
+ }
+
+ for {
+ select {
+ case b := <-p.ch:
+ if cap(b) < p.blen {
+ // just in case: throw away any buffer with insufficient capacity.
+ continue
+ }
+
+ return b[:p.blen]
+
+ default:
+ return make([]byte, p.blen)
+ }
+ }
+}
+
+func (p *bufPool) Put(b []byte) {
+ if p == nil {
+ // functional default: no reuse.
+ return
+ }
+
+ if cap(b) < p.blen || cap(b) > p.blen*2 {
+ // DO NOT reuse buffers with insufficient capacity.
+ // This could cause panics when resizing to p.blen.
+
+ // DO NOT reuse buffers with excessive capacity.
+ // This could cause memory leaks.
+ return
+ }
+
+ select {
+ case p.ch <- b:
+ default:
+ }
+}
+
+type resChanPool chan chan result
+
+func newResChanPool(depth int) resChanPool {
+ return make(chan chan result, depth)
+}
+
+func (p resChanPool) Get() chan result {
+ select {
+ case ch := <-p:
+ return ch
+ default:
+ return make(chan result, 1)
+ }
+}
+
+func (p resChanPool) Put(ch chan result) {
+ select {
+ case p <- ch:
+ default:
+ }
+}
diff --git a/vendor/github.com/pkg/sftp/request-attrs.go b/vendor/github.com/pkg/sftp/request-attrs.go
index 31b1eaa6..b5c95b4a 100644
--- a/vendor/github.com/pkg/sftp/request-attrs.go
+++ b/vendor/github.com/pkg/sftp/request-attrs.go
@@ -5,7 +5,7 @@ package sftp
// request and AttrFlags() and Attributes() when working with SetStat requests.
import "os"
-// File Open and Write Flags. Correlate directly with with os.OpenFile flags
+// FileOpenFlags defines Open and Write Flags. Correlate directly with with os.OpenFile flags
// (https://golang.org/pkg/os/#pkg-constants).
type FileOpenFlags struct {
Read, Write, Append, Creat, Trunc, Excl bool
@@ -13,12 +13,12 @@ type FileOpenFlags struct {
func newFileOpenFlags(flags uint32) FileOpenFlags {
return FileOpenFlags{
- Read: flags&ssh_FXF_READ != 0,
- Write: flags&ssh_FXF_WRITE != 0,
- Append: flags&ssh_FXF_APPEND != 0,
- Creat: flags&ssh_FXF_CREAT != 0,
- Trunc: flags&ssh_FXF_TRUNC != 0,
- Excl: flags&ssh_FXF_EXCL != 0,
+ Read: flags&sshFxfRead != 0,
+ Write: flags&sshFxfWrite != 0,
+ Append: flags&sshFxfAppend != 0,
+ Creat: flags&sshFxfCreat != 0,
+ Trunc: flags&sshFxfTrunc != 0,
+ Excl: flags&sshFxfExcl != 0,
}
}
@@ -28,7 +28,7 @@ func (r *Request) Pflags() FileOpenFlags {
return newFileOpenFlags(r.Flags)
}
-// Flags that indicate whether SFTP file attributes were passed. When a flag is
+// FileAttrFlags that indicate whether SFTP file attributes were passed. When a flag is
// true the corresponding attribute should be available from the FileStat
// object returned by Attributes method. Used with SetStat.
type FileAttrFlags struct {
@@ -37,14 +37,14 @@ type FileAttrFlags struct {
func newFileAttrFlags(flags uint32) FileAttrFlags {
return FileAttrFlags{
- Size: (flags & ssh_FILEXFER_ATTR_SIZE) != 0,
- UidGid: (flags & ssh_FILEXFER_ATTR_UIDGID) != 0,
- Permissions: (flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0,
- Acmodtime: (flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0,
+ Size: (flags & sshFileXferAttrSize) != 0,
+ UidGid: (flags & sshFileXferAttrUIDGID) != 0,
+ Permissions: (flags & sshFileXferAttrPermissions) != 0,
+ Acmodtime: (flags & sshFileXferAttrACmodTime) != 0,
}
}
-// FileAttrFlags returns a FileAttrFlags boolean struct based on the
+// AttrFlags returns a FileAttrFlags boolean struct based on the
// bitmap/uint32 file attribute flags from the SFTP packaet.
func (r *Request) AttrFlags() FileAttrFlags {
return newFileAttrFlags(r.Flags)
@@ -55,9 +55,9 @@ func (a FileStat) FileMode() os.FileMode {
return os.FileMode(a.Mode)
}
-// Attributres parses file attributes byte blob and return them in a
+// Attributes parses file attributes byte blob and return them in a
// FileStat object.
func (r *Request) Attributes() *FileStat {
- fs, _ := getFileStat(r.Flags, r.Attrs)
+ fs, _ := unmarshalFileStat(r.Flags, r.Attrs)
return fs
}
diff --git a/vendor/github.com/pkg/sftp/request-errors.go b/vendor/github.com/pkg/sftp/request-errors.go
index 00451e74..6505b5c7 100644
--- a/vendor/github.com/pkg/sftp/request-errors.go
+++ b/vendor/github.com/pkg/sftp/request-errors.go
@@ -1,42 +1,54 @@
package sftp
+type fxerr uint32
+
// Error types that match the SFTP's SSH_FXP_STATUS codes. Gives you more
// direct control of the errors being sent vs. letting the library work them
// out from the standard os/io errors.
+const (
+ ErrSSHFxOk = fxerr(sshFxOk)
+ ErrSSHFxEOF = fxerr(sshFxEOF)
+ ErrSSHFxNoSuchFile = fxerr(sshFxNoSuchFile)
+ ErrSSHFxPermissionDenied = fxerr(sshFxPermissionDenied)
+ ErrSSHFxFailure = fxerr(sshFxFailure)
+ ErrSSHFxBadMessage = fxerr(sshFxBadMessage)
+ ErrSSHFxNoConnection = fxerr(sshFxNoConnection)
+ ErrSSHFxConnectionLost = fxerr(sshFxConnectionLost)
+ ErrSSHFxOpUnsupported = fxerr(sshFxOPUnsupported)
+)
-type fxerr uint32
-
+// Deprecated error types, these are aliases for the new ones, please use the new ones directly
const (
- ErrSshFxOk = fxerr(ssh_FX_OK)
- ErrSshFxEof = fxerr(ssh_FX_EOF)
- ErrSshFxNoSuchFile = fxerr(ssh_FX_NO_SUCH_FILE)
- ErrSshFxPermissionDenied = fxerr(ssh_FX_PERMISSION_DENIED)
- ErrSshFxFailure = fxerr(ssh_FX_FAILURE)
- ErrSshFxBadMessage = fxerr(ssh_FX_BAD_MESSAGE)
- ErrSshFxNoConnection = fxerr(ssh_FX_NO_CONNECTION)
- ErrSshFxConnectionLost = fxerr(ssh_FX_CONNECTION_LOST)
- ErrSshFxOpUnsupported = fxerr(ssh_FX_OP_UNSUPPORTED)
+ ErrSshFxOk = ErrSSHFxOk
+ ErrSshFxEof = ErrSSHFxEOF
+ ErrSshFxNoSuchFile = ErrSSHFxNoSuchFile
+ ErrSshFxPermissionDenied = ErrSSHFxPermissionDenied
+ ErrSshFxFailure = ErrSSHFxFailure
+ ErrSshFxBadMessage = ErrSSHFxBadMessage
+ ErrSshFxNoConnection = ErrSSHFxNoConnection
+ ErrSshFxConnectionLost = ErrSSHFxConnectionLost
+ ErrSshFxOpUnsupported = ErrSSHFxOpUnsupported
)
func (e fxerr) Error() string {
switch e {
- case ErrSshFxOk:
+ case ErrSSHFxOk:
return "OK"
- case ErrSshFxEof:
+ case ErrSSHFxEOF:
return "EOF"
- case ErrSshFxNoSuchFile:
- return "No Such File"
- case ErrSshFxPermissionDenied:
- return "Permission Denied"
- case ErrSshFxBadMessage:
- return "Bad Message"
- case ErrSshFxNoConnection:
- return "No Connection"
- case ErrSshFxConnectionLost:
- return "Connection Lost"
- case ErrSshFxOpUnsupported:
- return "Operation Unsupported"
+ case ErrSSHFxNoSuchFile:
+ return "no such file"
+ case ErrSSHFxPermissionDenied:
+ return "permission denied"
+ case ErrSSHFxBadMessage:
+ return "bad message"
+ case ErrSSHFxNoConnection:
+ return "no connection"
+ case ErrSSHFxConnectionLost:
+ return "connection lost"
+ case ErrSSHFxOpUnsupported:
+ return "operation unsupported"
default:
- return "Failure"
+ return "failure"
}
}
diff --git a/vendor/github.com/pkg/sftp/request-example.go b/vendor/github.com/pkg/sftp/request-example.go
index e5abd184..ba22bcd0 100644
--- a/vendor/github.com/pkg/sftp/request-example.go
+++ b/vendor/github.com/pkg/sftp/request-example.go
@@ -5,67 +5,139 @@ package sftp
// works as a very simple filesystem with simple flat key-value lookup system.
import (
- "bytes"
- "fmt"
+ "errors"
"io"
"os"
- "path/filepath"
+ "path"
"sort"
+ "strings"
"sync"
"syscall"
"time"
)
+const maxSymlinkFollows = 5
+
+var errTooManySymlinks = errors.New("too many symbolic links")
+
// InMemHandler returns a Hanlders object with the test handlers.
func InMemHandler() Handlers {
root := &root{
- files: make(map[string]*memFile),
+ rootFile: &memFile{name: "/", modtime: time.Now(), isdir: true},
+ files: make(map[string]*memFile),
}
- root.memFile = newMemFile("/", true)
return Handlers{root, root, root, root}
}
// Example Handlers
func (fs *root) Fileread(r *Request) (io.ReaderAt, error) {
+ flags := r.Pflags()
+ if !flags.Read {
+ // sanity check
+ return nil, os.ErrInvalid
+ }
+
+ return fs.OpenFile(r)
+}
+
+func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
+ flags := r.Pflags()
+ if !flags.Write {
+ // sanity check
+ return nil, os.ErrInvalid
+ }
+
+ return fs.OpenFile(r)
+}
+
+func (fs *root) OpenFile(r *Request) (WriterAtReaderAt, error) {
if fs.mockErr != nil {
return nil, fs.mockErr
}
_ = r.WithContext(r.Context()) // initialize context for deadlock testing
- fs.filesLock.Lock()
- defer fs.filesLock.Unlock()
- file, err := fs.fetch(r.Filepath)
+
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+
+ return fs.openfile(r.Filepath, r.Flags)
+}
+
+func (fs *root) putfile(pathname string, file *memFile) error {
+ pathname, err := fs.canonName(pathname)
if err != nil {
- return nil, err
+ return err
}
- if file.symlink != "" {
- file, err = fs.fetch(file.symlink)
- if err != nil {
- return nil, err
- }
+
+ if !strings.HasPrefix(pathname, "/") {
+ return os.ErrInvalid
}
- return file.ReaderAt()
-}
-func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
- if fs.mockErr != nil {
- return nil, fs.mockErr
+ if _, err := fs.lfetch(pathname); err != os.ErrNotExist {
+ return os.ErrExist
}
- _ = r.WithContext(r.Context()) // initialize context for deadlock testing
- fs.filesLock.Lock()
- defer fs.filesLock.Unlock()
- file, err := fs.fetch(r.Filepath)
+
+ file.name = pathname
+ fs.files[pathname] = file
+
+ return nil
+}
+
+func (fs *root) openfile(pathname string, flags uint32) (*memFile, error) {
+ pflags := newFileOpenFlags(flags)
+
+ file, err := fs.fetch(pathname)
if err == os.ErrNotExist {
- dir, err := fs.fetch(filepath.Dir(r.Filepath))
- if err != nil {
+ if !pflags.Creat {
+ return nil, os.ErrNotExist
+ }
+
+ var count int
+ // You can create files through dangling symlinks.
+ link, err := fs.lfetch(pathname)
+ for err == nil && link.symlink != "" {
+ if pflags.Excl {
+ // unless you also passed in O_EXCL
+ return nil, os.ErrInvalid
+ }
+
+ if count++; count > maxSymlinkFollows {
+ return nil, errTooManySymlinks
+ }
+
+ pathname = link.symlink
+ link, err = fs.lfetch(pathname)
+ }
+
+ file := &memFile{
+ modtime: time.Now(),
+ }
+
+ if err := fs.putfile(pathname, file); err != nil {
return nil, err
}
- if !dir.isdir {
- return nil, os.ErrInvalid
+
+ return file, nil
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ if pflags.Creat && pflags.Excl {
+ return nil, os.ErrExist
+ }
+
+ if file.IsDir() {
+ return nil, os.ErrInvalid
+ }
+
+ if pflags.Trunc {
+ if err := file.Truncate(0); err != nil {
+ return nil, err
}
- file = newMemFile(r.Filepath, false)
- fs.files[r.Filepath] = file
}
- return file.WriterAt()
+
+ return file, nil
}
func (fs *root) Filecmd(r *Request) error {
@@ -73,44 +145,211 @@ func (fs *root) Filecmd(r *Request) error {
return fs.mockErr
}
_ = r.WithContext(r.Context()) // initialize context for deadlock testing
- fs.filesLock.Lock()
- defer fs.filesLock.Unlock()
+
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+
switch r.Method {
case "Setstat":
- return nil
- case "Rename":
- file, err := fs.fetch(r.Filepath)
+ file, err := fs.openfile(r.Filepath, sshFxfWrite)
if err != nil {
return err
}
- if _, ok := fs.files[r.Target]; ok {
- return &os.LinkError{Op: "rename", Old: r.Filepath, New: r.Target,
- Err: fmt.Errorf("dest file exists")}
+
+ if r.AttrFlags().Size {
+ return file.Truncate(int64(r.Attributes().Size))
}
- file.name = r.Target
- fs.files[r.Target] = file
- delete(fs.files, r.Filepath)
- case "Rmdir", "Remove":
- _, err := fs.fetch(filepath.Dir(r.Filepath))
- if err != nil {
- return err
+
+ return nil
+
+ case "Rename":
+ // SFTP-v2: "It is an error if there already exists a file with the name specified by newpath."
+ // This varies from the POSIX specification, which allows limited replacement of target files.
+ if fs.exists(r.Target) {
+ return os.ErrExist
}
- delete(fs.files, r.Filepath)
+
+ return fs.rename(r.Filepath, r.Target)
+
+ case "Rmdir":
+ return fs.rmdir(r.Filepath)
+
+ case "Remove":
+ // IEEE 1003.1 remove explicitly can unlink files and remove empty directories.
+ // We use instead here the semantics of unlink, which is allowed to be restricted against directories.
+ return fs.unlink(r.Filepath)
+
case "Mkdir":
- _, err := fs.fetch(filepath.Dir(r.Filepath))
- if err != nil {
- return err
- }
- fs.files[r.Filepath] = newMemFile(r.Filepath, true)
+ return fs.mkdir(r.Filepath)
+
+ case "Link":
+ return fs.link(r.Filepath, r.Target)
+
case "Symlink":
- _, err := fs.fetch(r.Filepath)
- if err != nil {
- return err
+ // NOTE: r.Filepath is the target, and r.Target is the linkpath.
+ return fs.symlink(r.Filepath, r.Target)
+ }
+
+ return errors.New("unsupported")
+}
+
+func (fs *root) rename(oldpath, newpath string) error {
+ file, err := fs.lfetch(oldpath)
+ if err != nil {
+ return err
+ }
+
+ newpath, err = fs.canonName(newpath)
+ if err != nil {
+ return err
+ }
+
+ if !strings.HasPrefix(newpath, "/") {
+ return os.ErrInvalid
+ }
+
+ target, err := fs.lfetch(newpath)
+ if err != os.ErrNotExist {
+ if target == file {
+ // IEEE 1003.1: if oldpath and newpath are the same directory entry,
+ // then return no error, and perform no further action.
+ return nil
+ }
+
+ switch {
+ case file.IsDir():
+ // IEEE 1003.1: if oldpath is a directory, and newpath exists,
+ // then newpath must be a directory, and empty.
+ // It is to be removed prior to rename.
+ if err := fs.rmdir(newpath); err != nil {
+ return err
+ }
+
+ case target.IsDir():
+ // IEEE 1003.1: if oldpath is not a directory, and newpath exists,
+ // then newpath may not be a directory.
+ return syscall.EISDIR
+ }
+ }
+
+ fs.files[newpath] = file
+
+ if file.IsDir() {
+ dirprefix := file.name + "/"
+
+ for name, file := range fs.files {
+ if strings.HasPrefix(name, dirprefix) {
+ newname := path.Join(newpath, strings.TrimPrefix(name, dirprefix))
+
+ fs.files[newname] = file
+ file.name = newname
+ delete(fs.files, name)
+ }
}
- link := newMemFile(r.Target, false)
- link.symlink = r.Filepath
- fs.files[r.Target] = link
}
+
+ file.name = newpath
+ delete(fs.files, oldpath)
+
+ return nil
+}
+
+func (fs *root) PosixRename(r *Request) error {
+ if fs.mockErr != nil {
+ return fs.mockErr
+ }
+ _ = r.WithContext(r.Context()) // initialize context for deadlock testing
+
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+
+ return fs.rename(r.Filepath, r.Target)
+}
+
+func (fs *root) StatVFS(r *Request) (*StatVFS, error) {
+ if fs.mockErr != nil {
+ return nil, fs.mockErr
+ }
+
+ return getStatVFSForPath(r.Filepath)
+}
+
+func (fs *root) mkdir(pathname string) error {
+ dir := &memFile{
+ modtime: time.Now(),
+ isdir: true,
+ }
+
+ return fs.putfile(pathname, dir)
+}
+
+func (fs *root) rmdir(pathname string) error {
+ // IEEE 1003.1: If pathname is a symlink, then rmdir should fail with ENOTDIR.
+ dir, err := fs.lfetch(pathname)
+ if err != nil {
+ return err
+ }
+
+ if !dir.IsDir() {
+ return syscall.ENOTDIR
+ }
+
+ // use the dir‘s internal name not the pathname we passed in.
+ // the dir.name is always the canonical name of a directory.
+ pathname = dir.name
+
+ for name := range fs.files {
+ if path.Dir(name) == pathname {
+ return errors.New("directory not empty")
+ }
+ }
+
+ delete(fs.files, pathname)
+
+ return nil
+}
+
+func (fs *root) link(oldpath, newpath string) error {
+ file, err := fs.lfetch(oldpath)
+ if err != nil {
+ return err
+ }
+
+ if file.IsDir() {
+ return errors.New("hard link not allowed for directory")
+ }
+
+ return fs.putfile(newpath, file)
+}
+
+// symlink() creates a symbolic link named `linkpath` which contains the string `target`.
+// NOTE! This would be called with `symlink(req.Filepath, req.Target)` due to different semantics.
+func (fs *root) symlink(target, linkpath string) error {
+ link := &memFile{
+ modtime: time.Now(),
+ symlink: target,
+ }
+
+ return fs.putfile(linkpath, link)
+}
+
+func (fs *root) unlink(pathname string) error {
+ // does not follow symlinks!
+ file, err := fs.lfetch(pathname)
+ if err != nil {
+ return err
+ }
+
+ if file.IsDir() {
+ // IEEE 1003.1: implementations may opt out of allowing the unlinking of directories.
+ // SFTP-v2: SSH_FXP_REMOVE may not remove directories.
+ return os.ErrInvalid
+ }
+
+ // DO NOT use the file’s internal name.
+ // because of hard-links files cannot have a single canonical name.
+ delete(fs.files, pathname)
+
return nil
}
@@ -134,51 +373,113 @@ func (fs *root) Filelist(r *Request) (ListerAt, error) {
return nil, fs.mockErr
}
_ = r.WithContext(r.Context()) // initialize context for deadlock testing
- fs.filesLock.Lock()
- defer fs.filesLock.Unlock()
- file, err := fs.fetch(r.Filepath)
- if err != nil {
- return nil, err
- }
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
switch r.Method {
case "List":
- if !file.IsDir() {
- return nil, syscall.ENOTDIR
- }
- ordered_names := []string{}
- for fn, _ := range fs.files {
- if filepath.Dir(fn) == r.Filepath {
- ordered_names = append(ordered_names, fn)
- }
- }
- sort.Strings(ordered_names)
- list := make([]os.FileInfo, len(ordered_names))
- for i, fn := range ordered_names {
- list[i] = fs.files[fn]
+ files, err := fs.readdir(r.Filepath)
+ if err != nil {
+ return nil, err
}
- return listerat(list), nil
+ return listerat(files), nil
+
case "Stat":
- return listerat([]os.FileInfo{file}), nil
+ file, err := fs.fetch(r.Filepath)
+ if err != nil {
+ return nil, err
+ }
+ return listerat{file}, nil
+
case "Readlink":
- if file.symlink != "" {
- file, err = fs.fetch(file.symlink)
- if err != nil {
- return nil, err
- }
+ symlink, err := fs.readlink(r.Filepath)
+ if err != nil {
+ return nil, err
+ }
+
+ // SFTP-v2: The server will respond with a SSH_FXP_NAME packet containing only
+ // one name and a dummy attributes value.
+ return listerat{
+ &memFile{
+ name: symlink,
+ err: os.ErrNotExist, // prevent accidental use as a reader/writer.
+ },
+ }, nil
+ }
+
+ return nil, errors.New("unsupported")
+}
+
+func (fs *root) readdir(pathname string) ([]os.FileInfo, error) {
+ dir, err := fs.fetch(pathname)
+ if err != nil {
+ return nil, err
+ }
+
+ if !dir.IsDir() {
+ return nil, syscall.ENOTDIR
+ }
+
+ var files []os.FileInfo
+
+ for name, file := range fs.files {
+ if path.Dir(name) == dir.name {
+ files = append(files, file)
}
- return listerat([]os.FileInfo{file}), nil
}
- return nil, nil
+
+ sort.Slice(files, func(i, j int) bool { return files[i].Name() < files[j].Name() })
+
+ return files, nil
+}
+
+func (fs *root) readlink(pathname string) (string, error) {
+ file, err := fs.lfetch(pathname)
+ if err != nil {
+ return "", err
+ }
+
+ if file.symlink == "" {
+ return "", os.ErrInvalid
+ }
+
+ return file.symlink, nil
+}
+
+// implements LstatFileLister interface
+func (fs *root) Lstat(r *Request) (ListerAt, error) {
+ if fs.mockErr != nil {
+ return nil, fs.mockErr
+ }
+ _ = r.WithContext(r.Context()) // initialize context for deadlock testing
+
+ fs.mu.Lock()
+ defer fs.mu.Unlock()
+
+ file, err := fs.lfetch(r.Filepath)
+ if err != nil {
+ return nil, err
+ }
+ return listerat{file}, nil
+}
+
+// implements RealpathFileLister interface
+func (fs *root) Realpath(p string) string {
+ if fs.startDirectory == "" || fs.startDirectory == "/" {
+ return cleanPath(p)
+ }
+ return cleanPathWithBase(fs.startDirectory, p)
}
// In memory file-system-y thing that the Hanlders live on
type root struct {
- *memFile
- files map[string]*memFile
- filesLock sync.Mutex
- mockErr error
+ rootFile *memFile
+ mockErr error
+ startDirectory string
+
+ mu sync.Mutex
+ files map[string]*memFile
}
// Set a mocked error that the next handler call will return.
@@ -187,48 +488,107 @@ func (fs *root) returnErr(err error) {
fs.mockErr = err
}
-func (fs *root) fetch(path string) (*memFile, error) {
+func (fs *root) lfetch(path string) (*memFile, error) {
if path == "/" {
- return fs.memFile, nil
+ return fs.rootFile, nil
}
- if file, ok := fs.files[path]; ok {
- return file, nil
+
+ file, ok := fs.files[path]
+ if file == nil {
+ if ok {
+ delete(fs.files, path)
+ }
+
+ return nil, os.ErrNotExist
+ }
+
+ return file, nil
+}
+
+// canonName returns the “canonical” name of a file, that is:
+// if the directory of the pathname is a symlink, it follows that symlink to the valid directory name.
+// this is relatively easy, since `dir.name` will be the only valid canonical path for a directory.
+func (fs *root) canonName(pathname string) (string, error) {
+ dirname, filename := path.Dir(pathname), path.Base(pathname)
+
+ dir, err := fs.fetch(dirname)
+ if err != nil {
+ return "", err
+ }
+
+ if !dir.IsDir() {
+ return "", syscall.ENOTDIR
+ }
+
+ return path.Join(dir.name, filename), nil
+}
+
+func (fs *root) exists(path string) bool {
+ path, err := fs.canonName(path)
+ if err != nil {
+ return false
+ }
+
+ _, err = fs.lfetch(path)
+
+ return err != os.ErrNotExist
+}
+
+func (fs *root) fetch(path string) (*memFile, error) {
+ file, err := fs.lfetch(path)
+ if err != nil {
+ return nil, err
+ }
+
+ var count int
+ for file.symlink != "" {
+ if count++; count > maxSymlinkFollows {
+ return nil, errTooManySymlinks
+ }
+
+ file, err = fs.lfetch(file.symlink)
+ if err != nil {
+ return nil, err
+ }
}
- return nil, os.ErrNotExist
+
+ return file, nil
}
-// Implements os.FileInfo, Reader and Writer interfaces.
+// Implements os.FileInfo, io.ReaderAt and io.WriterAt interfaces.
// These are the 3 interfaces necessary for the Handlers.
+// Implements the optional interface TransferError.
type memFile struct {
- name string
- modtime time.Time
- symlink string
- isdir bool
- content []byte
- contentLock sync.RWMutex
-}
-
-// factory to make sure modtime is set
-func newMemFile(name string, isdir bool) *memFile {
- return &memFile{
- name: name,
- modtime: time.Now(),
- isdir: isdir,
- }
+ name string
+ modtime time.Time
+ symlink string
+ isdir bool
+
+ mu sync.RWMutex
+ content []byte
+ err error
}
+// These are helper functions, they must be called while holding the memFile.mu mutex
+func (f *memFile) size() int64 { return int64(len(f.content)) }
+func (f *memFile) grow(n int64) { f.content = append(f.content, make([]byte, n)...) }
+
// Have memFile fulfill os.FileInfo interface
-func (f *memFile) Name() string { return filepath.Base(f.name) }
-func (f *memFile) Size() int64 { return int64(len(f.content)) }
+func (f *memFile) Name() string { return path.Base(f.name) }
+func (f *memFile) Size() int64 {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ return f.size()
+}
func (f *memFile) Mode() os.FileMode {
- ret := os.FileMode(0644)
if f.isdir {
- ret = os.FileMode(0755) | os.ModeDir
+ return os.FileMode(0755) | os.ModeDir
}
if f.symlink != "" {
- ret = os.FileMode(0777) | os.ModeSymlink
+ return os.FileMode(0777) | os.ModeSymlink
}
- return ret
+ return os.FileMode(0644)
}
func (f *memFile) ModTime() time.Time { return f.modtime }
func (f *memFile) IsDir() bool { return f.isdir }
@@ -236,32 +596,71 @@ func (f *memFile) Sys() interface{} {
return fakeFileInfoSys()
}
-// Read/Write
-func (f *memFile) ReaderAt() (io.ReaderAt, error) {
- if f.isdir {
- return nil, os.ErrInvalid
+func (f *memFile) ReadAt(b []byte, off int64) (int, error) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ if f.err != nil {
+ return 0, f.err
}
- return bytes.NewReader(f.content), nil
-}
-func (f *memFile) WriterAt() (io.WriterAt, error) {
- if f.isdir {
- return nil, os.ErrInvalid
+ if off < 0 {
+ return 0, errors.New("memFile.ReadAt: negative offset")
}
- return f, nil
+
+ if off >= f.size() {
+ return 0, io.EOF
+ }
+
+ n := copy(b, f.content[off:])
+ if n < len(b) {
+ return n, io.EOF
+ }
+
+ return n, nil
}
-func (f *memFile) WriteAt(p []byte, off int64) (int, error) {
+
+func (f *memFile) WriteAt(b []byte, off int64) (int, error) {
// fmt.Println(string(p), off)
// mimic write delays, should be optional
- time.Sleep(time.Microsecond * time.Duration(len(p)))
- f.contentLock.Lock()
- defer f.contentLock.Unlock()
- plen := len(p) + int(off)
- if plen >= len(f.content) {
- nc := make([]byte, plen)
- copy(nc, f.content)
- f.content = nc
- }
- copy(f.content[off:], p)
- return len(p), nil
+ time.Sleep(time.Microsecond * time.Duration(len(b)))
+
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ if f.err != nil {
+ return 0, f.err
+ }
+
+ grow := int64(len(b)) + off - f.size()
+ if grow > 0 {
+ f.grow(grow)
+ }
+
+ return copy(f.content[off:], b), nil
+}
+
+func (f *memFile) Truncate(size int64) error {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ if f.err != nil {
+ return f.err
+ }
+
+ grow := size - f.size()
+ if grow <= 0 {
+ f.content = f.content[:size]
+ } else {
+ f.grow(grow)
+ }
+
+ return nil
+}
+
+func (f *memFile) TransferError(err error) {
+ f.mu.Lock()
+ defer f.mu.Unlock()
+
+ f.err = err
}
diff --git a/vendor/github.com/pkg/sftp/request-interfaces.go b/vendor/github.com/pkg/sftp/request-interfaces.go
index f69cedc5..c8c424c9 100644
--- a/vendor/github.com/pkg/sftp/request-interfaces.go
+++ b/vendor/github.com/pkg/sftp/request-interfaces.go
@@ -5,6 +5,13 @@ import (
"os"
)
+// WriterAtReaderAt defines the interface to return when a file is to
+// be opened for reading and writing
+type WriterAtReaderAt interface {
+ io.WriterAt
+ io.ReaderAt
+}
+
// Interfaces are differentiated based on required returned values.
// All input arguments are to be pulled from Request (the only arg).
@@ -25,18 +32,45 @@ type FileReader interface {
// The request server code will call Close() on the returned io.WriterAt
// ojbect if an io.Closer type assertion succeeds.
// Note in cases of an error, the error text will be sent to the client.
+// Note when receiving an Append flag it is important to not open files using
+// O_APPEND if you plan to use WriteAt, as they conflict.
// Called for Methods: Put, Open
type FileWriter interface {
Filewrite(*Request) (io.WriterAt, error)
}
+// OpenFileWriter is a FileWriter that implements the generic OpenFile method.
+// You need to implement this optional interface if you want to be able
+// to read and write from/to the same handle.
+// Called for Methods: Open
+type OpenFileWriter interface {
+ FileWriter
+ OpenFile(*Request) (WriterAtReaderAt, error)
+}
+
// FileCmder should return an error
// Note in cases of an error, the error text will be sent to the client.
-// Called for Methods: Setstat, Rename, Rmdir, Mkdir, Symlink, Remove
+// Called for Methods: Setstat, Rename, Rmdir, Mkdir, Link, Symlink, Remove
type FileCmder interface {
Filecmd(*Request) error
}
+// PosixRenameFileCmder is a FileCmder that implements the PosixRename method.
+// If this interface is implemented PosixRename requests will call it
+// otherwise they will be handled in the same way as Rename
+type PosixRenameFileCmder interface {
+ FileCmder
+ PosixRename(*Request) error
+}
+
+// StatVFSFileCmder is a FileCmder that implements the StatVFS method.
+// You need to implement this interface if you want to handle statvfs requests.
+// Please also be sure that the statvfs@openssh.com extension is enabled
+type StatVFSFileCmder interface {
+ FileCmder
+ StatVFS(*Request) (*StatVFS, error)
+}
+
// FileLister should return an object that fulfils the ListerAt interface
// Note in cases of an error, the error text will be sent to the client.
// Called for Methods: List, Stat, Readlink
@@ -44,6 +78,31 @@ type FileLister interface {
Filelist(*Request) (ListerAt, error)
}
+// LstatFileLister is a FileLister that implements the Lstat method.
+// If this interface is implemented Lstat requests will call it
+// otherwise they will be handled in the same way as Stat
+type LstatFileLister interface {
+ FileLister
+ Lstat(*Request) (ListerAt, error)
+}
+
+// RealPathFileLister is a FileLister that implements the Realpath method.
+// We use "/" as start directory for relative paths, implementing this
+// interface you can customize the start directory.
+// You have to return an absolute POSIX path.
+type RealPathFileLister interface {
+ FileLister
+ RealPath(string) string
+}
+
+// NameLookupFileLister is a FileLister that implmeents the LookupUsername and LookupGroupName methods.
+// If this interface is implemented, then longname ls formatting will use these to convert usernames and groupnames.
+type NameLookupFileLister interface {
+ FileLister
+ LookupUserName(string) string
+ LookupGroupName(string) string
+}
+
// ListerAt does for file lists what io.ReaderAt does for files.
// ListAt should return the number of entries copied and an io.EOF
// error if at end of list. This is testable by comparing how many you
@@ -53,3 +112,10 @@ type FileLister interface {
type ListerAt interface {
ListAt([]os.FileInfo, int64) (int, error)
}
+
+// TransferError is an optional interface that readerAt and writerAt
+// can implement to be notified about the error causing Serve() to exit
+// with the request still open
+type TransferError interface {
+ TransferError(err error)
+}
diff --git a/vendor/github.com/pkg/sftp/request-plan9.go b/vendor/github.com/pkg/sftp/request-plan9.go
new file mode 100644
index 00000000..2444da59
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/request-plan9.go
@@ -0,0 +1,34 @@
+// +build plan9
+
+package sftp
+
+import (
+ "path"
+ "path/filepath"
+ "syscall"
+)
+
+func fakeFileInfoSys() interface{} {
+ return &syscall.Dir{}
+}
+
+func testOsSys(sys interface{}) error {
+ return nil
+}
+
+func toLocalPath(p string) string {
+ lp := filepath.FromSlash(p)
+
+ if path.IsAbs(p) {
+ tmp := lp[1:]
+
+ if filepath.IsAbs(tmp) {
+ // If the FromSlash without any starting slashes is absolute,
+ // then we have a filepath encoded with a prefix '/'.
+ // e.g. "/#s/boot" to "#s/boot"
+ return tmp
+ }
+ }
+
+ return lp
+}
diff --git a/vendor/github.com/pkg/sftp/request-server.go b/vendor/github.com/pkg/sftp/request-server.go
index 2e99720a..5fa828bb 100644
--- a/vendor/github.com/pkg/sftp/request-server.go
+++ b/vendor/github.com/pkg/sftp/request-server.go
@@ -2,14 +2,12 @@ package sftp
import (
"context"
+ "errors"
"io"
"path"
"path/filepath"
"strconv"
"sync"
- "syscall"
-
- "github.com/pkg/errors"
)
var maxTxPacket uint32 = 1 << 15
@@ -24,40 +22,66 @@ type Handlers struct {
// RequestServer abstracts the sftp protocol with an http request-like protocol
type RequestServer struct {
+ Handlers Handlers
+
*serverConn
- Handlers Handlers
- pktMgr *packetManager
- openRequests map[string]*Request
- openRequestLock sync.RWMutex
- handleCount int
+ pktMgr *packetManager
+
+ mu sync.RWMutex
+ handleCount int
+ openRequests map[string]*Request
+}
+
+// A RequestServerOption is a function which applies configuration to a RequestServer.
+type RequestServerOption func(*RequestServer)
+
+// WithRSAllocator enable the allocator.
+// After processing a packet we keep in memory the allocated slices
+// and we reuse them for new packets.
+// The allocator is experimental
+func WithRSAllocator() RequestServerOption {
+ return func(rs *RequestServer) {
+ alloc := newAllocator()
+ rs.pktMgr.alloc = alloc
+ rs.conn.alloc = alloc
+ }
}
// NewRequestServer creates/allocates/returns new RequestServer.
-// Normally there there will be one server per user-session.
-func NewRequestServer(rwc io.ReadWriteCloser, h Handlers) *RequestServer {
+// Normally there will be one server per user-session.
+func NewRequestServer(rwc io.ReadWriteCloser, h Handlers, options ...RequestServerOption) *RequestServer {
svrConn := &serverConn{
conn: conn{
Reader: rwc,
WriteCloser: rwc,
},
}
- return &RequestServer{
- serverConn: svrConn,
- Handlers: h,
- pktMgr: newPktMgr(svrConn),
+ rs := &RequestServer{
+ Handlers: h,
+
+ serverConn: svrConn,
+ pktMgr: newPktMgr(svrConn),
+
openRequests: make(map[string]*Request),
}
+
+ for _, o := range options {
+ o(rs)
+ }
+ return rs
}
// New Open packet/Request
func (rs *RequestServer) nextRequest(r *Request) string {
- rs.openRequestLock.Lock()
- defer rs.openRequestLock.Unlock()
+ rs.mu.Lock()
+ defer rs.mu.Unlock()
+
rs.handleCount++
- handle := strconv.Itoa(rs.handleCount)
- r.handle = handle
- rs.openRequests[handle] = r
- return handle
+
+ r.handle = strconv.Itoa(rs.handleCount)
+ rs.openRequests[r.handle] = r
+
+ return r.handle
}
// Returns Request from openRequests, bool is false if it is missing.
@@ -66,77 +90,98 @@ func (rs *RequestServer) nextRequest(r *Request) string {
// you can do different things with. What you are doing with it are denoted by
// the first packet of that type (read/write/etc).
func (rs *RequestServer) getRequest(handle string) (*Request, bool) {
- rs.openRequestLock.RLock()
- defer rs.openRequestLock.RUnlock()
+ rs.mu.RLock()
+ defer rs.mu.RUnlock()
+
r, ok := rs.openRequests[handle]
return r, ok
}
// Close the Request and clear from openRequests map
func (rs *RequestServer) closeRequest(handle string) error {
- rs.openRequestLock.Lock()
- defer rs.openRequestLock.Unlock()
+ rs.mu.Lock()
+ defer rs.mu.Unlock()
+
if r, ok := rs.openRequests[handle]; ok {
delete(rs.openRequests, handle)
return r.close()
}
- return syscall.EBADF
+
+ return EBADF
}
// Close the read/write/closer to trigger exiting the main server loop
func (rs *RequestServer) Close() error { return rs.conn.Close() }
-// Serve requests for user session
-func (rs *RequestServer) Serve() error {
- ctx, cancel := context.WithCancel(context.Background())
- defer cancel()
- var wg sync.WaitGroup
- runWorker := func(ch chan orderedRequest) {
- wg.Add(1)
- go func() {
- defer wg.Done()
- if err := rs.packetWorker(ctx, ch); err != nil {
- rs.conn.Close() // shuts down recvPacket
- }
- }()
- }
- pktChan := rs.pktMgr.workerChan(runWorker)
+func (rs *RequestServer) serveLoop(pktChan chan<- orderedRequest) error {
+ defer close(pktChan) // shuts down sftpServerWorkers
var err error
var pkt requestPacket
var pktType uint8
var pktBytes []byte
+
for {
- pktType, pktBytes, err = rs.recvPacket()
+ pktType, pktBytes, err = rs.serverConn.recvPacket(rs.pktMgr.getNextOrderID())
if err != nil {
- break
+ // we don't care about releasing allocated pages here, the server will quit and the allocator freed
+ return err
}
pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
if err != nil {
- switch errors.Cause(err) {
- case errUnknownExtendedPacket:
- if err := rs.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
- debug("failed to send err packet: %v", err)
- rs.conn.Close() // shuts down recvPacket
- break
- }
+ switch {
+ case errors.Is(err, errUnknownExtendedPacket):
+ // do nothing
default:
debug("makePacket err: %v", err)
rs.conn.Close() // shuts down recvPacket
- break
+ return err
}
}
pktChan <- rs.pktMgr.newOrderedRequest(pkt)
}
+}
+
+// Serve requests for user session
+func (rs *RequestServer) Serve() error {
+ defer func() {
+ if rs.pktMgr.alloc != nil {
+ rs.pktMgr.alloc.Free()
+ }
+ }()
+
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+
+ var wg sync.WaitGroup
+ runWorker := func(ch chan orderedRequest) {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ if err := rs.packetWorker(ctx, ch); err != nil {
+ rs.conn.Close() // shuts down recvPacket
+ }
+ }()
+ }
+ pktChan := rs.pktMgr.workerChan(runWorker)
+
+ err := rs.serveLoop(pktChan)
- close(pktChan) // shuts down sftpServerWorkers
- wg.Wait() // wait for all workers to exit
+ wg.Wait() // wait for all workers to exit
+
+ rs.mu.Lock()
+ defer rs.mu.Unlock()
// make sure all open requests are properly closed
// (eg. possible on dropped connections, client crashes, etc.)
for handle, req := range rs.openRequests {
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ req.transferError(err)
+
delete(rs.openRequests, handle)
req.close()
}
@@ -144,76 +189,116 @@ func (rs *RequestServer) Serve() error {
return err
}
-func (rs *RequestServer) packetWorker(
- ctx context.Context, pktChan chan orderedRequest,
-) error {
+func (rs *RequestServer) packetWorker(ctx context.Context, pktChan chan orderedRequest) error {
for pkt := range pktChan {
+ orderID := pkt.orderID()
+ if epkt, ok := pkt.requestPacket.(*sshFxpExtendedPacket); ok {
+ if epkt.SpecificPacket != nil {
+ pkt.requestPacket = epkt.SpecificPacket
+ }
+ }
+
var rpkt responsePacket
switch pkt := pkt.requestPacket.(type) {
case *sshFxInitPacket:
- rpkt = sshFxVersionPacket{Version: sftpProtocolVersion}
+ rpkt = &sshFxVersionPacket{Version: sftpProtocolVersion, Extensions: sftpExtensions}
case *sshFxpClosePacket:
handle := pkt.getHandle()
- rpkt = statusFromError(pkt, rs.closeRequest(handle))
+ rpkt = statusFromError(pkt.ID, rs.closeRequest(handle))
case *sshFxpRealpathPacket:
- rpkt = cleanPacketPath(pkt)
+ var realPath string
+ if realPather, ok := rs.Handlers.FileList.(RealPathFileLister); ok {
+ realPath = realPather.RealPath(pkt.getPath())
+ } else {
+ realPath = cleanPath(pkt.getPath())
+ }
+ rpkt = cleanPacketPath(pkt, realPath)
case *sshFxpOpendirPacket:
request := requestFromPacket(ctx, pkt)
- rs.nextRequest(request)
+ handle := rs.nextRequest(request)
rpkt = request.opendir(rs.Handlers, pkt)
+ if _, ok := rpkt.(*sshFxpHandlePacket); !ok {
+ // if we return an error we have to remove the handle from the active ones
+ rs.closeRequest(handle)
+ }
case *sshFxpOpenPacket:
request := requestFromPacket(ctx, pkt)
- rs.nextRequest(request)
+ handle := rs.nextRequest(request)
rpkt = request.open(rs.Handlers, pkt)
+ if _, ok := rpkt.(*sshFxpHandlePacket); !ok {
+ // if we return an error we have to remove the handle from the active ones
+ rs.closeRequest(handle)
+ }
case *sshFxpFstatPacket:
handle := pkt.getHandle()
request, ok := rs.getRequest(handle)
if !ok {
- rpkt = statusFromError(pkt, syscall.EBADF)
+ rpkt = statusFromError(pkt.ID, EBADF)
} else {
request = NewRequest("Stat", request.Filepath)
- rpkt = request.call(rs.Handlers, pkt)
+ rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
}
+ case *sshFxpFsetstatPacket:
+ handle := pkt.getHandle()
+ request, ok := rs.getRequest(handle)
+ if !ok {
+ rpkt = statusFromError(pkt.ID, EBADF)
+ } else {
+ request = NewRequest("Setstat", request.Filepath)
+ rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
+ }
+ case *sshFxpExtendedPacketPosixRename:
+ request := NewRequest("PosixRename", pkt.Oldpath)
+ request.Target = pkt.Newpath
+ rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
+ case *sshFxpExtendedPacketStatVFS:
+ request := NewRequest("StatVFS", pkt.Path)
+ rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
case hasHandle:
handle := pkt.getHandle()
request, ok := rs.getRequest(handle)
if !ok {
- rpkt = statusFromError(pkt, syscall.EBADF)
+ rpkt = statusFromError(pkt.id(), EBADF)
} else {
- rpkt = request.call(rs.Handlers, pkt)
+ rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
}
case hasPath:
request := requestFromPacket(ctx, pkt)
- rpkt = request.call(rs.Handlers, pkt)
+ rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
request.close()
default:
- return errors.Errorf("unexpected packet type %T", pkt)
+ rpkt = statusFromError(pkt.id(), ErrSSHFxOpUnsupported)
}
rs.pktMgr.readyPacket(
- rs.pktMgr.newOrderedResponse(rpkt, pkt.orderId()))
+ rs.pktMgr.newOrderedResponse(rpkt, orderID))
}
return nil
}
// clean and return name packet for file
-func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket {
- path := cleanPath(pkt.getPath())
+func cleanPacketPath(pkt *sshFxpRealpathPacket, realPath string) responsePacket {
return &sshFxpNamePacket{
ID: pkt.id(),
- NameAttrs: []sshFxpNameAttr{{
- Name: path,
- LongName: path,
- Attrs: emptyFileStat,
- }},
+ NameAttrs: []*sshFxpNameAttr{
+ {
+ Name: realPath,
+ LongName: realPath,
+ Attrs: emptyFileStat,
+ },
+ },
}
}
// Makes sure we have a clean POSIX (/) absolute path to work with
func cleanPath(p string) string {
- p = filepath.ToSlash(p)
- if !filepath.IsAbs(p) {
- p = "/" + p
+ return cleanPathWithBase("/", p)
+}
+
+func cleanPathWithBase(base, p string) string {
+ p = filepath.ToSlash(filepath.Clean(p))
+ if !path.IsAbs(p) {
+ return path.Join(base, p)
}
- return path.Clean(p)
+ return p
}
diff --git a/vendor/github.com/pkg/sftp/request-unix.go b/vendor/github.com/pkg/sftp/request-unix.go
index a71a8980..50b08a38 100644
--- a/vendor/github.com/pkg/sftp/request-unix.go
+++ b/vendor/github.com/pkg/sftp/request-unix.go
@@ -1,4 +1,4 @@
-// +build !windows
+// +build !windows,!plan9
package sftp
@@ -14,10 +14,14 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error {
fstat := sys.(*FileStat)
if fstat.UID != uint32(65534) {
- return errors.New("Uid failed to match.")
+ return errors.New("Uid failed to match")
}
if fstat.GID != uint32(65534) {
- return errors.New("Gid failed to match:")
+ return errors.New("Gid failed to match")
}
return nil
}
+
+func toLocalPath(p string) string {
+ return p
+}
diff --git a/vendor/github.com/pkg/sftp/request.go b/vendor/github.com/pkg/sftp/request.go
index e694e5f1..c6da4b60 100644
--- a/vendor/github.com/pkg/sftp/request.go
+++ b/vendor/github.com/pkg/sftp/request.go
@@ -2,42 +2,169 @@ package sftp
import (
"context"
+ "errors"
+ "fmt"
"io"
"os"
- "path"
- "path/filepath"
+ "strings"
"sync"
"syscall"
-
- "github.com/pkg/errors"
)
// MaxFilelist is the max number of files to return in a readdir batch.
var MaxFilelist int64 = 100
+// state encapsulates the reader/writer/readdir from handlers.
+type state struct {
+ mu sync.RWMutex
+
+ writerAt io.WriterAt
+ readerAt io.ReaderAt
+ writerAtReaderAt WriterAtReaderAt
+ listerAt ListerAt
+ lsoffset int64
+}
+
+// copy returns a shallow copy the state.
+// This is broken out to specific fields,
+// because we have to copy around the mutex in state.
+func (s *state) copy() state {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ return state{
+ writerAt: s.writerAt,
+ readerAt: s.readerAt,
+ writerAtReaderAt: s.writerAtReaderAt,
+ listerAt: s.listerAt,
+ lsoffset: s.lsoffset,
+ }
+}
+
+func (s *state) setReaderAt(rd io.ReaderAt) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.readerAt = rd
+}
+
+func (s *state) getReaderAt() io.ReaderAt {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ return s.readerAt
+}
+
+func (s *state) setWriterAt(rd io.WriterAt) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.writerAt = rd
+}
+
+func (s *state) getWriterAt() io.WriterAt {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ return s.writerAt
+}
+
+func (s *state) setWriterAtReaderAt(rw WriterAtReaderAt) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.writerAtReaderAt = rw
+}
+
+func (s *state) getWriterAtReaderAt() WriterAtReaderAt {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ return s.writerAtReaderAt
+}
+
+func (s *state) getAllReaderWriters() (io.ReaderAt, io.WriterAt, WriterAtReaderAt) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ return s.readerAt, s.writerAt, s.writerAtReaderAt
+}
+
+// Returns current offset for file list
+func (s *state) lsNext() int64 {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ return s.lsoffset
+}
+
+// Increases next offset
+func (s *state) lsInc(offset int64) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.lsoffset += offset
+}
+
+// manage file read/write state
+func (s *state) setListerAt(la ListerAt) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ s.listerAt = la
+}
+
+func (s *state) getListerAt() ListerAt {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ return s.listerAt
+}
+
// Request contains the data and state for the incoming service request.
type Request struct {
// Get, Put, Setstat, Stat, Rename, Remove
- // Rmdir, Mkdir, List, Readlink, Symlink
+ // Rmdir, Mkdir, List, Readlink, Link, Symlink
Method string
Filepath string
Flags uint32
Attrs []byte // convert to sub-struct
Target string // for renames and sym-links
handle string
+
// reader/writer/readdir from handlers
- state state
+ state
+
// context lasts duration of request
ctx context.Context
cancelCtx context.CancelFunc
}
-type state struct {
- *sync.RWMutex
- writerAt io.WriterAt
- readerAt io.ReaderAt
- listerAt ListerAt
- lsoffset int64
+// NewRequest creates a new Request object.
+func NewRequest(method, path string) *Request {
+ return &Request{
+ Method: method,
+ Filepath: cleanPath(path),
+ }
+}
+
+// copy returns a shallow copy of existing request.
+// This is broken out to specific fields,
+// because we have to copy around the mutex in state.
+func (r *Request) copy() *Request {
+ return &Request{
+ Method: r.Method,
+ Filepath: r.Filepath,
+ Flags: r.Flags,
+ Attrs: r.Attrs,
+ Target: r.Target,
+ handle: r.handle,
+
+ state: r.state.copy(),
+
+ ctx: r.ctx,
+ cancelCtx: r.cancelCtx,
+ }
}
// New Request initialized based on packet data
@@ -55,26 +182,15 @@ func requestFromPacket(ctx context.Context, pkt hasPath) *Request {
case *sshFxpRenamePacket:
request.Target = cleanPath(p.Newpath)
case *sshFxpSymlinkPacket:
+ // NOTE: given a POSIX compliant signature: symlink(target, linkpath string)
+ // this makes Request.Target the linkpath, and Request.Filepath the target.
request.Target = cleanPath(p.Linkpath)
+ case *sshFxpExtendedPacketHardlink:
+ request.Target = cleanPath(p.Newpath)
}
return request
}
-// NewRequest creates a new Request object.
-func NewRequest(method, path string) *Request {
- return &Request{Method: method, Filepath: cleanPath(path),
- state: state{RWMutex: new(sync.RWMutex)}}
-}
-
-// shallow copy of existing request
-func (r *Request) copy() *Request {
- r.state.Lock()
- defer r.state.Unlock()
- r2 := new(Request)
- *r2 = *r
- return r2
-}
-
// Context returns the request's context. To change the context,
// use WithContext.
//
@@ -102,33 +218,6 @@ func (r *Request) WithContext(ctx context.Context) *Request {
return r2
}
-// Returns current offset for file list
-func (r *Request) lsNext() int64 {
- r.state.RLock()
- defer r.state.RUnlock()
- return r.state.lsoffset
-}
-
-// Increases next offset
-func (r *Request) lsInc(offset int64) {
- r.state.Lock()
- defer r.state.Unlock()
- r.state.lsoffset = r.state.lsoffset + offset
-}
-
-// manage file read/write state
-func (r *Request) setListerState(la ListerAt) {
- r.state.Lock()
- defer r.state.Unlock()
- r.state.listerAt = la
-}
-
-func (r *Request) getLister() ListerAt {
- r.state.RLock()
- defer r.state.RUnlock()
- return r.state.listerAt
-}
-
// Close reader/writer if possible
func (r *Request) close() error {
defer func() {
@@ -136,90 +225,162 @@ func (r *Request) close() error {
r.cancelCtx()
}
}()
- r.state.RLock()
- rd := r.state.readerAt
- r.state.RUnlock()
+
+ rd, wr, rw := r.getAllReaderWriters()
+
+ var err error
+
+ // Close errors on a Writer are far more likely to be the important one.
+ // As they can be information that there was a loss of data.
+ if c, ok := wr.(io.Closer); ok {
+ if err2 := c.Close(); err == nil {
+ // update error if it is still nil
+ err = err2
+ }
+ }
+
+ if c, ok := rw.(io.Closer); ok {
+ if err2 := c.Close(); err == nil {
+ // update error if it is still nil
+ err = err2
+
+ r.setWriterAtReaderAt(nil)
+ }
+ }
+
if c, ok := rd.(io.Closer); ok {
- return c.Close()
+ if err2 := c.Close(); err == nil {
+ // update error if it is still nil
+ err = err2
+ }
+ }
+
+ return err
+}
+
+// Notify transfer error if any
+func (r *Request) transferError(err error) {
+ if err == nil {
+ return
}
- r.state.RLock()
- wt := r.state.writerAt
- r.state.RUnlock()
- if c, ok := wt.(io.Closer); ok {
- return c.Close()
+
+ rd, wr, rw := r.getAllReaderWriters()
+
+ if t, ok := wr.(TransferError); ok {
+ t.TransferError(err)
+ }
+
+ if t, ok := rw.(TransferError); ok {
+ t.TransferError(err)
+ }
+
+ if t, ok := rd.(TransferError); ok {
+ t.TransferError(err)
}
- return nil
}
// called from worker to handle packet/request
-func (r *Request) call(handlers Handlers, pkt requestPacket) responsePacket {
+func (r *Request) call(handlers Handlers, pkt requestPacket, alloc *allocator, orderID uint32) responsePacket {
switch r.Method {
case "Get":
- return fileget(handlers.FileGet, r, pkt)
+ return fileget(handlers.FileGet, r, pkt, alloc, orderID)
case "Put":
- return fileput(handlers.FilePut, r, pkt)
- case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
+ return fileput(handlers.FilePut, r, pkt, alloc, orderID)
+ case "Open":
+ return fileputget(handlers.FilePut, r, pkt, alloc, orderID)
+ case "Setstat", "Rename", "Rmdir", "Mkdir", "Link", "Symlink", "Remove", "PosixRename", "StatVFS":
return filecmd(handlers.FileCmd, r, pkt)
case "List":
return filelist(handlers.FileList, r, pkt)
- case "Stat", "Readlink":
+ case "Stat", "Lstat", "Readlink":
return filestat(handlers.FileList, r, pkt)
default:
- return statusFromError(pkt,
- errors.Errorf("unexpected method: %s", r.Method))
+ return statusFromError(pkt.id(), fmt.Errorf("unexpected method: %s", r.Method))
}
}
// Additional initialization for Open packets
func (r *Request) open(h Handlers, pkt requestPacket) responsePacket {
flags := r.Pflags()
- var err error
+
+ id := pkt.id()
+
switch {
case flags.Write, flags.Append, flags.Creat, flags.Trunc:
+ if flags.Read {
+ if openFileWriter, ok := h.FilePut.(OpenFileWriter); ok {
+ r.Method = "Open"
+ rw, err := openFileWriter.OpenFile(r)
+ if err != nil {
+ return statusFromError(id, err)
+ }
+
+ r.setWriterAtReaderAt(rw)
+
+ return &sshFxpHandlePacket{
+ ID: id,
+ Handle: r.handle,
+ }
+ }
+ }
+
r.Method = "Put"
- r.state.writerAt, err = h.FilePut.Filewrite(r)
+ wr, err := h.FilePut.Filewrite(r)
+ if err != nil {
+ return statusFromError(id, err)
+ }
+
+ r.setWriterAt(wr)
+
case flags.Read:
r.Method = "Get"
- r.state.readerAt, err = h.FileGet.Fileread(r)
+ rd, err := h.FileGet.Fileread(r)
+ if err != nil {
+ return statusFromError(id, err)
+ }
+
+ r.setReaderAt(rd)
+
default:
- return statusFromError(pkt, errors.New("bad file flags"))
+ return statusFromError(id, errors.New("bad file flags"))
}
- if err != nil {
- return statusFromError(pkt, err)
+
+ return &sshFxpHandlePacket{
+ ID: id,
+ Handle: r.handle,
}
- return &sshFxpHandlePacket{ID: pkt.id(), Handle: r.handle}
}
+
func (r *Request) opendir(h Handlers, pkt requestPacket) responsePacket {
- var err error
r.Method = "List"
- r.state.listerAt, err = h.FileList.Filelist(r)
+ la, err := h.FileList.Filelist(r)
if err != nil {
- switch err.(type) {
- case syscall.Errno:
- err = &os.PathError{Path: r.Filepath, Err: err}
- }
- return statusFromError(pkt, err)
+ return statusFromError(pkt.id(), wrapPathError(r.Filepath, err))
+ }
+
+ r.setListerAt(la)
+
+ return &sshFxpHandlePacket{
+ ID: pkt.id(),
+ Handle: r.handle,
}
- return &sshFxpHandlePacket{ID: pkt.id(), Handle: r.handle}
}
// wrap FileReader handler
-func fileget(h FileReader, r *Request, pkt requestPacket) responsePacket {
- //fmt.Println("fileget", r)
- r.state.RLock()
- reader := r.state.readerAt
- r.state.RUnlock()
- if reader == nil {
- return statusFromError(pkt, errors.New("unexpected read packet"))
- }
-
- _, offset, length := packetData(pkt)
- data := make([]byte, clamp(length, maxTxPacket))
- n, err := reader.ReadAt(data, offset)
- // only return EOF erro if no data left to read
+func fileget(h FileReader, r *Request, pkt requestPacket, alloc *allocator, orderID uint32) responsePacket {
+ rd := r.getReaderAt()
+ if rd == nil {
+ return statusFromError(pkt.id(), errors.New("unexpected read packet"))
+ }
+
+ data, offset, _ := packetData(pkt, alloc, orderID)
+
+ n, err := rd.ReadAt(data, offset)
+ // only return EOF error if no data left to read
if err != nil && (err != io.EOF || n == 0) {
- return statusFromError(pkt, err)
+ return statusFromError(pkt.id(), err)
}
+
return &sshFxpDataPacket{
ID: pkt.id(),
Length: uint32(n),
@@ -228,52 +389,105 @@ func fileget(h FileReader, r *Request, pkt requestPacket) responsePacket {
}
// wrap FileWriter handler
-func fileput(h FileWriter, r *Request, pkt requestPacket) responsePacket {
- //fmt.Println("fileput", r)
- r.state.RLock()
- writer := r.state.writerAt
- r.state.RUnlock()
- if writer == nil {
- return statusFromError(pkt, errors.New("unexpected write packet"))
+func fileput(h FileWriter, r *Request, pkt requestPacket, alloc *allocator, orderID uint32) responsePacket {
+ wr := r.getWriterAt()
+ if wr == nil {
+ return statusFromError(pkt.id(), errors.New("unexpected write packet"))
}
- data, offset, _ := packetData(pkt)
- _, err := writer.WriteAt(data, offset)
- return statusFromError(pkt, err)
+ data, offset, _ := packetData(pkt, alloc, orderID)
+
+ _, err := wr.WriteAt(data, offset)
+ return statusFromError(pkt.id(), err)
+}
+
+// wrap OpenFileWriter handler
+func fileputget(h FileWriter, r *Request, pkt requestPacket, alloc *allocator, orderID uint32) responsePacket {
+ rw := r.getWriterAtReaderAt()
+ if rw == nil {
+ return statusFromError(pkt.id(), errors.New("unexpected write and read packet"))
+ }
+
+ switch p := pkt.(type) {
+ case *sshFxpReadPacket:
+ data, offset := p.getDataSlice(alloc, orderID), int64(p.Offset)
+
+ n, err := rw.ReadAt(data, offset)
+ // only return EOF error if no data left to read
+ if err != nil && (err != io.EOF || n == 0) {
+ return statusFromError(pkt.id(), err)
+ }
+
+ return &sshFxpDataPacket{
+ ID: pkt.id(),
+ Length: uint32(n),
+ Data: data[:n],
+ }
+
+ case *sshFxpWritePacket:
+ data, offset := p.Data, int64(p.Offset)
+
+ _, err := rw.WriteAt(data, offset)
+ return statusFromError(pkt.id(), err)
+
+ default:
+ return statusFromError(pkt.id(), errors.New("unexpected packet type for read or write"))
+ }
}
// file data for additional read/write packets
-func packetData(p requestPacket) (data []byte, offset int64, length uint32) {
+func packetData(p requestPacket, alloc *allocator, orderID uint32) (data []byte, offset int64, length uint32) {
switch p := p.(type) {
case *sshFxpReadPacket:
- length = p.Len
- offset = int64(p.Offset)
+ return p.getDataSlice(alloc, orderID), int64(p.Offset), p.Len
case *sshFxpWritePacket:
- data = p.Data
- length = p.Length
- offset = int64(p.Offset)
+ return p.Data, int64(p.Offset), p.Length
}
return
}
// wrap FileCmder handler
func filecmd(h FileCmder, r *Request, pkt requestPacket) responsePacket {
-
switch p := pkt.(type) {
case *sshFxpFsetstatPacket:
r.Flags = p.Flags
r.Attrs = p.Attrs.([]byte)
}
+
+ switch r.Method {
+ case "PosixRename":
+ if posixRenamer, ok := h.(PosixRenameFileCmder); ok {
+ err := posixRenamer.PosixRename(r)
+ return statusFromError(pkt.id(), err)
+ }
+
+ // PosixRenameFileCmder not implemented handle this request as a Rename
+ r.Method = "Rename"
+ err := h.Filecmd(r)
+ return statusFromError(pkt.id(), err)
+
+ case "StatVFS":
+ if statVFSCmdr, ok := h.(StatVFSFileCmder); ok {
+ stat, err := statVFSCmdr.StatVFS(r)
+ if err != nil {
+ return statusFromError(pkt.id(), err)
+ }
+ stat.ID = pkt.id()
+ return stat
+ }
+
+ return statusFromError(pkt.id(), ErrSSHFxOpUnsupported)
+ }
+
err := h.Filecmd(r)
- return statusFromError(pkt, err)
+ return statusFromError(pkt.id(), err)
}
// wrap FileLister handler
func filelist(h FileLister, r *Request, pkt requestPacket) responsePacket {
- var err error
- lister := r.getLister()
+ lister := r.getListerAt()
if lister == nil {
- return statusFromError(pkt, errors.New("unexpected dir packet"))
+ return statusFromError(pkt.id(), errors.New("unexpected dir packet"))
}
offset := r.lsNext()
@@ -285,47 +499,69 @@ func filelist(h FileLister, r *Request, pkt requestPacket) responsePacket {
switch r.Method {
case "List":
- if err != nil && err != io.EOF {
- return statusFromError(pkt, err)
+ if err != nil && (err != io.EOF || n == 0) {
+ return statusFromError(pkt.id(), err)
}
- if err == io.EOF && n == 0 {
- return statusFromError(pkt, io.EOF)
- }
- dirname := filepath.ToSlash(path.Base(r.Filepath))
- ret := &sshFxpNamePacket{ID: pkt.id()}
+
+ nameAttrs := make([]*sshFxpNameAttr, 0, len(finfo))
+
+ // If the type conversion fails, we get untyped `nil`,
+ // which is handled by not looking up any names.
+ idLookup, _ := h.(NameLookupFileLister)
for _, fi := range finfo {
- ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
+ nameAttrs = append(nameAttrs, &sshFxpNameAttr{
Name: fi.Name(),
- LongName: runLs(dirname, fi),
+ LongName: runLs(idLookup, fi),
Attrs: []interface{}{fi},
})
}
- return ret
+
+ return &sshFxpNamePacket{
+ ID: pkt.id(),
+ NameAttrs: nameAttrs,
+ }
+
default:
- err = errors.Errorf("unexpected method: %s", r.Method)
- return statusFromError(pkt, err)
+ err = fmt.Errorf("unexpected method: %s", r.Method)
+ return statusFromError(pkt.id(), err)
}
}
func filestat(h FileLister, r *Request, pkt requestPacket) responsePacket {
- lister, err := h.Filelist(r)
+ var lister ListerAt
+ var err error
+
+ if r.Method == "Lstat" {
+ if lstatFileLister, ok := h.(LstatFileLister); ok {
+ lister, err = lstatFileLister.Lstat(r)
+ } else {
+ // LstatFileLister not implemented handle this request as a Stat
+ r.Method = "Stat"
+ lister, err = h.Filelist(r)
+ }
+ } else {
+ lister, err = h.Filelist(r)
+ }
if err != nil {
- return statusFromError(pkt, err)
+ return statusFromError(pkt.id(), err)
}
finfo := make([]os.FileInfo, 1)
n, err := lister.ListAt(finfo, 0)
finfo = finfo[:n] // avoid need for nil tests below
switch r.Method {
- case "Stat":
+ case "Stat", "Lstat":
if err != nil && err != io.EOF {
- return statusFromError(pkt, err)
+ return statusFromError(pkt.id(), err)
}
if n == 0 {
- err = &os.PathError{Op: "stat", Path: r.Filepath,
- Err: syscall.ENOENT}
- return statusFromError(pkt, err)
+ err = &os.PathError{
+ Op: strings.ToLower(r.Method),
+ Path: r.Filepath,
+ Err: syscall.ENOENT,
+ }
+ return statusFromError(pkt.id(), err)
}
return &sshFxpStatResponse{
ID: pkt.id(),
@@ -333,25 +569,30 @@ func filestat(h FileLister, r *Request, pkt requestPacket) responsePacket {
}
case "Readlink":
if err != nil && err != io.EOF {
- return statusFromError(pkt, err)
+ return statusFromError(pkt.id(), err)
}
if n == 0 {
- err = &os.PathError{Op: "readlink", Path: r.Filepath,
- Err: syscall.ENOENT}
- return statusFromError(pkt, err)
+ err = &os.PathError{
+ Op: "readlink",
+ Path: r.Filepath,
+ Err: syscall.ENOENT,
+ }
+ return statusFromError(pkt.id(), err)
}
filename := finfo[0].Name()
return &sshFxpNamePacket{
ID: pkt.id(),
- NameAttrs: []sshFxpNameAttr{{
- Name: filename,
- LongName: filename,
- Attrs: emptyFileStat,
- }},
+ NameAttrs: []*sshFxpNameAttr{
+ {
+ Name: filename,
+ LongName: filename,
+ Attrs: emptyFileStat,
+ },
+ },
}
default:
- err = errors.Errorf("unexpected method: %s", r.Method)
- return statusFromError(pkt, err)
+ err = fmt.Errorf("unexpected method: %s", r.Method)
+ return statusFromError(pkt.id(), err)
}
}
@@ -370,14 +611,18 @@ func requestMethod(p requestPacket) (method string) {
method = "Symlink"
case *sshFxpRemovePacket:
method = "Remove"
- case *sshFxpStatPacket, *sshFxpLstatPacket, *sshFxpFstatPacket:
+ case *sshFxpStatPacket, *sshFxpFstatPacket:
method = "Stat"
+ case *sshFxpLstatPacket:
+ method = "Lstat"
case *sshFxpRmdirPacket:
method = "Rmdir"
case *sshFxpReadlinkPacket:
method = "Readlink"
case *sshFxpMkdirPacket:
method = "Mkdir"
+ case *sshFxpExtendedPacketHardlink:
+ method = "Link"
}
return method
}
diff --git a/vendor/github.com/pkg/sftp/request_windows.go b/vendor/github.com/pkg/sftp/request_windows.go
index 94d306b6..1f6d3df1 100644
--- a/vendor/github.com/pkg/sftp/request_windows.go
+++ b/vendor/github.com/pkg/sftp/request_windows.go
@@ -1,6 +1,10 @@
package sftp
-import "syscall"
+import (
+ "path"
+ "path/filepath"
+ "syscall"
+)
func fakeFileInfoSys() interface{} {
return syscall.Win32FileAttributeData{}
@@ -9,3 +13,32 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error {
return nil
}
+
+func toLocalPath(p string) string {
+ lp := filepath.FromSlash(p)
+
+ if path.IsAbs(p) {
+ tmp := lp
+ for len(tmp) > 0 && tmp[0] == '\\' {
+ tmp = tmp[1:]
+ }
+
+ if filepath.IsAbs(tmp) {
+ // If the FromSlash without any starting slashes is absolute,
+ // then we have a filepath encoded with a prefix '/'.
+ // e.g. "/C:/Windows" to "C:\\Windows"
+ return tmp
+ }
+
+ tmp += "\\"
+
+ if filepath.IsAbs(tmp) {
+ // If the FromSlash without any starting slashes but with extra end slash is absolute,
+ // then we have a filepath encoded with a prefix '/' and a dropped '/' at the end.
+ // e.g. "/C:" to "C:\\"
+ return tmp
+ }
+ }
+
+ return lp
+}
diff --git a/vendor/github.com/pkg/sftp/server.go b/vendor/github.com/pkg/sftp/server.go
index 0fac1b66..529052b4 100644
--- a/vendor/github.com/pkg/sftp/server.go
+++ b/vendor/github.com/pkg/sftp/server.go
@@ -4,6 +4,7 @@ package sftp
import (
"encoding"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -13,11 +14,10 @@ import (
"sync"
"syscall"
"time"
-
- "github.com/pkg/errors"
)
const (
+ // SftpServerWorkerCount defines the number of workers for the SFTP server
SftpServerWorkerCount = 8
)
@@ -33,7 +33,6 @@ type Server struct {
openFiles map[string]*os.File
openFilesLock sync.RWMutex
handleCount int
- maxTxPacket uint32
}
func (svr *Server) nextHandle(f *os.File) string {
@@ -53,7 +52,7 @@ func (svr *Server) closeHandle(handle string) error {
return f.Close()
}
- return syscall.EBADF
+ return EBADF
}
func (svr *Server) getHandle(handle string) (*os.File, bool) {
@@ -86,7 +85,6 @@ func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error)
debugStream: ioutil.Discard,
pktMgr: newPktMgr(svrConn),
openFiles: make(map[string]*os.File),
- maxTxPacket: 1 << 15,
}
for _, o := range options {
@@ -117,6 +115,19 @@ func ReadOnly() ServerOption {
}
}
+// WithAllocator enable the allocator.
+// After processing a packet we keep in memory the allocated slices
+// and we reuse them for new packets.
+// The allocator is experimental
+func WithAllocator() ServerOption {
+ return func(s *Server) error {
+ alloc := newAllocator()
+ s.pktMgr.alloc = alloc
+ s.conn.alloc = alloc
+ return nil
+ }
+}
+
type rxPacket struct {
pktType fxp
pktBytes []byte
@@ -139,9 +150,9 @@ func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error {
// If server is operating read-only and a write operation is requested,
// return permission denied
if !readonly && svr.readOnly {
- svr.sendPacket(orderedResponse{
- responsePacket: statusFromError(pkt, syscall.EPERM),
- orderid: pkt.orderId()})
+ svr.pktMgr.readyPacket(
+ svr.pktMgr.newOrderedResponse(statusFromError(pkt.id(), syscall.EPERM), pkt.orderID()),
+ )
continue
}
@@ -154,141 +165,163 @@ func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error {
func handlePacket(s *Server, p orderedRequest) error {
var rpkt responsePacket
+ orderID := p.orderID()
switch p := p.requestPacket.(type) {
case *sshFxInitPacket:
- rpkt = sshFxVersionPacket{Version: sftpProtocolVersion}
+ rpkt = &sshFxVersionPacket{
+ Version: sftpProtocolVersion,
+ Extensions: sftpExtensions,
+ }
case *sshFxpStatPacket:
// stat the requested file
- info, err := os.Stat(p.Path)
- rpkt = sshFxpStatResponse{
+ info, err := os.Stat(toLocalPath(p.Path))
+ rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
}
if err != nil {
- rpkt = statusFromError(p, err)
+ rpkt = statusFromError(p.ID, err)
}
case *sshFxpLstatPacket:
// stat the requested file
- info, err := os.Lstat(p.Path)
- rpkt = sshFxpStatResponse{
+ info, err := os.Lstat(toLocalPath(p.Path))
+ rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
}
if err != nil {
- rpkt = statusFromError(p, err)
+ rpkt = statusFromError(p.ID, err)
}
case *sshFxpFstatPacket:
f, ok := s.getHandle(p.Handle)
- var err error = syscall.EBADF
+ var err error = EBADF
var info os.FileInfo
if ok {
info, err = f.Stat()
- rpkt = sshFxpStatResponse{
+ rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
}
}
if err != nil {
- rpkt = statusFromError(p, err)
+ rpkt = statusFromError(p.ID, err)
}
case *sshFxpMkdirPacket:
// TODO FIXME: ignore flags field
- err := os.Mkdir(p.Path, 0755)
- rpkt = statusFromError(p, err)
+ err := os.Mkdir(toLocalPath(p.Path), 0755)
+ rpkt = statusFromError(p.ID, err)
case *sshFxpRmdirPacket:
- err := os.Remove(p.Path)
- rpkt = statusFromError(p, err)
+ err := os.Remove(toLocalPath(p.Path))
+ rpkt = statusFromError(p.ID, err)
case *sshFxpRemovePacket:
- err := os.Remove(p.Filename)
- rpkt = statusFromError(p, err)
+ err := os.Remove(toLocalPath(p.Filename))
+ rpkt = statusFromError(p.ID, err)
case *sshFxpRenamePacket:
- err := os.Rename(p.Oldpath, p.Newpath)
- rpkt = statusFromError(p, err)
+ err := os.Rename(toLocalPath(p.Oldpath), toLocalPath(p.Newpath))
+ rpkt = statusFromError(p.ID, err)
case *sshFxpSymlinkPacket:
- err := os.Symlink(p.Targetpath, p.Linkpath)
- rpkt = statusFromError(p, err)
+ err := os.Symlink(toLocalPath(p.Targetpath), toLocalPath(p.Linkpath))
+ rpkt = statusFromError(p.ID, err)
case *sshFxpClosePacket:
- rpkt = statusFromError(p, s.closeHandle(p.Handle))
+ rpkt = statusFromError(p.ID, s.closeHandle(p.Handle))
case *sshFxpReadlinkPacket:
- f, err := os.Readlink(p.Path)
- rpkt = sshFxpNamePacket{
+ f, err := os.Readlink(toLocalPath(p.Path))
+ rpkt = &sshFxpNamePacket{
ID: p.ID,
- NameAttrs: []sshFxpNameAttr{{
- Name: f,
- LongName: f,
- Attrs: emptyFileStat,
- }},
+ NameAttrs: []*sshFxpNameAttr{
+ {
+ Name: f,
+ LongName: f,
+ Attrs: emptyFileStat,
+ },
+ },
}
if err != nil {
- rpkt = statusFromError(p, err)
+ rpkt = statusFromError(p.ID, err)
}
case *sshFxpRealpathPacket:
- f, err := filepath.Abs(p.Path)
+ f, err := filepath.Abs(toLocalPath(p.Path))
f = cleanPath(f)
- rpkt = sshFxpNamePacket{
+ rpkt = &sshFxpNamePacket{
ID: p.ID,
- NameAttrs: []sshFxpNameAttr{{
- Name: f,
- LongName: f,
- Attrs: emptyFileStat,
- }},
+ NameAttrs: []*sshFxpNameAttr{
+ {
+ Name: f,
+ LongName: f,
+ Attrs: emptyFileStat,
+ },
+ },
}
if err != nil {
- rpkt = statusFromError(p, err)
+ rpkt = statusFromError(p.ID, err)
}
case *sshFxpOpendirPacket:
+ p.Path = toLocalPath(p.Path)
+
if stat, err := os.Stat(p.Path); err != nil {
- rpkt = statusFromError(p, err)
+ rpkt = statusFromError(p.ID, err)
} else if !stat.IsDir() {
- rpkt = statusFromError(p, &os.PathError{
+ rpkt = statusFromError(p.ID, &os.PathError{
Path: p.Path, Err: syscall.ENOTDIR})
} else {
- rpkt = sshFxpOpenPacket{
+ rpkt = (&sshFxpOpenPacket{
ID: p.ID,
Path: p.Path,
- Pflags: ssh_FXF_READ,
- }.respond(s)
+ Pflags: sshFxfRead,
+ }).respond(s)
}
case *sshFxpReadPacket:
- var err error = syscall.EBADF
+ var err error = EBADF
f, ok := s.getHandle(p.Handle)
if ok {
err = nil
- data := make([]byte, clamp(p.Len, s.maxTxPacket))
+ data := p.getDataSlice(s.pktMgr.alloc, orderID)
n, _err := f.ReadAt(data, int64(p.Offset))
if _err != nil && (_err != io.EOF || n == 0) {
err = _err
}
- rpkt = sshFxpDataPacket{
+ rpkt = &sshFxpDataPacket{
ID: p.ID,
Length: uint32(n),
Data: data[:n],
+ // do not use data[:n:n] here to clamp the capacity, we allocated extra capacity above to avoid reallocations
}
}
if err != nil {
- rpkt = statusFromError(p, err)
+ rpkt = statusFromError(p.ID, err)
}
case *sshFxpWritePacket:
f, ok := s.getHandle(p.Handle)
- var err error = syscall.EBADF
+ var err error = EBADF
if ok {
_, err = f.WriteAt(p.Data, int64(p.Offset))
}
- rpkt = statusFromError(p, err)
+ rpkt = statusFromError(p.ID, err)
+ case *sshFxpExtendedPacket:
+ if p.SpecificPacket == nil {
+ rpkt = statusFromError(p.ID, ErrSSHFxOpUnsupported)
+ } else {
+ rpkt = p.respond(s)
+ }
case serverRespondablePacket:
rpkt = p.respond(s)
default:
- return errors.Errorf("unexpected packet type %T", p)
+ return fmt.Errorf("unexpected packet type %T", p)
}
- s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, p.orderId()))
+ s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, orderID))
return nil
}
// Serve serves SFTP connections until the streams stop or the SFTP subsystem
// is stopped.
func (svr *Server) Serve() error {
+ defer func() {
+ if svr.pktMgr.alloc != nil {
+ svr.pktMgr.alloc.Free()
+ }
+ }()
var wg sync.WaitGroup
runWorker := func(ch chan orderedRequest) {
wg.Add(1)
@@ -306,20 +339,21 @@ func (svr *Server) Serve() error {
var pktType uint8
var pktBytes []byte
for {
- pktType, pktBytes, err = svr.recvPacket()
+ pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID())
if err != nil {
+ // we don't care about releasing allocated pages here, the server will quit and the allocator freed
break
}
pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
if err != nil {
- switch errors.Cause(err) {
- case errUnknownExtendedPacket:
- if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
- debug("failed to send err packet: %v", err)
- svr.conn.Close() // shuts down recvPacket
- break
- }
+ switch {
+ case errors.Is(err, errUnknownExtendedPacket):
+ //if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
+ // debug("failed to send err packet: %v", err)
+ // svr.conn.Close() // shuts down recvPacket
+ // break
+ //}
default:
debug("makePacket err: %v", err)
svr.conn.Close() // shuts down recvPacket
@@ -346,27 +380,38 @@ type ider interface {
}
// The init packet has no ID, so we just return a zero-value ID
-func (p sshFxInitPacket) id() uint32 { return 0 }
+func (p *sshFxInitPacket) id() uint32 { return 0 }
type sshFxpStatResponse struct {
ID uint32
info os.FileInfo
}
-func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {
- b := []byte{ssh_FXP_ATTRS}
+func (p *sshFxpStatResponse) marshalPacket() ([]byte, []byte, error) {
+ l := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(id)
+
+ b := make([]byte, 4, l)
+ b = append(b, sshFxpAttrs)
b = marshalUint32(b, p.ID)
- b = marshalFileInfo(b, p.info)
- return b, nil
+
+ var payload []byte
+ payload = marshalFileInfo(payload, p.info)
+
+ return b, payload, nil
+}
+
+func (p *sshFxpStatResponse) MarshalBinary() ([]byte, error) {
+ header, payload, err := p.marshalPacket()
+ return append(header, payload...), err
}
var emptyFileStat = []interface{}{uint32(0)}
-func (p sshFxpOpenPacket) readonly() bool {
- return !p.hasPflags(ssh_FXF_WRITE)
+func (p *sshFxpOpenPacket) readonly() bool {
+ return !p.hasPflags(sshFxfWrite)
}
-func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
+func (p *sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
for _, f := range flags {
if p.Pflags&f == 0 {
return false
@@ -375,83 +420,86 @@ func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
return true
}
-func (p sshFxpOpenPacket) respond(svr *Server) responsePacket {
+func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket {
var osFlags int
- if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) {
+ if p.hasPflags(sshFxfRead, sshFxfWrite) {
osFlags |= os.O_RDWR
- } else if p.hasPflags(ssh_FXF_WRITE) {
+ } else if p.hasPflags(sshFxfWrite) {
osFlags |= os.O_WRONLY
- } else if p.hasPflags(ssh_FXF_READ) {
+ } else if p.hasPflags(sshFxfRead) {
osFlags |= os.O_RDONLY
} else {
// how are they opening?
- return statusFromError(p, syscall.EINVAL)
+ return statusFromError(p.ID, syscall.EINVAL)
}
- if p.hasPflags(ssh_FXF_APPEND) {
- osFlags |= os.O_APPEND
- }
- if p.hasPflags(ssh_FXF_CREAT) {
+ // Don't use O_APPEND flag as it conflicts with WriteAt.
+ // The sshFxfAppend flag is a no-op here as the client sends the offsets.
+
+ if p.hasPflags(sshFxfCreat) {
osFlags |= os.O_CREATE
}
- if p.hasPflags(ssh_FXF_TRUNC) {
+ if p.hasPflags(sshFxfTrunc) {
osFlags |= os.O_TRUNC
}
- if p.hasPflags(ssh_FXF_EXCL) {
+ if p.hasPflags(sshFxfExcl) {
osFlags |= os.O_EXCL
}
- f, err := os.OpenFile(p.Path, osFlags, 0644)
+ f, err := os.OpenFile(toLocalPath(p.Path), osFlags, 0644)
if err != nil {
- return statusFromError(p, err)
+ return statusFromError(p.ID, err)
}
handle := svr.nextHandle(f)
- return sshFxpHandlePacket{ID: p.id(), Handle: handle}
+ return &sshFxpHandlePacket{ID: p.ID, Handle: handle}
}
-func (p sshFxpReaddirPacket) respond(svr *Server) responsePacket {
+func (p *sshFxpReaddirPacket) respond(svr *Server) responsePacket {
f, ok := svr.getHandle(p.Handle)
if !ok {
- return statusFromError(p, syscall.EBADF)
+ return statusFromError(p.ID, EBADF)
}
- dirname := f.Name()
dirents, err := f.Readdir(128)
if err != nil {
- return statusFromError(p, err)
+ return statusFromError(p.ID, err)
}
- ret := sshFxpNamePacket{ID: p.ID}
+ idLookup := osIDLookup{}
+
+ ret := &sshFxpNamePacket{ID: p.ID}
for _, dirent := range dirents {
- ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
+ ret.NameAttrs = append(ret.NameAttrs, &sshFxpNameAttr{
Name: dirent.Name(),
- LongName: runLs(dirname, dirent),
+ LongName: runLs(idLookup, dirent),
Attrs: []interface{}{dirent},
})
}
return ret
}
-func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
+func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket {
// additional unmarshalling is required for each possibility here
b := p.Attrs.([]byte)
var err error
+ p.Path = toLocalPath(p.Path)
+
debug("setstat name \"%s\"", p.Path)
- if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
+ if (p.Flags & sshFileXferAttrSize) != 0 {
var size uint64
if size, b, err = unmarshalUint64Safe(b); err == nil {
err = os.Truncate(p.Path, int64(size))
}
}
- if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
+ if (p.Flags & sshFileXferAttrPermissions) != 0 {
var mode uint32
if mode, b, err = unmarshalUint32Safe(b); err == nil {
err = os.Chmod(p.Path, os.FileMode(mode))
}
}
- if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
+ if (p.Flags & sshFileXferAttrACmodTime) != 0 {
var atime uint32
var mtime uint32
if atime, b, err = unmarshalUint32Safe(b); err != nil {
@@ -462,7 +510,7 @@ func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
err = os.Chtimes(p.Path, atimeT, mtimeT)
}
}
- if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
+ if (p.Flags & sshFileXferAttrUIDGID) != 0 {
var uid uint32
var gid uint32
if uid, b, err = unmarshalUint32Safe(b); err != nil {
@@ -472,13 +520,13 @@ func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
}
}
- return statusFromError(p, err)
+ return statusFromError(p.ID, err)
}
-func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
+func (p *sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
f, ok := svr.getHandle(p.Handle)
if !ok {
- return statusFromError(p, syscall.EBADF)
+ return statusFromError(p.ID, EBADF)
}
// additional unmarshalling is required for each possibility here
@@ -486,19 +534,19 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
var err error
debug("fsetstat name \"%s\"", f.Name())
- if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
+ if (p.Flags & sshFileXferAttrSize) != 0 {
var size uint64
if size, b, err = unmarshalUint64Safe(b); err == nil {
err = f.Truncate(int64(size))
}
}
- if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
+ if (p.Flags & sshFileXferAttrPermissions) != 0 {
var mode uint32
if mode, b, err = unmarshalUint32Safe(b); err == nil {
err = f.Chmod(os.FileMode(mode))
}
}
- if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
+ if (p.Flags & sshFileXferAttrACmodTime) != 0 {
var atime uint32
var mtime uint32
if atime, b, err = unmarshalUint32Safe(b); err != nil {
@@ -509,7 +557,7 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
err = os.Chtimes(f.Name(), atimeT, mtimeT)
}
}
- if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
+ if (p.Flags & sshFileXferAttrUIDGID) != 0 {
var uid uint32
var gid uint32
if uid, b, err = unmarshalUint32Safe(b); err != nil {
@@ -519,37 +567,23 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
}
}
- return statusFromError(p, err)
+ return statusFromError(p.ID, err)
}
-// translateErrno translates a syscall error number to a SFTP error code.
-func translateErrno(errno syscall.Errno) uint32 {
- switch errno {
- case 0:
- return ssh_FX_OK
- case syscall.ENOENT:
- return ssh_FX_NO_SUCH_FILE
- case syscall.EPERM:
- return ssh_FX_PERMISSION_DENIED
- }
-
- return ssh_FX_FAILURE
-}
-
-func statusFromError(p ider, err error) sshFxpStatusPacket {
- ret := sshFxpStatusPacket{
- ID: p.id(),
+func statusFromError(id uint32, err error) *sshFxpStatusPacket {
+ ret := &sshFxpStatusPacket{
+ ID: id,
StatusError: StatusError{
- // ssh_FX_OK = 0
- // ssh_FX_EOF = 1
- // ssh_FX_NO_SUCH_FILE = 2 ENOENT
- // ssh_FX_PERMISSION_DENIED = 3
- // ssh_FX_FAILURE = 4
- // ssh_FX_BAD_MESSAGE = 5
- // ssh_FX_NO_CONNECTION = 6
- // ssh_FX_CONNECTION_LOST = 7
- // ssh_FX_OP_UNSUPPORTED = 8
- Code: ssh_FX_OK,
+ // sshFXOk = 0
+ // sshFXEOF = 1
+ // sshFXNoSuchFile = 2 ENOENT
+ // sshFXPermissionDenied = 3
+ // sshFXFailure = 4
+ // sshFXBadMessage = 5
+ // sshFXNoConnection = 6
+ // sshFXConnectionLost = 7
+ // sshFXOPUnsupported = 8
+ Code: sshFxOk,
},
}
if err == nil {
@@ -557,123 +591,26 @@ func statusFromError(p ider, err error) sshFxpStatusPacket {
}
debug("statusFromError: error is %T %#v", err, err)
- ret.StatusError.Code = ssh_FX_FAILURE
+ ret.StatusError.Code = sshFxFailure
ret.StatusError.msg = err.Error()
+ if os.IsNotExist(err) {
+ ret.StatusError.Code = sshFxNoSuchFile
+ return ret
+ }
+ if code, ok := translateSyscallError(err); ok {
+ ret.StatusError.Code = code
+ return ret
+ }
+
switch e := err.(type) {
- case syscall.Errno:
- ret.StatusError.Code = translateErrno(e)
- case *os.PathError:
- debug("statusFromError,pathError: error is %T %#v", e.Err, e.Err)
- if errno, ok := e.Err.(syscall.Errno); ok {
- ret.StatusError.Code = translateErrno(errno)
- }
case fxerr:
ret.StatusError.Code = uint32(e)
default:
- switch e {
- case io.EOF:
- ret.StatusError.Code = ssh_FX_EOF
- case os.ErrNotExist:
- ret.StatusError.Code = ssh_FX_NO_SUCH_FILE
+ if e == io.EOF {
+ ret.StatusError.Code = sshFxEOF
}
}
return ret
}
-
-func clamp(v, max uint32) uint32 {
- if v > max {
- return max
- }
- return v
-}
-
-func runLsTypeWord(dirent os.FileInfo) string {
- // find first character, the type char
- // b Block special file.
- // c Character special file.
- // d Directory.
- // l Symbolic link.
- // s Socket link.
- // p FIFO.
- // - Regular file.
- tc := '-'
- mode := dirent.Mode()
- if (mode & os.ModeDir) != 0 {
- tc = 'd'
- } else if (mode & os.ModeDevice) != 0 {
- tc = 'b'
- if (mode & os.ModeCharDevice) != 0 {
- tc = 'c'
- }
- } else if (mode & os.ModeSymlink) != 0 {
- tc = 'l'
- } else if (mode & os.ModeSocket) != 0 {
- tc = 's'
- } else if (mode & os.ModeNamedPipe) != 0 {
- tc = 'p'
- }
-
- // owner
- orc := '-'
- if (mode & 0400) != 0 {
- orc = 'r'
- }
- owc := '-'
- if (mode & 0200) != 0 {
- owc = 'w'
- }
- oxc := '-'
- ox := (mode & 0100) != 0
- setuid := (mode & os.ModeSetuid) != 0
- if ox && setuid {
- oxc = 's'
- } else if setuid {
- oxc = 'S'
- } else if ox {
- oxc = 'x'
- }
-
- // group
- grc := '-'
- if (mode & 040) != 0 {
- grc = 'r'
- }
- gwc := '-'
- if (mode & 020) != 0 {
- gwc = 'w'
- }
- gxc := '-'
- gx := (mode & 010) != 0
- setgid := (mode & os.ModeSetgid) != 0
- if gx && setgid {
- gxc = 's'
- } else if setgid {
- gxc = 'S'
- } else if gx {
- gxc = 'x'
- }
-
- // all / others
- arc := '-'
- if (mode & 04) != 0 {
- arc = 'r'
- }
- awc := '-'
- if (mode & 02) != 0 {
- awc = 'w'
- }
- axc := '-'
- ax := (mode & 01) != 0
- sticky := (mode & os.ModeSticky) != 0
- if ax && sticky {
- axc = 't'
- } else if sticky {
- axc = 'T'
- } else if ax {
- axc = 'x'
- }
-
- return fmt.Sprintf("%c%c%c%c%c%c%c%c%c%c", tc, orc, owc, oxc, grc, gwc, gxc, arc, awc, axc)
-}
diff --git a/vendor/github.com/pkg/sftp/server_statvfs_impl.go b/vendor/github.com/pkg/sftp/server_statvfs_impl.go
index 4cf91dc8..94b6d832 100644
--- a/vendor/github.com/pkg/sftp/server_statvfs_impl.go
+++ b/vendor/github.com/pkg/sftp/server_statvfs_impl.go
@@ -9,17 +9,21 @@ import (
"syscall"
)
-func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) responsePacket {
- stat := &syscall.Statfs_t{}
- if err := syscall.Statfs(p.Path, stat); err != nil {
- return statusFromError(p, err)
- }
-
- retPkt, err := statvfsFromStatfst(stat)
+func (p *sshFxpExtendedPacketStatVFS) respond(svr *Server) responsePacket {
+ retPkt, err := getStatVFSForPath(p.Path)
if err != nil {
- return statusFromError(p, err)
+ return statusFromError(p.ID, err)
}
retPkt.ID = p.ID
return retPkt
}
+
+func getStatVFSForPath(name string) (*StatVFS, error) {
+ var stat syscall.Statfs_t
+ if err := syscall.Statfs(name, &stat); err != nil {
+ return nil, err
+ }
+
+ return statvfsFromStatfst(&stat)
+}
diff --git a/vendor/github.com/pkg/sftp/server_statvfs_plan9.go b/vendor/github.com/pkg/sftp/server_statvfs_plan9.go
new file mode 100644
index 00000000..e71a27d3
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/server_statvfs_plan9.go
@@ -0,0 +1,13 @@
+package sftp
+
+import (
+ "syscall"
+)
+
+func (p *sshFxpExtendedPacketStatVFS) respond(svr *Server) responsePacket {
+ return statusFromError(p.ID, syscall.EPLAN9)
+}
+
+func getStatVFSForPath(name string) (*StatVFS, error) {
+ return nil, syscall.EPLAN9
+}
diff --git a/vendor/github.com/pkg/sftp/server_statvfs_stubs.go b/vendor/github.com/pkg/sftp/server_statvfs_stubs.go
index c6f61643..fbf49068 100644
--- a/vendor/github.com/pkg/sftp/server_statvfs_stubs.go
+++ b/vendor/github.com/pkg/sftp/server_statvfs_stubs.go
@@ -1,4 +1,4 @@
-// +build !darwin,!linux
+// +build !darwin,!linux,!plan9
package sftp
@@ -6,6 +6,10 @@ import (
"syscall"
)
-func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) responsePacket {
- return statusFromError(p, syscall.ENOTSUP)
+func (p *sshFxpExtendedPacketStatVFS) respond(svr *Server) responsePacket {
+ return statusFromError(p.ID, syscall.ENOTSUP)
+}
+
+func getStatVFSForPath(name string) (*StatVFS, error) {
+ return nil, syscall.ENOTSUP
}
diff --git a/vendor/github.com/pkg/sftp/server_stubs.go b/vendor/github.com/pkg/sftp/server_stubs.go
deleted file mode 100644
index a14c7348..00000000
--- a/vendor/github.com/pkg/sftp/server_stubs.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// +build !cgo,!plan9 windows android
-
-package sftp
-
-import (
- "os"
- "time"
- "fmt"
-)
-
-func runLs(dirname string, dirent os.FileInfo) string {
- typeword := runLsTypeWord(dirent)
- numLinks := 1
- if dirent.IsDir() {
- numLinks = 0
- }
- username := "root"
- groupname := "root"
- mtime := dirent.ModTime()
- monthStr := mtime.Month().String()[0:3]
- day := mtime.Day()
- year := mtime.Year()
- now := time.Now()
- isOld := mtime.Before(now.Add(-time.Hour * 24 * 365 / 2))
-
- yearOrTime := fmt.Sprintf("%02d:%02d", mtime.Hour(), mtime.Minute())
- if isOld {
- yearOrTime = fmt.Sprintf("%d", year)
- }
-
- return fmt.Sprintf("%s %4d %-8s %-8s %8d %s %2d %5s %s", typeword, numLinks, username, groupname, dirent.Size(), monthStr, day, yearOrTime, dirent.Name())
-}
diff --git a/vendor/github.com/pkg/sftp/server_unix.go b/vendor/github.com/pkg/sftp/server_unix.go
deleted file mode 100644
index abceca49..00000000
--- a/vendor/github.com/pkg/sftp/server_unix.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris aix
-// +build cgo
-
-package sftp
-
-import (
- "fmt"
- "os"
- "path"
- "syscall"
- "time"
-)
-
-func runLsStatt(dirent os.FileInfo, statt *syscall.Stat_t) string {
- // example from openssh sftp server:
- // crw-rw-rw- 1 root wheel 0 Jul 31 20:52 ttyvd
- // format:
- // {directory / char device / etc}{rwxrwxrwx} {number of links} owner group size month day [time (this year) | year (otherwise)] name
-
- typeword := runLsTypeWord(dirent)
- numLinks := statt.Nlink
- uid := statt.Uid
- gid := statt.Gid
- username := fmt.Sprintf("%d", uid)
- groupname := fmt.Sprintf("%d", gid)
- // TODO FIXME: uid -> username, gid -> groupname lookup for ls -l format output
-
- mtime := dirent.ModTime()
- monthStr := mtime.Month().String()[0:3]
- day := mtime.Day()
- year := mtime.Year()
- now := time.Now()
- isOld := mtime.Before(now.Add(-time.Hour * 24 * 365 / 2))
-
- yearOrTime := fmt.Sprintf("%02d:%02d", mtime.Hour(), mtime.Minute())
- if isOld {
- yearOrTime = fmt.Sprintf("%d", year)
- }
-
- return fmt.Sprintf("%s %4d %-8s %-8s %8d %s %2d %5s %s", typeword, numLinks, username, groupname, dirent.Size(), monthStr, day, yearOrTime, dirent.Name())
-}
-
-// ls -l style output for a file, which is in the 'long output' section of a readdir response packet
-// this is a very simple (lazy) implementation, just enough to look almost like openssh in a few basic cases
-func runLs(dirname string, dirent os.FileInfo) string {
- dsys := dirent.Sys()
- if dsys == nil {
- } else if statt, ok := dsys.(*syscall.Stat_t); !ok {
- } else {
- return runLsStatt(dirent, statt)
- }
-
- return path.Join(dirname, dirent.Name())
-}
diff --git a/vendor/github.com/pkg/sftp/sftp.go b/vendor/github.com/pkg/sftp/sftp.go
index 3cdb14df..9a63c39d 100644
--- a/vendor/github.com/pkg/sftp/sftp.go
+++ b/vendor/github.com/pkg/sftp/sftp.go
@@ -4,144 +4,152 @@ package sftp
import (
"fmt"
-
- "github.com/pkg/errors"
)
const (
- ssh_FXP_INIT = 1
- ssh_FXP_VERSION = 2
- ssh_FXP_OPEN = 3
- ssh_FXP_CLOSE = 4
- ssh_FXP_READ = 5
- ssh_FXP_WRITE = 6
- ssh_FXP_LSTAT = 7
- ssh_FXP_FSTAT = 8
- ssh_FXP_SETSTAT = 9
- ssh_FXP_FSETSTAT = 10
- ssh_FXP_OPENDIR = 11
- ssh_FXP_READDIR = 12
- ssh_FXP_REMOVE = 13
- ssh_FXP_MKDIR = 14
- ssh_FXP_RMDIR = 15
- ssh_FXP_REALPATH = 16
- ssh_FXP_STAT = 17
- ssh_FXP_RENAME = 18
- ssh_FXP_READLINK = 19
- ssh_FXP_SYMLINK = 20
- ssh_FXP_STATUS = 101
- ssh_FXP_HANDLE = 102
- ssh_FXP_DATA = 103
- ssh_FXP_NAME = 104
- ssh_FXP_ATTRS = 105
- ssh_FXP_EXTENDED = 200
- ssh_FXP_EXTENDED_REPLY = 201
+ sshFxpInit = 1
+ sshFxpVersion = 2
+ sshFxpOpen = 3
+ sshFxpClose = 4
+ sshFxpRead = 5
+ sshFxpWrite = 6
+ sshFxpLstat = 7
+ sshFxpFstat = 8
+ sshFxpSetstat = 9
+ sshFxpFsetstat = 10
+ sshFxpOpendir = 11
+ sshFxpReaddir = 12
+ sshFxpRemove = 13
+ sshFxpMkdir = 14
+ sshFxpRmdir = 15
+ sshFxpRealpath = 16
+ sshFxpStat = 17
+ sshFxpRename = 18
+ sshFxpReadlink = 19
+ sshFxpSymlink = 20
+ sshFxpStatus = 101
+ sshFxpHandle = 102
+ sshFxpData = 103
+ sshFxpName = 104
+ sshFxpAttrs = 105
+ sshFxpExtended = 200
+ sshFxpExtendedReply = 201
)
const (
- ssh_FX_OK = 0
- ssh_FX_EOF = 1
- ssh_FX_NO_SUCH_FILE = 2
- ssh_FX_PERMISSION_DENIED = 3
- ssh_FX_FAILURE = 4
- ssh_FX_BAD_MESSAGE = 5
- ssh_FX_NO_CONNECTION = 6
- ssh_FX_CONNECTION_LOST = 7
- ssh_FX_OP_UNSUPPORTED = 8
+ sshFxOk = 0
+ sshFxEOF = 1
+ sshFxNoSuchFile = 2
+ sshFxPermissionDenied = 3
+ sshFxFailure = 4
+ sshFxBadMessage = 5
+ sshFxNoConnection = 6
+ sshFxConnectionLost = 7
+ sshFxOPUnsupported = 8
// see draft-ietf-secsh-filexfer-13
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1
- ssh_FX_INVALID_HANDLE = 9
- ssh_FX_NO_SUCH_PATH = 10
- ssh_FX_FILE_ALREADY_EXISTS = 11
- ssh_FX_WRITE_PROTECT = 12
- ssh_FX_NO_MEDIA = 13
- ssh_FX_NO_SPACE_ON_FILESYSTEM = 14
- ssh_FX_QUOTA_EXCEEDED = 15
- ssh_FX_UNKNOWN_PRINCIPAL = 16
- ssh_FX_LOCK_CONFLICT = 17
- ssh_FX_DIR_NOT_EMPTY = 18
- ssh_FX_NOT_A_DIRECTORY = 19
- ssh_FX_INVALID_FILENAME = 20
- ssh_FX_LINK_LOOP = 21
- ssh_FX_CANNOT_DELETE = 22
- ssh_FX_INVALID_PARAMETER = 23
- ssh_FX_FILE_IS_A_DIRECTORY = 24
- ssh_FX_BYTE_RANGE_LOCK_CONFLICT = 25
- ssh_FX_BYTE_RANGE_LOCK_REFUSED = 26
- ssh_FX_DELETE_PENDING = 27
- ssh_FX_FILE_CORRUPT = 28
- ssh_FX_OWNER_INVALID = 29
- ssh_FX_GROUP_INVALID = 30
- ssh_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31
+ sshFxInvalidHandle = 9
+ sshFxNoSuchPath = 10
+ sshFxFileAlreadyExists = 11
+ sshFxWriteProtect = 12
+ sshFxNoMedia = 13
+ sshFxNoSpaceOnFilesystem = 14
+ sshFxQuotaExceeded = 15
+ sshFxUnknownPrincipal = 16
+ sshFxLockConflict = 17
+ sshFxDirNotEmpty = 18
+ sshFxNotADirectory = 19
+ sshFxInvalidFilename = 20
+ sshFxLinkLoop = 21
+ sshFxCannotDelete = 22
+ sshFxInvalidParameter = 23
+ sshFxFileIsADirectory = 24
+ sshFxByteRangeLockConflict = 25
+ sshFxByteRangeLockRefused = 26
+ sshFxDeletePending = 27
+ sshFxFileCorrupt = 28
+ sshFxOwnerInvalid = 29
+ sshFxGroupInvalid = 30
+ sshFxNoMatchingByteRangeLock = 31
)
const (
- ssh_FXF_READ = 0x00000001
- ssh_FXF_WRITE = 0x00000002
- ssh_FXF_APPEND = 0x00000004
- ssh_FXF_CREAT = 0x00000008
- ssh_FXF_TRUNC = 0x00000010
- ssh_FXF_EXCL = 0x00000020
+ sshFxfRead = 0x00000001
+ sshFxfWrite = 0x00000002
+ sshFxfAppend = 0x00000004
+ sshFxfCreat = 0x00000008
+ sshFxfTrunc = 0x00000010
+ sshFxfExcl = 0x00000020
+)
+
+var (
+ // supportedSFTPExtensions defines the supported extensions
+ supportedSFTPExtensions = []sshExtensionPair{
+ {"hardlink@openssh.com", "1"},
+ {"posix-rename@openssh.com", "1"},
+ {"statvfs@openssh.com", "2"},
+ }
+ sftpExtensions = supportedSFTPExtensions
)
type fxp uint8
func (f fxp) String() string {
switch f {
- case ssh_FXP_INIT:
+ case sshFxpInit:
return "SSH_FXP_INIT"
- case ssh_FXP_VERSION:
+ case sshFxpVersion:
return "SSH_FXP_VERSION"
- case ssh_FXP_OPEN:
+ case sshFxpOpen:
return "SSH_FXP_OPEN"
- case ssh_FXP_CLOSE:
+ case sshFxpClose:
return "SSH_FXP_CLOSE"
- case ssh_FXP_READ:
+ case sshFxpRead:
return "SSH_FXP_READ"
- case ssh_FXP_WRITE:
+ case sshFxpWrite:
return "SSH_FXP_WRITE"
- case ssh_FXP_LSTAT:
+ case sshFxpLstat:
return "SSH_FXP_LSTAT"
- case ssh_FXP_FSTAT:
+ case sshFxpFstat:
return "SSH_FXP_FSTAT"
- case ssh_FXP_SETSTAT:
+ case sshFxpSetstat:
return "SSH_FXP_SETSTAT"
- case ssh_FXP_FSETSTAT:
+ case sshFxpFsetstat:
return "SSH_FXP_FSETSTAT"
- case ssh_FXP_OPENDIR:
+ case sshFxpOpendir:
return "SSH_FXP_OPENDIR"
- case ssh_FXP_READDIR:
+ case sshFxpReaddir:
return "SSH_FXP_READDIR"
- case ssh_FXP_REMOVE:
+ case sshFxpRemove:
return "SSH_FXP_REMOVE"
- case ssh_FXP_MKDIR:
+ case sshFxpMkdir:
return "SSH_FXP_MKDIR"
- case ssh_FXP_RMDIR:
+ case sshFxpRmdir:
return "SSH_FXP_RMDIR"
- case ssh_FXP_REALPATH:
+ case sshFxpRealpath:
return "SSH_FXP_REALPATH"
- case ssh_FXP_STAT:
+ case sshFxpStat:
return "SSH_FXP_STAT"
- case ssh_FXP_RENAME:
+ case sshFxpRename:
return "SSH_FXP_RENAME"
- case ssh_FXP_READLINK:
+ case sshFxpReadlink:
return "SSH_FXP_READLINK"
- case ssh_FXP_SYMLINK:
+ case sshFxpSymlink:
return "SSH_FXP_SYMLINK"
- case ssh_FXP_STATUS:
+ case sshFxpStatus:
return "SSH_FXP_STATUS"
- case ssh_FXP_HANDLE:
+ case sshFxpHandle:
return "SSH_FXP_HANDLE"
- case ssh_FXP_DATA:
+ case sshFxpData:
return "SSH_FXP_DATA"
- case ssh_FXP_NAME:
+ case sshFxpName:
return "SSH_FXP_NAME"
- case ssh_FXP_ATTRS:
+ case sshFxpAttrs:
return "SSH_FXP_ATTRS"
- case ssh_FXP_EXTENDED:
+ case sshFxpExtended:
return "SSH_FXP_EXTENDED"
- case ssh_FXP_EXTENDED_REPLY:
+ case sshFxpExtendedReply:
return "SSH_FXP_EXTENDED_REPLY"
default:
return "unknown"
@@ -152,23 +160,23 @@ type fx uint8
func (f fx) String() string {
switch f {
- case ssh_FX_OK:
+ case sshFxOk:
return "SSH_FX_OK"
- case ssh_FX_EOF:
+ case sshFxEOF:
return "SSH_FX_EOF"
- case ssh_FX_NO_SUCH_FILE:
+ case sshFxNoSuchFile:
return "SSH_FX_NO_SUCH_FILE"
- case ssh_FX_PERMISSION_DENIED:
+ case sshFxPermissionDenied:
return "SSH_FX_PERMISSION_DENIED"
- case ssh_FX_FAILURE:
+ case sshFxFailure:
return "SSH_FX_FAILURE"
- case ssh_FX_BAD_MESSAGE:
+ case sshFxBadMessage:
return "SSH_FX_BAD_MESSAGE"
- case ssh_FX_NO_CONNECTION:
+ case sshFxNoConnection:
return "SSH_FX_NO_CONNECTION"
- case ssh_FX_CONNECTION_LOST:
+ case sshFxConnectionLost:
return "SSH_FX_CONNECTION_LOST"
- case ssh_FX_OP_UNSUPPORTED:
+ case sshFxOPUnsupported:
return "SSH_FX_OP_UNSUPPORTED"
default:
return "unknown"
@@ -184,21 +192,21 @@ func (u *unexpectedPacketErr) Error() string {
}
func unimplementedPacketErr(u uint8) error {
- return errors.Errorf("sftp: unimplemented packet type: got %v", fxp(u))
+ return fmt.Errorf("sftp: unimplemented packet type: got %v", fxp(u))
}
type unexpectedIDErr struct{ want, got uint32 }
func (u *unexpectedIDErr) Error() string {
- return fmt.Sprintf("sftp: unexpected id: want %v, got %v", u.want, u.got)
+ return fmt.Sprintf("sftp: unexpected id: want %d, got %d", u.want, u.got)
}
func unimplementedSeekWhence(whence int) error {
- return errors.Errorf("sftp: unimplemented seek whence %v", whence)
+ return fmt.Errorf("sftp: unimplemented seek whence %d", whence)
}
func unexpectedCount(want, got uint32) error {
- return errors.Errorf("sftp: unexpected count: want %v, got %v", want, got)
+ return fmt.Errorf("sftp: unexpected count: want %d, got %d", want, got)
}
type unexpectedVersionErr struct{ want, got uint32 }
@@ -214,4 +222,37 @@ type StatusError struct {
msg, lang string
}
-func (s *StatusError) Error() string { return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code)) }
+func (s *StatusError) Error() string {
+ return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code))
+}
+
+// FxCode returns the error code typed to match against the exported codes
+func (s *StatusError) FxCode() fxerr {
+ return fxerr(s.Code)
+}
+
+func getSupportedExtensionByName(extensionName string) (sshExtensionPair, error) {
+ for _, supportedExtension := range supportedSFTPExtensions {
+ if supportedExtension.Name == extensionName {
+ return supportedExtension, nil
+ }
+ }
+ return sshExtensionPair{}, fmt.Errorf("unsupported extension: %s", extensionName)
+}
+
+// SetSFTPExtensions allows to customize the supported server extensions.
+// See the variable supportedSFTPExtensions for supported extensions.
+// This method accepts a slice of sshExtensionPair names for example 'hardlink@openssh.com'.
+// If an invalid extension is given an error will be returned and nothing will be changed
+func SetSFTPExtensions(extensions ...string) error {
+ tempExtensions := []sshExtensionPair{}
+ for _, extension := range extensions {
+ sftpExtension, err := getSupportedExtensionByName(extension)
+ if err != nil {
+ return err
+ }
+ tempExtensions = append(tempExtensions, sftpExtension)
+ }
+ sftpExtensions = tempExtensions
+ return nil
+}
diff --git a/vendor/github.com/pkg/sftp/stat_plan9.go b/vendor/github.com/pkg/sftp/stat_plan9.go
new file mode 100644
index 00000000..761abdf5
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/stat_plan9.go
@@ -0,0 +1,103 @@
+package sftp
+
+import (
+ "os"
+ "syscall"
+)
+
+var EBADF = syscall.NewError("fd out of range or not open")
+
+func wrapPathError(filepath string, err error) error {
+ if errno, ok := err.(syscall.ErrorString); ok {
+ return &os.PathError{Path: filepath, Err: errno}
+ }
+ return err
+}
+
+// translateErrno translates a syscall error number to a SFTP error code.
+func translateErrno(errno syscall.ErrorString) uint32 {
+ switch errno {
+ case "":
+ return sshFxOk
+ case syscall.ENOENT:
+ return sshFxNoSuchFile
+ case syscall.EPERM:
+ return sshFxPermissionDenied
+ }
+
+ return sshFxFailure
+}
+
+func translateSyscallError(err error) (uint32, bool) {
+ switch e := err.(type) {
+ case syscall.ErrorString:
+ return translateErrno(e), true
+ case *os.PathError:
+ debug("statusFromError,pathError: error is %T %#v", e.Err, e.Err)
+ if errno, ok := e.Err.(syscall.ErrorString); ok {
+ return translateErrno(errno), true
+ }
+ }
+ return 0, false
+}
+
+// isRegular returns true if the mode describes a regular file.
+func isRegular(mode uint32) bool {
+ return mode&S_IFMT == syscall.S_IFREG
+}
+
+// toFileMode converts sftp filemode bits to the os.FileMode specification
+func toFileMode(mode uint32) os.FileMode {
+ var fm = os.FileMode(mode & 0777)
+
+ switch mode & S_IFMT {
+ case syscall.S_IFBLK:
+ fm |= os.ModeDevice
+ case syscall.S_IFCHR:
+ fm |= os.ModeDevice | os.ModeCharDevice
+ case syscall.S_IFDIR:
+ fm |= os.ModeDir
+ case syscall.S_IFIFO:
+ fm |= os.ModeNamedPipe
+ case syscall.S_IFLNK:
+ fm |= os.ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fm |= os.ModeSocket
+ }
+
+ return fm
+}
+
+// fromFileMode converts from the os.FileMode specification to sftp filemode bits
+func fromFileMode(mode os.FileMode) uint32 {
+ ret := uint32(mode & os.ModePerm)
+
+ switch mode & os.ModeType {
+ case os.ModeDevice | os.ModeCharDevice:
+ ret |= syscall.S_IFCHR
+ case os.ModeDevice:
+ ret |= syscall.S_IFBLK
+ case os.ModeDir:
+ ret |= syscall.S_IFDIR
+ case os.ModeNamedPipe:
+ ret |= syscall.S_IFIFO
+ case os.ModeSymlink:
+ ret |= syscall.S_IFLNK
+ case 0:
+ ret |= syscall.S_IFREG
+ case os.ModeSocket:
+ ret |= syscall.S_IFSOCK
+ }
+
+ return ret
+}
+
+// Plan 9 doesn't have setuid, setgid or sticky, but a Plan 9 client should
+// be able to send these bits to a POSIX server.
+const (
+ s_ISUID = 04000
+ s_ISGID = 02000
+ s_ISVTX = 01000
+)
diff --git a/vendor/github.com/pkg/sftp/stat_posix.go b/vendor/github.com/pkg/sftp/stat_posix.go
new file mode 100644
index 00000000..92f76e2f
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/stat_posix.go
@@ -0,0 +1,123 @@
+// +build !plan9
+
+package sftp
+
+import (
+ "os"
+ "syscall"
+)
+
+const EBADF = syscall.EBADF
+
+func wrapPathError(filepath string, err error) error {
+ if errno, ok := err.(syscall.Errno); ok {
+ return &os.PathError{Path: filepath, Err: errno}
+ }
+ return err
+}
+
+// translateErrno translates a syscall error number to a SFTP error code.
+func translateErrno(errno syscall.Errno) uint32 {
+ switch errno {
+ case 0:
+ return sshFxOk
+ case syscall.ENOENT:
+ return sshFxNoSuchFile
+ case syscall.EPERM:
+ return sshFxPermissionDenied
+ }
+
+ return sshFxFailure
+}
+
+func translateSyscallError(err error) (uint32, bool) {
+ switch e := err.(type) {
+ case syscall.Errno:
+ return translateErrno(e), true
+ case *os.PathError:
+ debug("statusFromError,pathError: error is %T %#v", e.Err, e.Err)
+ if errno, ok := e.Err.(syscall.Errno); ok {
+ return translateErrno(errno), true
+ }
+ }
+ return 0, false
+}
+
+// isRegular returns true if the mode describes a regular file.
+func isRegular(mode uint32) bool {
+ return mode&S_IFMT == syscall.S_IFREG
+}
+
+// toFileMode converts sftp filemode bits to the os.FileMode specification
+func toFileMode(mode uint32) os.FileMode {
+ var fm = os.FileMode(mode & 0777)
+
+ switch mode & S_IFMT {
+ case syscall.S_IFBLK:
+ fm |= os.ModeDevice
+ case syscall.S_IFCHR:
+ fm |= os.ModeDevice | os.ModeCharDevice
+ case syscall.S_IFDIR:
+ fm |= os.ModeDir
+ case syscall.S_IFIFO:
+ fm |= os.ModeNamedPipe
+ case syscall.S_IFLNK:
+ fm |= os.ModeSymlink
+ case syscall.S_IFREG:
+ // nothing to do
+ case syscall.S_IFSOCK:
+ fm |= os.ModeSocket
+ }
+
+ if mode&syscall.S_ISUID != 0 {
+ fm |= os.ModeSetuid
+ }
+ if mode&syscall.S_ISGID != 0 {
+ fm |= os.ModeSetgid
+ }
+ if mode&syscall.S_ISVTX != 0 {
+ fm |= os.ModeSticky
+ }
+
+ return fm
+}
+
+// fromFileMode converts from the os.FileMode specification to sftp filemode bits
+func fromFileMode(mode os.FileMode) uint32 {
+ ret := uint32(mode & os.ModePerm)
+
+ switch mode & os.ModeType {
+ case os.ModeDevice | os.ModeCharDevice:
+ ret |= syscall.S_IFCHR
+ case os.ModeDevice:
+ ret |= syscall.S_IFBLK
+ case os.ModeDir:
+ ret |= syscall.S_IFDIR
+ case os.ModeNamedPipe:
+ ret |= syscall.S_IFIFO
+ case os.ModeSymlink:
+ ret |= syscall.S_IFLNK
+ case 0:
+ ret |= syscall.S_IFREG
+ case os.ModeSocket:
+ ret |= syscall.S_IFSOCK
+ }
+
+ if mode&os.ModeSetuid != 0 {
+ ret |= syscall.S_ISUID
+ }
+ if mode&os.ModeSetgid != 0 {
+ ret |= syscall.S_ISGID
+ }
+ if mode&os.ModeSticky != 0 {
+ ret |= syscall.S_ISVTX
+ }
+
+ return ret
+}
+
+const (
+ s_ISUID = syscall.S_ISUID
+ s_ISGID = syscall.S_ISGID
+ s_ISVTX = syscall.S_ISVTX
+)
diff --git a/vendor/github.com/pkg/sftp/syscall_fixed.go b/vendor/github.com/pkg/sftp/syscall_fixed.go
new file mode 100644
index 00000000..d4045777
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/syscall_fixed.go
@@ -0,0 +1,9 @@
+// +build plan9 windows js,wasm
+
+// Go defines S_IFMT on windows, plan9 and js/wasm as 0x1f000 instead of
+// 0xf000. None of the the other S_IFxyz values include the "1" (in 0x1f000)
+// which prevents them from matching the bitmask.
+
+package sftp
+
+const S_IFMT = 0xf000
diff --git a/vendor/github.com/pkg/sftp/syscall_good.go b/vendor/github.com/pkg/sftp/syscall_good.go
new file mode 100644
index 00000000..4c2b240c
--- /dev/null
+++ b/vendor/github.com/pkg/sftp/syscall_good.go
@@ -0,0 +1,8 @@
+// +build !plan9,!windows
+// +build !js !wasm
+
+package sftp
+
+import "syscall"
+
+const S_IFMT = syscall.S_IFMT
diff --git a/vendor/github.com/stretchr/testify/LICENSE b/vendor/github.com/stretchr/testify/LICENSE
index f38ec595..4b0421cf 100644
--- a/vendor/github.com/stretchr/testify/LICENSE
+++ b/vendor/github.com/stretchr/testify/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2012-2018 Mat Ryer and Tyler Bunnell
+Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_compare.go b/vendor/github.com/stretchr/testify/assert/assertion_compare.go
new file mode 100644
index 00000000..41649d26
--- /dev/null
+++ b/vendor/github.com/stretchr/testify/assert/assertion_compare.go
@@ -0,0 +1,394 @@
+package assert
+
+import (
+ "fmt"
+ "reflect"
+)
+
+type CompareType int
+
+const (
+ compareLess CompareType = iota - 1
+ compareEqual
+ compareGreater
+)
+
+var (
+ intType = reflect.TypeOf(int(1))
+ int8Type = reflect.TypeOf(int8(1))
+ int16Type = reflect.TypeOf(int16(1))
+ int32Type = reflect.TypeOf(int32(1))
+ int64Type = reflect.TypeOf(int64(1))
+
+ uintType = reflect.TypeOf(uint(1))
+ uint8Type = reflect.TypeOf(uint8(1))
+ uint16Type = reflect.TypeOf(uint16(1))
+ uint32Type = reflect.TypeOf(uint32(1))
+ uint64Type = reflect.TypeOf(uint64(1))
+
+ float32Type = reflect.TypeOf(float32(1))
+ float64Type = reflect.TypeOf(float64(1))
+
+ stringType = reflect.TypeOf("")
+)
+
+func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
+ obj1Value := reflect.ValueOf(obj1)
+ obj2Value := reflect.ValueOf(obj2)
+
+ // throughout this switch we try and avoid calling .Convert() if possible,
+ // as this has a pretty big performance impact
+ switch kind {
+ case reflect.Int:
+ {
+ intobj1, ok := obj1.(int)
+ if !ok {
+ intobj1 = obj1Value.Convert(intType).Interface().(int)
+ }
+ intobj2, ok := obj2.(int)
+ if !ok {
+ intobj2 = obj2Value.Convert(intType).Interface().(int)
+ }
+ if intobj1 > intobj2 {
+ return compareGreater, true
+ }
+ if intobj1 == intobj2 {
+ return compareEqual, true
+ }
+ if intobj1 < intobj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Int8:
+ {
+ int8obj1, ok := obj1.(int8)
+ if !ok {
+ int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
+ }
+ int8obj2, ok := obj2.(int8)
+ if !ok {
+ int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
+ }
+ if int8obj1 > int8obj2 {
+ return compareGreater, true
+ }
+ if int8obj1 == int8obj2 {
+ return compareEqual, true
+ }
+ if int8obj1 < int8obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Int16:
+ {
+ int16obj1, ok := obj1.(int16)
+ if !ok {
+ int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
+ }
+ int16obj2, ok := obj2.(int16)
+ if !ok {
+ int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
+ }
+ if int16obj1 > int16obj2 {
+ return compareGreater, true
+ }
+ if int16obj1 == int16obj2 {
+ return compareEqual, true
+ }
+ if int16obj1 < int16obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Int32:
+ {
+ int32obj1, ok := obj1.(int32)
+ if !ok {
+ int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
+ }
+ int32obj2, ok := obj2.(int32)
+ if !ok {
+ int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
+ }
+ if int32obj1 > int32obj2 {
+ return compareGreater, true
+ }
+ if int32obj1 == int32obj2 {
+ return compareEqual, true
+ }
+ if int32obj1 < int32obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Int64:
+ {
+ int64obj1, ok := obj1.(int64)
+ if !ok {
+ int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
+ }
+ int64obj2, ok := obj2.(int64)
+ if !ok {
+ int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
+ }
+ if int64obj1 > int64obj2 {
+ return compareGreater, true
+ }
+ if int64obj1 == int64obj2 {
+ return compareEqual, true
+ }
+ if int64obj1 < int64obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Uint:
+ {
+ uintobj1, ok := obj1.(uint)
+ if !ok {
+ uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
+ }
+ uintobj2, ok := obj2.(uint)
+ if !ok {
+ uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
+ }
+ if uintobj1 > uintobj2 {
+ return compareGreater, true
+ }
+ if uintobj1 == uintobj2 {
+ return compareEqual, true
+ }
+ if uintobj1 < uintobj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Uint8:
+ {
+ uint8obj1, ok := obj1.(uint8)
+ if !ok {
+ uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
+ }
+ uint8obj2, ok := obj2.(uint8)
+ if !ok {
+ uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
+ }
+ if uint8obj1 > uint8obj2 {
+ return compareGreater, true
+ }
+ if uint8obj1 == uint8obj2 {
+ return compareEqual, true
+ }
+ if uint8obj1 < uint8obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Uint16:
+ {
+ uint16obj1, ok := obj1.(uint16)
+ if !ok {
+ uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
+ }
+ uint16obj2, ok := obj2.(uint16)
+ if !ok {
+ uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
+ }
+ if uint16obj1 > uint16obj2 {
+ return compareGreater, true
+ }
+ if uint16obj1 == uint16obj2 {
+ return compareEqual, true
+ }
+ if uint16obj1 < uint16obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Uint32:
+ {
+ uint32obj1, ok := obj1.(uint32)
+ if !ok {
+ uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
+ }
+ uint32obj2, ok := obj2.(uint32)
+ if !ok {
+ uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
+ }
+ if uint32obj1 > uint32obj2 {
+ return compareGreater, true
+ }
+ if uint32obj1 == uint32obj2 {
+ return compareEqual, true
+ }
+ if uint32obj1 < uint32obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Uint64:
+ {
+ uint64obj1, ok := obj1.(uint64)
+ if !ok {
+ uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
+ }
+ uint64obj2, ok := obj2.(uint64)
+ if !ok {
+ uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
+ }
+ if uint64obj1 > uint64obj2 {
+ return compareGreater, true
+ }
+ if uint64obj1 == uint64obj2 {
+ return compareEqual, true
+ }
+ if uint64obj1 < uint64obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Float32:
+ {
+ float32obj1, ok := obj1.(float32)
+ if !ok {
+ float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
+ }
+ float32obj2, ok := obj2.(float32)
+ if !ok {
+ float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
+ }
+ if float32obj1 > float32obj2 {
+ return compareGreater, true
+ }
+ if float32obj1 == float32obj2 {
+ return compareEqual, true
+ }
+ if float32obj1 < float32obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.Float64:
+ {
+ float64obj1, ok := obj1.(float64)
+ if !ok {
+ float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
+ }
+ float64obj2, ok := obj2.(float64)
+ if !ok {
+ float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
+ }
+ if float64obj1 > float64obj2 {
+ return compareGreater, true
+ }
+ if float64obj1 == float64obj2 {
+ return compareEqual, true
+ }
+ if float64obj1 < float64obj2 {
+ return compareLess, true
+ }
+ }
+ case reflect.String:
+ {
+ stringobj1, ok := obj1.(string)
+ if !ok {
+ stringobj1 = obj1Value.Convert(stringType).Interface().(string)
+ }
+ stringobj2, ok := obj2.(string)
+ if !ok {
+ stringobj2 = obj2Value.Convert(stringType).Interface().(string)
+ }
+ if stringobj1 > stringobj2 {
+ return compareGreater, true
+ }
+ if stringobj1 == stringobj2 {
+ return compareEqual, true
+ }
+ if stringobj1 < stringobj2 {
+ return compareLess, true
+ }
+ }
+ }
+
+ return compareEqual, false
+}
+
+// Greater asserts that the first element is greater than the second
+//
+// assert.Greater(t, 2, 1)
+// assert.Greater(t, float64(2), float64(1))
+// assert.Greater(t, "b", "a")
+func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
+ return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
+}
+
+// GreaterOrEqual asserts that the first element is greater than or equal to the second
+//
+// assert.GreaterOrEqual(t, 2, 1)
+// assert.GreaterOrEqual(t, 2, 2)
+// assert.GreaterOrEqual(t, "b", "a")
+// assert.GreaterOrEqual(t, "b", "b")
+func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
+ return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
+}
+
+// Less asserts that the first element is less than the second
+//
+// assert.Less(t, 1, 2)
+// assert.Less(t, float64(1), float64(2))
+// assert.Less(t, "a", "b")
+func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
+ return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
+}
+
+// LessOrEqual asserts that the first element is less than or equal to the second
+//
+// assert.LessOrEqual(t, 1, 2)
+// assert.LessOrEqual(t, 2, 2)
+// assert.LessOrEqual(t, "a", "b")
+// assert.LessOrEqual(t, "b", "b")
+func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
+ return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
+}
+
+// Positive asserts that the specified element is positive
+//
+// assert.Positive(t, 1)
+// assert.Positive(t, 1.23)
+func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
+ zero := reflect.Zero(reflect.TypeOf(e))
+ return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs)
+}
+
+// Negative asserts that the specified element is negative
+//
+// assert.Negative(t, -1)
+// assert.Negative(t, -1.23)
+func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
+ zero := reflect.Zero(reflect.TypeOf(e))
+ return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs)
+}
+
+func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+
+ e1Kind := reflect.ValueOf(e1).Kind()
+ e2Kind := reflect.ValueOf(e2).Kind()
+ if e1Kind != e2Kind {
+ return Fail(t, "Elements should be the same type", msgAndArgs...)
+ }
+
+ compareResult, isComparable := compare(e1, e2, e1Kind)
+ if !isComparable {
+ return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
+ }
+
+ if !containsValue(allowedComparesResults, compareResult) {
+ return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...)
+ }
+
+ return true
+}
+
+func containsValue(values []CompareType, value CompareType) bool {
+ for _, v := range values {
+ if v == value {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go
index bf89ecd2..4dfd1229 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_format.go
+++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go
@@ -93,7 +93,7 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args
// EqualValuesf asserts that two objects are equal or convertable to the same types
// and equal.
//
-// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
+// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@@ -114,6 +114,24 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
return Error(t, err, append([]interface{}{msg}, args...)...)
}
+// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
+// This is a wrapper for errors.As.
+func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
+}
+
+// ErrorIsf asserts that at least one of the errors in err's chain matches target.
+// This is a wrapper for errors.Is.
+func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
+}
+
// Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick.
//
@@ -127,7 +145,7 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick
// Exactlyf asserts that two objects are equal in value and type.
//
-// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
+// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@@ -173,7 +191,7 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool
// Greaterf asserts that the first element is greater than the second
//
// assert.Greaterf(t, 2, 1, "error message %s", "formatted")
-// assert.Greaterf(t, float64(2, "error message %s", "formatted"), float64(1))
+// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted")
// assert.Greaterf(t, "b", "a", "error message %s", "formatted")
func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
@@ -225,7 +243,7 @@ func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, u
//
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
-// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+// Returns whether the assertion was successful (true) or not (false).
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@@ -237,7 +255,7 @@ func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string,
//
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
-// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+// Returns whether the assertion was successful (true) or not (false).
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@@ -245,6 +263,18 @@ func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url stri
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
}
+// HTTPStatusCodef asserts that a specified handler returns a specified status code.
+//
+// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return HTTPStatusCode(t, handler, method, url, values, statuscode, append([]interface{}{msg}, args...)...)
+}
+
// HTTPSuccessf asserts that a specified handler returns a success status code.
//
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
@@ -259,7 +289,7 @@ func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url strin
// Implementsf asserts that an object is implemented by the specified interface.
//
-// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
+// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
@@ -309,6 +339,54 @@ func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsil
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
+// IsDecreasingf asserts that the collection is decreasing
+//
+// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
+// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
+// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
+func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsDecreasing(t, object, append([]interface{}{msg}, args...)...)
+}
+
+// IsIncreasingf asserts that the collection is increasing
+//
+// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
+// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
+// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
+func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsIncreasing(t, object, append([]interface{}{msg}, args...)...)
+}
+
+// IsNonDecreasingf asserts that the collection is not decreasing
+//
+// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
+// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
+// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
+func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...)
+}
+
+// IsNonIncreasingf asserts that the collection is not increasing
+//
+// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
+// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
+// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
+func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
+}
+
// IsTypef asserts that the specified objects are of the same type.
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
@@ -341,7 +419,7 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf
// Lessf asserts that the first element is less than the second
//
// assert.Lessf(t, 1, 2, "error message %s", "formatted")
-// assert.Lessf(t, float64(1, "error message %s", "formatted"), float64(2))
+// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted")
// assert.Lessf(t, "a", "b", "error message %s", "formatted")
func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
@@ -363,6 +441,17 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args .
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
}
+// Negativef asserts that the specified element is negative
+//
+// assert.Negativef(t, -1, "error message %s", "formatted")
+// assert.Negativef(t, -1.23, "error message %s", "formatted")
+func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return Negative(t, e, append([]interface{}{msg}, args...)...)
+}
+
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
@@ -454,6 +543,25 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string,
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
}
+// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
+//
+// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted")
+func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
+}
+
+// NotErrorIsf asserts that at none of the errors in err's chain matches target.
+// This is a wrapper for errors.Is.
+func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
+}
+
// NotNilf asserts that the specified object is not nil.
//
// assert.NotNilf(t, err, "error message %s", "formatted")
@@ -476,7 +584,7 @@ func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bo
// NotRegexpf asserts that a specified regexp does not match a string.
//
-// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
+// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
@@ -550,9 +658,20 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
}
+// Positivef asserts that the specified element is positive
+//
+// assert.Positivef(t, 1, "error message %s", "formatted")
+// assert.Positivef(t, 1.23, "error message %s", "formatted")
+func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ return Positive(t, e, append([]interface{}{msg}, args...)...)
+}
+
// Regexpf asserts that a specified regexp matches a string.
//
-// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
+// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go
index 75ecdcaa..25337a6f 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go
+++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go
@@ -169,7 +169,7 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn
// EqualValuesf asserts that two objects are equal or convertable to the same types
// and equal.
//
-// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
+// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted")
func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@@ -204,6 +204,42 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
return Error(a.t, err, msgAndArgs...)
}
+// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
+// This is a wrapper for errors.As.
+func (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return ErrorAs(a.t, err, target, msgAndArgs...)
+}
+
+// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
+// This is a wrapper for errors.As.
+func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return ErrorAsf(a.t, err, target, msg, args...)
+}
+
+// ErrorIs asserts that at least one of the errors in err's chain matches target.
+// This is a wrapper for errors.Is.
+func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return ErrorIs(a.t, err, target, msgAndArgs...)
+}
+
+// ErrorIsf asserts that at least one of the errors in err's chain matches target.
+// This is a wrapper for errors.Is.
+func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return ErrorIsf(a.t, err, target, msg, args...)
+}
+
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
@@ -251,7 +287,7 @@ func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArg
// Exactlyf asserts that two objects are equal in value and type.
//
-// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
+// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted")
func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@@ -370,7 +406,7 @@ func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string,
// Greaterf asserts that the first element is greater than the second
//
// a.Greaterf(2, 1, "error message %s", "formatted")
-// a.Greaterf(float64(2, "error message %s", "formatted"), float64(1))
+// a.Greaterf(float64(2), float64(1), "error message %s", "formatted")
// a.Greaterf("b", "a", "error message %s", "formatted")
func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
@@ -447,7 +483,7 @@ func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url stri
//
// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
-// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@@ -471,7 +507,7 @@ func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url s
//
// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
//
-// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
+// Returns whether the assertion was successful (true) or not (false).
func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@@ -479,6 +515,30 @@ func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url
return HTTPRedirectf(a.t, handler, method, url, values, msg, args...)
}
+// HTTPStatusCode asserts that a specified handler returns a specified status code.
+//
+// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return HTTPStatusCode(a.t, handler, method, url, values, statuscode, msgAndArgs...)
+}
+
+// HTTPStatusCodef asserts that a specified handler returns a specified status code.
+//
+// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted")
+//
+// Returns whether the assertion was successful (true) or not (false).
+func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return HTTPStatusCodef(a.t, handler, method, url, values, statuscode, msg, args...)
+}
+
// HTTPSuccess asserts that a specified handler returns a success status code.
//
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
@@ -515,7 +575,7 @@ func (a *Assertions) Implements(interfaceObject interface{}, object interface{},
// Implementsf asserts that an object is implemented by the specified interface.
//
-// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
+// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted")
func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
@@ -607,6 +667,102 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo
return InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
}
+// IsDecreasing asserts that the collection is decreasing
+//
+// a.IsDecreasing([]int{2, 1, 0})
+// a.IsDecreasing([]float{2, 1})
+// a.IsDecreasing([]string{"b", "a"})
+func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsDecreasing(a.t, object, msgAndArgs...)
+}
+
+// IsDecreasingf asserts that the collection is decreasing
+//
+// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted")
+// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted")
+// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted")
+func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsDecreasingf(a.t, object, msg, args...)
+}
+
+// IsIncreasing asserts that the collection is increasing
+//
+// a.IsIncreasing([]int{1, 2, 3})
+// a.IsIncreasing([]float{1, 2})
+// a.IsIncreasing([]string{"a", "b"})
+func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsIncreasing(a.t, object, msgAndArgs...)
+}
+
+// IsIncreasingf asserts that the collection is increasing
+//
+// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted")
+// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted")
+// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted")
+func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsIncreasingf(a.t, object, msg, args...)
+}
+
+// IsNonDecreasing asserts that the collection is not decreasing
+//
+// a.IsNonDecreasing([]int{1, 1, 2})
+// a.IsNonDecreasing([]float{1, 2})
+// a.IsNonDecreasing([]string{"a", "b"})
+func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsNonDecreasing(a.t, object, msgAndArgs...)
+}
+
+// IsNonDecreasingf asserts that the collection is not decreasing
+//
+// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted")
+// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted")
+// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted")
+func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsNonDecreasingf(a.t, object, msg, args...)
+}
+
+// IsNonIncreasing asserts that the collection is not increasing
+//
+// a.IsNonIncreasing([]int{2, 1, 1})
+// a.IsNonIncreasing([]float{2, 1})
+// a.IsNonIncreasing([]string{"b", "a"})
+func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsNonIncreasing(a.t, object, msgAndArgs...)
+}
+
+// IsNonIncreasingf asserts that the collection is not increasing
+//
+// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted")
+// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted")
+// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted")
+func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return IsNonIncreasingf(a.t, object, msg, args...)
+}
+
// IsType asserts that the specified objects are of the same type.
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
@@ -706,7 +862,7 @@ func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, ar
// Lessf asserts that the first element is less than the second
//
// a.Lessf(1, 2, "error message %s", "formatted")
-// a.Lessf(float64(1, "error message %s", "formatted"), float64(2))
+// a.Lessf(float64(1), float64(2), "error message %s", "formatted")
// a.Lessf("a", "b", "error message %s", "formatted")
func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
@@ -715,6 +871,28 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i
return Lessf(a.t, e1, e2, msg, args...)
}
+// Negative asserts that the specified element is negative
+//
+// a.Negative(-1)
+// a.Negative(-1.23)
+func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return Negative(a.t, e, msgAndArgs...)
+}
+
+// Negativef asserts that the specified element is negative
+//
+// a.Negativef(-1, "error message %s", "formatted")
+// a.Negativef(-1.23, "error message %s", "formatted")
+func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return Negativef(a.t, e, msg, args...)
+}
+
// Never asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
@@ -884,6 +1062,26 @@ func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndAr
return NotEqual(a.t, expected, actual, msgAndArgs...)
}
+// NotEqualValues asserts that two objects are not equal even when converted to the same type
+//
+// a.NotEqualValues(obj1, obj2)
+func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return NotEqualValues(a.t, expected, actual, msgAndArgs...)
+}
+
+// NotEqualValuesf asserts that two objects are not equal even when converted to the same type
+//
+// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted")
+func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return NotEqualValuesf(a.t, expected, actual, msg, args...)
+}
+
// NotEqualf asserts that the specified values are NOT equal.
//
// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
@@ -897,6 +1095,24 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str
return NotEqualf(a.t, expected, actual, msg, args...)
}
+// NotErrorIs asserts that at none of the errors in err's chain matches target.
+// This is a wrapper for errors.Is.
+func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return NotErrorIs(a.t, err, target, msgAndArgs...)
+}
+
+// NotErrorIsf asserts that at none of the errors in err's chain matches target.
+// This is a wrapper for errors.Is.
+func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return NotErrorIsf(a.t, err, target, msg, args...)
+}
+
// NotNil asserts that the specified object is not nil.
//
// a.NotNil(err)
@@ -950,7 +1166,7 @@ func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...in
// NotRegexpf asserts that a specified regexp does not match a string.
//
-// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
+// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted")
// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
@@ -1089,6 +1305,28 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b
return Panicsf(a.t, f, msg, args...)
}
+// Positive asserts that the specified element is positive
+//
+// a.Positive(1)
+// a.Positive(1.23)
+func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return Positive(a.t, e, msgAndArgs...)
+}
+
+// Positivef asserts that the specified element is positive
+//
+// a.Positivef(1, "error message %s", "formatted")
+// a.Positivef(1.23, "error message %s", "formatted")
+func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) bool {
+ if h, ok := a.t.(tHelper); ok {
+ h.Helper()
+ }
+ return Positivef(a.t, e, msg, args...)
+}
+
// Regexp asserts that a specified regexp matches a string.
//
// a.Regexp(regexp.MustCompile("start"), "it's starting")
@@ -1102,7 +1340,7 @@ func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...inter
// Regexpf asserts that a specified regexp matches a string.
//
-// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
+// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")
// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
diff --git a/vendor/github.com/stretchr/testify/assert/assertion_order.go b/vendor/github.com/stretchr/testify/assert/assertion_order.go
index 15a486ca..1c3b4718 100644
--- a/vendor/github.com/stretchr/testify/assert/assertion_order.go
+++ b/vendor/github.com/stretchr/testify/assert/assertion_order.go
@@ -5,305 +5,77 @@ import (
"reflect"
)
-func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
- switch kind {
- case reflect.Int:
- {
- intobj1 := obj1.(int)
- intobj2 := obj2.(int)
- if intobj1 > intobj2 {
- return -1, true
- }
- if intobj1 == intobj2 {
- return 0, true
- }
- if intobj1 < intobj2 {
- return 1, true
- }
- }
- case reflect.Int8:
- {
- int8obj1 := obj1.(int8)
- int8obj2 := obj2.(int8)
- if int8obj1 > int8obj2 {
- return -1, true
- }
- if int8obj1 == int8obj2 {
- return 0, true
- }
- if int8obj1 < int8obj2 {
- return 1, true
- }
- }
- case reflect.Int16:
- {
- int16obj1 := obj1.(int16)
- int16obj2 := obj2.(int16)
- if int16obj1 > int16obj2 {
- return -1, true
- }
- if int16obj1 == int16obj2 {
- return 0, true
- }
- if int16obj1 < int16obj2 {
- return 1, true
- }
- }
- case reflect.Int32:
- {
- int32obj1 := obj1.(int32)
- int32obj2 := obj2.(int32)
- if int32obj1 > int32obj2 {
- return -1, true
- }
- if int32obj1 == int32obj2 {
- return 0, true
- }
- if int32obj1 < int32obj2 {
- return 1, true
- }
- }
- case reflect.Int64:
- {
- int64obj1 := obj1.(int64)
- int64obj2 := obj2.(int64)
- if int64obj1 > int64obj2 {
- return -1, true
- }
- if int64obj1 == int64obj2 {
- return 0, true
- }
- if int64obj1 < int64obj2 {
- return 1, true
- }
- }
- case reflect.Uint:
- {
- uintobj1 := obj1.(uint)
- uintobj2 := obj2.(uint)
- if uintobj1 > uintobj2 {
- return -1, true
- }
- if uintobj1 == uintobj2 {
- return 0, true
- }
- if uintobj1 < uintobj2 {
- return 1, true
- }
- }
- case reflect.Uint8:
- {
- uint8obj1 := obj1.(uint8)
- uint8obj2 := obj2.(uint8)
- if uint8obj1 > uint8obj2 {
- return -1, true
- }
- if uint8obj1 == uint8obj2 {
- return 0, true
- }
- if uint8obj1 < uint8obj2 {
- return 1, true
- }
- }
- case reflect.Uint16:
- {
- uint16obj1 := obj1.(uint16)
- uint16obj2 := obj2.(uint16)
- if uint16obj1 > uint16obj2 {
- return -1, true
- }
- if uint16obj1 == uint16obj2 {
- return 0, true
- }
- if uint16obj1 < uint16obj2 {
- return 1, true
- }
- }
- case reflect.Uint32:
- {
- uint32obj1 := obj1.(uint32)
- uint32obj2 := obj2.(uint32)
- if uint32obj1 > uint32obj2 {
- return -1, true
- }
- if uint32obj1 == uint32obj2 {
- return 0, true
- }
- if uint32obj1 < uint32obj2 {
- return 1, true
- }
- }
- case reflect.Uint64:
- {
- uint64obj1 := obj1.(uint64)
- uint64obj2 := obj2.(uint64)
- if uint64obj1 > uint64obj2 {
- return -1, true
- }
- if uint64obj1 == uint64obj2 {
- return 0, true
- }
- if uint64obj1 < uint64obj2 {
- return 1, true
- }
- }
- case reflect.Float32:
- {
- float32obj1 := obj1.(float32)
- float32obj2 := obj2.(float32)
- if float32obj1 > float32obj2 {
- return -1, true
- }
- if float32obj1 == float32obj2 {
- return 0, true
- }
- if float32obj1 < float32obj2 {
- return 1, true
- }
- }
- case reflect.Float64:
- {
- float64obj1 := obj1.(float64)
- float64obj2 := obj2.(float64)
- if float64obj1 > float64obj2 {
- return -1, true
- }
- if float64obj1 == float64obj2 {
- return 0, true
- }
- if float64obj1 < float64obj2 {
- return 1, true
- }
- }
- case reflect.String:
- {
- stringobj1 := obj1.(string)
- stringobj2 := obj2.(string)
- if stringobj1 > stringobj2 {
- return -1, true
- }
- if stringobj1 == stringobj2 {
- return 0, true
- }
- if stringobj1 < stringobj2 {
- return 1, true
- }
- }
- }
-
- return 0, false
-}
-
-// Greater asserts that the first element is greater than the second
-//
-// assert.Greater(t, 2, 1)
-// assert.Greater(t, float64(2), float64(1))
-// assert.Greater(t, "b", "a")
-func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
+// isOrdered checks that collection contains orderable elements.
+func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
+ objKind := reflect.TypeOf(object).Kind()
+ if objKind != reflect.Slice && objKind != reflect.Array {
+ return false
}
- e1Kind := reflect.ValueOf(e1).Kind()
- e2Kind := reflect.ValueOf(e2).Kind()
- if e1Kind != e2Kind {
- return Fail(t, "Elements should be the same type", msgAndArgs...)
- }
+ objValue := reflect.ValueOf(object)
+ objLen := objValue.Len()
- res, isComparable := compare(e1, e2, e1Kind)
- if !isComparable {
- return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
+ if objLen <= 1 {
+ return true
}
- if res != -1 {
- return Fail(t, fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2), msgAndArgs...)
- }
+ value := objValue.Index(0)
+ valueInterface := value.Interface()
+ firstValueKind := value.Kind()
- return true
-}
+ for i := 1; i < objLen; i++ {
+ prevValue := value
+ prevValueInterface := valueInterface
-// GreaterOrEqual asserts that the first element is greater than or equal to the second
-//
-// assert.GreaterOrEqual(t, 2, 1)
-// assert.GreaterOrEqual(t, 2, 2)
-// assert.GreaterOrEqual(t, "b", "a")
-// assert.GreaterOrEqual(t, "b", "b")
-func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
+ value = objValue.Index(i)
+ valueInterface = value.Interface()
- e1Kind := reflect.ValueOf(e1).Kind()
- e2Kind := reflect.ValueOf(e2).Kind()
- if e1Kind != e2Kind {
- return Fail(t, "Elements should be the same type", msgAndArgs...)
- }
+ compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
- res, isComparable := compare(e1, e2, e1Kind)
- if !isComparable {
- return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
- }
+ if !isComparable {
+ return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...)
+ }
- if res != -1 && res != 0 {
- return Fail(t, fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2), msgAndArgs...)
+ if !containsValue(allowedComparesResults, compareResult) {
+ return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...)
+ }
}
return true
}
-// Less asserts that the first element is less than the second
+// IsIncreasing asserts that the collection is increasing
//
-// assert.Less(t, 1, 2)
-// assert.Less(t, float64(1), float64(2))
-// assert.Less(t, "a", "b")
-func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
-
- e1Kind := reflect.ValueOf(e1).Kind()
- e2Kind := reflect.ValueOf(e2).Kind()
- if e1Kind != e2Kind {
- return Fail(t, "Elements should be the same type", msgAndArgs...)
- }
-
- res, isComparable := compare(e1, e2, e1Kind)
- if !isComparable {
- return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
- }
-
- if res != 1 {
- return Fail(t, fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2), msgAndArgs...)
- }
-
- return true
+// assert.IsIncreasing(t, []int{1, 2, 3})
+// assert.IsIncreasing(t, []float{1, 2})
+// assert.IsIncreasing(t, []string{"a", "b"})
+func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
+ return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
}
-// LessOrEqual asserts that the first element is less than or equal to the second
+// IsNonIncreasing asserts that the collection is not increasing
//
-// assert.LessOrEqual(t, 1, 2)
-// assert.LessOrEqual(t, 2, 2)
-// assert.LessOrEqual(t, "a", "b")
-// assert.LessOrEqual(t, "b", "b")
-func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
-
- e1Kind := reflect.ValueOf(e1).Kind()
- e2Kind := reflect.ValueOf(e2).Kind()
- if e1Kind != e2Kind {
- return Fail(t, "Elements should be the same type", msgAndArgs...)
- }
-
- res, isComparable := compare(e1, e2, e1Kind)
- if !isComparable {
- return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
- }
+// assert.IsNonIncreasing(t, []int{2, 1, 1})
+// assert.IsNonIncreasing(t, []float{2, 1})
+// assert.IsNonIncreasing(t, []string{"b", "a"})
+func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
+ return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
+}
- if res != 1 && res != 0 {
- return Fail(t, fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2), msgAndArgs...)
- }
+// IsDecreasing asserts that the collection is decreasing
+//
+// assert.IsDecreasing(t, []int{2, 1, 0})
+// assert.IsDecreasing(t, []float{2, 1})
+// assert.IsDecreasing(t, []string{"b", "a"})
+func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
+ return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
+}
- return true
+// IsNonDecreasing asserts that the collection is not decreasing
+//
+// assert.IsNonDecreasing(t, []int{1, 1, 2})
+// assert.IsNonDecreasing(t, []float{1, 2})
+// assert.IsNonDecreasing(t, []string{"a", "b"})
+func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
+ return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
}
diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go
index bdd81389..bcac4401 100644
--- a/vendor/github.com/stretchr/testify/assert/assertions.go
+++ b/vendor/github.com/stretchr/testify/assert/assertions.go
@@ -19,7 +19,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/pmezard/go-difflib/difflib"
- yaml "gopkg.in/yaml.v2"
+ yaml "gopkg.in/yaml.v3"
)
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl"
@@ -45,7 +45,7 @@ type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool
// for table driven tests.
type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool
-// Comparison a custom function that returns true on success and false on failure
+// Comparison is a custom function that returns true on success and false on failure
type Comparison func() (success bool)
/*
@@ -104,11 +104,11 @@ the problem actually occurred in calling code.*/
// failed.
func CallerInfo() []string {
- pc := uintptr(0)
- file := ""
- line := 0
- ok := false
- name := ""
+ var pc uintptr
+ var ok bool
+ var file string
+ var line int
+ var name string
callers := []string{}
for i := 0; ; i++ {
@@ -172,8 +172,8 @@ func isTest(name, prefix string) bool {
if len(name) == len(prefix) { // "Test" is ok
return true
}
- rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
- return !unicode.IsLower(rune)
+ r, _ := utf8.DecodeRuneInString(name[len(prefix):])
+ return !unicode.IsLower(r)
}
func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
@@ -429,14 +429,27 @@ func samePointers(first, second interface{}) bool {
// to a type conversion in the Go grammar.
func formatUnequalValues(expected, actual interface{}) (e string, a string) {
if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
- return fmt.Sprintf("%T(%#v)", expected, expected),
- fmt.Sprintf("%T(%#v)", actual, actual)
+ return fmt.Sprintf("%T(%s)", expected, truncatingFormat(expected)),
+ fmt.Sprintf("%T(%s)", actual, truncatingFormat(actual))
}
switch expected.(type) {
case time.Duration:
return fmt.Sprintf("%v", expected), fmt.Sprintf("%v", actual)
}
- return fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual)
+ return truncatingFormat(expected), truncatingFormat(actual)
+}
+
+// truncatingFormat formats the data and truncates it if it's too long.
+//
+// This helps keep formatted error messages lines from exceeding the
+// bufio.MaxScanTokenSize max line length that the go testing framework imposes.
+func truncatingFormat(data interface{}) string {
+ value := fmt.Sprintf("%#v", data)
+ max := bufio.MaxScanTokenSize - 100 // Give us some space the type info too if needed.
+ if len(value) > max {
+ value = value[0:max] + "<... truncated>"
+ }
+ return value
}
// EqualValues asserts that two objects are equal or convertable to the same types
@@ -483,12 +496,12 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}
//
// assert.NotNil(t, err)
func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
if !isNil(object) {
return true
}
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Fail(t, "Expected value not to be nil.", msgAndArgs...)
}
@@ -529,12 +542,12 @@ func isNil(object interface{}) bool {
//
// assert.Nil(t, err)
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
if isNil(object) {
return true
}
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...)
}
@@ -571,12 +584,11 @@ func isEmpty(object interface{}) bool {
//
// assert.Empty(t, obj)
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
-
pass := isEmpty(object)
if !pass {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...)
}
@@ -591,12 +603,11 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
// assert.Equal(t, "two", obj[1])
// }
func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
-
pass := !isEmpty(object)
if !pass {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...)
}
@@ -639,16 +650,10 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{})
//
// assert.True(t, myBool)
func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
- if h, ok := t.(interface {
- Helper()
- }); ok {
- h.Helper()
- }
-
- if value != true {
+ if !value {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Fail(t, "Should be true", msgAndArgs...)
}
@@ -660,11 +665,10 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
//
// assert.False(t, myBool)
func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
-
- if value != false {
+ if value {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Fail(t, "Should be false", msgAndArgs...)
}
@@ -695,6 +699,21 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{
}
+// NotEqualValues asserts that two objects are not equal even when converted to the same type
+//
+// assert.NotEqualValues(t, obj1, obj2)
+func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+
+ if ObjectsAreEqualValues(expected, actual) {
+ return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...)
+ }
+
+ return true
+}
+
// containsElement try loop over the list check if the list includes the element.
// return (false, false) if impossible.
// return (true, false) if element was not found.
@@ -747,10 +766,10 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo
ok, found := includeElement(s, contains)
if !ok {
- return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...)
+ return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...)
}
if !found {
- return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", s, contains), msgAndArgs...)
+ return Fail(t, fmt.Sprintf("%#v does not contain %#v", s, contains), msgAndArgs...)
}
return true
@@ -881,27 +900,39 @@ func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface
return true
}
- aKind := reflect.TypeOf(listA).Kind()
- bKind := reflect.TypeOf(listB).Kind()
+ if !isList(t, listA, msgAndArgs...) || !isList(t, listB, msgAndArgs...) {
+ return false
+ }
+
+ extraA, extraB := diffLists(listA, listB)
- if aKind != reflect.Array && aKind != reflect.Slice {
- return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...)
+ if len(extraA) == 0 && len(extraB) == 0 {
+ return true
}
- if bKind != reflect.Array && bKind != reflect.Slice {
- return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...)
+ return Fail(t, formatListDiff(listA, listB, extraA, extraB), msgAndArgs...)
+}
+
+// isList checks that the provided value is array or slice.
+func isList(t TestingT, list interface{}, msgAndArgs ...interface{}) (ok bool) {
+ kind := reflect.TypeOf(list).Kind()
+ if kind != reflect.Array && kind != reflect.Slice {
+ return Fail(t, fmt.Sprintf("%q has an unsupported type %s, expecting array or slice", list, kind),
+ msgAndArgs...)
}
+ return true
+}
+// diffLists diffs two arrays/slices and returns slices of elements that are only in A and only in B.
+// If some element is present multiple times, each instance is counted separately (e.g. if something is 2x in A and
+// 5x in B, it will be 0x in extraA and 3x in extraB). The order of items in both lists is ignored.
+func diffLists(listA, listB interface{}) (extraA, extraB []interface{}) {
aValue := reflect.ValueOf(listA)
bValue := reflect.ValueOf(listB)
aLen := aValue.Len()
bLen := bValue.Len()
- if aLen != bLen {
- return Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...)
- }
-
// Mark indexes in bValue that we already used
visited := make([]bool, bLen)
for i := 0; i < aLen; i++ {
@@ -918,11 +949,38 @@ func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface
}
}
if !found {
- return Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...)
+ extraA = append(extraA, element)
}
}
- return true
+ for j := 0; j < bLen; j++ {
+ if visited[j] {
+ continue
+ }
+ extraB = append(extraB, bValue.Index(j).Interface())
+ }
+
+ return
+}
+
+func formatListDiff(listA, listB interface{}, extraA, extraB []interface{}) string {
+ var msg bytes.Buffer
+
+ msg.WriteString("elements differ")
+ if len(extraA) > 0 {
+ msg.WriteString("\n\nextra elements in list A:\n")
+ msg.WriteString(spewConfig.Sdump(extraA))
+ }
+ if len(extraB) > 0 {
+ msg.WriteString("\n\nextra elements in list B:\n")
+ msg.WriteString(spewConfig.Sdump(extraB))
+ }
+ msg.WriteString("\n\nlistA:\n")
+ msg.WriteString(spewConfig.Sdump(listA))
+ msg.WriteString("\n\nlistB:\n")
+ msg.WriteString(spewConfig.Sdump(listB))
+
+ return msg.String()
}
// Condition uses a Comparison to assert a complex condition.
@@ -1058,6 +1116,8 @@ func toFloat(x interface{}) (float64, bool) {
xok := true
switch xn := x.(type) {
+ case uint:
+ xf = float64(xn)
case uint8:
xf = float64(xn)
case uint16:
@@ -1079,7 +1139,7 @@ func toFloat(x interface{}) (float64, bool) {
case float32:
xf = float64(xn)
case float64:
- xf = float64(xn)
+ xf = xn
case time.Duration:
xf = float64(xn)
default:
@@ -1193,6 +1253,9 @@ func calcRelativeError(expected, actual interface{}) (float64, error) {
if !aok {
return 0, fmt.Errorf("expected value %q cannot be converted to float", expected)
}
+ if math.IsNaN(af) {
+ return 0, errors.New("expected value must not be NaN")
+ }
if af == 0 {
return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error")
}
@@ -1200,6 +1263,9 @@ func calcRelativeError(expected, actual interface{}) (float64, error) {
if !bok {
return 0, fmt.Errorf("actual value %q cannot be converted to float", actual)
}
+ if math.IsNaN(bf) {
+ return 0, errors.New("actual value must not be NaN")
+ }
return math.Abs(af-bf) / math.Abs(af), nil
}
@@ -1209,6 +1275,9 @@ func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAnd
if h, ok := t.(tHelper); ok {
h.Helper()
}
+ if math.IsNaN(epsilon) {
+ return Fail(t, "epsilon must not be NaN")
+ }
actualEpsilon, err := calcRelativeError(expected, actual)
if err != nil {
return Fail(t, err.Error(), msgAndArgs...)
@@ -1256,10 +1325,10 @@ func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, m
// assert.Equal(t, expectedObj, actualObj)
// }
func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
if err != nil {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...)
}
@@ -1273,11 +1342,10 @@ func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
// assert.Equal(t, expectedError, err)
// }
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
- if h, ok := t.(tHelper); ok {
- h.Helper()
- }
-
if err == nil {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
return Fail(t, "An error is expected but got nil.", msgAndArgs...)
}
@@ -1553,6 +1621,8 @@ var spewConfig = spew.ConfigState{
DisablePointerAddresses: true,
DisableCapacities: true,
SortKeys: true,
+ DisableMethods: true,
+ MaxDepth: 10,
}
type tHelper interface {
@@ -1624,3 +1694,81 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D
}
}
}
+
+// ErrorIs asserts that at least one of the errors in err's chain matches target.
+// This is a wrapper for errors.Is.
+func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ if errors.Is(err, target) {
+ return true
+ }
+
+ var expectedText string
+ if target != nil {
+ expectedText = target.Error()
+ }
+
+ chain := buildErrorChainString(err)
+
+ return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+
+ "expected: %q\n"+
+ "in chain: %s", expectedText, chain,
+ ), msgAndArgs...)
+}
+
+// NotErrorIs asserts that at none of the errors in err's chain matches target.
+// This is a wrapper for errors.Is.
+func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ if !errors.Is(err, target) {
+ return true
+ }
+
+ var expectedText string
+ if target != nil {
+ expectedText = target.Error()
+ }
+
+ chain := buildErrorChainString(err)
+
+ return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
+ "found: %q\n"+
+ "in chain: %s", expectedText, chain,
+ ), msgAndArgs...)
+}
+
+// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
+// This is a wrapper for errors.As.
+func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ if errors.As(err, target) {
+ return true
+ }
+
+ chain := buildErrorChainString(err)
+
+ return Fail(t, fmt.Sprintf("Should be in error chain:\n"+
+ "expected: %q\n"+
+ "in chain: %s", target, chain,
+ ), msgAndArgs...)
+}
+
+func buildErrorChainString(err error) string {
+ if err == nil {
+ return ""
+ }
+
+ e := errors.Unwrap(err)
+ chain := fmt.Sprintf("%q", err.Error())
+ for e != nil {
+ chain += fmt.Sprintf("\n\t%q", e.Error())
+ e = errors.Unwrap(e)
+ }
+ return chain
+}
diff --git a/vendor/github.com/stretchr/testify/assert/http_assertions.go b/vendor/github.com/stretchr/testify/assert/http_assertions.go
index df46fa77..4ed341dd 100644
--- a/vendor/github.com/stretchr/testify/assert/http_assertions.go
+++ b/vendor/github.com/stretchr/testify/assert/http_assertions.go
@@ -33,7 +33,6 @@ func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, value
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
- return false
}
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
@@ -56,7 +55,6 @@ func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, valu
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
- return false
}
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
@@ -79,7 +77,6 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values
code, err := httpCode(handler, method, url, values)
if err != nil {
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
- return false
}
isErrorCode := code >= http.StatusBadRequest
@@ -90,6 +87,28 @@ func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values
return isErrorCode
}
+// HTTPStatusCode asserts that a specified handler returns a specified status code.
+//
+// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501)
+//
+// Returns whether the assertion was successful (true) or not (false).
+func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) bool {
+ if h, ok := t.(tHelper); ok {
+ h.Helper()
+ }
+ code, err := httpCode(handler, method, url, values)
+ if err != nil {
+ Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
+ }
+
+ successful := code == statuscode
+ if !successful {
+ Fail(t, fmt.Sprintf("Expected HTTP status code %d for %q but received %d", statuscode, url+"?"+values.Encode(), code))
+ }
+
+ return successful
+}
+
// HTTPBody is a helper that returns HTTP body of the response. It returns
// empty string if building a new request fails.
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
diff --git a/vendor/golang.org/x/crypto/ssh/knownhosts/knownhosts.go b/vendor/golang.org/x/crypto/ssh/knownhosts/knownhosts.go
new file mode 100644
index 00000000..260cfe58
--- /dev/null
+++ b/vendor/golang.org/x/crypto/ssh/knownhosts/knownhosts.go
@@ -0,0 +1,540 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package knownhosts implements a parser for the OpenSSH known_hosts
+// host key database, and provides utility functions for writing
+// OpenSSH compliant known_hosts files.
+package knownhosts
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/hmac"
+ "crypto/rand"
+ "crypto/sha1"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "strings"
+
+ "golang.org/x/crypto/ssh"
+)
+
+// See the sshd manpage
+// (http://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT) for
+// background.
+
+type addr struct{ host, port string }
+
+func (a *addr) String() string {
+ h := a.host
+ if strings.Contains(h, ":") {
+ h = "[" + h + "]"
+ }
+ return h + ":" + a.port
+}
+
+type matcher interface {
+ match(addr) bool
+}
+
+type hostPattern struct {
+ negate bool
+ addr addr
+}
+
+func (p *hostPattern) String() string {
+ n := ""
+ if p.negate {
+ n = "!"
+ }
+
+ return n + p.addr.String()
+}
+
+type hostPatterns []hostPattern
+
+func (ps hostPatterns) match(a addr) bool {
+ matched := false
+ for _, p := range ps {
+ if !p.match(a) {
+ continue
+ }
+ if p.negate {
+ return false
+ }
+ matched = true
+ }
+ return matched
+}
+
+// See
+// https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/addrmatch.c
+// The matching of * has no regard for separators, unlike filesystem globs
+func wildcardMatch(pat []byte, str []byte) bool {
+ for {
+ if len(pat) == 0 {
+ return len(str) == 0
+ }
+ if len(str) == 0 {
+ return false
+ }
+
+ if pat[0] == '*' {
+ if len(pat) == 1 {
+ return true
+ }
+
+ for j := range str {
+ if wildcardMatch(pat[1:], str[j:]) {
+ return true
+ }
+ }
+ return false
+ }
+
+ if pat[0] == '?' || pat[0] == str[0] {
+ pat = pat[1:]
+ str = str[1:]
+ } else {
+ return false
+ }
+ }
+}
+
+func (p *hostPattern) match(a addr) bool {
+ return wildcardMatch([]byte(p.addr.host), []byte(a.host)) && p.addr.port == a.port
+}
+
+type keyDBLine struct {
+ cert bool
+ matcher matcher
+ knownKey KnownKey
+}
+
+func serialize(k ssh.PublicKey) string {
+ return k.Type() + " " + base64.StdEncoding.EncodeToString(k.Marshal())
+}
+
+func (l *keyDBLine) match(a addr) bool {
+ return l.matcher.match(a)
+}
+
+type hostKeyDB struct {
+ // Serialized version of revoked keys
+ revoked map[string]*KnownKey
+ lines []keyDBLine
+}
+
+func newHostKeyDB() *hostKeyDB {
+ db := &hostKeyDB{
+ revoked: make(map[string]*KnownKey),
+ }
+
+ return db
+}
+
+func keyEq(a, b ssh.PublicKey) bool {
+ return bytes.Equal(a.Marshal(), b.Marshal())
+}
+
+// IsAuthorityForHost can be used as a callback in ssh.CertChecker
+func (db *hostKeyDB) IsHostAuthority(remote ssh.PublicKey, address string) bool {
+ h, p, err := net.SplitHostPort(address)
+ if err != nil {
+ return false
+ }
+ a := addr{host: h, port: p}
+
+ for _, l := range db.lines {
+ if l.cert && keyEq(l.knownKey.Key, remote) && l.match(a) {
+ return true
+ }
+ }
+ return false
+}
+
+// IsRevoked can be used as a callback in ssh.CertChecker
+func (db *hostKeyDB) IsRevoked(key *ssh.Certificate) bool {
+ _, ok := db.revoked[string(key.Marshal())]
+ return ok
+}
+
+const markerCert = "@cert-authority"
+const markerRevoked = "@revoked"
+
+func nextWord(line []byte) (string, []byte) {
+ i := bytes.IndexAny(line, "\t ")
+ if i == -1 {
+ return string(line), nil
+ }
+
+ return string(line[:i]), bytes.TrimSpace(line[i:])
+}
+
+func parseLine(line []byte) (marker, host string, key ssh.PublicKey, err error) {
+ if w, next := nextWord(line); w == markerCert || w == markerRevoked {
+ marker = w
+ line = next
+ }
+
+ host, line = nextWord(line)
+ if len(line) == 0 {
+ return "", "", nil, errors.New("knownhosts: missing host pattern")
+ }
+
+ // ignore the keytype as it's in the key blob anyway.
+ _, line = nextWord(line)
+ if len(line) == 0 {
+ return "", "", nil, errors.New("knownhosts: missing key type pattern")
+ }
+
+ keyBlob, _ := nextWord(line)
+
+ keyBytes, err := base64.StdEncoding.DecodeString(keyBlob)
+ if err != nil {
+ return "", "", nil, err
+ }
+ key, err = ssh.ParsePublicKey(keyBytes)
+ if err != nil {
+ return "", "", nil, err
+ }
+
+ return marker, host, key, nil
+}
+
+func (db *hostKeyDB) parseLine(line []byte, filename string, linenum int) error {
+ marker, pattern, key, err := parseLine(line)
+ if err != nil {
+ return err
+ }
+
+ if marker == markerRevoked {
+ db.revoked[string(key.Marshal())] = &KnownKey{
+ Key: key,
+ Filename: filename,
+ Line: linenum,
+ }
+
+ return nil
+ }
+
+ entry := keyDBLine{
+ cert: marker == markerCert,
+ knownKey: KnownKey{
+ Filename: filename,
+ Line: linenum,
+ Key: key,
+ },
+ }
+
+ if pattern[0] == '|' {
+ entry.matcher, err = newHashedHost(pattern)
+ } else {
+ entry.matcher, err = newHostnameMatcher(pattern)
+ }
+
+ if err != nil {
+ return err
+ }
+
+ db.lines = append(db.lines, entry)
+ return nil
+}
+
+func newHostnameMatcher(pattern string) (matcher, error) {
+ var hps hostPatterns
+ for _, p := range strings.Split(pattern, ",") {
+ if len(p) == 0 {
+ continue
+ }
+
+ var a addr
+ var negate bool
+ if p[0] == '!' {
+ negate = true
+ p = p[1:]
+ }
+
+ if len(p) == 0 {
+ return nil, errors.New("knownhosts: negation without following hostname")
+ }
+
+ var err error
+ if p[0] == '[' {
+ a.host, a.port, err = net.SplitHostPort(p)
+ if err != nil {
+ return nil, err
+ }
+ } else {
+ a.host, a.port, err = net.SplitHostPort(p)
+ if err != nil {
+ a.host = p
+ a.port = "22"
+ }
+ }
+ hps = append(hps, hostPattern{
+ negate: negate,
+ addr: a,
+ })
+ }
+ return hps, nil
+}
+
+// KnownKey represents a key declared in a known_hosts file.
+type KnownKey struct {
+ Key ssh.PublicKey
+ Filename string
+ Line int
+}
+
+func (k *KnownKey) String() string {
+ return fmt.Sprintf("%s:%d: %s", k.Filename, k.Line, serialize(k.Key))
+}
+
+// KeyError is returned if we did not find the key in the host key
+// database, or there was a mismatch. Typically, in batch
+// applications, this should be interpreted as failure. Interactive
+// applications can offer an interactive prompt to the user.
+type KeyError struct {
+ // Want holds the accepted host keys. For each key algorithm,
+ // there can be one hostkey. If Want is empty, the host is
+ // unknown. If Want is non-empty, there was a mismatch, which
+ // can signify a MITM attack.
+ Want []KnownKey
+}
+
+func (u *KeyError) Error() string {
+ if len(u.Want) == 0 {
+ return "knownhosts: key is unknown"
+ }
+ return "knownhosts: key mismatch"
+}
+
+// RevokedError is returned if we found a key that was revoked.
+type RevokedError struct {
+ Revoked KnownKey
+}
+
+func (r *RevokedError) Error() string {
+ return "knownhosts: key is revoked"
+}
+
+// check checks a key against the host database. This should not be
+// used for verifying certificates.
+func (db *hostKeyDB) check(address string, remote net.Addr, remoteKey ssh.PublicKey) error {
+ if revoked := db.revoked[string(remoteKey.Marshal())]; revoked != nil {
+ return &RevokedError{Revoked: *revoked}
+ }
+
+ host, port, err := net.SplitHostPort(remote.String())
+ if err != nil {
+ return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", remote, err)
+ }
+
+ hostToCheck := addr{host, port}
+ if address != "" {
+ // Give preference to the hostname if available.
+ host, port, err := net.SplitHostPort(address)
+ if err != nil {
+ return fmt.Errorf("knownhosts: SplitHostPort(%s): %v", address, err)
+ }
+
+ hostToCheck = addr{host, port}
+ }
+
+ return db.checkAddr(hostToCheck, remoteKey)
+}
+
+// checkAddr checks if we can find the given public key for the
+// given address. If we only find an entry for the IP address,
+// or only the hostname, then this still succeeds.
+func (db *hostKeyDB) checkAddr(a addr, remoteKey ssh.PublicKey) error {
+ // TODO(hanwen): are these the right semantics? What if there
+ // is just a key for the IP address, but not for the
+ // hostname?
+
+ // Algorithm => key.
+ knownKeys := map[string]KnownKey{}
+ for _, l := range db.lines {
+ if l.match(a) {
+ typ := l.knownKey.Key.Type()
+ if _, ok := knownKeys[typ]; !ok {
+ knownKeys[typ] = l.knownKey
+ }
+ }
+ }
+
+ keyErr := &KeyError{}
+ for _, v := range knownKeys {
+ keyErr.Want = append(keyErr.Want, v)
+ }
+
+ // Unknown remote host.
+ if len(knownKeys) == 0 {
+ return keyErr
+ }
+
+ // If the remote host starts using a different, unknown key type, we
+ // also interpret that as a mismatch.
+ if known, ok := knownKeys[remoteKey.Type()]; !ok || !keyEq(known.Key, remoteKey) {
+ return keyErr
+ }
+
+ return nil
+}
+
+// The Read function parses file contents.
+func (db *hostKeyDB) Read(r io.Reader, filename string) error {
+ scanner := bufio.NewScanner(r)
+
+ lineNum := 0
+ for scanner.Scan() {
+ lineNum++
+ line := scanner.Bytes()
+ line = bytes.TrimSpace(line)
+ if len(line) == 0 || line[0] == '#' {
+ continue
+ }
+
+ if err := db.parseLine(line, filename, lineNum); err != nil {
+ return fmt.Errorf("knownhosts: %s:%d: %v", filename, lineNum, err)
+ }
+ }
+ return scanner.Err()
+}
+
+// New creates a host key callback from the given OpenSSH host key
+// files. The returned callback is for use in
+// ssh.ClientConfig.HostKeyCallback. By preference, the key check
+// operates on the hostname if available, i.e. if a server changes its
+// IP address, the host key check will still succeed, even though a
+// record of the new IP address is not available.
+func New(files ...string) (ssh.HostKeyCallback, error) {
+ db := newHostKeyDB()
+ for _, fn := range files {
+ f, err := os.Open(fn)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ if err := db.Read(f, fn); err != nil {
+ return nil, err
+ }
+ }
+
+ var certChecker ssh.CertChecker
+ certChecker.IsHostAuthority = db.IsHostAuthority
+ certChecker.IsRevoked = db.IsRevoked
+ certChecker.HostKeyFallback = db.check
+
+ return certChecker.CheckHostKey, nil
+}
+
+// Normalize normalizes an address into the form used in known_hosts
+func Normalize(address string) string {
+ host, port, err := net.SplitHostPort(address)
+ if err != nil {
+ host = address
+ port = "22"
+ }
+ entry := host
+ if port != "22" {
+ entry = "[" + entry + "]:" + port
+ } else if strings.Contains(host, ":") && !strings.HasPrefix(host, "[") {
+ entry = "[" + entry + "]"
+ }
+ return entry
+}
+
+// Line returns a line to add append to the known_hosts files.
+func Line(addresses []string, key ssh.PublicKey) string {
+ var trimmed []string
+ for _, a := range addresses {
+ trimmed = append(trimmed, Normalize(a))
+ }
+
+ return strings.Join(trimmed, ",") + " " + serialize(key)
+}
+
+// HashHostname hashes the given hostname. The hostname is not
+// normalized before hashing.
+func HashHostname(hostname string) string {
+ // TODO(hanwen): check if we can safely normalize this always.
+ salt := make([]byte, sha1.Size)
+
+ _, err := rand.Read(salt)
+ if err != nil {
+ panic(fmt.Sprintf("crypto/rand failure %v", err))
+ }
+
+ hash := hashHost(hostname, salt)
+ return encodeHash(sha1HashType, salt, hash)
+}
+
+func decodeHash(encoded string) (hashType string, salt, hash []byte, err error) {
+ if len(encoded) == 0 || encoded[0] != '|' {
+ err = errors.New("knownhosts: hashed host must start with '|'")
+ return
+ }
+ components := strings.Split(encoded, "|")
+ if len(components) != 4 {
+ err = fmt.Errorf("knownhosts: got %d components, want 3", len(components))
+ return
+ }
+
+ hashType = components[1]
+ if salt, err = base64.StdEncoding.DecodeString(components[2]); err != nil {
+ return
+ }
+ if hash, err = base64.StdEncoding.DecodeString(components[3]); err != nil {
+ return
+ }
+ return
+}
+
+func encodeHash(typ string, salt []byte, hash []byte) string {
+ return strings.Join([]string{"",
+ typ,
+ base64.StdEncoding.EncodeToString(salt),
+ base64.StdEncoding.EncodeToString(hash),
+ }, "|")
+}
+
+// See https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
+func hashHost(hostname string, salt []byte) []byte {
+ mac := hmac.New(sha1.New, salt)
+ mac.Write([]byte(hostname))
+ return mac.Sum(nil)
+}
+
+type hashedHost struct {
+ salt []byte
+ hash []byte
+}
+
+const sha1HashType = "1"
+
+func newHashedHost(encoded string) (*hashedHost, error) {
+ typ, salt, hash, err := decodeHash(encoded)
+ if err != nil {
+ return nil, err
+ }
+
+ // The type field seems for future algorithm agility, but it's
+ // actually hardcoded in openssh currently, see
+ // https://android.googlesource.com/platform/external/openssh/+/ab28f5495c85297e7a597c1ba62e996416da7c7e/hostfile.c#120
+ if typ != sha1HashType {
+ return nil, fmt.Errorf("knownhosts: got hash type %s, must be '1'", typ)
+ }
+
+ return &hashedHost{salt: salt, hash: hash}, nil
+}
+
+func (h *hashedHost) match(a addr) bool {
+ return bytes.Equal(hashHost(Normalize(a.String()), h.salt), h.hash)
+}
diff --git a/vendor/golang.org/x/sys/unix/ioctl_linux.go b/vendor/golang.org/x/sys/unix/ioctl_linux.go
index 1dadead2..884430b8 100644
--- a/vendor/golang.org/x/sys/unix/ioctl_linux.go
+++ b/vendor/golang.org/x/sys/unix/ioctl_linux.go
@@ -194,3 +194,26 @@ func ioctlIfreqData(fd int, req uint, value *ifreqData) error {
// identical so pass *IfreqData directly.
return ioctlPtr(fd, req, unsafe.Pointer(value))
}
+
+// IoctlKCMClone attaches a new file descriptor to a multiplexor by cloning an
+// existing KCM socket, returning a structure containing the file descriptor of
+// the new socket.
+func IoctlKCMClone(fd int) (*KCMClone, error) {
+ var info KCMClone
+ if err := ioctlPtr(fd, SIOCKCMCLONE, unsafe.Pointer(&info)); err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
+
+// IoctlKCMAttach attaches a TCP socket and associated BPF program file
+// descriptor to a multiplexor.
+func IoctlKCMAttach(fd int, info KCMAttach) error {
+ return ioctlPtr(fd, SIOCKCMATTACH, unsafe.Pointer(&info))
+}
+
+// IoctlKCMUnattach unattaches a TCP socket file descriptor from a multiplexor.
+func IoctlKCMUnattach(fd int, info KCMUnattach) error {
+ return ioctlPtr(fd, SIOCKCMUNATTACH, unsafe.Pointer(&info))
+}
diff --git a/vendor/golang.org/x/sys/unix/mkerrors.sh b/vendor/golang.org/x/sys/unix/mkerrors.sh
index a47b035f..a0370874 100644
--- a/vendor/golang.org/x/sys/unix/mkerrors.sh
+++ b/vendor/golang.org/x/sys/unix/mkerrors.sh
@@ -205,6 +205,7 @@ struct ltchars {
#include
#include
#include
+#include
#include
#include
#include
@@ -231,6 +232,7 @@ struct ltchars {
#include
#include
#include
+#include
#include
#include
#include
@@ -503,6 +505,7 @@ ccflags="$@"
$2 ~ /^O?XTABS$/ ||
$2 ~ /^TC[IO](ON|OFF)$/ ||
$2 ~ /^IN_/ ||
+ $2 ~ /^KCM/ ||
$2 ~ /^LANDLOCK_/ ||
$2 ~ /^LOCK_(SH|EX|NB|UN)$/ ||
$2 ~ /^LO_(KEY|NAME)_SIZE$/ ||
@@ -597,6 +600,7 @@ ccflags="$@"
$2 ~ /^DEVLINK_/ ||
$2 ~ /^ETHTOOL_/ ||
$2 ~ /^LWTUNNEL_IP/ ||
+ $2 ~ /^ITIMER_/ ||
$2 !~ "WMESGLEN" &&
$2 ~ /^W[A-Z0-9]+$/ ||
$2 ~/^PPPIOC/ ||
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go
index f432b068..5f28f8fd 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux.go
@@ -14,6 +14,7 @@ package unix
import (
"encoding/binary"
"syscall"
+ "time"
"unsafe"
)
@@ -249,6 +250,13 @@ func Getwd() (wd string, err error) {
if n < 1 || n > len(buf) || buf[n-1] != 0 {
return "", EINVAL
}
+ // In some cases, Linux can return a path that starts with the
+ // "(unreachable)" prefix, which can potentially be a valid relative
+ // path. To work around that, return ENOENT if path is not absolute.
+ if buf[0] != '/' {
+ return "", ENOENT
+ }
+
return string(buf[0 : n-1]), nil
}
@@ -2314,11 +2322,56 @@ type RemoteIovec struct {
//sys shmdt(addr uintptr) (err error)
//sys shmget(key int, size int, flag int) (id int, err error)
+//sys getitimer(which int, currValue *Itimerval) (err error)
+//sys setitimer(which int, newValue *Itimerval, oldValue *Itimerval) (err error)
+
+// MakeItimerval creates an Itimerval from interval and value durations.
+func MakeItimerval(interval, value time.Duration) Itimerval {
+ return Itimerval{
+ Interval: NsecToTimeval(interval.Nanoseconds()),
+ Value: NsecToTimeval(value.Nanoseconds()),
+ }
+}
+
+// A value which may be passed to the which parameter for Getitimer and
+// Setitimer.
+type ItimerWhich int
+
+// Possible which values for Getitimer and Setitimer.
+const (
+ ItimerReal ItimerWhich = ITIMER_REAL
+ ItimerVirtual ItimerWhich = ITIMER_VIRTUAL
+ ItimerProf ItimerWhich = ITIMER_PROF
+)
+
+// Getitimer wraps getitimer(2) to return the current value of the timer
+// specified by which.
+func Getitimer(which ItimerWhich) (Itimerval, error) {
+ var it Itimerval
+ if err := getitimer(int(which), &it); err != nil {
+ return Itimerval{}, err
+ }
+
+ return it, nil
+}
+
+// Setitimer wraps setitimer(2) to arm or disarm the timer specified by which.
+// It returns the previous value of the timer.
+//
+// If the Itimerval argument is the zero value, the timer will be disarmed.
+func Setitimer(which ItimerWhich, it Itimerval) (Itimerval, error) {
+ var prev Itimerval
+ if err := setitimer(int(which), &it, &prev); err != nil {
+ return Itimerval{}, err
+ }
+
+ return prev, nil
+}
+
/*
* Unimplemented
*/
// AfsSyscall
-// Alarm
// ArchPrctl
// Brk
// ClockNanosleep
@@ -2334,7 +2387,6 @@ type RemoteIovec struct {
// GetMempolicy
// GetRobustList
// GetThreadArea
-// Getitimer
// Getpmsg
// IoCancel
// IoDestroy
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_386.go b/vendor/golang.org/x/sys/unix/syscall_linux_386.go
index 5f757e8a..d44b8ad5 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_386.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_386.go
@@ -173,14 +173,6 @@ const (
_SENDMMSG = 20
)
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- fd, e := socketcall(_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), 0, 0, 0)
- if e != 0 {
- err = e
- }
- return
-}
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
fd, e := socketcall(_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
if e != 0 {
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_alarm.go b/vendor/golang.org/x/sys/unix/syscall_linux_alarm.go
new file mode 100644
index 00000000..08086ac6
--- /dev/null
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_alarm.go
@@ -0,0 +1,14 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build linux && (386 || amd64 || mips || mipsle || mips64 || mipsle || ppc64 || ppc64le || ppc || s390x || sparc64)
+// +build linux
+// +build 386 amd64 mips mipsle mips64 mipsle ppc64 ppc64le ppc s390x sparc64
+
+package unix
+
+// SYS_ALARM is not defined on arm or riscv, but is available for other GOARCH
+// values.
+
+//sys Alarm(seconds uint) (remaining uint, err error)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_amd64.go b/vendor/golang.org/x/sys/unix/syscall_linux_amd64.go
index 4299125a..bd21d93b 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_amd64.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_amd64.go
@@ -62,7 +62,6 @@ func Stat(path string, stat *Stat_t) (err error) {
//sys SyncFileRange(fd int, off int64, n int64, flags int) (err error)
//sys Truncate(path string, length int64) (err error)
//sys Ustat(dev int, ubuf *Ustat_t) (err error)
-//sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_arm.go b/vendor/golang.org/x/sys/unix/syscall_linux_arm.go
index 79edeb9c..343c91f6 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_arm.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_arm.go
@@ -27,7 +27,6 @@ func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
return newoffset, nil
}
-//sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go
index 862890de..8c562868 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_arm64.go
@@ -66,7 +66,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
return ENOSYS
}
-//sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go b/vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go
index 8932e34a..f0b13800 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go
@@ -48,7 +48,6 @@ func Select(nfd int, r *FdSet, w *FdSet, e *FdSet, timeout *Timeval) (n int, err
//sys SyncFileRange(fd int, off int64, n int64, flags int) (err error)
//sys Truncate(path string, length int64) (err error)
//sys Ustat(dev int, ubuf *Ustat_t) (err error)
-//sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go b/vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go
index 7821c25d..e6163c30 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go
@@ -41,7 +41,6 @@ func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr,
//sys SyncFileRange(fd int, off int64, n int64, flags int) (err error)
//sys Truncate(path string, length int64) (err error) = SYS_TRUNCATE64
//sys Ustat(dev int, ubuf *Ustat_t) (err error)
-//sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_ppc.go b/vendor/golang.org/x/sys/unix/syscall_linux_ppc.go
index c5053a0f..4740e80a 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_ppc.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_ppc.go
@@ -43,7 +43,6 @@ import (
//sys Stat(path string, stat *Stat_t) (err error) = SYS_STAT64
//sys Truncate(path string, length int64) (err error) = SYS_TRUNCATE64
//sys Ustat(dev int, ubuf *Ustat_t) (err error)
-//sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go b/vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go
index 25786c42..78bc9166 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go
@@ -45,7 +45,6 @@ package unix
//sys Statfs(path string, buf *Statfs_t) (err error)
//sys Truncate(path string, length int64) (err error)
//sys Ustat(dev int, ubuf *Ustat_t) (err error)
-//sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go
index 6f9f7104..3d6c4eb0 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go
@@ -65,7 +65,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
return ENOSYS
}
-//sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_s390x.go b/vendor/golang.org/x/sys/unix/syscall_linux_s390x.go
index 6aa59cb2..89ce84a4 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_s390x.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_s390x.go
@@ -145,15 +145,6 @@ const (
netSendMMsg = 20
)
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (int, error) {
- args := [3]uintptr{uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen))}
- fd, _, err := Syscall(SYS_SOCKETCALL, netAccept, uintptr(unsafe.Pointer(&args)), 0)
- if err != 0 {
- return 0, err
- }
- return int(fd), nil
-}
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (int, error) {
args := [4]uintptr{uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags)}
fd, _, err := Syscall(SYS_SOCKETCALL, netAccept4, uintptr(unsafe.Pointer(&args)), 0)
diff --git a/vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go b/vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go
index bbe8d174..35bdb098 100644
--- a/vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go
+++ b/vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go
@@ -42,7 +42,6 @@ package unix
//sys Statfs(path string, buf *Statfs_t) (err error)
//sys SyncFileRange(fd int, off int64, n int64, flags int) (err error)
//sys Truncate(path string, length int64) (err error)
-//sys accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error)
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
//sys bind(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
//sys connect(s int, addr unsafe.Pointer, addrlen _Socklen) (err error)
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux.go b/vendor/golang.org/x/sys/unix/zerrors_linux.go
index bcc45d10..bc7c9d07 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux.go
@@ -38,7 +38,8 @@ const (
AF_KEY = 0xf
AF_LLC = 0x1a
AF_LOCAL = 0x1
- AF_MAX = 0x2d
+ AF_MAX = 0x2e
+ AF_MCTP = 0x2d
AF_MPLS = 0x1c
AF_NETBEUI = 0xd
AF_NETLINK = 0x10
@@ -259,6 +260,17 @@ const (
BUS_USB = 0x3
BUS_VIRTUAL = 0x6
CAN_BCM = 0x2
+ CAN_CTRLMODE_3_SAMPLES = 0x4
+ CAN_CTRLMODE_BERR_REPORTING = 0x10
+ CAN_CTRLMODE_CC_LEN8_DLC = 0x100
+ CAN_CTRLMODE_FD = 0x20
+ CAN_CTRLMODE_FD_NON_ISO = 0x80
+ CAN_CTRLMODE_LISTENONLY = 0x2
+ CAN_CTRLMODE_LOOPBACK = 0x1
+ CAN_CTRLMODE_ONE_SHOT = 0x8
+ CAN_CTRLMODE_PRESUME_ACK = 0x40
+ CAN_CTRLMODE_TDC_AUTO = 0x200
+ CAN_CTRLMODE_TDC_MANUAL = 0x400
CAN_EFF_FLAG = 0x80000000
CAN_EFF_ID_BITS = 0x1d
CAN_EFF_MASK = 0x1fffffff
@@ -336,6 +348,7 @@ const (
CAN_RTR_FLAG = 0x40000000
CAN_SFF_ID_BITS = 0xb
CAN_SFF_MASK = 0x7ff
+ CAN_TERMINATION_DISABLED = 0x0
CAN_TP16 = 0x3
CAN_TP20 = 0x4
CAP_AUDIT_CONTROL = 0x1e
@@ -741,6 +754,7 @@ const (
ETH_P_QINQ2 = 0x9200
ETH_P_QINQ3 = 0x9300
ETH_P_RARP = 0x8035
+ ETH_P_REALTEK = 0x8899
ETH_P_SCA = 0x6007
ETH_P_SLOW = 0x8809
ETH_P_SNAP = 0x5
@@ -810,10 +824,12 @@ const (
FAN_EPIDFD = -0x2
FAN_EVENT_INFO_TYPE_DFID = 0x3
FAN_EVENT_INFO_TYPE_DFID_NAME = 0x2
+ FAN_EVENT_INFO_TYPE_ERROR = 0x5
FAN_EVENT_INFO_TYPE_FID = 0x1
FAN_EVENT_INFO_TYPE_PIDFD = 0x4
FAN_EVENT_METADATA_LEN = 0x18
FAN_EVENT_ON_CHILD = 0x8000000
+ FAN_FS_ERROR = 0x8000
FAN_MARK_ADD = 0x1
FAN_MARK_DONT_FOLLOW = 0x4
FAN_MARK_FILESYSTEM = 0x100
@@ -1264,9 +1280,14 @@ const (
IP_XFRM_POLICY = 0x11
ISOFS_SUPER_MAGIC = 0x9660
ISTRIP = 0x20
+ ITIMER_PROF = 0x2
+ ITIMER_REAL = 0x0
+ ITIMER_VIRTUAL = 0x1
IUTF8 = 0x4000
IXANY = 0x800
JFFS2_SUPER_MAGIC = 0x72b6
+ KCMPROTO_CONNECTED = 0x0
+ KCM_RECV_DISABLE = 0x1
KEXEC_ARCH_386 = 0x30000
KEXEC_ARCH_68K = 0x40000
KEXEC_ARCH_AARCH64 = 0xb70000
@@ -1827,6 +1848,8 @@ const (
PERF_MEM_BLK_DATA = 0x2
PERF_MEM_BLK_NA = 0x1
PERF_MEM_BLK_SHIFT = 0x28
+ PERF_MEM_HOPS_0 = 0x1
+ PERF_MEM_HOPS_SHIFT = 0x2b
PERF_MEM_LOCK_LOCKED = 0x2
PERF_MEM_LOCK_NA = 0x1
PERF_MEM_LOCK_SHIFT = 0x18
@@ -1986,6 +2009,9 @@ const (
PR_SCHED_CORE_CREATE = 0x1
PR_SCHED_CORE_GET = 0x0
PR_SCHED_CORE_MAX = 0x4
+ PR_SCHED_CORE_SCOPE_PROCESS_GROUP = 0x2
+ PR_SCHED_CORE_SCOPE_THREAD = 0x0
+ PR_SCHED_CORE_SCOPE_THREAD_GROUP = 0x1
PR_SCHED_CORE_SHARE_FROM = 0x3
PR_SCHED_CORE_SHARE_TO = 0x2
PR_SET_CHILD_SUBREAPER = 0x24
@@ -2167,12 +2193,23 @@ const (
RTCF_NAT = 0x800000
RTCF_VALVE = 0x200000
RTC_AF = 0x20
+ RTC_BSM_DIRECT = 0x1
+ RTC_BSM_DISABLED = 0x0
+ RTC_BSM_LEVEL = 0x2
+ RTC_BSM_STANDBY = 0x3
RTC_FEATURE_ALARM = 0x0
+ RTC_FEATURE_ALARM_RES_2S = 0x3
RTC_FEATURE_ALARM_RES_MINUTE = 0x1
- RTC_FEATURE_CNT = 0x3
+ RTC_FEATURE_BACKUP_SWITCH_MODE = 0x6
+ RTC_FEATURE_CNT = 0x7
+ RTC_FEATURE_CORRECTION = 0x5
RTC_FEATURE_NEED_WEEK_DAY = 0x2
+ RTC_FEATURE_UPDATE_INTERRUPT = 0x4
RTC_IRQF = 0x80
RTC_MAX_FREQ = 0x2000
+ RTC_PARAM_BACKUP_SWITCH_MODE = 0x2
+ RTC_PARAM_CORRECTION = 0x1
+ RTC_PARAM_FEATURES = 0x0
RTC_PF = 0x40
RTC_UF = 0x10
RTF_ADDRCLASSMASK = 0xf8000000
@@ -2423,6 +2460,9 @@ const (
SIOCGSTAMPNS = 0x8907
SIOCGSTAMPNS_OLD = 0x8907
SIOCGSTAMP_OLD = 0x8906
+ SIOCKCMATTACH = 0x89e0
+ SIOCKCMCLONE = 0x89e2
+ SIOCKCMUNATTACH = 0x89e1
SIOCOUTQNSD = 0x894b
SIOCPROTOPRIVATE = 0x89e0
SIOCRTMSG = 0x890d
@@ -2532,6 +2572,8 @@ const (
SO_VM_SOCKETS_BUFFER_MIN_SIZE = 0x1
SO_VM_SOCKETS_BUFFER_SIZE = 0x0
SO_VM_SOCKETS_CONNECT_TIMEOUT = 0x6
+ SO_VM_SOCKETS_CONNECT_TIMEOUT_NEW = 0x8
+ SO_VM_SOCKETS_CONNECT_TIMEOUT_OLD = 0x6
SO_VM_SOCKETS_NONBLOCK_TXRX = 0x7
SO_VM_SOCKETS_PEER_HOST_VM_ID = 0x3
SO_VM_SOCKETS_TRUSTED = 0x5
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go
index 3ca40ca7..234fd4a5 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_386.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_386.go
@@ -250,6 +250,8 @@ const (
RTC_EPOCH_SET = 0x4004700e
RTC_IRQP_READ = 0x8004700b
RTC_IRQP_SET = 0x4004700c
+ RTC_PARAM_GET = 0x40187013
+ RTC_PARAM_SET = 0x40187014
RTC_PIE_OFF = 0x7006
RTC_PIE_ON = 0x7005
RTC_PLL_GET = 0x801c7011
@@ -327,6 +329,7 @@ const (
SO_RCVTIMEO = 0x14
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x14
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x2
SO_REUSEPORT = 0xf
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go
index ead33209..58619b75 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go
@@ -251,6 +251,8 @@ const (
RTC_EPOCH_SET = 0x4008700e
RTC_IRQP_READ = 0x8008700b
RTC_IRQP_SET = 0x4008700c
+ RTC_PARAM_GET = 0x40187013
+ RTC_PARAM_SET = 0x40187014
RTC_PIE_OFF = 0x7006
RTC_PIE_ON = 0x7005
RTC_PLL_GET = 0x80207011
@@ -328,6 +330,7 @@ const (
SO_RCVTIMEO = 0x14
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x14
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x2
SO_REUSEPORT = 0xf
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go
index 39bdc945..3a64ff59 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm.go
@@ -257,6 +257,8 @@ const (
RTC_EPOCH_SET = 0x4004700e
RTC_IRQP_READ = 0x8004700b
RTC_IRQP_SET = 0x4004700c
+ RTC_PARAM_GET = 0x40187013
+ RTC_PARAM_SET = 0x40187014
RTC_PIE_OFF = 0x7006
RTC_PIE_ON = 0x7005
RTC_PLL_GET = 0x801c7011
@@ -334,6 +336,7 @@ const (
SO_RCVTIMEO = 0x14
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x14
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x2
SO_REUSEPORT = 0xf
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
index 9aec987d..abe0b925 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
@@ -247,6 +247,8 @@ const (
RTC_EPOCH_SET = 0x4008700e
RTC_IRQP_READ = 0x8008700b
RTC_IRQP_SET = 0x4008700c
+ RTC_PARAM_GET = 0x40187013
+ RTC_PARAM_SET = 0x40187014
RTC_PIE_OFF = 0x7006
RTC_PIE_ON = 0x7005
RTC_PLL_GET = 0x80207011
@@ -324,6 +326,7 @@ const (
SO_RCVTIMEO = 0x14
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x14
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x2
SO_REUSEPORT = 0xf
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go
index a8bba949..14d7a843 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips.go
@@ -250,6 +250,8 @@ const (
RTC_EPOCH_SET = 0x8004700e
RTC_IRQP_READ = 0x4004700b
RTC_IRQP_SET = 0x8004700c
+ RTC_PARAM_GET = 0x80187013
+ RTC_PARAM_SET = 0x80187014
RTC_PIE_OFF = 0x20007006
RTC_PIE_ON = 0x20007005
RTC_PLL_GET = 0x401c7011
@@ -327,6 +329,7 @@ const (
SO_RCVTIMEO = 0x1006
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x1006
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x4
SO_REUSEPORT = 0x200
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go
index ee9e7e20..99e7c4ac 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go
@@ -250,6 +250,8 @@ const (
RTC_EPOCH_SET = 0x8008700e
RTC_IRQP_READ = 0x4008700b
RTC_IRQP_SET = 0x8008700c
+ RTC_PARAM_GET = 0x80187013
+ RTC_PARAM_SET = 0x80187014
RTC_PIE_OFF = 0x20007006
RTC_PIE_ON = 0x20007005
RTC_PLL_GET = 0x40207011
@@ -327,6 +329,7 @@ const (
SO_RCVTIMEO = 0x1006
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x1006
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x4
SO_REUSEPORT = 0x200
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go
index ba4b288a..496364c3 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go
@@ -250,6 +250,8 @@ const (
RTC_EPOCH_SET = 0x8008700e
RTC_IRQP_READ = 0x4008700b
RTC_IRQP_SET = 0x8008700c
+ RTC_PARAM_GET = 0x80187013
+ RTC_PARAM_SET = 0x80187014
RTC_PIE_OFF = 0x20007006
RTC_PIE_ON = 0x20007005
RTC_PLL_GET = 0x40207011
@@ -327,6 +329,7 @@ const (
SO_RCVTIMEO = 0x1006
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x1006
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x4
SO_REUSEPORT = 0x200
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go
index bc93afc3..3e408308 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go
@@ -250,6 +250,8 @@ const (
RTC_EPOCH_SET = 0x8004700e
RTC_IRQP_READ = 0x4004700b
RTC_IRQP_SET = 0x8004700c
+ RTC_PARAM_GET = 0x80187013
+ RTC_PARAM_SET = 0x80187014
RTC_PIE_OFF = 0x20007006
RTC_PIE_ON = 0x20007005
RTC_PLL_GET = 0x401c7011
@@ -327,6 +329,7 @@ const (
SO_RCVTIMEO = 0x1006
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x1006
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x4
SO_REUSEPORT = 0x200
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go
index 9295e694..1151a7df 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go
@@ -305,6 +305,8 @@ const (
RTC_EPOCH_SET = 0x8004700e
RTC_IRQP_READ = 0x4004700b
RTC_IRQP_SET = 0x8004700c
+ RTC_PARAM_GET = 0x80187013
+ RTC_PARAM_SET = 0x80187014
RTC_PIE_OFF = 0x20007006
RTC_PIE_ON = 0x20007005
RTC_PLL_GET = 0x401c7011
@@ -382,6 +384,7 @@ const (
SO_RCVTIMEO = 0x12
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x12
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x2
SO_REUSEPORT = 0xf
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go
index 1fa081c9..ed17f249 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go
@@ -309,6 +309,8 @@ const (
RTC_EPOCH_SET = 0x8008700e
RTC_IRQP_READ = 0x4008700b
RTC_IRQP_SET = 0x8008700c
+ RTC_PARAM_GET = 0x80187013
+ RTC_PARAM_SET = 0x80187014
RTC_PIE_OFF = 0x20007006
RTC_PIE_ON = 0x20007005
RTC_PLL_GET = 0x40207011
@@ -386,6 +388,7 @@ const (
SO_RCVTIMEO = 0x12
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x12
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x2
SO_REUSEPORT = 0xf
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go
index 74b32114..d84a37c1 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go
@@ -309,6 +309,8 @@ const (
RTC_EPOCH_SET = 0x8008700e
RTC_IRQP_READ = 0x4008700b
RTC_IRQP_SET = 0x8008700c
+ RTC_PARAM_GET = 0x80187013
+ RTC_PARAM_SET = 0x80187014
RTC_PIE_OFF = 0x20007006
RTC_PIE_ON = 0x20007005
RTC_PLL_GET = 0x40207011
@@ -386,6 +388,7 @@ const (
SO_RCVTIMEO = 0x12
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x12
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x2
SO_REUSEPORT = 0xf
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go
index c91c8ac5..5cafba83 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go
@@ -238,6 +238,8 @@ const (
RTC_EPOCH_SET = 0x4008700e
RTC_IRQP_READ = 0x8008700b
RTC_IRQP_SET = 0x4008700c
+ RTC_PARAM_GET = 0x40187013
+ RTC_PARAM_SET = 0x40187014
RTC_PIE_OFF = 0x7006
RTC_PIE_ON = 0x7005
RTC_PLL_GET = 0x80207011
@@ -315,6 +317,7 @@ const (
SO_RCVTIMEO = 0x14
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x14
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x2
SO_REUSEPORT = 0xf
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go
index b66bf222..6d122da4 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go
@@ -313,6 +313,8 @@ const (
RTC_EPOCH_SET = 0x4008700e
RTC_IRQP_READ = 0x8008700b
RTC_IRQP_SET = 0x4008700c
+ RTC_PARAM_GET = 0x40187013
+ RTC_PARAM_SET = 0x40187014
RTC_PIE_OFF = 0x7006
RTC_PIE_ON = 0x7005
RTC_PLL_GET = 0x80207011
@@ -390,6 +392,7 @@ const (
SO_RCVTIMEO = 0x14
SO_RCVTIMEO_NEW = 0x42
SO_RCVTIMEO_OLD = 0x14
+ SO_RESERVE_MEM = 0x49
SO_REUSEADDR = 0x2
SO_REUSEPORT = 0xf
SO_RXQ_OVFL = 0x28
diff --git a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go
index f7fb149b..6bd19e51 100644
--- a/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go
+++ b/vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go
@@ -304,6 +304,8 @@ const (
RTC_EPOCH_SET = 0x8008700e
RTC_IRQP_READ = 0x4008700b
RTC_IRQP_SET = 0x8008700c
+ RTC_PARAM_GET = 0x80187013
+ RTC_PARAM_SET = 0x80187014
RTC_PIE_OFF = 0x20007006
RTC_PIE_ON = 0x20007005
RTC_PLL_GET = 0x40207011
@@ -381,6 +383,7 @@ const (
SO_RCVTIMEO = 0x2000
SO_RCVTIMEO_NEW = 0x44
SO_RCVTIMEO_OLD = 0x2000
+ SO_RESERVE_MEM = 0x52
SO_REUSEADDR = 0x4
SO_REUSEPORT = 0x200
SO_RXQ_OVFL = 0x24
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go
index 93edda4c..30fa4055 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go
@@ -2032,3 +2032,23 @@ func shmget(key int, size int, flag int) (id int, err error) {
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func getitimer(which int, currValue *Itimerval) (err error) {
+ _, _, e1 := Syscall(SYS_GETITIMER, uintptr(which), uintptr(unsafe.Pointer(currValue)), 0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func setitimer(which int, newValue *Itimerval, oldValue *Itimerval) (err error) {
+ _, _, e1 := Syscall(SYS_SETITIMER, uintptr(which), uintptr(unsafe.Pointer(newValue)), uintptr(unsafe.Pointer(oldValue)))
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_386.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_386.go
index ff90c81e..2fc6271f 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_386.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_386.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -l32 -tags linux,386 syscall_linux.go syscall_linux_386.go
+// go run mksyscall.go -l32 -tags linux,386 syscall_linux.go syscall_linux_386.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && 386
@@ -524,3 +524,14 @@ func utimes(path string, times *[2]Timeval) (err error) {
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go
index fa7d3dbe..43d9f012 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -tags linux,amd64 syscall_linux.go syscall_linux_amd64.go
+// go run mksyscall.go -tags linux,amd64 syscall_linux.go syscall_linux_amd64.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && amd64
@@ -444,17 +444,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
@@ -691,3 +680,14 @@ func kexecFileLoad(kernelFd int, initrdFd int, cmdlineLen int, cmdline string, f
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go
index 654f9153..7df0cb17 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go
@@ -46,17 +46,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go
index e893f987..076e8f1c 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go
@@ -389,17 +389,6 @@ func Truncate(path string, length int64) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go
index 6d155288..7b3c8474 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -b32 -arm -tags linux,mips syscall_linux.go syscall_linux_mipsx.go
+// go run mksyscall.go -b32 -arm -tags linux,mips syscall_linux.go syscall_linux_mipsx.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && mips
@@ -344,17 +344,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
@@ -702,3 +691,14 @@ func setrlimit(resource int, rlim *rlimit32) (err error) {
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go
index 1e20d72d..0d3c45fb 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -tags linux,mips64 syscall_linux.go syscall_linux_mips64x.go
+// go run mksyscall.go -tags linux,mips64 syscall_linux.go syscall_linux_mips64x.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && mips64
@@ -399,17 +399,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
@@ -696,3 +685,14 @@ func stat(path string, st *stat_t) (err error) {
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go
index 82b5e2d9..cb46b2aa 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go
@@ -399,17 +399,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go
index a0440c1d..21c9baa6 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -l32 -arm -tags linux,mipsle syscall_linux.go syscall_linux_mipsx.go
+// go run mksyscall.go -l32 -arm -tags linux,mipsle syscall_linux.go syscall_linux_mipsx.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && mipsle
@@ -344,17 +344,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
@@ -702,3 +691,14 @@ func setrlimit(resource int, rlim *rlimit32) (err error) {
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go
index 5864b9ca..02b8f088 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -b32 -tags linux,ppc syscall_linux.go syscall_linux_ppc.go
+// go run mksyscall.go -b32 -tags linux,ppc syscall_linux.go syscall_linux_ppc.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && ppc
@@ -409,17 +409,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
@@ -707,3 +696,14 @@ func kexecFileLoad(kernelFd int, initrdFd int, cmdlineLen int, cmdline string, f
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go
index beeb49e3..ac8cb09b 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -tags linux,ppc64 syscall_linux.go syscall_linux_ppc64x.go
+// go run mksyscall.go -tags linux,ppc64 syscall_linux.go syscall_linux_ppc64x.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && ppc64
@@ -475,17 +475,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
@@ -753,3 +742,14 @@ func kexecFileLoad(kernelFd int, initrdFd int, cmdlineLen int, cmdline string, f
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go
index 53139b82..bd08d887 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -tags linux,ppc64le syscall_linux.go syscall_linux_ppc64x.go
+// go run mksyscall.go -tags linux,ppc64le syscall_linux.go syscall_linux_ppc64x.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && ppc64le
@@ -475,17 +475,6 @@ func Ustat(dev int, ubuf *Ustat_t) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
@@ -753,3 +742,14 @@ func kexecFileLoad(kernelFd int, initrdFd int, cmdlineLen int, cmdline string, f
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go
index 63b393b8..a834d217 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go
@@ -369,17 +369,6 @@ func Truncate(path string, length int64) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go
index 202add37..9e462a96 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -tags linux,s390x syscall_linux.go syscall_linux_s390x.go
+// go run mksyscall.go -tags linux,s390x syscall_linux.go syscall_linux_s390x.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && s390x
@@ -533,3 +533,14 @@ func kexecFileLoad(kernelFd int, initrdFd int, cmdlineLen int, cmdline string, f
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go
index 2ab268c3..96d34024 100644
--- a/vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go
+++ b/vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go
@@ -1,4 +1,4 @@
-// go run mksyscall.go -tags linux,sparc64 syscall_linux.go syscall_linux_sparc64.go
+// go run mksyscall.go -tags linux,sparc64 syscall_linux.go syscall_linux_sparc64.go syscall_linux_alarm.go
// Code generated by the command above; see README.md. DO NOT EDIT.
//go:build linux && sparc64
@@ -455,17 +455,6 @@ func Truncate(path string, length int64) (err error) {
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-func accept(s int, rsa *RawSockaddrAny, addrlen *_Socklen) (fd int, err error) {
- r0, _, e1 := Syscall(SYS_ACCEPT, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)))
- fd = int(r0)
- if e1 != 0 {
- err = errnoErr(e1)
- }
- return
-}
-
-// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
-
func accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error) {
r0, _, e1 := Syscall6(SYS_ACCEPT4, uintptr(s), uintptr(unsafe.Pointer(rsa)), uintptr(unsafe.Pointer(addrlen)), uintptr(flags), 0, 0)
fd = int(r0)
@@ -697,3 +686,14 @@ func utimes(path string, times *[2]Timeval) (err error) {
}
return
}
+
+// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
+
+func Alarm(seconds uint) (remaining uint, err error) {
+ r0, _, e1 := Syscall(SYS_ALARM, uintptr(seconds), 0, 0)
+ remaining = uint(r0)
+ if e1 != 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go
index 31847d23..cac1f758 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_386.go
@@ -445,4 +445,5 @@ const (
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_MEMFD_SECRET = 447
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go
index 3503cbbd..f327e4a0 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go
@@ -367,4 +367,5 @@ const (
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_MEMFD_SECRET = 447
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go
index 5ecd24bf..fb06a08d 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go
@@ -409,4 +409,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go
index 7e5c94cc..58285646 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go
@@ -312,4 +312,5 @@ const (
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_MEMFD_SECRET = 447
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go
index e1e2a2bf..3b0418e6 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go
@@ -429,4 +429,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 4445
SYS_LANDLOCK_RESTRICT_SELF = 4446
SYS_PROCESS_MRELEASE = 4448
+ SYS_FUTEX_WAITV = 4449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go
index 7651915a..314ebf16 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go
@@ -359,4 +359,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 5445
SYS_LANDLOCK_RESTRICT_SELF = 5446
SYS_PROCESS_MRELEASE = 5448
+ SYS_FUTEX_WAITV = 5449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go
index a26a2c05..b8fbb937 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go
@@ -359,4 +359,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 5445
SYS_LANDLOCK_RESTRICT_SELF = 5446
SYS_PROCESS_MRELEASE = 5448
+ SYS_FUTEX_WAITV = 5449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go
index fda9a6a9..ee309b2b 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go
@@ -429,4 +429,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 4445
SYS_LANDLOCK_RESTRICT_SELF = 4446
SYS_PROCESS_MRELEASE = 4448
+ SYS_FUTEX_WAITV = 4449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go
index e8496150..ac374810 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go
@@ -436,4 +436,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go
index 5ee0678a..5aa47211 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go
@@ -408,4 +408,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go
index 29c0f9a3..0793ac1a 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go
@@ -408,4 +408,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go
index 5c9a9a3b..a520962e 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go
@@ -310,4 +310,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go
index 913f50f9..d1738586 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go
@@ -373,4 +373,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go
index 0de03a72..dfd5660f 100644
--- a/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go
+++ b/vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go
@@ -387,4 +387,5 @@ const (
SYS_LANDLOCK_ADD_RULE = 445
SYS_LANDLOCK_RESTRICT_SELF = 446
SYS_PROCESS_MRELEASE = 448
+ SYS_FUTEX_WAITV = 449
)
diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go
index f6f0d79c..e6a8d88c 100644
--- a/vendor/golang.org/x/sys/unix/ztypes_linux.go
+++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go
@@ -24,6 +24,11 @@ type ItimerSpec struct {
Value Timespec
}
+type Itimerval struct {
+ Interval Timeval
+ Value Timeval
+}
+
const (
TIME_OK = 0x0
TIME_INS = 0x1
@@ -1144,7 +1149,8 @@ const (
PERF_RECORD_BPF_EVENT = 0x12
PERF_RECORD_CGROUP = 0x13
PERF_RECORD_TEXT_POKE = 0x14
- PERF_RECORD_MAX = 0x15
+ PERF_RECORD_AUX_OUTPUT_HW_ID = 0x15
+ PERF_RECORD_MAX = 0x16
PERF_RECORD_KSYMBOL_TYPE_UNKNOWN = 0x0
PERF_RECORD_KSYMBOL_TYPE_BPF = 0x1
PERF_RECORD_KSYMBOL_TYPE_OOL = 0x2
@@ -1784,7 +1790,8 @@ const (
const (
NF_NETDEV_INGRESS = 0x0
- NF_NETDEV_NUMHOOKS = 0x1
+ NF_NETDEV_EGRESS = 0x1
+ NF_NETDEV_NUMHOOKS = 0x2
)
const (
@@ -3166,7 +3173,13 @@ const (
DEVLINK_ATTR_RELOAD_ACTION_INFO = 0xa2
DEVLINK_ATTR_RELOAD_ACTION_STATS = 0xa3
DEVLINK_ATTR_PORT_PCI_SF_NUMBER = 0xa4
- DEVLINK_ATTR_MAX = 0xa9
+ DEVLINK_ATTR_RATE_TYPE = 0xa5
+ DEVLINK_ATTR_RATE_TX_SHARE = 0xa6
+ DEVLINK_ATTR_RATE_TX_MAX = 0xa7
+ DEVLINK_ATTR_RATE_NODE_NAME = 0xa8
+ DEVLINK_ATTR_RATE_PARENT_NODE_NAME = 0xa9
+ DEVLINK_ATTR_REGION_MAX_SNAPSHOTS = 0xaa
+ DEVLINK_ATTR_MAX = 0xaa
DEVLINK_DPIPE_FIELD_MAPPING_TYPE_NONE = 0x0
DEVLINK_DPIPE_FIELD_MAPPING_TYPE_IFINDEX = 0x1
DEVLINK_DPIPE_MATCH_TYPE_FIELD_EXACT = 0x0
@@ -3463,7 +3476,14 @@ const (
ETHTOOL_MSG_CABLE_TEST_ACT = 0x1a
ETHTOOL_MSG_CABLE_TEST_TDR_ACT = 0x1b
ETHTOOL_MSG_TUNNEL_INFO_GET = 0x1c
- ETHTOOL_MSG_USER_MAX = 0x21
+ ETHTOOL_MSG_FEC_GET = 0x1d
+ ETHTOOL_MSG_FEC_SET = 0x1e
+ ETHTOOL_MSG_MODULE_EEPROM_GET = 0x1f
+ ETHTOOL_MSG_STATS_GET = 0x20
+ ETHTOOL_MSG_PHC_VCLOCKS_GET = 0x21
+ ETHTOOL_MSG_MODULE_GET = 0x22
+ ETHTOOL_MSG_MODULE_SET = 0x23
+ ETHTOOL_MSG_USER_MAX = 0x23
ETHTOOL_MSG_KERNEL_NONE = 0x0
ETHTOOL_MSG_STRSET_GET_REPLY = 0x1
ETHTOOL_MSG_LINKINFO_GET_REPLY = 0x2
@@ -3494,7 +3514,14 @@ const (
ETHTOOL_MSG_CABLE_TEST_NTF = 0x1b
ETHTOOL_MSG_CABLE_TEST_TDR_NTF = 0x1c
ETHTOOL_MSG_TUNNEL_INFO_GET_REPLY = 0x1d
- ETHTOOL_MSG_KERNEL_MAX = 0x22
+ ETHTOOL_MSG_FEC_GET_REPLY = 0x1e
+ ETHTOOL_MSG_FEC_NTF = 0x1f
+ ETHTOOL_MSG_MODULE_EEPROM_GET_REPLY = 0x20
+ ETHTOOL_MSG_STATS_GET_REPLY = 0x21
+ ETHTOOL_MSG_PHC_VCLOCKS_GET_REPLY = 0x22
+ ETHTOOL_MSG_MODULE_GET_REPLY = 0x23
+ ETHTOOL_MSG_MODULE_NTF = 0x24
+ ETHTOOL_MSG_KERNEL_MAX = 0x24
ETHTOOL_A_HEADER_UNSPEC = 0x0
ETHTOOL_A_HEADER_DEV_INDEX = 0x1
ETHTOOL_A_HEADER_DEV_NAME = 0x2
@@ -4043,3 +4070,91 @@ const (
NL_POLICY_TYPE_ATTR_MASK = 0xc
NL_POLICY_TYPE_ATTR_MAX = 0xc
)
+
+type CANBitTiming struct {
+ Bitrate uint32
+ Sample_point uint32
+ Tq uint32
+ Prop_seg uint32
+ Phase_seg1 uint32
+ Phase_seg2 uint32
+ Sjw uint32
+ Brp uint32
+}
+
+type CANBitTimingConst struct {
+ Name [16]uint8
+ Tseg1_min uint32
+ Tseg1_max uint32
+ Tseg2_min uint32
+ Tseg2_max uint32
+ Sjw_max uint32
+ Brp_min uint32
+ Brp_max uint32
+ Brp_inc uint32
+}
+
+type CANClock struct {
+ Freq uint32
+}
+
+type CANBusErrorCounters struct {
+ Txerr uint16
+ Rxerr uint16
+}
+
+type CANCtrlMode struct {
+ Mask uint32
+ Flags uint32
+}
+
+type CANDeviceStats struct {
+ Bus_error uint32
+ Error_warning uint32
+ Error_passive uint32
+ Bus_off uint32
+ Arbitration_lost uint32
+ Restarts uint32
+}
+
+const (
+ CAN_STATE_ERROR_ACTIVE = 0x0
+ CAN_STATE_ERROR_WARNING = 0x1
+ CAN_STATE_ERROR_PASSIVE = 0x2
+ CAN_STATE_BUS_OFF = 0x3
+ CAN_STATE_STOPPED = 0x4
+ CAN_STATE_SLEEPING = 0x5
+ CAN_STATE_MAX = 0x6
+)
+
+const (
+ IFLA_CAN_UNSPEC = 0x0
+ IFLA_CAN_BITTIMING = 0x1
+ IFLA_CAN_BITTIMING_CONST = 0x2
+ IFLA_CAN_CLOCK = 0x3
+ IFLA_CAN_STATE = 0x4
+ IFLA_CAN_CTRLMODE = 0x5
+ IFLA_CAN_RESTART_MS = 0x6
+ IFLA_CAN_RESTART = 0x7
+ IFLA_CAN_BERR_COUNTER = 0x8
+ IFLA_CAN_DATA_BITTIMING = 0x9
+ IFLA_CAN_DATA_BITTIMING_CONST = 0xa
+ IFLA_CAN_TERMINATION = 0xb
+ IFLA_CAN_TERMINATION_CONST = 0xc
+ IFLA_CAN_BITRATE_CONST = 0xd
+ IFLA_CAN_DATA_BITRATE_CONST = 0xe
+ IFLA_CAN_BITRATE_MAX = 0xf
+)
+
+type KCMAttach struct {
+ Fd int32
+ Bpf_fd int32
+}
+
+type KCMUnattach struct {
+ Fd int32
+}
+
+type KCMClone struct {
+ Fd int32
+}
diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go
index 63588061..c426c357 100644
--- a/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go
+++ b/vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go
@@ -210,8 +210,8 @@ type PtraceFpregs struct {
}
type PtracePer struct {
- _ [0]uint64
- _ [32]byte
+ Control_regs [3]uint64
+ _ [8]byte
Starting_addr uint64
Ending_addr uint64
Perc_atmid uint16
diff --git a/vendor/golang.org/x/sys/windows/syscall_windows.go b/vendor/golang.org/x/sys/windows/syscall_windows.go
index 200b62a0..cf44e693 100644
--- a/vendor/golang.org/x/sys/windows/syscall_windows.go
+++ b/vendor/golang.org/x/sys/windows/syscall_windows.go
@@ -363,6 +363,8 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys SetProcessWorkingSetSizeEx(hProcess Handle, dwMinimumWorkingSetSize uintptr, dwMaximumWorkingSetSize uintptr, flags uint32) (err error)
//sys GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
//sys SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
+//sys GetActiveProcessorCount(groupNumber uint16) (ret uint32)
+//sys GetMaximumProcessorCount(groupNumber uint16) (ret uint32)
// Volume Management Functions
//sys DefineDosDevice(flags uint32, deviceName *uint16, targetPath *uint16) (err error) = DefineDosDeviceW
diff --git a/vendor/golang.org/x/sys/windows/types_windows.go b/vendor/golang.org/x/sys/windows/types_windows.go
index bb31abda..e19471c6 100644
--- a/vendor/golang.org/x/sys/windows/types_windows.go
+++ b/vendor/golang.org/x/sys/windows/types_windows.go
@@ -3172,3 +3172,5 @@ type ModuleInfo struct {
SizeOfImage uint32
EntryPoint uintptr
}
+
+const ALL_PROCESSOR_GROUPS = 0xFFFF
diff --git a/vendor/golang.org/x/sys/windows/zsyscall_windows.go b/vendor/golang.org/x/sys/windows/zsyscall_windows.go
index 1055d47e..9ea1a44f 100644
--- a/vendor/golang.org/x/sys/windows/zsyscall_windows.go
+++ b/vendor/golang.org/x/sys/windows/zsyscall_windows.go
@@ -226,6 +226,7 @@ var (
procFreeLibrary = modkernel32.NewProc("FreeLibrary")
procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent")
procGetACP = modkernel32.NewProc("GetACP")
+ procGetActiveProcessorCount = modkernel32.NewProc("GetActiveProcessorCount")
procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts")
procGetCommandLineW = modkernel32.NewProc("GetCommandLineW")
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
@@ -251,6 +252,7 @@ var (
procGetLogicalDriveStringsW = modkernel32.NewProc("GetLogicalDriveStringsW")
procGetLogicalDrives = modkernel32.NewProc("GetLogicalDrives")
procGetLongPathNameW = modkernel32.NewProc("GetLongPathNameW")
+ procGetMaximumProcessorCount = modkernel32.NewProc("GetMaximumProcessorCount")
procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW")
procGetModuleHandleExW = modkernel32.NewProc("GetModuleHandleExW")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
@@ -1967,6 +1969,12 @@ func GetACP() (acp uint32) {
return
}
+func GetActiveProcessorCount(groupNumber uint16) (ret uint32) {
+ r0, _, _ := syscall.Syscall(procGetActiveProcessorCount.Addr(), 1, uintptr(groupNumber), 0, 0)
+ ret = uint32(r0)
+ return
+}
+
func GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
if r1 == 0 {
@@ -2169,6 +2177,12 @@ func GetLongPathName(path *uint16, buf *uint16, buflen uint32) (n uint32, err er
return
}
+func GetMaximumProcessorCount(groupNumber uint16) (ret uint32) {
+ r0, _, _ := syscall.Syscall(procGetMaximumProcessorCount.Addr(), 1, uintptr(groupNumber), 0, 0)
+ ret = uint32(r0)
+ return
+}
+
func GetModuleFileName(module Handle, filename *uint16, size uint32) (n uint32, err error) {
r0, _, e1 := syscall.Syscall(procGetModuleFileNameW.Addr(), 3, uintptr(module), uintptr(unsafe.Pointer(filename)), uintptr(size))
n = uint32(r0)
diff --git a/vendor/gopkg.in/yaml.v2/encode.go b/vendor/gopkg.in/yaml.v2/encode.go
deleted file mode 100644
index 0ee738e1..00000000
--- a/vendor/gopkg.in/yaml.v2/encode.go
+++ /dev/null
@@ -1,390 +0,0 @@
-package yaml
-
-import (
- "encoding"
- "fmt"
- "io"
- "reflect"
- "regexp"
- "sort"
- "strconv"
- "strings"
- "time"
- "unicode/utf8"
-)
-
-// jsonNumber is the interface of the encoding/json.Number datatype.
-// Repeating the interface here avoids a dependency on encoding/json, and also
-// supports other libraries like jsoniter, which use a similar datatype with
-// the same interface. Detecting this interface is useful when dealing with
-// structures containing json.Number, which is a string under the hood. The
-// encoder should prefer the use of Int64(), Float64() and string(), in that
-// order, when encoding this type.
-type jsonNumber interface {
- Float64() (float64, error)
- Int64() (int64, error)
- String() string
-}
-
-type encoder struct {
- emitter yaml_emitter_t
- event yaml_event_t
- out []byte
- flow bool
- // doneInit holds whether the initial stream_start_event has been
- // emitted.
- doneInit bool
-}
-
-func newEncoder() *encoder {
- e := &encoder{}
- yaml_emitter_initialize(&e.emitter)
- yaml_emitter_set_output_string(&e.emitter, &e.out)
- yaml_emitter_set_unicode(&e.emitter, true)
- return e
-}
-
-func newEncoderWithWriter(w io.Writer) *encoder {
- e := &encoder{}
- yaml_emitter_initialize(&e.emitter)
- yaml_emitter_set_output_writer(&e.emitter, w)
- yaml_emitter_set_unicode(&e.emitter, true)
- return e
-}
-
-func (e *encoder) init() {
- if e.doneInit {
- return
- }
- yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
- e.emit()
- e.doneInit = true
-}
-
-func (e *encoder) finish() {
- e.emitter.open_ended = false
- yaml_stream_end_event_initialize(&e.event)
- e.emit()
-}
-
-func (e *encoder) destroy() {
- yaml_emitter_delete(&e.emitter)
-}
-
-func (e *encoder) emit() {
- // This will internally delete the e.event value.
- e.must(yaml_emitter_emit(&e.emitter, &e.event))
-}
-
-func (e *encoder) must(ok bool) {
- if !ok {
- msg := e.emitter.problem
- if msg == "" {
- msg = "unknown problem generating YAML content"
- }
- failf("%s", msg)
- }
-}
-
-func (e *encoder) marshalDoc(tag string, in reflect.Value) {
- e.init()
- yaml_document_start_event_initialize(&e.event, nil, nil, true)
- e.emit()
- e.marshal(tag, in)
- yaml_document_end_event_initialize(&e.event, true)
- e.emit()
-}
-
-func (e *encoder) marshal(tag string, in reflect.Value) {
- if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
- e.nilv()
- return
- }
- iface := in.Interface()
- switch m := iface.(type) {
- case jsonNumber:
- integer, err := m.Int64()
- if err == nil {
- // In this case the json.Number is a valid int64
- in = reflect.ValueOf(integer)
- break
- }
- float, err := m.Float64()
- if err == nil {
- // In this case the json.Number is a valid float64
- in = reflect.ValueOf(float)
- break
- }
- // fallback case - no number could be obtained
- in = reflect.ValueOf(m.String())
- case time.Time, *time.Time:
- // Although time.Time implements TextMarshaler,
- // we don't want to treat it as a string for YAML
- // purposes because YAML has special support for
- // timestamps.
- case Marshaler:
- v, err := m.MarshalYAML()
- if err != nil {
- fail(err)
- }
- if v == nil {
- e.nilv()
- return
- }
- in = reflect.ValueOf(v)
- case encoding.TextMarshaler:
- text, err := m.MarshalText()
- if err != nil {
- fail(err)
- }
- in = reflect.ValueOf(string(text))
- case nil:
- e.nilv()
- return
- }
- switch in.Kind() {
- case reflect.Interface:
- e.marshal(tag, in.Elem())
- case reflect.Map:
- e.mapv(tag, in)
- case reflect.Ptr:
- if in.Type() == ptrTimeType {
- e.timev(tag, in.Elem())
- } else {
- e.marshal(tag, in.Elem())
- }
- case reflect.Struct:
- if in.Type() == timeType {
- e.timev(tag, in)
- } else {
- e.structv(tag, in)
- }
- case reflect.Slice, reflect.Array:
- if in.Type().Elem() == mapItemType {
- e.itemsv(tag, in)
- } else {
- e.slicev(tag, in)
- }
- case reflect.String:
- e.stringv(tag, in)
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- if in.Type() == durationType {
- e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String()))
- } else {
- e.intv(tag, in)
- }
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
- e.uintv(tag, in)
- case reflect.Float32, reflect.Float64:
- e.floatv(tag, in)
- case reflect.Bool:
- e.boolv(tag, in)
- default:
- panic("cannot marshal type: " + in.Type().String())
- }
-}
-
-func (e *encoder) mapv(tag string, in reflect.Value) {
- e.mappingv(tag, func() {
- keys := keyList(in.MapKeys())
- sort.Sort(keys)
- for _, k := range keys {
- e.marshal("", k)
- e.marshal("", in.MapIndex(k))
- }
- })
-}
-
-func (e *encoder) itemsv(tag string, in reflect.Value) {
- e.mappingv(tag, func() {
- slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem)
- for _, item := range slice {
- e.marshal("", reflect.ValueOf(item.Key))
- e.marshal("", reflect.ValueOf(item.Value))
- }
- })
-}
-
-func (e *encoder) structv(tag string, in reflect.Value) {
- sinfo, err := getStructInfo(in.Type())
- if err != nil {
- panic(err)
- }
- e.mappingv(tag, func() {
- for _, info := range sinfo.FieldsList {
- var value reflect.Value
- if info.Inline == nil {
- value = in.Field(info.Num)
- } else {
- value = in.FieldByIndex(info.Inline)
- }
- if info.OmitEmpty && isZero(value) {
- continue
- }
- e.marshal("", reflect.ValueOf(info.Key))
- e.flow = info.Flow
- e.marshal("", value)
- }
- if sinfo.InlineMap >= 0 {
- m := in.Field(sinfo.InlineMap)
- if m.Len() > 0 {
- e.flow = false
- keys := keyList(m.MapKeys())
- sort.Sort(keys)
- for _, k := range keys {
- if _, found := sinfo.FieldsMap[k.String()]; found {
- panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String()))
- }
- e.marshal("", k)
- e.flow = false
- e.marshal("", m.MapIndex(k))
- }
- }
- }
- })
-}
-
-func (e *encoder) mappingv(tag string, f func()) {
- implicit := tag == ""
- style := yaml_BLOCK_MAPPING_STYLE
- if e.flow {
- e.flow = false
- style = yaml_FLOW_MAPPING_STYLE
- }
- yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
- e.emit()
- f()
- yaml_mapping_end_event_initialize(&e.event)
- e.emit()
-}
-
-func (e *encoder) slicev(tag string, in reflect.Value) {
- implicit := tag == ""
- style := yaml_BLOCK_SEQUENCE_STYLE
- if e.flow {
- e.flow = false
- style = yaml_FLOW_SEQUENCE_STYLE
- }
- e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
- e.emit()
- n := in.Len()
- for i := 0; i < n; i++ {
- e.marshal("", in.Index(i))
- }
- e.must(yaml_sequence_end_event_initialize(&e.event))
- e.emit()
-}
-
-// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
-//
-// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
-// in YAML 1.2 and by this package, but these should be marshalled quoted for
-// the time being for compatibility with other parsers.
-func isBase60Float(s string) (result bool) {
- // Fast path.
- if s == "" {
- return false
- }
- c := s[0]
- if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
- return false
- }
- // Do the full match.
- return base60float.MatchString(s)
-}
-
-// From http://yaml.org/type/float.html, except the regular expression there
-// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
-var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
-
-func (e *encoder) stringv(tag string, in reflect.Value) {
- var style yaml_scalar_style_t
- s := in.String()
- canUsePlain := true
- switch {
- case !utf8.ValidString(s):
- if tag == yaml_BINARY_TAG {
- failf("explicitly tagged !!binary data must be base64-encoded")
- }
- if tag != "" {
- failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
- }
- // It can't be encoded directly as YAML so use a binary tag
- // and encode it as base64.
- tag = yaml_BINARY_TAG
- s = encodeBase64(s)
- case tag == "":
- // Check to see if it would resolve to a specific
- // tag when encoded unquoted. If it doesn't,
- // there's no need to quote it.
- rtag, _ := resolve("", s)
- canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s)
- }
- // Note: it's possible for user code to emit invalid YAML
- // if they explicitly specify a tag and a string containing
- // text that's incompatible with that tag.
- switch {
- case strings.Contains(s, "\n"):
- style = yaml_LITERAL_SCALAR_STYLE
- case canUsePlain:
- style = yaml_PLAIN_SCALAR_STYLE
- default:
- style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
- }
- e.emitScalar(s, "", tag, style)
-}
-
-func (e *encoder) boolv(tag string, in reflect.Value) {
- var s string
- if in.Bool() {
- s = "true"
- } else {
- s = "false"
- }
- e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
-}
-
-func (e *encoder) intv(tag string, in reflect.Value) {
- s := strconv.FormatInt(in.Int(), 10)
- e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
-}
-
-func (e *encoder) uintv(tag string, in reflect.Value) {
- s := strconv.FormatUint(in.Uint(), 10)
- e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
-}
-
-func (e *encoder) timev(tag string, in reflect.Value) {
- t := in.Interface().(time.Time)
- s := t.Format(time.RFC3339Nano)
- e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
-}
-
-func (e *encoder) floatv(tag string, in reflect.Value) {
- // Issue #352: When formatting, use the precision of the underlying value
- precision := 64
- if in.Kind() == reflect.Float32 {
- precision = 32
- }
-
- s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
- switch s {
- case "+Inf":
- s = ".inf"
- case "-Inf":
- s = "-.inf"
- case "NaN":
- s = ".nan"
- }
- e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
-}
-
-func (e *encoder) nilv() {
- e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE)
-}
-
-func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
- implicit := tag == ""
- e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
- e.emit()
-}
diff --git a/vendor/gopkg.in/yaml.v2/writerc.go b/vendor/gopkg.in/yaml.v2/writerc.go
deleted file mode 100644
index a2dde608..00000000
--- a/vendor/gopkg.in/yaml.v2/writerc.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package yaml
-
-// Set the writer error and return false.
-func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
- emitter.error = yaml_WRITER_ERROR
- emitter.problem = problem
- return false
-}
-
-// Flush the output buffer.
-func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
- if emitter.write_handler == nil {
- panic("write handler not set")
- }
-
- // Check if the buffer is empty.
- if emitter.buffer_pos == 0 {
- return true
- }
-
- if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
- return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
- }
- emitter.buffer_pos = 0
- return true
-}
diff --git a/vendor/gopkg.in/yaml.v2/.travis.yml b/vendor/gopkg.in/yaml.v3/.travis.yml
similarity index 84%
rename from vendor/gopkg.in/yaml.v2/.travis.yml
rename to vendor/gopkg.in/yaml.v3/.travis.yml
index 055480b9..04d4dae0 100644
--- a/vendor/gopkg.in/yaml.v2/.travis.yml
+++ b/vendor/gopkg.in/yaml.v3/.travis.yml
@@ -13,4 +13,4 @@ go:
- "1.13.x"
- "tip"
-go_import_path: gopkg.in/yaml.v2
+go_import_path: gopkg.in/yaml.v3
diff --git a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml b/vendor/gopkg.in/yaml.v3/LICENSE
similarity index 51%
rename from vendor/gopkg.in/yaml.v2/LICENSE.libyaml
rename to vendor/gopkg.in/yaml.v3/LICENSE
index 8da58fbf..2683e4bb 100644
--- a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml
+++ b/vendor/gopkg.in/yaml.v3/LICENSE
@@ -1,16 +1,17 @@
+
+This project is covered by two different licenses: MIT and Apache.
+
+#### MIT License ####
+
The following files were ported to Go from C files of libyaml, and thus
-are still covered by their original copyright and license:
+are still covered by their original MIT license, with the additional
+copyright staring in 2011 when the project was ported over:
- apic.go
- emitterc.go
- parserc.go
- readerc.go
- scannerc.go
- writerc.go
- yamlh.go
- yamlprivateh.go
+ apic.go emitterc.go parserc.go readerc.go scannerc.go
+ writerc.go yamlh.go yamlprivateh.go
-Copyright (c) 2006 Kirill Simonov
+Copyright (c) 2006-2010 Kirill Simonov
+Copyright (c) 2006-2011 Kirill Simonov
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
@@ -29,3 +30,21 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+
+### Apache License ###
+
+All the remaining project files are covered by the Apache license:
+
+Copyright (c) 2011-2019 Canonical Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/vendor/gopkg.in/yaml.v2/NOTICE b/vendor/gopkg.in/yaml.v3/NOTICE
similarity index 100%
rename from vendor/gopkg.in/yaml.v2/NOTICE
rename to vendor/gopkg.in/yaml.v3/NOTICE
diff --git a/vendor/gopkg.in/yaml.v2/README.md b/vendor/gopkg.in/yaml.v3/README.md
similarity index 66%
rename from vendor/gopkg.in/yaml.v2/README.md
rename to vendor/gopkg.in/yaml.v3/README.md
index b50c6e87..08eb1bab 100644
--- a/vendor/gopkg.in/yaml.v2/README.md
+++ b/vendor/gopkg.in/yaml.v3/README.md
@@ -12,7 +12,23 @@ C library to parse and generate YAML data quickly and reliably.
Compatibility
-------------
-The yaml package supports most of YAML 1.1 and 1.2, including support for
+The yaml package supports most of YAML 1.2, but preserves some behavior
+from 1.1 for backwards compatibility.
+
+Specifically, as of v3 of the yaml package:
+
+ - YAML 1.1 bools (_yes/no, on/off_) are supported as long as they are being
+ decoded into a typed bool value. Otherwise they behave as a string. Booleans
+ in YAML 1.2 are _true/false_ only.
+ - Octals encode and decode as _0777_ per YAML 1.1, rather than _0o777_
+ as specified in YAML 1.2, because most parsers still use the old format.
+ Octals in the _0o777_ format are supported though, so new files work.
+ - Does not support base-60 floats. These are gone from YAML 1.2, and were
+ actually never supported by this package as it's clearly a poor choice.
+
+and offers backwards
+compatibility with YAML 1.1 in some cases.
+1.2, including support for
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
implemented, and base-60 floats from YAML 1.1 are purposefully not
supported since they're a poor design and are gone in YAML 1.2.
@@ -20,29 +36,30 @@ supported since they're a poor design and are gone in YAML 1.2.
Installation and usage
----------------------
-The import path for the package is *gopkg.in/yaml.v2*.
+The import path for the package is *gopkg.in/yaml.v3*.
To install it, run:
- go get gopkg.in/yaml.v2
+ go get gopkg.in/yaml.v3
API documentation
-----------------
If opened in a browser, the import path itself leads to the API documentation:
- * [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2)
+ - [https://gopkg.in/yaml.v3](https://gopkg.in/yaml.v3)
API stability
-------------
-The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in).
+The package API for yaml v3 will remain stable as described in [gopkg.in](https://gopkg.in).
License
-------
-The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details.
+The yaml package is licensed under the MIT and Apache License 2.0 licenses.
+Please see the LICENSE file for details.
Example
@@ -55,7 +72,7 @@ import (
"fmt"
"log"
- "gopkg.in/yaml.v2"
+ "gopkg.in/yaml.v3"
)
var data = `
diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v3/apic.go
similarity index 93%
rename from vendor/gopkg.in/yaml.v2/apic.go
rename to vendor/gopkg.in/yaml.v3/apic.go
index 1f7e87e6..65846e67 100644
--- a/vendor/gopkg.in/yaml.v2/apic.go
+++ b/vendor/gopkg.in/yaml.v3/apic.go
@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+// Copyright (c) 2006-2010 Kirill Simonov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
package yaml
import (
@@ -138,7 +160,7 @@ func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
emitter.canonical = canonical
}
-//// Set the indentation increment.
+// Set the indentation increment.
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
if indent < 2 || indent > 9 {
indent = 2
@@ -288,29 +310,14 @@ func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
}
}
-///*
-// * Create ALIAS.
-// */
-//
-//YAML_DECLARE(int)
-//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t)
-//{
-// mark yaml_mark_t = { 0, 0, 0 }
-// anchor_copy *yaml_char_t = NULL
-//
-// assert(event) // Non-NULL event object is expected.
-// assert(anchor) // Non-NULL anchor is expected.
-//
-// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0
-//
-// anchor_copy = yaml_strdup(anchor)
-// if (!anchor_copy)
-// return 0
-//
-// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark)
-//
-// return 1
-//}
+// Create ALIAS.
+func yaml_alias_event_initialize(event *yaml_event_t, anchor []byte) bool {
+ *event = yaml_event_t{
+ typ: yaml_ALIAS_EVENT,
+ anchor: anchor,
+ }
+ return true
+}
// Create SCALAR.
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v3/decode.go
similarity index 55%
rename from vendor/gopkg.in/yaml.v2/decode.go
rename to vendor/gopkg.in/yaml.v3/decode.go
index 129bc2a9..be63169b 100644
--- a/vendor/gopkg.in/yaml.v2/decode.go
+++ b/vendor/gopkg.in/yaml.v3/decode.go
@@ -1,3 +1,18 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package yaml
import (
@@ -11,33 +26,14 @@ import (
"time"
)
-const (
- documentNode = 1 << iota
- mappingNode
- sequenceNode
- scalarNode
- aliasNode
-)
-
-type node struct {
- kind int
- line, column int
- tag string
- // For an alias node, alias holds the resolved alias.
- alias *node
- value string
- implicit bool
- children []*node
- anchors map[string]*node
-}
-
// ----------------------------------------------------------------------------
// Parser, produces a node tree out of a libyaml event stream.
type parser struct {
parser yaml_parser_t
event yaml_event_t
- doc *node
+ doc *Node
+ anchors map[string]*Node
doneInit bool
}
@@ -66,6 +62,7 @@ func (p *parser) init() {
if p.doneInit {
return
}
+ p.anchors = make(map[string]*Node)
p.expect(yaml_STREAM_START_EVENT)
p.doneInit = true
}
@@ -132,13 +129,14 @@ func (p *parser) fail() {
failf("%s%s", where, msg)
}
-func (p *parser) anchor(n *node, anchor []byte) {
+func (p *parser) anchor(n *Node, anchor []byte) {
if anchor != nil {
- p.doc.anchors[string(anchor)] = n
+ n.Anchor = string(anchor)
+ p.anchors[n.Anchor] = n
}
}
-func (p *parser) parse() *node {
+func (p *parser) parse() *Node {
p.init()
switch p.peek() {
case yaml_SCALAR_EVENT:
@@ -154,67 +152,145 @@ func (p *parser) parse() *node {
case yaml_STREAM_END_EVENT:
// Happens when attempting to decode an empty buffer.
return nil
+ case yaml_TAIL_COMMENT_EVENT:
+ panic("internal error: unexpected tail comment event (please report)")
default:
- panic("attempted to parse unknown event: " + p.event.typ.String())
+ panic("internal error: attempted to parse unknown event (please report): " + p.event.typ.String())
}
}
-func (p *parser) node(kind int) *node {
- return &node{
- kind: kind,
- line: p.event.start_mark.line,
- column: p.event.start_mark.column,
+func (p *parser) node(kind Kind, defaultTag, tag, value string) *Node {
+ var style Style
+ if tag != "" && tag != "!" {
+ tag = shortTag(tag)
+ style = TaggedStyle
+ } else if defaultTag != "" {
+ tag = defaultTag
+ } else if kind == ScalarNode {
+ tag, _ = resolve("", value)
+ }
+ return &Node{
+ Kind: kind,
+ Tag: tag,
+ Value: value,
+ Style: style,
+ Line: p.event.start_mark.line + 1,
+ Column: p.event.start_mark.column + 1,
+ HeadComment: string(p.event.head_comment),
+ LineComment: string(p.event.line_comment),
+ FootComment: string(p.event.foot_comment),
}
}
-func (p *parser) document() *node {
- n := p.node(documentNode)
- n.anchors = make(map[string]*node)
+func (p *parser) parseChild(parent *Node) *Node {
+ child := p.parse()
+ parent.Content = append(parent.Content, child)
+ return child
+}
+
+func (p *parser) document() *Node {
+ n := p.node(DocumentNode, "", "", "")
p.doc = n
p.expect(yaml_DOCUMENT_START_EVENT)
- n.children = append(n.children, p.parse())
+ p.parseChild(n)
+ if p.peek() == yaml_DOCUMENT_END_EVENT {
+ n.FootComment = string(p.event.foot_comment)
+ }
p.expect(yaml_DOCUMENT_END_EVENT)
return n
}
-func (p *parser) alias() *node {
- n := p.node(aliasNode)
- n.value = string(p.event.anchor)
- n.alias = p.doc.anchors[n.value]
- if n.alias == nil {
- failf("unknown anchor '%s' referenced", n.value)
+func (p *parser) alias() *Node {
+ n := p.node(AliasNode, "", "", string(p.event.anchor))
+ n.Alias = p.anchors[n.Value]
+ if n.Alias == nil {
+ failf("unknown anchor '%s' referenced", n.Value)
}
p.expect(yaml_ALIAS_EVENT)
return n
}
-func (p *parser) scalar() *node {
- n := p.node(scalarNode)
- n.value = string(p.event.value)
- n.tag = string(p.event.tag)
- n.implicit = p.event.implicit
+func (p *parser) scalar() *Node {
+ var parsedStyle = p.event.scalar_style()
+ var nodeStyle Style
+ switch {
+ case parsedStyle&yaml_DOUBLE_QUOTED_SCALAR_STYLE != 0:
+ nodeStyle = DoubleQuotedStyle
+ case parsedStyle&yaml_SINGLE_QUOTED_SCALAR_STYLE != 0:
+ nodeStyle = SingleQuotedStyle
+ case parsedStyle&yaml_LITERAL_SCALAR_STYLE != 0:
+ nodeStyle = LiteralStyle
+ case parsedStyle&yaml_FOLDED_SCALAR_STYLE != 0:
+ nodeStyle = FoldedStyle
+ }
+ var nodeValue = string(p.event.value)
+ var nodeTag = string(p.event.tag)
+ var defaultTag string
+ if nodeStyle == 0 {
+ if nodeValue == "<<" {
+ defaultTag = mergeTag
+ }
+ } else {
+ defaultTag = strTag
+ }
+ n := p.node(ScalarNode, defaultTag, nodeTag, nodeValue)
+ n.Style |= nodeStyle
p.anchor(n, p.event.anchor)
p.expect(yaml_SCALAR_EVENT)
return n
}
-func (p *parser) sequence() *node {
- n := p.node(sequenceNode)
+func (p *parser) sequence() *Node {
+ n := p.node(SequenceNode, seqTag, string(p.event.tag), "")
+ if p.event.sequence_style()&yaml_FLOW_SEQUENCE_STYLE != 0 {
+ n.Style |= FlowStyle
+ }
p.anchor(n, p.event.anchor)
p.expect(yaml_SEQUENCE_START_EVENT)
for p.peek() != yaml_SEQUENCE_END_EVENT {
- n.children = append(n.children, p.parse())
+ p.parseChild(n)
}
+ n.LineComment = string(p.event.line_comment)
+ n.FootComment = string(p.event.foot_comment)
p.expect(yaml_SEQUENCE_END_EVENT)
return n
}
-func (p *parser) mapping() *node {
- n := p.node(mappingNode)
+func (p *parser) mapping() *Node {
+ n := p.node(MappingNode, mapTag, string(p.event.tag), "")
+ block := true
+ if p.event.mapping_style()&yaml_FLOW_MAPPING_STYLE != 0 {
+ block = false
+ n.Style |= FlowStyle
+ }
p.anchor(n, p.event.anchor)
p.expect(yaml_MAPPING_START_EVENT)
for p.peek() != yaml_MAPPING_END_EVENT {
- n.children = append(n.children, p.parse(), p.parse())
+ k := p.parseChild(n)
+ if block && k.FootComment != "" {
+ // Must be a foot comment for the prior value when being dedented.
+ if len(n.Content) > 2 {
+ n.Content[len(n.Content)-3].FootComment = k.FootComment
+ k.FootComment = ""
+ }
+ }
+ v := p.parseChild(n)
+ if k.FootComment == "" && v.FootComment != "" {
+ k.FootComment = v.FootComment
+ v.FootComment = ""
+ }
+ if p.peek() == yaml_TAIL_COMMENT_EVENT {
+ if k.FootComment == "" {
+ k.FootComment = string(p.event.foot_comment)
+ }
+ p.expect(yaml_TAIL_COMMENT_EVENT)
+ }
+ }
+ n.LineComment = string(p.event.line_comment)
+ n.FootComment = string(p.event.foot_comment)
+ if n.Style&FlowStyle == 0 && n.FootComment != "" && len(n.Content) > 1 {
+ n.Content[len(n.Content)-2].FootComment = n.FootComment
+ n.FootComment = ""
}
p.expect(yaml_MAPPING_END_EVENT)
return n
@@ -224,48 +300,68 @@ func (p *parser) mapping() *node {
// Decoder, unmarshals a node into a provided value.
type decoder struct {
- doc *node
- aliases map[*node]bool
- mapType reflect.Type
+ doc *Node
+ aliases map[*Node]bool
terrors []string
- strict bool
+ stringMapType reflect.Type
+ generalMapType reflect.Type
+
+ knownFields bool
+ uniqueKeys bool
decodeCount int
aliasCount int
aliasDepth int
}
var (
- mapItemType = reflect.TypeOf(MapItem{})
+ nodeType = reflect.TypeOf(Node{})
durationType = reflect.TypeOf(time.Duration(0))
- defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
- ifaceType = defaultMapType.Elem()
+ stringMapType = reflect.TypeOf(map[string]interface{}{})
+ generalMapType = reflect.TypeOf(map[interface{}]interface{}{})
+ ifaceType = generalMapType.Elem()
timeType = reflect.TypeOf(time.Time{})
ptrTimeType = reflect.TypeOf(&time.Time{})
)
-func newDecoder(strict bool) *decoder {
- d := &decoder{mapType: defaultMapType, strict: strict}
- d.aliases = make(map[*node]bool)
+func newDecoder() *decoder {
+ d := &decoder{
+ stringMapType: stringMapType,
+ generalMapType: generalMapType,
+ uniqueKeys: true,
+ }
+ d.aliases = make(map[*Node]bool)
return d
}
-func (d *decoder) terror(n *node, tag string, out reflect.Value) {
- if n.tag != "" {
- tag = n.tag
+func (d *decoder) terror(n *Node, tag string, out reflect.Value) {
+ if n.Tag != "" {
+ tag = n.Tag
}
- value := n.value
- if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG {
+ value := n.Value
+ if tag != seqTag && tag != mapTag {
if len(value) > 10 {
value = " `" + value[:7] + "...`"
} else {
value = " `" + value + "`"
}
}
- d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type()))
+ d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.Line, shortTag(tag), value, out.Type()))
}
-func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
+func (d *decoder) callUnmarshaler(n *Node, u Unmarshaler) (good bool) {
+ err := u.UnmarshalYAML(n)
+ if e, ok := err.(*TypeError); ok {
+ d.terrors = append(d.terrors, e.Errors...)
+ return false
+ }
+ if err != nil {
+ fail(err)
+ }
+ return true
+}
+
+func (d *decoder) callObsoleteUnmarshaler(n *Node, u obsoleteUnmarshaler) (good bool) {
terrlen := len(d.terrors)
err := u.UnmarshalYAML(func(v interface{}) (err error) {
defer handleErr(&err)
@@ -294,8 +390,8 @@ func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
// its types unmarshalled appropriately.
//
// If n holds a null value, prepare returns before doing anything.
-func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
- if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) {
+func (d *decoder) prepare(n *Node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
+ if n.ShortTag() == nullTag {
return out, false, false
}
again := true
@@ -309,15 +405,40 @@ func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unm
again = true
}
if out.CanAddr() {
- if u, ok := out.Addr().Interface().(Unmarshaler); ok {
+ outi := out.Addr().Interface()
+ if u, ok := outi.(Unmarshaler); ok {
good = d.callUnmarshaler(n, u)
return out, true, good
}
+ if u, ok := outi.(obsoleteUnmarshaler); ok {
+ good = d.callObsoleteUnmarshaler(n, u)
+ return out, true, good
+ }
}
}
return out, false, false
}
+func (d *decoder) fieldByIndex(n *Node, v reflect.Value, index []int) (field reflect.Value) {
+ if n.ShortTag() == nullTag {
+ return reflect.Value{}
+ }
+ for _, num := range index {
+ for {
+ if v.Kind() == reflect.Ptr {
+ if v.IsNil() {
+ v.Set(reflect.New(v.Type().Elem()))
+ }
+ v = v.Elem()
+ continue
+ }
+ break
+ }
+ v = v.Field(num)
+ }
+ return v
+}
+
const (
// 400,000 decode operations is ~500kb of dense object declarations, or
// ~5kb of dense object declarations with 10000% alias expansion
@@ -347,7 +468,7 @@ func allowedAliasRatio(decodeCount int) float64 {
}
}
-func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
+func (d *decoder) unmarshal(n *Node, out reflect.Value) (good bool) {
d.decodeCount++
if d.aliasDepth > 0 {
d.aliasCount++
@@ -355,46 +476,50 @@ func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) {
failf("document contains excessive aliasing")
}
- switch n.kind {
- case documentNode:
+ if out.Type() == nodeType {
+ out.Set(reflect.ValueOf(n).Elem())
+ return true
+ }
+ switch n.Kind {
+ case DocumentNode:
return d.document(n, out)
- case aliasNode:
+ case AliasNode:
return d.alias(n, out)
}
out, unmarshaled, good := d.prepare(n, out)
if unmarshaled {
return good
}
- switch n.kind {
- case scalarNode:
+ switch n.Kind {
+ case ScalarNode:
good = d.scalar(n, out)
- case mappingNode:
+ case MappingNode:
good = d.mapping(n, out)
- case sequenceNode:
+ case SequenceNode:
good = d.sequence(n, out)
default:
- panic("internal error: unknown node kind: " + strconv.Itoa(n.kind))
+ panic("internal error: unknown node kind: " + strconv.Itoa(int(n.Kind)))
}
return good
}
-func (d *decoder) document(n *node, out reflect.Value) (good bool) {
- if len(n.children) == 1 {
+func (d *decoder) document(n *Node, out reflect.Value) (good bool) {
+ if len(n.Content) == 1 {
d.doc = n
- d.unmarshal(n.children[0], out)
+ d.unmarshal(n.Content[0], out)
return true
}
return false
}
-func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
+func (d *decoder) alias(n *Node, out reflect.Value) (good bool) {
if d.aliases[n] {
// TODO this could actually be allowed in some circumstances.
- failf("anchor '%s' value contains itself", n.value)
+ failf("anchor '%s' value contains itself", n.Value)
}
d.aliases[n] = true
d.aliasDepth++
- good = d.unmarshal(n.alias, out)
+ good = d.unmarshal(n.Alias, out)
d.aliasDepth--
delete(d.aliases, n)
return good
@@ -408,15 +533,15 @@ func resetMap(out reflect.Value) {
}
}
-func (d *decoder) scalar(n *node, out reflect.Value) bool {
+func (d *decoder) scalar(n *Node, out reflect.Value) bool {
var tag string
var resolved interface{}
- if n.tag == "" && !n.implicit {
- tag = yaml_STR_TAG
- resolved = n.value
+ if n.indicatedString() {
+ tag = strTag
+ resolved = n.Value
} else {
- tag, resolved = resolve(n.tag, n.value)
- if tag == yaml_BINARY_TAG {
+ tag, resolved = resolve(n.Tag, n.Value)
+ if tag == binaryTag {
data, err := base64.StdEncoding.DecodeString(resolved.(string))
if err != nil {
failf("!!binary value contains invalid base64 data")
@@ -425,12 +550,14 @@ func (d *decoder) scalar(n *node, out reflect.Value) bool {
}
}
if resolved == nil {
- if out.Kind() == reflect.Map && !out.CanAddr() {
- resetMap(out)
- } else {
- out.Set(reflect.Zero(out.Type()))
+ if out.CanAddr() {
+ switch out.Kind() {
+ case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
+ out.Set(reflect.Zero(out.Type()))
+ return true
+ }
}
- return true
+ return false
}
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
// We've resolved to exactly the type we want, so use that.
@@ -443,13 +570,13 @@ func (d *decoder) scalar(n *node, out reflect.Value) bool {
u, ok := out.Addr().Interface().(encoding.TextUnmarshaler)
if ok {
var text []byte
- if tag == yaml_BINARY_TAG {
+ if tag == binaryTag {
text = []byte(resolved.(string))
} else {
// We let any value be unmarshaled into TextUnmarshaler.
// That might be more lax than we'd like, but the
// TextUnmarshaler itself should bowl out any dubious values.
- text = []byte(n.value)
+ text = []byte(n.Value)
}
err := u.UnmarshalText(text)
if err != nil {
@@ -460,47 +587,37 @@ func (d *decoder) scalar(n *node, out reflect.Value) bool {
}
switch out.Kind() {
case reflect.String:
- if tag == yaml_BINARY_TAG {
+ if tag == binaryTag {
out.SetString(resolved.(string))
return true
}
- if resolved != nil {
- out.SetString(n.value)
- return true
- }
+ out.SetString(n.Value)
+ return true
case reflect.Interface:
- if resolved == nil {
- out.Set(reflect.Zero(out.Type()))
- } else if tag == yaml_TIMESTAMP_TAG {
- // It looks like a timestamp but for backward compatibility
- // reasons we set it as a string, so that code that unmarshals
- // timestamp-like values into interface{} will continue to
- // see a string and not a time.Time.
- // TODO(v3) Drop this.
- out.Set(reflect.ValueOf(n.value))
- } else {
- out.Set(reflect.ValueOf(resolved))
- }
+ out.Set(reflect.ValueOf(resolved))
return true
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ // This used to work in v2, but it's very unfriendly.
+ isDuration := out.Type() == durationType
+
switch resolved := resolved.(type) {
case int:
- if !out.OverflowInt(int64(resolved)) {
+ if !isDuration && !out.OverflowInt(int64(resolved)) {
out.SetInt(int64(resolved))
return true
}
case int64:
- if !out.OverflowInt(resolved) {
+ if !isDuration && !out.OverflowInt(resolved) {
out.SetInt(resolved)
return true
}
case uint64:
- if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
+ if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
out.SetInt(int64(resolved))
return true
}
case float64:
- if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
+ if !isDuration && resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
out.SetInt(int64(resolved))
return true
}
@@ -541,6 +658,17 @@ func (d *decoder) scalar(n *node, out reflect.Value) bool {
case bool:
out.SetBool(resolved)
return true
+ case string:
+ // This offers some compatibility with the 1.1 spec (https://yaml.org/type/bool.html).
+ // It only works if explicitly attempting to unmarshal into a typed bool value.
+ switch resolved {
+ case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON":
+ out.SetBool(true)
+ return true
+ case "n", "N", "no", "No", "NO", "off", "Off", "OFF":
+ out.SetBool(false)
+ return true
+ }
}
case reflect.Float32, reflect.Float64:
switch resolved := resolved.(type) {
@@ -563,13 +691,7 @@ func (d *decoder) scalar(n *node, out reflect.Value) bool {
return true
}
case reflect.Ptr:
- if out.Type().Elem() == reflect.TypeOf(resolved) {
- // TODO DOes this make sense? When is out a Ptr except when decoding a nil value?
- elem := reflect.New(out.Type().Elem())
- elem.Elem().Set(reflect.ValueOf(resolved))
- out.Set(elem)
- return true
- }
+ panic("yaml internal error: please report the issue")
}
d.terror(n, tag, out)
return false
@@ -582,8 +704,8 @@ func settableValueOf(i interface{}) reflect.Value {
return sv
}
-func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
- l := len(n.children)
+func (d *decoder) sequence(n *Node, out reflect.Value) (good bool) {
+ l := len(n.Content)
var iface reflect.Value
switch out.Kind() {
@@ -598,7 +720,7 @@ func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
iface = out
out = settableValueOf(make([]interface{}, l))
default:
- d.terror(n, yaml_SEQ_TAG, out)
+ d.terror(n, seqTag, out)
return false
}
et := out.Type().Elem()
@@ -606,7 +728,7 @@ func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
j := 0
for i := 0; i < l; i++ {
e := reflect.New(et).Elem()
- if ok := d.unmarshal(n.children[i], e); ok {
+ if ok := d.unmarshal(n.Content[i], e); ok {
out.Index(j).Set(e)
j++
}
@@ -620,51 +742,65 @@ func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
return true
}
-func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
+func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
+ l := len(n.Content)
+ if d.uniqueKeys {
+ nerrs := len(d.terrors)
+ for i := 0; i < l; i += 2 {
+ ni := n.Content[i]
+ for j := i + 2; j < l; j += 2 {
+ nj := n.Content[j]
+ if ni.Kind == nj.Kind && ni.Value == nj.Value {
+ d.terrors = append(d.terrors, fmt.Sprintf("line %d: mapping key %#v already defined at line %d", nj.Line, nj.Value, ni.Line))
+ }
+ }
+ }
+ if len(d.terrors) > nerrs {
+ return false
+ }
+ }
switch out.Kind() {
case reflect.Struct:
return d.mappingStruct(n, out)
- case reflect.Slice:
- return d.mappingSlice(n, out)
case reflect.Map:
// okay
case reflect.Interface:
- if d.mapType.Kind() == reflect.Map {
- iface := out
- out = reflect.MakeMap(d.mapType)
- iface.Set(out)
+ iface := out
+ if isStringMap(n) {
+ out = reflect.MakeMap(d.stringMapType)
} else {
- slicev := reflect.New(d.mapType).Elem()
- if !d.mappingSlice(n, slicev) {
- return false
- }
- out.Set(slicev)
- return true
+ out = reflect.MakeMap(d.generalMapType)
}
+ iface.Set(out)
default:
- d.terror(n, yaml_MAP_TAG, out)
+ d.terror(n, mapTag, out)
return false
}
+
outt := out.Type()
kt := outt.Key()
et := outt.Elem()
- mapType := d.mapType
- if outt.Key() == ifaceType && outt.Elem() == ifaceType {
- d.mapType = outt
+ stringMapType := d.stringMapType
+ generalMapType := d.generalMapType
+ if outt.Elem() == ifaceType {
+ if outt.Key().Kind() == reflect.String {
+ d.stringMapType = outt
+ } else if outt.Key() == ifaceType {
+ d.generalMapType = outt
+ }
}
if out.IsNil() {
out.Set(reflect.MakeMap(outt))
}
- l := len(n.children)
for i := 0; i < l; i += 2 {
- if isMerge(n.children[i]) {
- d.merge(n.children[i+1], out)
+ if isMerge(n.Content[i]) {
+ d.merge(n.Content[i+1], out)
continue
}
k := reflect.New(kt).Elem()
- if d.unmarshal(n.children[i], k) {
+ if d.unmarshal(n.Content[i], k) {
kkind := k.Kind()
if kkind == reflect.Interface {
kkind = k.Elem().Kind()
@@ -673,61 +809,34 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
failf("invalid map key: %#v", k.Interface())
}
e := reflect.New(et).Elem()
- if d.unmarshal(n.children[i+1], e) {
- d.setMapIndex(n.children[i+1], out, k, e)
+ if d.unmarshal(n.Content[i+1], e) {
+ out.SetMapIndex(k, e)
}
}
}
- d.mapType = mapType
+ d.stringMapType = stringMapType
+ d.generalMapType = generalMapType
return true
}
-func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) {
- if d.strict && out.MapIndex(k) != zeroValue {
- d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface()))
- return
- }
- out.SetMapIndex(k, v)
-}
-
-func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
- outt := out.Type()
- if outt.Elem() != mapItemType {
- d.terror(n, yaml_MAP_TAG, out)
+func isStringMap(n *Node) bool {
+ if n.Kind != MappingNode {
return false
}
-
- mapType := d.mapType
- d.mapType = outt
-
- var slice []MapItem
- var l = len(n.children)
+ l := len(n.Content)
for i := 0; i < l; i += 2 {
- if isMerge(n.children[i]) {
- d.merge(n.children[i+1], out)
- continue
- }
- item := MapItem{}
- k := reflect.ValueOf(&item.Key).Elem()
- if d.unmarshal(n.children[i], k) {
- v := reflect.ValueOf(&item.Value).Elem()
- if d.unmarshal(n.children[i+1], v) {
- slice = append(slice, item)
- }
+ if n.Content[i].ShortTag() != strTag {
+ return false
}
}
- out.Set(reflect.ValueOf(slice))
- d.mapType = mapType
return true
}
-func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
+func (d *decoder) mappingStruct(n *Node, out reflect.Value) (good bool) {
sinfo, err := getStructInfo(out.Type())
if err != nil {
panic(err)
}
- name := settableValueOf("")
- l := len(n.children)
var inlineMap reflect.Value
var elemType reflect.Type
@@ -737,23 +846,30 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
elemType = inlineMap.Type().Elem()
}
+ for _, index := range sinfo.InlineUnmarshalers {
+ field := d.fieldByIndex(n, out, index)
+ d.prepare(n, field)
+ }
+
var doneFields []bool
- if d.strict {
+ if d.uniqueKeys {
doneFields = make([]bool, len(sinfo.FieldsList))
}
+ name := settableValueOf("")
+ l := len(n.Content)
for i := 0; i < l; i += 2 {
- ni := n.children[i]
+ ni := n.Content[i]
if isMerge(ni) {
- d.merge(n.children[i+1], out)
+ d.merge(n.Content[i+1], out)
continue
}
if !d.unmarshal(ni, name) {
continue
}
if info, ok := sinfo.FieldsMap[name.String()]; ok {
- if d.strict {
+ if d.uniqueKeys {
if doneFields[info.Id] {
- d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type()))
+ d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.Line, name.String(), out.Type()))
continue
}
doneFields[info.Id] = true
@@ -762,18 +878,18 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
if info.Inline == nil {
field = out.Field(info.Num)
} else {
- field = out.FieldByIndex(info.Inline)
+ field = d.fieldByIndex(n, out, info.Inline)
}
- d.unmarshal(n.children[i+1], field)
+ d.unmarshal(n.Content[i+1], field)
} else if sinfo.InlineMap != -1 {
if inlineMap.IsNil() {
inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
}
value := reflect.New(elemType).Elem()
- d.unmarshal(n.children[i+1], value)
- d.setMapIndex(n.children[i+1], inlineMap, name, value)
- } else if d.strict {
- d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type()))
+ d.unmarshal(n.Content[i+1], value)
+ inlineMap.SetMapIndex(name, value)
+ } else if d.knownFields {
+ d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.Line, name.String(), out.Type()))
}
}
return true
@@ -783,24 +899,24 @@ func failWantMap() {
failf("map merge requires map or sequence of maps as the value")
}
-func (d *decoder) merge(n *node, out reflect.Value) {
- switch n.kind {
- case mappingNode:
+func (d *decoder) merge(n *Node, out reflect.Value) {
+ switch n.Kind {
+ case MappingNode:
d.unmarshal(n, out)
- case aliasNode:
- if n.alias != nil && n.alias.kind != mappingNode {
+ case AliasNode:
+ if n.Alias != nil && n.Alias.Kind != MappingNode {
failWantMap()
}
d.unmarshal(n, out)
- case sequenceNode:
+ case SequenceNode:
// Step backwards as earlier nodes take precedence.
- for i := len(n.children) - 1; i >= 0; i-- {
- ni := n.children[i]
- if ni.kind == aliasNode {
- if ni.alias != nil && ni.alias.kind != mappingNode {
+ for i := len(n.Content) - 1; i >= 0; i-- {
+ ni := n.Content[i]
+ if ni.Kind == AliasNode {
+ if ni.Alias != nil && ni.Alias.Kind != MappingNode {
failWantMap()
}
- } else if ni.kind != mappingNode {
+ } else if ni.Kind != MappingNode {
failWantMap()
}
d.unmarshal(ni, out)
@@ -810,6 +926,6 @@ func (d *decoder) merge(n *node, out reflect.Value) {
}
}
-func isMerge(n *node) bool {
- return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
+func isMerge(n *Node) bool {
+ return n.Kind == ScalarNode && n.Value == "<<" && (n.Tag == "" || n.Tag == "!" || shortTag(n.Tag) == mergeTag)
}
diff --git a/vendor/gopkg.in/yaml.v2/emitterc.go b/vendor/gopkg.in/yaml.v3/emitterc.go
similarity index 82%
rename from vendor/gopkg.in/yaml.v2/emitterc.go
rename to vendor/gopkg.in/yaml.v3/emitterc.go
index a1c2cc52..ab2a0661 100644
--- a/vendor/gopkg.in/yaml.v2/emitterc.go
+++ b/vendor/gopkg.in/yaml.v3/emitterc.go
@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+// Copyright (c) 2006-2010 Kirill Simonov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
package yaml
import (
@@ -43,8 +65,13 @@ func put_break(emitter *yaml_emitter_t) bool {
default:
panic("unknown line break setting")
}
+ if emitter.column == 0 {
+ emitter.space_above = true
+ }
emitter.column = 0
emitter.line++
+ // [Go] Do this here and below and drop from everywhere else (see commented lines).
+ emitter.indention = true
return true
}
@@ -97,8 +124,13 @@ func write_break(emitter *yaml_emitter_t, s []byte, i *int) bool {
if !write(emitter, s, i) {
return false
}
+ if emitter.column == 0 {
+ emitter.space_above = true
+ }
emitter.column = 0
emitter.line++
+ // [Go] Do this here and above and drop from everywhere else (see commented lines).
+ emitter.indention = true
}
return true
}
@@ -204,6 +236,10 @@ func yaml_emitter_increase_indent(emitter *yaml_emitter_t, flow, indentless bool
}
} else if !indentless {
emitter.indent += emitter.best_indent
+ // [Go] If inside a block sequence item, discount the space taken by the indicator.
+ if emitter.best_indent > 2 && emitter.states[len(emitter.states)-1] == yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE {
+ emitter.indent -= 2
+ }
}
return true
}
@@ -228,16 +264,22 @@ func yaml_emitter_state_machine(emitter *yaml_emitter_t, event *yaml_event_t) bo
return yaml_emitter_emit_document_end(emitter, event)
case yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE:
- return yaml_emitter_emit_flow_sequence_item(emitter, event, true)
+ return yaml_emitter_emit_flow_sequence_item(emitter, event, true, false)
+
+ case yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE:
+ return yaml_emitter_emit_flow_sequence_item(emitter, event, false, true)
case yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE:
- return yaml_emitter_emit_flow_sequence_item(emitter, event, false)
+ return yaml_emitter_emit_flow_sequence_item(emitter, event, false, false)
case yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE:
- return yaml_emitter_emit_flow_mapping_key(emitter, event, true)
+ return yaml_emitter_emit_flow_mapping_key(emitter, event, true, false)
+
+ case yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE:
+ return yaml_emitter_emit_flow_mapping_key(emitter, event, false, true)
case yaml_EMIT_FLOW_MAPPING_KEY_STATE:
- return yaml_emitter_emit_flow_mapping_key(emitter, event, false)
+ return yaml_emitter_emit_flow_mapping_key(emitter, event, false, false)
case yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE:
return yaml_emitter_emit_flow_mapping_value(emitter, event, true)
@@ -298,6 +340,8 @@ func yaml_emitter_emit_stream_start(emitter *yaml_emitter_t, event *yaml_event_t
emitter.column = 0
emitter.whitespace = true
emitter.indention = true
+ emitter.space_above = true
+ emitter.foot_indent = -1
if emitter.encoding != yaml_UTF8_ENCODING {
if !yaml_emitter_write_bom(emitter) {
@@ -392,13 +436,22 @@ func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event
if !yaml_emitter_write_indicator(emitter, []byte("---"), true, false, false) {
return false
}
- if emitter.canonical {
+ if emitter.canonical || true {
if !yaml_emitter_write_indent(emitter) {
return false
}
}
}
+ if len(emitter.head_comment) > 0 {
+ if !yaml_emitter_process_head_comment(emitter) {
+ return false
+ }
+ if !put_break(emitter) {
+ return false
+ }
+ }
+
emitter.state = yaml_EMIT_DOCUMENT_CONTENT_STATE
return true
}
@@ -425,7 +478,20 @@ func yaml_emitter_emit_document_start(emitter *yaml_emitter_t, event *yaml_event
// Expect the root node.
func yaml_emitter_emit_document_content(emitter *yaml_emitter_t, event *yaml_event_t) bool {
emitter.states = append(emitter.states, yaml_EMIT_DOCUMENT_END_STATE)
- return yaml_emitter_emit_node(emitter, event, true, false, false, false)
+
+ if !yaml_emitter_process_head_comment(emitter) {
+ return false
+ }
+ if !yaml_emitter_emit_node(emitter, event, true, false, false, false) {
+ return false
+ }
+ if !yaml_emitter_process_line_comment(emitter) {
+ return false
+ }
+ if !yaml_emitter_process_foot_comment(emitter) {
+ return false
+ }
+ return true
}
// Expect DOCUMENT-END.
@@ -433,6 +499,12 @@ func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t
if event.typ != yaml_DOCUMENT_END_EVENT {
return yaml_emitter_set_emitter_error(emitter, "expected DOCUMENT-END")
}
+ // [Go] Force document foot separation.
+ emitter.foot_indent = 0
+ if !yaml_emitter_process_foot_comment(emitter) {
+ return false
+ }
+ emitter.foot_indent = -1
if !yaml_emitter_write_indent(emitter) {
return false
}
@@ -454,7 +526,7 @@ func yaml_emitter_emit_document_end(emitter *yaml_emitter_t, event *yaml_event_t
}
// Expect a flow item node.
-func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool {
+func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first, trail bool) bool {
if first {
if !yaml_emitter_write_indicator(emitter, []byte{'['}, true, true, false) {
return false
@@ -466,13 +538,15 @@ func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_e
}
if event.typ == yaml_SEQUENCE_END_EVENT {
- emitter.flow_level--
- emitter.indent = emitter.indents[len(emitter.indents)-1]
- emitter.indents = emitter.indents[:len(emitter.indents)-1]
- if emitter.canonical && !first {
+ if emitter.canonical && !first && !trail {
if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
return false
}
+ }
+ emitter.flow_level--
+ emitter.indent = emitter.indents[len(emitter.indents)-1]
+ emitter.indents = emitter.indents[:len(emitter.indents)-1]
+ if emitter.column == 0 || emitter.canonical && !first {
if !yaml_emitter_write_indent(emitter) {
return false
}
@@ -480,29 +554,62 @@ func yaml_emitter_emit_flow_sequence_item(emitter *yaml_emitter_t, event *yaml_e
if !yaml_emitter_write_indicator(emitter, []byte{']'}, false, false, false) {
return false
}
+ if !yaml_emitter_process_line_comment(emitter) {
+ return false
+ }
+ if !yaml_emitter_process_foot_comment(emitter) {
+ return false
+ }
emitter.state = emitter.states[len(emitter.states)-1]
emitter.states = emitter.states[:len(emitter.states)-1]
return true
}
- if !first {
+ if !first && !trail {
if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
return false
}
}
+ if !yaml_emitter_process_head_comment(emitter) {
+ return false
+ }
+ if emitter.column == 0 {
+ if !yaml_emitter_write_indent(emitter) {
+ return false
+ }
+ }
+
if emitter.canonical || emitter.column > emitter.best_width {
if !yaml_emitter_write_indent(emitter) {
return false
}
}
- emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE)
- return yaml_emitter_emit_node(emitter, event, false, true, false, false)
+ if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 {
+ emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE)
+ } else {
+ emitter.states = append(emitter.states, yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE)
+ }
+ if !yaml_emitter_emit_node(emitter, event, false, true, false, false) {
+ return false
+ }
+ if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 {
+ if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
+ return false
+ }
+ }
+ if !yaml_emitter_process_line_comment(emitter) {
+ return false
+ }
+ if !yaml_emitter_process_foot_comment(emitter) {
+ return false
+ }
+ return true
}
// Expect a flow key node.
-func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool {
+func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_event_t, first, trail bool) bool {
if first {
if !yaml_emitter_write_indicator(emitter, []byte{'{'}, true, true, false) {
return false
@@ -514,13 +621,18 @@ func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_eve
}
if event.typ == yaml_MAPPING_END_EVENT {
+ if (emitter.canonical || len(emitter.head_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0) && !first && !trail {
+ if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
+ return false
+ }
+ }
+ if !yaml_emitter_process_head_comment(emitter) {
+ return false
+ }
emitter.flow_level--
emitter.indent = emitter.indents[len(emitter.indents)-1]
emitter.indents = emitter.indents[:len(emitter.indents)-1]
if emitter.canonical && !first {
- if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
- return false
- }
if !yaml_emitter_write_indent(emitter) {
return false
}
@@ -528,16 +640,33 @@ func yaml_emitter_emit_flow_mapping_key(emitter *yaml_emitter_t, event *yaml_eve
if !yaml_emitter_write_indicator(emitter, []byte{'}'}, false, false, false) {
return false
}
+ if !yaml_emitter_process_line_comment(emitter) {
+ return false
+ }
+ if !yaml_emitter_process_foot_comment(emitter) {
+ return false
+ }
emitter.state = emitter.states[len(emitter.states)-1]
emitter.states = emitter.states[:len(emitter.states)-1]
return true
}
- if !first {
+ if !first && !trail {
if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
return false
}
}
+
+ if !yaml_emitter_process_head_comment(emitter) {
+ return false
+ }
+
+ if emitter.column == 0 {
+ if !yaml_emitter_write_indent(emitter) {
+ return false
+ }
+ }
+
if emitter.canonical || emitter.column > emitter.best_width {
if !yaml_emitter_write_indent(emitter) {
return false
@@ -571,16 +700,41 @@ func yaml_emitter_emit_flow_mapping_value(emitter *yaml_emitter_t, event *yaml_e
return false
}
}
- emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE)
- return yaml_emitter_emit_node(emitter, event, false, false, true, false)
+ if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 {
+ emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE)
+ } else {
+ emitter.states = append(emitter.states, yaml_EMIT_FLOW_MAPPING_KEY_STATE)
+ }
+ if !yaml_emitter_emit_node(emitter, event, false, false, true, false) {
+ return false
+ }
+ if len(emitter.line_comment)+len(emitter.foot_comment)+len(emitter.tail_comment) > 0 {
+ if !yaml_emitter_write_indicator(emitter, []byte{','}, false, false, false) {
+ return false
+ }
+ }
+ if !yaml_emitter_process_line_comment(emitter) {
+ return false
+ }
+ if !yaml_emitter_process_foot_comment(emitter) {
+ return false
+ }
+ return true
}
// Expect a block item node.
func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_event_t, first bool) bool {
if first {
- if !yaml_emitter_increase_indent(emitter, false, emitter.mapping_context && !emitter.indention) {
+ // [Go] The original logic here would not indent the sequence when inside a mapping.
+ // In Go we always indent it, but take the sequence indicator out of the indentation.
+ indentless := emitter.best_indent == 2 && emitter.mapping_context && (emitter.column == 0 || !emitter.indention)
+ original := emitter.indent
+ if !yaml_emitter_increase_indent(emitter, false, indentless) {
return false
}
+ if emitter.indent > original+2 {
+ emitter.indent -= 2
+ }
}
if event.typ == yaml_SEQUENCE_END_EVENT {
emitter.indent = emitter.indents[len(emitter.indents)-1]
@@ -589,6 +743,9 @@ func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_
emitter.states = emitter.states[:len(emitter.states)-1]
return true
}
+ if !yaml_emitter_process_head_comment(emitter) {
+ return false
+ }
if !yaml_emitter_write_indent(emitter) {
return false
}
@@ -596,7 +753,16 @@ func yaml_emitter_emit_block_sequence_item(emitter *yaml_emitter_t, event *yaml_
return false
}
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE)
- return yaml_emitter_emit_node(emitter, event, false, true, false, false)
+ if !yaml_emitter_emit_node(emitter, event, false, true, false, false) {
+ return false
+ }
+ if !yaml_emitter_process_line_comment(emitter) {
+ return false
+ }
+ if !yaml_emitter_process_foot_comment(emitter) {
+ return false
+ }
+ return true
}
// Expect a block key node.
@@ -606,6 +772,9 @@ func yaml_emitter_emit_block_mapping_key(emitter *yaml_emitter_t, event *yaml_ev
return false
}
}
+ if !yaml_emitter_process_head_comment(emitter) {
+ return false
+ }
if event.typ == yaml_MAPPING_END_EVENT {
emitter.indent = emitter.indents[len(emitter.indents)-1]
emitter.indents = emitter.indents[:len(emitter.indents)-1]
@@ -642,7 +811,16 @@ func yaml_emitter_emit_block_mapping_value(emitter *yaml_emitter_t, event *yaml_
}
}
emitter.states = append(emitter.states, yaml_EMIT_BLOCK_MAPPING_KEY_STATE)
- return yaml_emitter_emit_node(emitter, event, false, false, true, false)
+ if !yaml_emitter_emit_node(emitter, event, false, false, true, false) {
+ return false
+ }
+ if !yaml_emitter_process_line_comment(emitter) {
+ return false
+ }
+ if !yaml_emitter_process_foot_comment(emitter) {
+ return false
+ }
+ return true
}
// Expect a node.
@@ -908,6 +1086,71 @@ func yaml_emitter_process_scalar(emitter *yaml_emitter_t) bool {
panic("unknown scalar style")
}
+// Write a head comment.
+func yaml_emitter_process_head_comment(emitter *yaml_emitter_t) bool {
+ if len(emitter.tail_comment) > 0 {
+ if !yaml_emitter_write_indent(emitter) {
+ return false
+ }
+ if !yaml_emitter_write_comment(emitter, emitter.tail_comment) {
+ return false
+ }
+ emitter.tail_comment = emitter.tail_comment[:0]
+ emitter.foot_indent = emitter.indent
+ if emitter.foot_indent < 0 {
+ emitter.foot_indent = 0
+ }
+ }
+
+ if len(emitter.head_comment) == 0 {
+ return true
+ }
+ if !yaml_emitter_write_indent(emitter) {
+ return false
+ }
+ if !yaml_emitter_write_comment(emitter, emitter.head_comment) {
+ return false
+ }
+ emitter.head_comment = emitter.head_comment[:0]
+ return true
+}
+
+// Write an line comment.
+func yaml_emitter_process_line_comment(emitter *yaml_emitter_t) bool {
+ if len(emitter.line_comment) == 0 {
+ return true
+ }
+ if !emitter.whitespace {
+ if !put(emitter, ' ') {
+ return false
+ }
+ }
+ if !yaml_emitter_write_comment(emitter, emitter.line_comment) {
+ return false
+ }
+ emitter.line_comment = emitter.line_comment[:0]
+ return true
+}
+
+// Write a foot comment.
+func yaml_emitter_process_foot_comment(emitter *yaml_emitter_t) bool {
+ if len(emitter.foot_comment) == 0 {
+ return true
+ }
+ if !yaml_emitter_write_indent(emitter) {
+ return false
+ }
+ if !yaml_emitter_write_comment(emitter, emitter.foot_comment) {
+ return false
+ }
+ emitter.foot_comment = emitter.foot_comment[:0]
+ emitter.foot_indent = emitter.indent
+ if emitter.foot_indent < 0 {
+ emitter.foot_indent = 0
+ }
+ return true
+}
+
// Check if a %YAML directive is valid.
func yaml_emitter_analyze_version_directive(emitter *yaml_emitter_t, version_directive *yaml_version_directive_t) bool {
if version_directive.major != 1 || version_directive.minor != 1 {
@@ -987,6 +1230,7 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
flow_indicators = false
line_breaks = false
special_characters = false
+ tab_characters = false
leading_space = false
leading_break = false
@@ -1055,7 +1299,9 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
}
}
- if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode {
+ if value[i] == '\t' {
+ tab_characters = true
+ } else if !is_printable(value, i) || !is_ascii(value, i) && !emitter.unicode {
special_characters = true
}
if is_space(value, i) {
@@ -1110,10 +1356,12 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
emitter.scalar_data.block_plain_allowed = false
emitter.scalar_data.single_quoted_allowed = false
}
- if space_break || special_characters {
+ if space_break || tab_characters || special_characters {
emitter.scalar_data.flow_plain_allowed = false
emitter.scalar_data.block_plain_allowed = false
emitter.scalar_data.single_quoted_allowed = false
+ }
+ if space_break || special_characters {
emitter.scalar_data.block_allowed = false
}
if line_breaks {
@@ -1137,6 +1385,19 @@ func yaml_emitter_analyze_event(emitter *yaml_emitter_t, event *yaml_event_t) bo
emitter.tag_data.suffix = nil
emitter.scalar_data.value = nil
+ if len(event.head_comment) > 0 {
+ emitter.head_comment = event.head_comment
+ }
+ if len(event.line_comment) > 0 {
+ emitter.line_comment = event.line_comment
+ }
+ if len(event.foot_comment) > 0 {
+ emitter.foot_comment = event.foot_comment
+ }
+ if len(event.tail_comment) > 0 {
+ emitter.tail_comment = event.tail_comment
+ }
+
switch event.typ {
case yaml_ALIAS_EVENT:
if !yaml_emitter_analyze_anchor(emitter, event.anchor, true) {
@@ -1208,13 +1469,20 @@ func yaml_emitter_write_indent(emitter *yaml_emitter_t) bool {
return false
}
}
+ if emitter.foot_indent == indent {
+ if !put_break(emitter) {
+ return false
+ }
+ }
for emitter.column < indent {
if !put(emitter, ' ') {
return false
}
}
emitter.whitespace = true
- emitter.indention = true
+ //emitter.indention = true
+ emitter.space_above = false
+ emitter.foot_indent = -1
return true
}
@@ -1311,7 +1579,7 @@ func yaml_emitter_write_tag_content(emitter *yaml_emitter_t, value []byte, need_
}
func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allow_breaks bool) bool {
- if !emitter.whitespace {
+ if len(value) > 0 && !emitter.whitespace {
if !put(emitter, ' ') {
return false
}
@@ -1341,7 +1609,7 @@ func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allo
if !write_break(emitter, value, &i) {
return false
}
- emitter.indention = true
+ //emitter.indention = true
breaks = true
} else {
if breaks {
@@ -1358,7 +1626,9 @@ func yaml_emitter_write_plain_scalar(emitter *yaml_emitter_t, value []byte, allo
}
}
- emitter.whitespace = false
+ if len(value) > 0 {
+ emitter.whitespace = false
+ }
emitter.indention = false
if emitter.root_context {
emitter.open_ended = true
@@ -1397,7 +1667,7 @@ func yaml_emitter_write_single_quoted_scalar(emitter *yaml_emitter_t, value []by
if !write_break(emitter, value, &i) {
return false
}
- emitter.indention = true
+ //emitter.indention = true
breaks = true
} else {
if breaks {
@@ -1599,7 +1869,7 @@ func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bo
if !put_break(emitter) {
return false
}
- emitter.indention = true
+ //emitter.indention = true
emitter.whitespace = true
breaks := true
for i := 0; i < len(value); {
@@ -1607,7 +1877,7 @@ func yaml_emitter_write_literal_scalar(emitter *yaml_emitter_t, value []byte) bo
if !write_break(emitter, value, &i) {
return false
}
- emitter.indention = true
+ //emitter.indention = true
breaks = true
} else {
if breaks {
@@ -1637,7 +1907,7 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo
if !put_break(emitter) {
return false
}
- emitter.indention = true
+ //emitter.indention = true
emitter.whitespace = true
breaks := true
@@ -1658,7 +1928,7 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo
if !write_break(emitter, value, &i) {
return false
}
- emitter.indention = true
+ //emitter.indention = true
breaks = true
} else {
if breaks {
@@ -1683,3 +1953,40 @@ func yaml_emitter_write_folded_scalar(emitter *yaml_emitter_t, value []byte) boo
}
return true
}
+
+func yaml_emitter_write_comment(emitter *yaml_emitter_t, comment []byte) bool {
+ breaks := false
+ pound := false
+ for i := 0; i < len(comment); {
+ if is_break(comment, i) {
+ if !write_break(emitter, comment, &i) {
+ return false
+ }
+ //emitter.indention = true
+ breaks = true
+ pound = false
+ } else {
+ if breaks && !yaml_emitter_write_indent(emitter) {
+ return false
+ }
+ if !pound {
+ if comment[i] != '#' && (!put(emitter, '#') || !put(emitter, ' ')) {
+ return false
+ }
+ pound = true
+ }
+ if !write(emitter, comment, &i) {
+ return false
+ }
+ emitter.indention = false
+ breaks = false
+ }
+ }
+ if !breaks && !put_break(emitter) {
+ return false
+ }
+
+ emitter.whitespace = true
+ //emitter.indention = true
+ return true
+}
diff --git a/vendor/gopkg.in/yaml.v3/encode.go b/vendor/gopkg.in/yaml.v3/encode.go
new file mode 100644
index 00000000..1f37271c
--- /dev/null
+++ b/vendor/gopkg.in/yaml.v3/encode.go
@@ -0,0 +1,561 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package yaml
+
+import (
+ "encoding"
+ "fmt"
+ "io"
+ "reflect"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+ "unicode/utf8"
+)
+
+type encoder struct {
+ emitter yaml_emitter_t
+ event yaml_event_t
+ out []byte
+ flow bool
+ indent int
+ doneInit bool
+}
+
+func newEncoder() *encoder {
+ e := &encoder{}
+ yaml_emitter_initialize(&e.emitter)
+ yaml_emitter_set_output_string(&e.emitter, &e.out)
+ yaml_emitter_set_unicode(&e.emitter, true)
+ return e
+}
+
+func newEncoderWithWriter(w io.Writer) *encoder {
+ e := &encoder{}
+ yaml_emitter_initialize(&e.emitter)
+ yaml_emitter_set_output_writer(&e.emitter, w)
+ yaml_emitter_set_unicode(&e.emitter, true)
+ return e
+}
+
+func (e *encoder) init() {
+ if e.doneInit {
+ return
+ }
+ if e.indent == 0 {
+ e.indent = 4
+ }
+ e.emitter.best_indent = e.indent
+ yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
+ e.emit()
+ e.doneInit = true
+}
+
+func (e *encoder) finish() {
+ e.emitter.open_ended = false
+ yaml_stream_end_event_initialize(&e.event)
+ e.emit()
+}
+
+func (e *encoder) destroy() {
+ yaml_emitter_delete(&e.emitter)
+}
+
+func (e *encoder) emit() {
+ // This will internally delete the e.event value.
+ e.must(yaml_emitter_emit(&e.emitter, &e.event))
+}
+
+func (e *encoder) must(ok bool) {
+ if !ok {
+ msg := e.emitter.problem
+ if msg == "" {
+ msg = "unknown problem generating YAML content"
+ }
+ failf("%s", msg)
+ }
+}
+
+func (e *encoder) marshalDoc(tag string, in reflect.Value) {
+ e.init()
+ var node *Node
+ if in.IsValid() {
+ node, _ = in.Interface().(*Node)
+ }
+ if node != nil && node.Kind == DocumentNode {
+ e.nodev(in)
+ } else {
+ yaml_document_start_event_initialize(&e.event, nil, nil, true)
+ e.emit()
+ e.marshal(tag, in)
+ yaml_document_end_event_initialize(&e.event, true)
+ e.emit()
+ }
+}
+
+func (e *encoder) marshal(tag string, in reflect.Value) {
+ tag = shortTag(tag)
+ if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
+ e.nilv()
+ return
+ }
+ iface := in.Interface()
+ switch value := iface.(type) {
+ case *Node:
+ e.nodev(in)
+ return
+ case time.Time:
+ e.timev(tag, in)
+ return
+ case *time.Time:
+ e.timev(tag, in.Elem())
+ return
+ case time.Duration:
+ e.stringv(tag, reflect.ValueOf(value.String()))
+ return
+ case Marshaler:
+ v, err := value.MarshalYAML()
+ if err != nil {
+ fail(err)
+ }
+ if v == nil {
+ e.nilv()
+ return
+ }
+ e.marshal(tag, reflect.ValueOf(v))
+ return
+ case encoding.TextMarshaler:
+ text, err := value.MarshalText()
+ if err != nil {
+ fail(err)
+ }
+ in = reflect.ValueOf(string(text))
+ case nil:
+ e.nilv()
+ return
+ }
+ switch in.Kind() {
+ case reflect.Interface:
+ e.marshal(tag, in.Elem())
+ case reflect.Map:
+ e.mapv(tag, in)
+ case reflect.Ptr:
+ e.marshal(tag, in.Elem())
+ case reflect.Struct:
+ e.structv(tag, in)
+ case reflect.Slice, reflect.Array:
+ e.slicev(tag, in)
+ case reflect.String:
+ e.stringv(tag, in)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ e.intv(tag, in)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ e.uintv(tag, in)
+ case reflect.Float32, reflect.Float64:
+ e.floatv(tag, in)
+ case reflect.Bool:
+ e.boolv(tag, in)
+ default:
+ panic("cannot marshal type: " + in.Type().String())
+ }
+}
+
+func (e *encoder) mapv(tag string, in reflect.Value) {
+ e.mappingv(tag, func() {
+ keys := keyList(in.MapKeys())
+ sort.Sort(keys)
+ for _, k := range keys {
+ e.marshal("", k)
+ e.marshal("", in.MapIndex(k))
+ }
+ })
+}
+
+func (e *encoder) fieldByIndex(v reflect.Value, index []int) (field reflect.Value) {
+ for _, num := range index {
+ for {
+ if v.Kind() == reflect.Ptr {
+ if v.IsNil() {
+ return reflect.Value{}
+ }
+ v = v.Elem()
+ continue
+ }
+ break
+ }
+ v = v.Field(num)
+ }
+ return v
+}
+
+func (e *encoder) structv(tag string, in reflect.Value) {
+ sinfo, err := getStructInfo(in.Type())
+ if err != nil {
+ panic(err)
+ }
+ e.mappingv(tag, func() {
+ for _, info := range sinfo.FieldsList {
+ var value reflect.Value
+ if info.Inline == nil {
+ value = in.Field(info.Num)
+ } else {
+ value = e.fieldByIndex(in, info.Inline)
+ if !value.IsValid() {
+ continue
+ }
+ }
+ if info.OmitEmpty && isZero(value) {
+ continue
+ }
+ e.marshal("", reflect.ValueOf(info.Key))
+ e.flow = info.Flow
+ e.marshal("", value)
+ }
+ if sinfo.InlineMap >= 0 {
+ m := in.Field(sinfo.InlineMap)
+ if m.Len() > 0 {
+ e.flow = false
+ keys := keyList(m.MapKeys())
+ sort.Sort(keys)
+ for _, k := range keys {
+ if _, found := sinfo.FieldsMap[k.String()]; found {
+ panic(fmt.Sprintf("cannot have key %q in inlined map: conflicts with struct field", k.String()))
+ }
+ e.marshal("", k)
+ e.flow = false
+ e.marshal("", m.MapIndex(k))
+ }
+ }
+ }
+ })
+}
+
+func (e *encoder) mappingv(tag string, f func()) {
+ implicit := tag == ""
+ style := yaml_BLOCK_MAPPING_STYLE
+ if e.flow {
+ e.flow = false
+ style = yaml_FLOW_MAPPING_STYLE
+ }
+ yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
+ e.emit()
+ f()
+ yaml_mapping_end_event_initialize(&e.event)
+ e.emit()
+}
+
+func (e *encoder) slicev(tag string, in reflect.Value) {
+ implicit := tag == ""
+ style := yaml_BLOCK_SEQUENCE_STYLE
+ if e.flow {
+ e.flow = false
+ style = yaml_FLOW_SEQUENCE_STYLE
+ }
+ e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
+ e.emit()
+ n := in.Len()
+ for i := 0; i < n; i++ {
+ e.marshal("", in.Index(i))
+ }
+ e.must(yaml_sequence_end_event_initialize(&e.event))
+ e.emit()
+}
+
+// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
+//
+// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
+// in YAML 1.2 and by this package, but these should be marshalled quoted for
+// the time being for compatibility with other parsers.
+func isBase60Float(s string) (result bool) {
+ // Fast path.
+ if s == "" {
+ return false
+ }
+ c := s[0]
+ if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
+ return false
+ }
+ // Do the full match.
+ return base60float.MatchString(s)
+}
+
+// From http://yaml.org/type/float.html, except the regular expression there
+// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
+var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
+
+// isOldBool returns whether s is bool notation as defined in YAML 1.1.
+//
+// We continue to force strings that YAML 1.1 would interpret as booleans to be
+// rendered as quotes strings so that the marshalled output valid for YAML 1.1
+// parsing.
+func isOldBool(s string) (result bool) {
+ switch s {
+ case "y", "Y", "yes", "Yes", "YES", "on", "On", "ON",
+ "n", "N", "no", "No", "NO", "off", "Off", "OFF":
+ return true
+ default:
+ return false
+ }
+}
+
+func (e *encoder) stringv(tag string, in reflect.Value) {
+ var style yaml_scalar_style_t
+ s := in.String()
+ canUsePlain := true
+ switch {
+ case !utf8.ValidString(s):
+ if tag == binaryTag {
+ failf("explicitly tagged !!binary data must be base64-encoded")
+ }
+ if tag != "" {
+ failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
+ }
+ // It can't be encoded directly as YAML so use a binary tag
+ // and encode it as base64.
+ tag = binaryTag
+ s = encodeBase64(s)
+ case tag == "":
+ // Check to see if it would resolve to a specific
+ // tag when encoded unquoted. If it doesn't,
+ // there's no need to quote it.
+ rtag, _ := resolve("", s)
+ canUsePlain = rtag == strTag && !(isBase60Float(s) || isOldBool(s))
+ }
+ // Note: it's possible for user code to emit invalid YAML
+ // if they explicitly specify a tag and a string containing
+ // text that's incompatible with that tag.
+ switch {
+ case strings.Contains(s, "\n"):
+ if e.flow {
+ style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
+ } else {
+ style = yaml_LITERAL_SCALAR_STYLE
+ }
+ case canUsePlain:
+ style = yaml_PLAIN_SCALAR_STYLE
+ default:
+ style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
+ }
+ e.emitScalar(s, "", tag, style, nil, nil, nil, nil)
+}
+
+func (e *encoder) boolv(tag string, in reflect.Value) {
+ var s string
+ if in.Bool() {
+ s = "true"
+ } else {
+ s = "false"
+ }
+ e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
+}
+
+func (e *encoder) intv(tag string, in reflect.Value) {
+ s := strconv.FormatInt(in.Int(), 10)
+ e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
+}
+
+func (e *encoder) uintv(tag string, in reflect.Value) {
+ s := strconv.FormatUint(in.Uint(), 10)
+ e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
+}
+
+func (e *encoder) timev(tag string, in reflect.Value) {
+ t := in.Interface().(time.Time)
+ s := t.Format(time.RFC3339Nano)
+ e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
+}
+
+func (e *encoder) floatv(tag string, in reflect.Value) {
+ // Issue #352: When formatting, use the precision of the underlying value
+ precision := 64
+ if in.Kind() == reflect.Float32 {
+ precision = 32
+ }
+
+ s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
+ switch s {
+ case "+Inf":
+ s = ".inf"
+ case "-Inf":
+ s = "-.inf"
+ case "NaN":
+ s = ".nan"
+ }
+ e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
+}
+
+func (e *encoder) nilv() {
+ e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE, nil, nil, nil, nil)
+}
+
+func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t, head, line, foot, tail []byte) {
+ // TODO Kill this function. Replace all initialize calls by their underlining Go literals.
+ implicit := tag == ""
+ if !implicit {
+ tag = longTag(tag)
+ }
+ e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
+ e.event.head_comment = head
+ e.event.line_comment = line
+ e.event.foot_comment = foot
+ e.event.tail_comment = tail
+ e.emit()
+}
+
+func (e *encoder) nodev(in reflect.Value) {
+ e.node(in.Interface().(*Node), "")
+}
+
+func (e *encoder) node(node *Node, tail string) {
+ // If the tag was not explicitly requested, and dropping it won't change the
+ // implicit tag of the value, don't include it in the presentation.
+ var tag = node.Tag
+ var stag = shortTag(tag)
+ var rtag string
+ var forceQuoting bool
+ if tag != "" && node.Style&TaggedStyle == 0 {
+ if node.Kind == ScalarNode {
+ if stag == strTag && node.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0 {
+ tag = ""
+ } else {
+ rtag, _ = resolve("", node.Value)
+ if rtag == stag {
+ tag = ""
+ } else if stag == strTag {
+ tag = ""
+ forceQuoting = true
+ }
+ }
+ } else {
+ switch node.Kind {
+ case MappingNode:
+ rtag = mapTag
+ case SequenceNode:
+ rtag = seqTag
+ }
+ if rtag == stag {
+ tag = ""
+ }
+ }
+ }
+
+ switch node.Kind {
+ case DocumentNode:
+ yaml_document_start_event_initialize(&e.event, nil, nil, true)
+ e.event.head_comment = []byte(node.HeadComment)
+ e.emit()
+ for _, node := range node.Content {
+ e.node(node, "")
+ }
+ yaml_document_end_event_initialize(&e.event, true)
+ e.event.foot_comment = []byte(node.FootComment)
+ e.emit()
+
+ case SequenceNode:
+ style := yaml_BLOCK_SEQUENCE_STYLE
+ if node.Style&FlowStyle != 0 {
+ style = yaml_FLOW_SEQUENCE_STYLE
+ }
+ e.must(yaml_sequence_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style))
+ e.event.head_comment = []byte(node.HeadComment)
+ e.emit()
+ for _, node := range node.Content {
+ e.node(node, "")
+ }
+ e.must(yaml_sequence_end_event_initialize(&e.event))
+ e.event.line_comment = []byte(node.LineComment)
+ e.event.foot_comment = []byte(node.FootComment)
+ e.emit()
+
+ case MappingNode:
+ style := yaml_BLOCK_MAPPING_STYLE
+ if node.Style&FlowStyle != 0 {
+ style = yaml_FLOW_MAPPING_STYLE
+ }
+ yaml_mapping_start_event_initialize(&e.event, []byte(node.Anchor), []byte(tag), tag == "", style)
+ e.event.tail_comment = []byte(tail)
+ e.event.head_comment = []byte(node.HeadComment)
+ e.emit()
+
+ // The tail logic below moves the foot comment of prior keys to the following key,
+ // since the value for each key may be a nested structure and the foot needs to be
+ // processed only the entirety of the value is streamed. The last tail is processed
+ // with the mapping end event.
+ var tail string
+ for i := 0; i+1 < len(node.Content); i += 2 {
+ k := node.Content[i]
+ foot := k.FootComment
+ if foot != "" {
+ kopy := *k
+ kopy.FootComment = ""
+ k = &kopy
+ }
+ e.node(k, tail)
+ tail = foot
+
+ v := node.Content[i+1]
+ e.node(v, "")
+ }
+
+ yaml_mapping_end_event_initialize(&e.event)
+ e.event.tail_comment = []byte(tail)
+ e.event.line_comment = []byte(node.LineComment)
+ e.event.foot_comment = []byte(node.FootComment)
+ e.emit()
+
+ case AliasNode:
+ yaml_alias_event_initialize(&e.event, []byte(node.Value))
+ e.event.head_comment = []byte(node.HeadComment)
+ e.event.line_comment = []byte(node.LineComment)
+ e.event.foot_comment = []byte(node.FootComment)
+ e.emit()
+
+ case ScalarNode:
+ value := node.Value
+ if !utf8.ValidString(value) {
+ if tag == binaryTag {
+ failf("explicitly tagged !!binary data must be base64-encoded")
+ }
+ if tag != "" {
+ failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
+ }
+ // It can't be encoded directly as YAML so use a binary tag
+ // and encode it as base64.
+ tag = binaryTag
+ value = encodeBase64(value)
+ }
+
+ style := yaml_PLAIN_SCALAR_STYLE
+ switch {
+ case node.Style&DoubleQuotedStyle != 0:
+ style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
+ case node.Style&SingleQuotedStyle != 0:
+ style = yaml_SINGLE_QUOTED_SCALAR_STYLE
+ case node.Style&LiteralStyle != 0:
+ style = yaml_LITERAL_SCALAR_STYLE
+ case node.Style&FoldedStyle != 0:
+ style = yaml_FOLDED_SCALAR_STYLE
+ case strings.Contains(value, "\n"):
+ style = yaml_LITERAL_SCALAR_STYLE
+ case forceQuoting:
+ style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
+ }
+
+ e.emitScalar(value, node.Anchor, tag, style, []byte(node.HeadComment), []byte(node.LineComment), []byte(node.FootComment), []byte(tail))
+ }
+}
diff --git a/vendor/gopkg.in/yaml.v2/parserc.go b/vendor/gopkg.in/yaml.v3/parserc.go
similarity index 86%
rename from vendor/gopkg.in/yaml.v2/parserc.go
rename to vendor/gopkg.in/yaml.v3/parserc.go
index 81d05dfe..aea9050b 100644
--- a/vendor/gopkg.in/yaml.v2/parserc.go
+++ b/vendor/gopkg.in/yaml.v3/parserc.go
@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+// Copyright (c) 2006-2010 Kirill Simonov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
package yaml
import (
@@ -45,11 +67,46 @@ import (
// Peek the next token in the token queue.
func peek_token(parser *yaml_parser_t) *yaml_token_t {
if parser.token_available || yaml_parser_fetch_more_tokens(parser) {
- return &parser.tokens[parser.tokens_head]
+ token := &parser.tokens[parser.tokens_head]
+ yaml_parser_unfold_comments(parser, token)
+ return token
}
return nil
}
+// yaml_parser_unfold_comments walks through the comments queue and joins all
+// comments behind the position of the provided token into the respective
+// top-level comment slices in the parser.
+func yaml_parser_unfold_comments(parser *yaml_parser_t, token *yaml_token_t) {
+ for parser.comments_head < len(parser.comments) && token.start_mark.index >= parser.comments[parser.comments_head].token_mark.index {
+ comment := &parser.comments[parser.comments_head]
+ if len(comment.head) > 0 {
+ if token.typ == yaml_BLOCK_END_TOKEN {
+ // No heads on ends, so keep comment.head for a follow up token.
+ break
+ }
+ if len(parser.head_comment) > 0 {
+ parser.head_comment = append(parser.head_comment, '\n')
+ }
+ parser.head_comment = append(parser.head_comment, comment.head...)
+ }
+ if len(comment.foot) > 0 {
+ if len(parser.foot_comment) > 0 {
+ parser.foot_comment = append(parser.foot_comment, '\n')
+ }
+ parser.foot_comment = append(parser.foot_comment, comment.foot...)
+ }
+ if len(comment.line) > 0 {
+ if len(parser.line_comment) > 0 {
+ parser.line_comment = append(parser.line_comment, '\n')
+ }
+ parser.line_comment = append(parser.line_comment, comment.line...)
+ }
+ *comment = yaml_comment_t{}
+ parser.comments_head++
+ }
+}
+
// Remove the next token from the queue (must be called after peek_token).
func skip_token(parser *yaml_parser_t) {
parser.token_available = false
@@ -224,10 +281,32 @@ func yaml_parser_parse_document_start(parser *yaml_parser_t, event *yaml_event_t
parser.states = append(parser.states, yaml_PARSE_DOCUMENT_END_STATE)
parser.state = yaml_PARSE_BLOCK_NODE_STATE
+ var head_comment []byte
+ if len(parser.head_comment) > 0 {
+ // [Go] Scan the header comment backwards, and if an empty line is found, break
+ // the header so the part before the last empty line goes into the
+ // document header, while the bottom of it goes into a follow up event.
+ for i := len(parser.head_comment) - 1; i > 0; i-- {
+ if parser.head_comment[i] == '\n' {
+ if i == len(parser.head_comment)-1 {
+ head_comment = parser.head_comment[:i]
+ parser.head_comment = parser.head_comment[i+1:]
+ break
+ } else if parser.head_comment[i-1] == '\n' {
+ head_comment = parser.head_comment[:i-1]
+ parser.head_comment = parser.head_comment[i+1:]
+ break
+ }
+ }
+ }
+ }
+
*event = yaml_event_t{
typ: yaml_DOCUMENT_START_EVENT,
start_mark: token.start_mark,
end_mark: token.end_mark,
+
+ head_comment: head_comment,
}
} else if token.typ != yaml_STREAM_END_TOKEN {
@@ -284,6 +363,7 @@ func yaml_parser_parse_document_content(parser *yaml_parser_t, event *yaml_event
if token == nil {
return false
}
+
if token.typ == yaml_VERSION_DIRECTIVE_TOKEN ||
token.typ == yaml_TAG_DIRECTIVE_TOKEN ||
token.typ == yaml_DOCUMENT_START_TOKEN ||
@@ -327,9 +407,25 @@ func yaml_parser_parse_document_end(parser *yaml_parser_t, event *yaml_event_t)
end_mark: end_mark,
implicit: implicit,
}
+ yaml_parser_set_event_comments(parser, event)
+ if len(event.head_comment) > 0 && len(event.foot_comment) == 0 {
+ event.foot_comment = event.head_comment
+ event.head_comment = nil
+ }
return true
}
+func yaml_parser_set_event_comments(parser *yaml_parser_t, event *yaml_event_t) {
+ event.head_comment = parser.head_comment
+ event.line_comment = parser.line_comment
+ event.foot_comment = parser.foot_comment
+ parser.head_comment = nil
+ parser.line_comment = nil
+ parser.foot_comment = nil
+ parser.tail_comment = nil
+ parser.stem_comment = nil
+}
+
// Parse the productions:
// block_node_or_indentless_sequence ::=
// ALIAS
@@ -373,6 +469,7 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
end_mark: token.end_mark,
anchor: token.value,
}
+ yaml_parser_set_event_comments(parser, event)
skip_token(parser)
return true
}
@@ -486,6 +583,7 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
quoted_implicit: quoted_implicit,
style: yaml_style_t(token.style),
}
+ yaml_parser_set_event_comments(parser, event)
skip_token(parser)
return true
}
@@ -502,6 +600,7 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
implicit: implicit,
style: yaml_style_t(yaml_FLOW_SEQUENCE_STYLE),
}
+ yaml_parser_set_event_comments(parser, event)
return true
}
if token.typ == yaml_FLOW_MAPPING_START_TOKEN {
@@ -516,6 +615,7 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
implicit: implicit,
style: yaml_style_t(yaml_FLOW_MAPPING_STYLE),
}
+ yaml_parser_set_event_comments(parser, event)
return true
}
if block && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN {
@@ -530,6 +630,10 @@ func yaml_parser_parse_node(parser *yaml_parser_t, event *yaml_event_t, block, i
implicit: implicit,
style: yaml_style_t(yaml_BLOCK_SEQUENCE_STYLE),
}
+ if parser.stem_comment != nil {
+ event.head_comment = parser.stem_comment
+ parser.stem_comment = nil
+ }
return true
}
if block && token.typ == yaml_BLOCK_MAPPING_START_TOKEN {
@@ -590,11 +694,25 @@ func yaml_parser_parse_block_sequence_entry(parser *yaml_parser_t, event *yaml_e
if token.typ == yaml_BLOCK_ENTRY_TOKEN {
mark := token.end_mark
+ prior_head := len(parser.head_comment)
skip_token(parser)
token = peek_token(parser)
if token == nil {
return false
}
+ if prior_head > 0 && token.typ == yaml_BLOCK_SEQUENCE_START_TOKEN {
+ // [Go] It's a sequence under a sequence entry, so the former head comment
+ // is for the list itself, not the first list item under it.
+ parser.stem_comment = parser.head_comment[:prior_head]
+ if len(parser.head_comment) == prior_head {
+ parser.head_comment = nil
+ } else {
+ // Copy suffix to prevent very strange bugs if someone ever appends
+ // further bytes to the prefix in the stem_comment slice above.
+ parser.head_comment = append([]byte(nil), parser.head_comment[prior_head+1:]...)
+ }
+
+ }
if token.typ != yaml_BLOCK_ENTRY_TOKEN && token.typ != yaml_BLOCK_END_TOKEN {
parser.states = append(parser.states, yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE)
return yaml_parser_parse_node(parser, event, true, false)
@@ -684,6 +802,19 @@ func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_even
return false
}
+ // [Go] A tail comment was left from the prior mapping value processed. Emit an event
+ // as it needs to be processed with that value and not the following key.
+ if len(parser.tail_comment) > 0 {
+ *event = yaml_event_t{
+ typ: yaml_TAIL_COMMENT_EVENT,
+ start_mark: token.start_mark,
+ end_mark: token.end_mark,
+ foot_comment: parser.tail_comment,
+ }
+ parser.tail_comment = nil
+ return true
+ }
+
if token.typ == yaml_KEY_TOKEN {
mark := token.end_mark
skip_token(parser)
@@ -709,6 +840,7 @@ func yaml_parser_parse_block_mapping_key(parser *yaml_parser_t, event *yaml_even
start_mark: token.start_mark,
end_mark: token.end_mark,
}
+ yaml_parser_set_event_comments(parser, event)
skip_token(parser)
return true
}
@@ -820,6 +952,7 @@ func yaml_parser_parse_flow_sequence_entry(parser *yaml_parser_t, event *yaml_ev
start_mark: token.start_mark,
end_mark: token.end_mark,
}
+ yaml_parser_set_event_comments(parser, event)
skip_token(parser)
return true
@@ -959,6 +1092,7 @@ func yaml_parser_parse_flow_mapping_key(parser *yaml_parser_t, event *yaml_event
start_mark: token.start_mark,
end_mark: token.end_mark,
}
+ yaml_parser_set_event_comments(parser, event)
skip_token(parser)
return true
}
diff --git a/vendor/gopkg.in/yaml.v2/readerc.go b/vendor/gopkg.in/yaml.v3/readerc.go
similarity index 91%
rename from vendor/gopkg.in/yaml.v2/readerc.go
rename to vendor/gopkg.in/yaml.v3/readerc.go
index 7c1f5fac..b7de0a89 100644
--- a/vendor/gopkg.in/yaml.v2/readerc.go
+++ b/vendor/gopkg.in/yaml.v3/readerc.go
@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+// Copyright (c) 2006-2010 Kirill Simonov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
package yaml
import (
@@ -95,7 +117,7 @@ func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
// [Go] This function was changed to guarantee the requested length size at EOF.
// The fact we need to do this is pretty awful, but the description above implies
- // for that to be the case, and there are tests
+ // for that to be the case, and there are tests
// If the EOF flag is set and the raw buffer is empty, do nothing.
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {
diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v3/resolve.go
similarity index 60%
rename from vendor/gopkg.in/yaml.v2/resolve.go
rename to vendor/gopkg.in/yaml.v3/resolve.go
index 4120e0c9..64ae8880 100644
--- a/vendor/gopkg.in/yaml.v2/resolve.go
+++ b/vendor/gopkg.in/yaml.v3/resolve.go
@@ -1,3 +1,18 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package yaml
import (
@@ -34,18 +49,14 @@ func init() {
tag string
l []string
}{
- {true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
- {true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
- {true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
- {false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
- {false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
- {false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
- {nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
- {math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
- {math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
- {math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
- {math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
- {"<<", yaml_MERGE_TAG, []string{"<<"}},
+ {true, boolTag, []string{"true", "True", "TRUE"}},
+ {false, boolTag, []string{"false", "False", "FALSE"}},
+ {nil, nullTag, []string{"", "~", "null", "Null", "NULL"}},
+ {math.NaN(), floatTag, []string{".nan", ".NaN", ".NAN"}},
+ {math.Inf(+1), floatTag, []string{".inf", ".Inf", ".INF"}},
+ {math.Inf(+1), floatTag, []string{"+.inf", "+.Inf", "+.INF"}},
+ {math.Inf(-1), floatTag, []string{"-.inf", "-.Inf", "-.INF"}},
+ {"<<", mergeTag, []string{"<<"}},
}
m := resolveMap
@@ -56,11 +67,37 @@ func init() {
}
}
+const (
+ nullTag = "!!null"
+ boolTag = "!!bool"
+ strTag = "!!str"
+ intTag = "!!int"
+ floatTag = "!!float"
+ timestampTag = "!!timestamp"
+ seqTag = "!!seq"
+ mapTag = "!!map"
+ binaryTag = "!!binary"
+ mergeTag = "!!merge"
+)
+
+var longTags = make(map[string]string)
+var shortTags = make(map[string]string)
+
+func init() {
+ for _, stag := range []string{nullTag, boolTag, strTag, intTag, floatTag, timestampTag, seqTag, mapTag, binaryTag, mergeTag} {
+ ltag := longTag(stag)
+ longTags[stag] = ltag
+ shortTags[ltag] = stag
+ }
+}
+
const longTagPrefix = "tag:yaml.org,2002:"
func shortTag(tag string) string {
- // TODO This can easily be made faster and produce less garbage.
if strings.HasPrefix(tag, longTagPrefix) {
+ if stag, ok := shortTags[tag]; ok {
+ return stag
+ }
return "!!" + tag[len(longTagPrefix):]
}
return tag
@@ -68,6 +105,9 @@ func shortTag(tag string) string {
func longTag(tag string) string {
if strings.HasPrefix(tag, "!!") {
+ if ltag, ok := longTags[tag]; ok {
+ return ltag
+ }
return longTagPrefix + tag[2:]
}
return tag
@@ -75,7 +115,7 @@ func longTag(tag string) string {
func resolvableTag(tag string) bool {
switch tag {
- case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG:
+ case "", strTag, boolTag, intTag, floatTag, nullTag, timestampTag:
return true
}
return false
@@ -84,23 +124,24 @@ func resolvableTag(tag string) bool {
var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`)
func resolve(tag string, in string) (rtag string, out interface{}) {
+ tag = shortTag(tag)
if !resolvableTag(tag) {
return tag, in
}
defer func() {
switch tag {
- case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
+ case "", rtag, strTag, binaryTag:
return
- case yaml_FLOAT_TAG:
- if rtag == yaml_INT_TAG {
+ case floatTag:
+ if rtag == intTag {
switch v := out.(type) {
case int64:
- rtag = yaml_FLOAT_TAG
+ rtag = floatTag
out = float64(v)
return
case int:
- rtag = yaml_FLOAT_TAG
+ rtag = floatTag
out = float64(v)
return
}
@@ -115,7 +156,7 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
if in != "" {
hint = resolveTable[in[0]]
}
- if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
+ if hint != 0 && tag != strTag && tag != binaryTag {
// Handle things we can lookup in a map.
if item, ok := resolveMap[in]; ok {
return item.tag, item.value
@@ -133,17 +174,17 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
// Not in the map, so maybe a normal float.
floatv, err := strconv.ParseFloat(in, 64)
if err == nil {
- return yaml_FLOAT_TAG, floatv
+ return floatTag, floatv
}
case 'D', 'S':
// Int, float, or timestamp.
// Only try values as a timestamp if the value is unquoted or there's an explicit
// !!timestamp tag.
- if tag == "" || tag == yaml_TIMESTAMP_TAG {
+ if tag == "" || tag == timestampTag {
t, ok := parseTimestamp(in)
if ok {
- return yaml_TIMESTAMP_TAG, t
+ return timestampTag, t
}
}
@@ -151,49 +192,76 @@ func resolve(tag string, in string) (rtag string, out interface{}) {
intv, err := strconv.ParseInt(plain, 0, 64)
if err == nil {
if intv == int64(int(intv)) {
- return yaml_INT_TAG, int(intv)
+ return intTag, int(intv)
} else {
- return yaml_INT_TAG, intv
+ return intTag, intv
}
}
uintv, err := strconv.ParseUint(plain, 0, 64)
if err == nil {
- return yaml_INT_TAG, uintv
+ return intTag, uintv
}
if yamlStyleFloat.MatchString(plain) {
floatv, err := strconv.ParseFloat(plain, 64)
if err == nil {
- return yaml_FLOAT_TAG, floatv
+ return floatTag, floatv
}
}
if strings.HasPrefix(plain, "0b") {
intv, err := strconv.ParseInt(plain[2:], 2, 64)
if err == nil {
if intv == int64(int(intv)) {
- return yaml_INT_TAG, int(intv)
+ return intTag, int(intv)
} else {
- return yaml_INT_TAG, intv
+ return intTag, intv
}
}
uintv, err := strconv.ParseUint(plain[2:], 2, 64)
if err == nil {
- return yaml_INT_TAG, uintv
+ return intTag, uintv
}
} else if strings.HasPrefix(plain, "-0b") {
- intv, err := strconv.ParseInt("-" + plain[3:], 2, 64)
+ intv, err := strconv.ParseInt("-"+plain[3:], 2, 64)
+ if err == nil {
+ if true || intv == int64(int(intv)) {
+ return intTag, int(intv)
+ } else {
+ return intTag, intv
+ }
+ }
+ }
+ // Octals as introduced in version 1.2 of the spec.
+ // Octals from the 1.1 spec, spelled as 0777, are still
+ // decoded by default in v3 as well for compatibility.
+ // May be dropped in v4 depending on how usage evolves.
+ if strings.HasPrefix(plain, "0o") {
+ intv, err := strconv.ParseInt(plain[2:], 8, 64)
+ if err == nil {
+ if intv == int64(int(intv)) {
+ return intTag, int(intv)
+ } else {
+ return intTag, intv
+ }
+ }
+ uintv, err := strconv.ParseUint(plain[2:], 8, 64)
+ if err == nil {
+ return intTag, uintv
+ }
+ } else if strings.HasPrefix(plain, "-0o") {
+ intv, err := strconv.ParseInt("-"+plain[3:], 8, 64)
if err == nil {
if true || intv == int64(int(intv)) {
- return yaml_INT_TAG, int(intv)
+ return intTag, int(intv)
} else {
- return yaml_INT_TAG, intv
+ return intTag, intv
}
}
}
default:
- panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
+ panic("internal error: missing handler for resolver table: " + string(rune(hint)) + " (with " + in + ")")
}
}
- return yaml_STR_TAG, in
+ return strTag, in
}
// encodeBase64 encodes s as base64 that is broken up into multiple lines
diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v3/scannerc.go
similarity index 87%
rename from vendor/gopkg.in/yaml.v2/scannerc.go
rename to vendor/gopkg.in/yaml.v3/scannerc.go
index 0b9bb603..57e954ca 100644
--- a/vendor/gopkg.in/yaml.v2/scannerc.go
+++ b/vendor/gopkg.in/yaml.v3/scannerc.go
@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+// Copyright (c) 2006-2010 Kirill Simonov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
package yaml
import (
@@ -489,6 +511,9 @@ func cache(parser *yaml_parser_t, length int) bool {
// Advance the buffer pointer.
func skip(parser *yaml_parser_t) {
+ if !is_blank(parser.buffer, parser.buffer_pos) {
+ parser.newlines = 0
+ }
parser.mark.index++
parser.mark.column++
parser.unread--
@@ -502,17 +527,22 @@ func skip_line(parser *yaml_parser_t) {
parser.mark.line++
parser.unread -= 2
parser.buffer_pos += 2
+ parser.newlines++
} else if is_break(parser.buffer, parser.buffer_pos) {
parser.mark.index++
parser.mark.column = 0
parser.mark.line++
parser.unread--
parser.buffer_pos += width(parser.buffer[parser.buffer_pos])
+ parser.newlines++
}
}
// Copy a character to a string buffer and advance pointers.
func read(parser *yaml_parser_t, s []byte) []byte {
+ if !is_blank(parser.buffer, parser.buffer_pos) {
+ parser.newlines = 0
+ }
w := width(parser.buffer[parser.buffer_pos])
if w == 0 {
panic("invalid character sequence")
@@ -564,6 +594,7 @@ func read_line(parser *yaml_parser_t, s []byte) []byte {
parser.mark.column = 0
parser.mark.line++
parser.unread--
+ parser.newlines++
return s
}
@@ -626,9 +657,13 @@ func trace(args ...interface{}) func() {
func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool {
// While we need more tokens to fetch, do it.
for {
- if parser.tokens_head != len(parser.tokens) {
- // If queue is non-empty, check if any potential simple key may
- // occupy the head position.
+ // [Go] The comment parsing logic requires a lookahead of two tokens
+ // so that foot comments may be parsed in time of associating them
+ // with the tokens that are parsed before them, and also for line
+ // comments to be transformed into head comments in some edge cases.
+ if parser.tokens_head < len(parser.tokens)-2 {
+ // If a potential simple key is at the head position, we need to fetch
+ // the next token to disambiguate it.
head_tok_idx, ok := parser.simple_keys_by_tok[parser.tokens_parsed]
if !ok {
break
@@ -649,7 +684,7 @@ func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool {
}
// The dispatcher for token fetchers.
-func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool {
+func yaml_parser_fetch_next_token(parser *yaml_parser_t) (ok bool) {
// Ensure that the buffer is initialized.
if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
return false
@@ -660,13 +695,19 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool {
return yaml_parser_fetch_stream_start(parser)
}
+ scan_mark := parser.mark
+
// Eat whitespaces and comments until we reach the next token.
if !yaml_parser_scan_to_next_token(parser) {
return false
}
+ // [Go] While unrolling indents, transform the head comments of prior
+ // indentation levels observed after scan_start into foot comments at
+ // the respective indexes.
+
// Check the indentation level against the current column.
- if !yaml_parser_unroll_indent(parser, parser.mark.column) {
+ if !yaml_parser_unroll_indent(parser, parser.mark.column, scan_mark) {
return false
}
@@ -699,6 +740,21 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool {
return yaml_parser_fetch_document_indicator(parser, yaml_DOCUMENT_END_TOKEN)
}
+ comment_mark := parser.mark
+ if len(parser.tokens) > 0 && (parser.flow_level == 0 && buf[pos] == ':' || parser.flow_level > 0 && buf[pos] == ',') {
+ // Associate any following comments with the prior token.
+ comment_mark = parser.tokens[len(parser.tokens)-1].start_mark
+ }
+ defer func() {
+ if !ok {
+ return
+ }
+ if !yaml_parser_scan_line_comment(parser, comment_mark) {
+ ok = false
+ return
+ }
+ }()
+
// Is it the flow sequence start indicator?
if buf[pos] == '[' {
return yaml_parser_fetch_flow_collection_start(parser, yaml_FLOW_SEQUENCE_START_TOKEN)
@@ -792,7 +848,7 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool {
// if it is followed by a non-space character.
//
// The last rule is more restrictive than the specification requires.
- // [Go] Make this logic more reasonable.
+ // [Go] TODO Make this logic more reasonable.
//switch parser.buffer[parser.buffer_pos] {
//case '-', '?', ':', ',', '?', '-', ',', ':', ']', '[', '}', '{', '&', '#', '!', '*', '>', '|', '"', '\'', '@', '%', '-', '`':
//}
@@ -965,19 +1021,49 @@ func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml
// Pop indentation levels from the indents stack until the current level
// becomes less or equal to the column. For each indentation level, append
// the BLOCK-END token.
-func yaml_parser_unroll_indent(parser *yaml_parser_t, column int) bool {
+func yaml_parser_unroll_indent(parser *yaml_parser_t, column int, scan_mark yaml_mark_t) bool {
// In the flow context, do nothing.
if parser.flow_level > 0 {
return true
}
+ block_mark := scan_mark
+ block_mark.index--
+
// Loop through the indentation levels in the stack.
for parser.indent > column {
+
+ // [Go] Reposition the end token before potential following
+ // foot comments of parent blocks. For that, search
+ // backwards for recent comments that were at the same
+ // indent as the block that is ending now.
+ stop_index := block_mark.index
+ for i := len(parser.comments) - 1; i >= 0; i-- {
+ comment := &parser.comments[i]
+
+ if comment.end_mark.index < stop_index {
+ // Don't go back beyond the start of the comment/whitespace scan, unless column < 0.
+ // If requested indent column is < 0, then the document is over and everything else
+ // is a foot anyway.
+ break
+ }
+ if comment.start_mark.column == parser.indent+1 {
+ // This is a good match. But maybe there's a former comment
+ // at that same indent level, so keep searching.
+ block_mark = comment.start_mark
+ }
+
+ // While the end of the former comment matches with
+ // the start of the following one, we know there's
+ // nothing in between and scanning is still safe.
+ stop_index = comment.scan_mark.index
+ }
+
// Create a token and append it to the queue.
token := yaml_token_t{
typ: yaml_BLOCK_END_TOKEN,
- start_mark: parser.mark,
- end_mark: parser.mark,
+ start_mark: block_mark,
+ end_mark: block_mark,
}
yaml_insert_token(parser, -1, &token)
@@ -1026,7 +1112,7 @@ func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool {
}
// Reset the indentation level.
- if !yaml_parser_unroll_indent(parser, -1) {
+ if !yaml_parser_unroll_indent(parser, -1, parser.mark) {
return false
}
@@ -1050,7 +1136,7 @@ func yaml_parser_fetch_stream_end(parser *yaml_parser_t) bool {
// Produce a VERSION-DIRECTIVE or TAG-DIRECTIVE token.
func yaml_parser_fetch_directive(parser *yaml_parser_t) bool {
// Reset the indentation level.
- if !yaml_parser_unroll_indent(parser, -1) {
+ if !yaml_parser_unroll_indent(parser, -1, parser.mark) {
return false
}
@@ -1074,7 +1160,7 @@ func yaml_parser_fetch_directive(parser *yaml_parser_t) bool {
// Produce the DOCUMENT-START or DOCUMENT-END token.
func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_type_t) bool {
// Reset the indentation level.
- if !yaml_parser_unroll_indent(parser, -1) {
+ if !yaml_parser_unroll_indent(parser, -1, parser.mark) {
return false
}
@@ -1107,6 +1193,7 @@ func yaml_parser_fetch_document_indicator(parser *yaml_parser_t, typ yaml_token_
// Produce the FLOW-SEQUENCE-START or FLOW-MAPPING-START token.
func yaml_parser_fetch_flow_collection_start(parser *yaml_parser_t, typ yaml_token_type_t) bool {
+
// The indicators '[' and '{' may start a simple key.
if !yaml_parser_save_simple_key(parser) {
return false
@@ -1442,6 +1529,8 @@ func yaml_parser_fetch_plain_scalar(parser *yaml_parser_t) bool {
// Eat whitespaces and comments until the next token is found.
func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool {
+ scan_mark := parser.mark
+
// Until the next token is not found.
for {
// Allow the BOM mark to start a line.
@@ -1468,13 +1557,33 @@ func yaml_parser_scan_to_next_token(parser *yaml_parser_t) bool {
}
}
+ // Check if we just had a line comment under a sequence entry that
+ // looks more like a header to the following content. Similar to this:
+ //
+ // - # The comment
+ // - Some data
+ //
+ // If so, transform the line comment to a head comment and reposition.
+ if len(parser.comments) > 0 && len(parser.tokens) > 1 {
+ tokenA := parser.tokens[len(parser.tokens)-2]
+ tokenB := parser.tokens[len(parser.tokens)-1]
+ comment := &parser.comments[len(parser.comments)-1]
+ if tokenA.typ == yaml_BLOCK_SEQUENCE_START_TOKEN && tokenB.typ == yaml_BLOCK_ENTRY_TOKEN && len(comment.line) > 0 && !is_break(parser.buffer, parser.buffer_pos) {
+ // If it was in the prior line, reposition so it becomes a
+ // header of the follow up token. Otherwise, keep it in place
+ // so it becomes a header of the former.
+ comment.head = comment.line
+ comment.line = nil
+ if comment.start_mark.line == parser.mark.line-1 {
+ comment.token_mark = parser.mark
+ }
+ }
+ }
+
// Eat a comment until a line break.
if parser.buffer[parser.buffer_pos] == '#' {
- for !is_breakz(parser.buffer, parser.buffer_pos) {
- skip(parser)
- if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
- return false
- }
+ if !yaml_parser_scan_comments(parser, scan_mark) {
+ return false
}
}
@@ -1572,6 +1681,10 @@ func yaml_parser_scan_directive(parser *yaml_parser_t, token *yaml_token_t) bool
}
if parser.buffer[parser.buffer_pos] == '#' {
+ // [Go] Discard this inline comment for the time being.
+ //if !yaml_parser_scan_line_comment(parser, start_mark) {
+ // return false
+ //}
for !is_breakz(parser.buffer, parser.buffer_pos) {
skip(parser)
if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
@@ -1987,7 +2100,7 @@ func yaml_parser_scan_tag_uri(parser *yaml_parser_t, directive bool, head []byte
// '0'-'9', 'A'-'Z', 'a'-'z', '_', '-', ';', '/', '?', ':', '@', '&',
// '=', '+', '$', ',', '.', '!', '~', '*', '\'', '(', ')', '[', ']',
// '%'.
- // [Go] Convert this into more reasonable logic.
+ // [Go] TODO Convert this into more reasonable logic.
for is_alpha(parser.buffer, parser.buffer_pos) || parser.buffer[parser.buffer_pos] == ';' ||
parser.buffer[parser.buffer_pos] == '/' || parser.buffer[parser.buffer_pos] == '?' ||
parser.buffer[parser.buffer_pos] == ':' || parser.buffer[parser.buffer_pos] == '@' ||
@@ -2142,6 +2255,10 @@ func yaml_parser_scan_block_scalar(parser *yaml_parser_t, token *yaml_token_t, l
}
}
if parser.buffer[parser.buffer_pos] == '#' {
+ // TODO Test this and then re-enable it.
+ //if !yaml_parser_scan_line_comment(parser, start_mark) {
+ // return false
+ //}
for !is_breakz(parser.buffer, parser.buffer_pos) {
skip(parser)
if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
@@ -2709,3 +2826,200 @@ func yaml_parser_scan_plain_scalar(parser *yaml_parser_t, token *yaml_token_t) b
}
return true
}
+
+func yaml_parser_scan_line_comment(parser *yaml_parser_t, token_mark yaml_mark_t) bool {
+ if parser.newlines > 0 {
+ return true
+ }
+
+ var start_mark yaml_mark_t
+ var text []byte
+
+ for peek := 0; peek < 512; peek++ {
+ if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) {
+ break
+ }
+ if is_blank(parser.buffer, parser.buffer_pos+peek) {
+ continue
+ }
+ if parser.buffer[parser.buffer_pos+peek] == '#' {
+ seen := parser.mark.index+peek
+ for {
+ if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
+ return false
+ }
+ if is_breakz(parser.buffer, parser.buffer_pos) {
+ if parser.mark.index >= seen {
+ break
+ }
+ if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) {
+ return false
+ }
+ skip_line(parser)
+ } else {
+ if parser.mark.index >= seen {
+ if len(text) == 0 {
+ start_mark = parser.mark
+ }
+ text = append(text, parser.buffer[parser.buffer_pos])
+ }
+ skip(parser)
+ }
+ }
+ }
+ break
+ }
+ if len(text) > 0 {
+ parser.comments = append(parser.comments, yaml_comment_t{
+ token_mark: token_mark,
+ start_mark: start_mark,
+ line: text,
+ })
+ }
+ return true
+}
+
+func yaml_parser_scan_comments(parser *yaml_parser_t, scan_mark yaml_mark_t) bool {
+ token := parser.tokens[len(parser.tokens)-1]
+
+ if token.typ == yaml_FLOW_ENTRY_TOKEN && len(parser.tokens) > 1 {
+ token = parser.tokens[len(parser.tokens)-2]
+ }
+
+ var token_mark = token.start_mark
+ var start_mark yaml_mark_t
+
+ var recent_empty = false
+ var first_empty = parser.newlines <= 1
+
+ var line = parser.mark.line
+ var column = parser.mark.column
+
+ var text []byte
+
+ // The foot line is the place where a comment must start to
+ // still be considered as a foot of the prior content.
+ // If there's some content in the currently parsed line, then
+ // the foot is the line below it.
+ var foot_line = -1
+ if scan_mark.line > 0 {
+ foot_line = parser.mark.line-parser.newlines+1
+ if parser.newlines == 0 && parser.mark.column > 1 {
+ foot_line++
+ }
+ }
+
+ var peek = 0
+ for ; peek < 512; peek++ {
+ if parser.unread < peek+1 && !yaml_parser_update_buffer(parser, peek+1) {
+ break
+ }
+ column++
+ if is_blank(parser.buffer, parser.buffer_pos+peek) {
+ continue
+ }
+ c := parser.buffer[parser.buffer_pos+peek]
+ if is_breakz(parser.buffer, parser.buffer_pos+peek) || parser.flow_level > 0 && (c == ']' || c == '}') {
+ // Got line break or terminator.
+ if !recent_empty {
+ if first_empty && (start_mark.line == foot_line || start_mark.column-1 < parser.indent) {
+ // This is the first empty line and there were no empty lines before,
+ // so this initial part of the comment is a foot of the prior token
+ // instead of being a head for the following one. Split it up.
+ if len(text) > 0 {
+ if start_mark.column-1 < parser.indent {
+ // If dedented it's unrelated to the prior token.
+ token_mark = start_mark
+ }
+ parser.comments = append(parser.comments, yaml_comment_t{
+ scan_mark: scan_mark,
+ token_mark: token_mark,
+ start_mark: start_mark,
+ end_mark: yaml_mark_t{parser.mark.index + peek, line, column},
+ foot: text,
+ })
+ scan_mark = yaml_mark_t{parser.mark.index + peek, line, column}
+ token_mark = scan_mark
+ text = nil
+ }
+ } else {
+ if len(text) > 0 && parser.buffer[parser.buffer_pos+peek] != 0 {
+ text = append(text, '\n')
+ }
+ }
+ }
+ if !is_break(parser.buffer, parser.buffer_pos+peek) {
+ break
+ }
+ first_empty = false
+ recent_empty = true
+ column = 0
+ line++
+ continue
+ }
+
+ if len(text) > 0 && column < parser.indent+1 && column != start_mark.column {
+ // The comment at the different indentation is a foot of the
+ // preceding data rather than a head of the upcoming one.
+ parser.comments = append(parser.comments, yaml_comment_t{
+ scan_mark: scan_mark,
+ token_mark: token_mark,
+ start_mark: start_mark,
+ end_mark: yaml_mark_t{parser.mark.index + peek, line, column},
+ foot: text,
+ })
+ scan_mark = yaml_mark_t{parser.mark.index + peek, line, column}
+ token_mark = scan_mark
+ text = nil
+ }
+
+ if parser.buffer[parser.buffer_pos+peek] != '#' {
+ break
+ }
+
+ if len(text) == 0 {
+ start_mark = yaml_mark_t{parser.mark.index + peek, line, column}
+ } else {
+ text = append(text, '\n')
+ }
+
+ recent_empty = false
+
+ // Consume until after the consumed comment line.
+ seen := parser.mark.index+peek
+ for {
+ if parser.unread < 1 && !yaml_parser_update_buffer(parser, 1) {
+ return false
+ }
+ if is_breakz(parser.buffer, parser.buffer_pos) {
+ if parser.mark.index >= seen {
+ break
+ }
+ if parser.unread < 2 && !yaml_parser_update_buffer(parser, 2) {
+ return false
+ }
+ skip_line(parser)
+ } else {
+ if parser.mark.index >= seen {
+ text = append(text, parser.buffer[parser.buffer_pos])
+ }
+ skip(parser)
+ }
+ }
+
+ peek = 0
+ column = 0
+ line = parser.mark.line
+ }
+
+ if len(text) > 0 {
+ parser.comments = append(parser.comments, yaml_comment_t{
+ scan_mark: scan_mark,
+ token_mark: start_mark,
+ start_mark: start_mark,
+ end_mark: yaml_mark_t{parser.mark.index + peek - 1, line, column},
+ head: text,
+ })
+ }
+ return true
+}
diff --git a/vendor/gopkg.in/yaml.v2/sorter.go b/vendor/gopkg.in/yaml.v3/sorter.go
similarity index 76%
rename from vendor/gopkg.in/yaml.v2/sorter.go
rename to vendor/gopkg.in/yaml.v3/sorter.go
index 4c45e660..9210ece7 100644
--- a/vendor/gopkg.in/yaml.v2/sorter.go
+++ b/vendor/gopkg.in/yaml.v3/sorter.go
@@ -1,3 +1,18 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
package yaml
import (
@@ -37,8 +52,10 @@ func (l keyList) Less(i, j int) bool {
return ak < bk
}
ar, br := []rune(a.String()), []rune(b.String())
+ digits := false
for i := 0; i < len(ar) && i < len(br); i++ {
if ar[i] == br[i] {
+ digits = unicode.IsDigit(ar[i])
continue
}
al := unicode.IsLetter(ar[i])
@@ -47,12 +64,16 @@ func (l keyList) Less(i, j int) bool {
return ar[i] < br[i]
}
if al || bl {
- return bl
+ if digits {
+ return al
+ } else {
+ return bl
+ }
}
var ai, bi int
var an, bn int64
if ar[i] == '0' || br[i] == '0' {
- for j := i-1; j >= 0 && unicode.IsDigit(ar[j]); j-- {
+ for j := i - 1; j >= 0 && unicode.IsDigit(ar[j]); j-- {
if ar[j] != '0' {
an = 1
bn = 1
diff --git a/vendor/gopkg.in/yaml.v3/writerc.go b/vendor/gopkg.in/yaml.v3/writerc.go
new file mode 100644
index 00000000..b8a116bf
--- /dev/null
+++ b/vendor/gopkg.in/yaml.v3/writerc.go
@@ -0,0 +1,48 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+// Copyright (c) 2006-2010 Kirill Simonov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+package yaml
+
+// Set the writer error and return false.
+func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
+ emitter.error = yaml_WRITER_ERROR
+ emitter.problem = problem
+ return false
+}
+
+// Flush the output buffer.
+func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
+ if emitter.write_handler == nil {
+ panic("write handler not set")
+ }
+
+ // Check if the buffer is empty.
+ if emitter.buffer_pos == 0 {
+ return true
+ }
+
+ if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
+ return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
+ }
+ emitter.buffer_pos = 0
+ return true
+}
diff --git a/vendor/gopkg.in/yaml.v2/yaml.go b/vendor/gopkg.in/yaml.v3/yaml.go
similarity index 59%
rename from vendor/gopkg.in/yaml.v2/yaml.go
rename to vendor/gopkg.in/yaml.v3/yaml.go
index 89650e29..b5d35a50 100644
--- a/vendor/gopkg.in/yaml.v2/yaml.go
+++ b/vendor/gopkg.in/yaml.v3/yaml.go
@@ -1,3 +1,18 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
// Package yaml implements YAML support for the Go language.
//
// Source code and other details for the project are available at GitHub:
@@ -13,23 +28,16 @@ import (
"reflect"
"strings"
"sync"
+ "unicode/utf8"
)
-// MapSlice encodes and decodes as a YAML map.
-// The order of keys is preserved when encoding and decoding.
-type MapSlice []MapItem
-
-// MapItem is an item in a MapSlice.
-type MapItem struct {
- Key, Value interface{}
-}
-
// The Unmarshaler interface may be implemented by types to customize their
-// behavior when being unmarshaled from a YAML document. The UnmarshalYAML
-// method receives a function that may be called to unmarshal the original
-// YAML value into a field or variable. It is safe to call the unmarshal
-// function parameter more than once if necessary.
+// behavior when being unmarshaled from a YAML document.
type Unmarshaler interface {
+ UnmarshalYAML(value *Node) error
+}
+
+type obsoleteUnmarshaler interface {
UnmarshalYAML(unmarshal func(interface{}) error) error
}
@@ -81,18 +89,10 @@ func Unmarshal(in []byte, out interface{}) (err error) {
return unmarshal(in, out, false)
}
-// UnmarshalStrict is like Unmarshal except that any fields that are found
-// in the data that do not have corresponding struct members, or mapping
-// keys that are duplicates, will result in
-// an error.
-func UnmarshalStrict(in []byte, out interface{}) (err error) {
- return unmarshal(in, out, true)
-}
-
-// A Decoder reads and decodes YAML values from an input stream.
+// A Decorder reads and decodes YAML values from an input stream.
type Decoder struct {
- strict bool
- parser *parser
+ parser *parser
+ knownFields bool
}
// NewDecoder returns a new decoder that reads from r.
@@ -105,10 +105,10 @@ func NewDecoder(r io.Reader) *Decoder {
}
}
-// SetStrict sets whether strict decoding behaviour is enabled when
-// decoding items in the data (see UnmarshalStrict). By default, decoding is not strict.
-func (dec *Decoder) SetStrict(strict bool) {
- dec.strict = strict
+// KnownFields ensures that the keys in decoded mappings to
+// exist as fields in the struct being decoded into.
+func (dec *Decoder) KnownFields(enable bool) {
+ dec.knownFields = enable
}
// Decode reads the next YAML-encoded value from its input
@@ -117,7 +117,8 @@ func (dec *Decoder) SetStrict(strict bool) {
// See the documentation for Unmarshal for details about the
// conversion of YAML into a Go value.
func (dec *Decoder) Decode(v interface{}) (err error) {
- d := newDecoder(dec.strict)
+ d := newDecoder()
+ d.knownFields = dec.knownFields
defer handleErr(&err)
node := dec.parser.parse()
if node == nil {
@@ -134,9 +135,27 @@ func (dec *Decoder) Decode(v interface{}) (err error) {
return nil
}
+// Decode decodes the node and stores its data into the value pointed to by v.
+//
+// See the documentation for Unmarshal for details about the
+// conversion of YAML into a Go value.
+func (n *Node) Decode(v interface{}) (err error) {
+ d := newDecoder()
+ defer handleErr(&err)
+ out := reflect.ValueOf(v)
+ if out.Kind() == reflect.Ptr && !out.IsNil() {
+ out = out.Elem()
+ }
+ d.unmarshal(n, out)
+ if len(d.terrors) > 0 {
+ return &TypeError{d.terrors}
+ }
+ return nil
+}
+
func unmarshal(in []byte, out interface{}, strict bool) (err error) {
defer handleErr(&err)
- d := newDecoder(strict)
+ d := newDecoder()
p := newParser(in)
defer p.destroy()
node := p.parse()
@@ -233,6 +252,14 @@ func (e *Encoder) Encode(v interface{}) (err error) {
return nil
}
+// SetIndent changes the used indentation used when encoding.
+func (e *Encoder) SetIndent(spaces int) {
+ if spaces < 0 {
+ panic("yaml: cannot indent to a negative number of spaces")
+ }
+ e.encoder.indent = spaces
+}
+
// Close closes the encoder by writing any remaining data.
// It does not write a stream terminating string "...".
func (e *Encoder) Close() (err error) {
@@ -275,6 +302,150 @@ func (e *TypeError) Error() string {
return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n "))
}
+type Kind uint32
+
+const (
+ DocumentNode Kind = 1 << iota
+ SequenceNode
+ MappingNode
+ ScalarNode
+ AliasNode
+)
+
+type Style uint32
+
+const (
+ TaggedStyle Style = 1 << iota
+ DoubleQuotedStyle
+ SingleQuotedStyle
+ LiteralStyle
+ FoldedStyle
+ FlowStyle
+)
+
+// Node represents an element in the YAML document hierarchy. While documents
+// are typically encoded and decoded into higher level types, such as structs
+// and maps, Node is an intermediate representation that allows detailed
+// control over the content being decoded or encoded.
+//
+// Values that make use of the Node type interact with the yaml package in the
+// same way any other type would do, by encoding and decoding yaml data
+// directly or indirectly into them.
+//
+// For example:
+//
+// var person struct {
+// Name string
+// Address yaml.Node
+// }
+// err := yaml.Unmarshal(data, &person)
+//
+// Or by itself:
+//
+// var person Node
+// err := yaml.Unmarshal(data, &person)
+//
+type Node struct {
+ // Kind defines whether the node is a document, a mapping, a sequence,
+ // a scalar value, or an alias to another node. The specific data type of
+ // scalar nodes may be obtained via the ShortTag and LongTag methods.
+ Kind Kind
+
+ // Style allows customizing the apperance of the node in the tree.
+ Style Style
+
+ // Tag holds the YAML tag defining the data type for the value.
+ // When decoding, this field will always be set to the resolved tag,
+ // even when it wasn't explicitly provided in the YAML content.
+ // When encoding, if this field is unset the value type will be
+ // implied from the node properties, and if it is set, it will only
+ // be serialized into the representation if TaggedStyle is used or
+ // the implicit tag diverges from the provided one.
+ Tag string
+
+ // Value holds the unescaped and unquoted represenation of the value.
+ Value string
+
+ // Anchor holds the anchor name for this node, which allows aliases to point to it.
+ Anchor string
+
+ // Alias holds the node that this alias points to. Only valid when Kind is AliasNode.
+ Alias *Node
+
+ // Content holds contained nodes for documents, mappings, and sequences.
+ Content []*Node
+
+ // HeadComment holds any comments in the lines preceding the node and
+ // not separated by an empty line.
+ HeadComment string
+
+ // LineComment holds any comments at the end of the line where the node is in.
+ LineComment string
+
+ // FootComment holds any comments following the node and before empty lines.
+ FootComment string
+
+ // Line and Column hold the node position in the decoded YAML text.
+ // These fields are not respected when encoding the node.
+ Line int
+ Column int
+}
+
+// LongTag returns the long form of the tag that indicates the data type for
+// the node. If the Tag field isn't explicitly defined, one will be computed
+// based on the node properties.
+func (n *Node) LongTag() string {
+ return longTag(n.ShortTag())
+}
+
+// ShortTag returns the short form of the YAML tag that indicates data type for
+// the node. If the Tag field isn't explicitly defined, one will be computed
+// based on the node properties.
+func (n *Node) ShortTag() string {
+ if n.indicatedString() {
+ return strTag
+ }
+ if n.Tag == "" || n.Tag == "!" {
+ switch n.Kind {
+ case MappingNode:
+ return mapTag
+ case SequenceNode:
+ return seqTag
+ case AliasNode:
+ if n.Alias != nil {
+ return n.Alias.ShortTag()
+ }
+ case ScalarNode:
+ tag, _ := resolve("", n.Value)
+ return tag
+ }
+ return ""
+ }
+ return shortTag(n.Tag)
+}
+
+func (n *Node) indicatedString() bool {
+ return n.Kind == ScalarNode &&
+ (shortTag(n.Tag) == strTag ||
+ (n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0)
+}
+
+// SetString is a convenience function that sets the node to a string value
+// and defines its style in a pleasant way depending on its content.
+func (n *Node) SetString(s string) {
+ n.Kind = ScalarNode
+ if utf8.ValidString(s) {
+ n.Value = s
+ n.Tag = strTag
+ } else {
+ n.Value = encodeBase64(s)
+ n.Tag = binaryTag
+ }
+ if strings.Contains(n.Value, "\n") {
+ n.Style = LiteralStyle
+ }
+}
+
// --------------------------------------------------------------------------
// Maintain a mapping of keys to structure field indexes
@@ -289,6 +460,10 @@ type structInfo struct {
// InlineMap is the number of the field in the struct that
// contains an ,inline map, or -1 if there's none.
InlineMap int
+
+ // InlineUnmarshalers holds indexes to inlined fields that
+ // contain unmarshaler values.
+ InlineUnmarshalers [][]int
}
type fieldInfo struct {
@@ -306,6 +481,12 @@ type fieldInfo struct {
var structMap = make(map[reflect.Type]*structInfo)
var fieldMapMutex sync.RWMutex
+var unmarshalerType reflect.Type
+
+func init() {
+ var v Unmarshaler
+ unmarshalerType = reflect.ValueOf(&v).Elem().Type()
+}
func getStructInfo(st reflect.Type) (*structInfo, error) {
fieldMapMutex.RLock()
@@ -319,6 +500,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
fieldsMap := make(map[string]fieldInfo)
fieldsList := make([]fieldInfo, 0, n)
inlineMap := -1
+ inlineUnmarshalers := [][]int(nil)
for i := 0; i != n; i++ {
field := st.Field(i)
if field.PkgPath != "" && !field.Anonymous {
@@ -347,7 +529,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
case "inline":
inline = true
default:
- return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st))
+ return nil, errors.New(fmt.Sprintf("unsupported flag %q in tag %q of type %s", flag, tag, st))
}
}
tag = fields[0]
@@ -357,34 +539,47 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
switch field.Type.Kind() {
case reflect.Map:
if inlineMap >= 0 {
- return nil, errors.New("Multiple ,inline maps in struct " + st.String())
+ return nil, errors.New("multiple ,inline maps in struct " + st.String())
}
if field.Type.Key() != reflect.TypeOf("") {
- return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
+ return nil, errors.New("option ,inline needs a map with string keys in struct " + st.String())
}
inlineMap = info.Num
- case reflect.Struct:
- sinfo, err := getStructInfo(field.Type)
- if err != nil {
- return nil, err
+ case reflect.Struct, reflect.Ptr:
+ ftype := field.Type
+ for ftype.Kind() == reflect.Ptr {
+ ftype = ftype.Elem()
+ }
+ if ftype.Kind() != reflect.Struct {
+ return nil, errors.New("option ,inline may only be used on a struct or map field")
}
- for _, finfo := range sinfo.FieldsList {
- if _, found := fieldsMap[finfo.Key]; found {
- msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String()
- return nil, errors.New(msg)
+ if reflect.PtrTo(ftype).Implements(unmarshalerType) {
+ inlineUnmarshalers = append(inlineUnmarshalers, []int{i})
+ } else {
+ sinfo, err := getStructInfo(ftype)
+ if err != nil {
+ return nil, err
+ }
+ for _, index := range sinfo.InlineUnmarshalers {
+ inlineUnmarshalers = append(inlineUnmarshalers, append([]int{i}, index...))
}
- if finfo.Inline == nil {
- finfo.Inline = []int{i, finfo.Num}
- } else {
- finfo.Inline = append([]int{i}, finfo.Inline...)
+ for _, finfo := range sinfo.FieldsList {
+ if _, found := fieldsMap[finfo.Key]; found {
+ msg := "duplicated key '" + finfo.Key + "' in struct " + st.String()
+ return nil, errors.New(msg)
+ }
+ if finfo.Inline == nil {
+ finfo.Inline = []int{i, finfo.Num}
+ } else {
+ finfo.Inline = append([]int{i}, finfo.Inline...)
+ }
+ finfo.Id = len(fieldsList)
+ fieldsMap[finfo.Key] = finfo
+ fieldsList = append(fieldsList, finfo)
}
- finfo.Id = len(fieldsList)
- fieldsMap[finfo.Key] = finfo
- fieldsList = append(fieldsList, finfo)
}
default:
- //return nil, errors.New("Option ,inline needs a struct value or map field")
- return nil, errors.New("Option ,inline needs a struct value field")
+ return nil, errors.New("option ,inline may only be used on a struct or map field")
}
continue
}
@@ -396,7 +591,7 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
}
if _, found = fieldsMap[info.Key]; found {
- msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
+ msg := "duplicated key '" + info.Key + "' in struct " + st.String()
return nil, errors.New(msg)
}
@@ -406,9 +601,10 @@ func getStructInfo(st reflect.Type) (*structInfo, error) {
}
sinfo = &structInfo{
- FieldsMap: fieldsMap,
- FieldsList: fieldsList,
- InlineMap: inlineMap,
+ FieldsMap: fieldsMap,
+ FieldsList: fieldsList,
+ InlineMap: inlineMap,
+ InlineUnmarshalers: inlineUnmarshalers,
}
fieldMapMutex.Lock()
diff --git a/vendor/gopkg.in/yaml.v2/yamlh.go b/vendor/gopkg.in/yaml.v3/yamlh.go
similarity index 88%
rename from vendor/gopkg.in/yaml.v2/yamlh.go
rename to vendor/gopkg.in/yaml.v3/yamlh.go
index f6a9c8e3..2719cfbb 100644
--- a/vendor/gopkg.in/yaml.v2/yamlh.go
+++ b/vendor/gopkg.in/yaml.v3/yamlh.go
@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+// Copyright (c) 2006-2010 Kirill Simonov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
package yaml
import (
@@ -73,13 +95,13 @@ type yaml_scalar_style_t yaml_style_t
// Scalar styles.
const (
// Let the emitter choose the style.
- yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota
+ yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = 0
- yaml_PLAIN_SCALAR_STYLE // The plain scalar style.
- yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
- yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
- yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
- yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
+ yaml_PLAIN_SCALAR_STYLE yaml_scalar_style_t = 1 << iota // The plain scalar style.
+ yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
+ yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
+ yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
+ yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
)
type yaml_sequence_style_t yaml_style_t
@@ -238,6 +260,7 @@ const (
yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event.
yaml_MAPPING_START_EVENT // A MAPPING-START event.
yaml_MAPPING_END_EVENT // A MAPPING-END event.
+ yaml_TAIL_COMMENT_EVENT
)
var eventStrings = []string{
@@ -252,6 +275,7 @@ var eventStrings = []string{
yaml_SEQUENCE_END_EVENT: "sequence end",
yaml_MAPPING_START_EVENT: "mapping start",
yaml_MAPPING_END_EVENT: "mapping end",
+ yaml_TAIL_COMMENT_EVENT: "tail comment",
}
func (e yaml_event_type_t) String() string {
@@ -279,6 +303,12 @@ type yaml_event_t struct {
// The list of tag directives (for yaml_DOCUMENT_START_EVENT).
tag_directives []yaml_tag_directive_t
+ // The comments
+ head_comment []byte
+ line_comment []byte
+ foot_comment []byte
+ tail_comment []byte
+
// The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT).
anchor []byte
@@ -554,6 +584,8 @@ type yaml_parser_t struct {
unread int // The number of unread characters in the buffer.
+ newlines int // The number of line breaks since last non-break/non-blank character
+
raw_buffer []byte // The raw buffer.
raw_buffer_pos int // The current position of the buffer.
@@ -562,6 +594,17 @@ type yaml_parser_t struct {
offset int // The offset of the current position (in bytes).
mark yaml_mark_t // The mark of the current position.
+ // Comments
+
+ head_comment []byte // The current head comments
+ line_comment []byte // The current line comments
+ foot_comment []byte // The current foot comments
+ tail_comment []byte // Foot comment that happens at the end of a block.
+ stem_comment []byte // Comment in item preceding a nested structure (list inside list item, etc)
+
+ comments []yaml_comment_t // The folded comments for all parsed tokens
+ comments_head int
+
// Scanner stuff
stream_start_produced bool // Have we started to scan the input stream?
@@ -595,6 +638,18 @@ type yaml_parser_t struct {
document *yaml_document_t // The currently parsed document.
}
+type yaml_comment_t struct {
+
+ scan_mark yaml_mark_t // Position where scanning for comments started
+ token_mark yaml_mark_t // Position after which tokens will be associated with this comment
+ start_mark yaml_mark_t // Position of '#' comment mark
+ end_mark yaml_mark_t // Position where comment terminated
+
+ head []byte
+ line []byte
+ foot []byte
+}
+
// Emitter Definitions
// The prototype of a write handler.
@@ -625,8 +680,10 @@ const (
yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document.
yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END.
yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence.
+ yaml_EMIT_FLOW_SEQUENCE_TRAIL_ITEM_STATE // Expect the next item of a flow sequence, with the comma already written out
yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence.
yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
+ yaml_EMIT_FLOW_MAPPING_TRAIL_KEY_STATE // Expect the next key of a flow mapping, with the comma already written out
yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping.
yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
@@ -698,6 +755,9 @@ type yaml_emitter_t struct {
indention bool // If the last character was an indentation character (' ', '-', '?', ':')?
open_ended bool // If an explicit document end is required?
+ space_above bool // Is there's an empty line above?
+ foot_indent int // The indent used to write the foot comment above, or -1 if none.
+
// Anchor analysis.
anchor_data struct {
anchor []byte // The anchor value.
@@ -721,6 +781,12 @@ type yaml_emitter_t struct {
style yaml_scalar_style_t // The output style.
}
+ // Comments
+ head_comment []byte
+ line_comment []byte
+ foot_comment []byte
+ tail_comment []byte
+
// Dumper stuff
opened bool // If the stream was already opened?
diff --git a/vendor/gopkg.in/yaml.v2/yamlprivateh.go b/vendor/gopkg.in/yaml.v3/yamlprivateh.go
similarity index 78%
rename from vendor/gopkg.in/yaml.v2/yamlprivateh.go
rename to vendor/gopkg.in/yaml.v3/yamlprivateh.go
index 8110ce3c..e88f9c54 100644
--- a/vendor/gopkg.in/yaml.v2/yamlprivateh.go
+++ b/vendor/gopkg.in/yaml.v3/yamlprivateh.go
@@ -1,3 +1,25 @@
+//
+// Copyright (c) 2011-2019 Canonical Ltd
+// Copyright (c) 2006-2010 Kirill Simonov
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy of
+// this software and associated documentation files (the "Software"), to deal in
+// the Software without restriction, including without limitation the rights to
+// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+// of the Software, and to permit persons to whom the Software is furnished to do
+// so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
package yaml
const (
@@ -114,8 +136,9 @@ func is_crlf(b []byte, i int) bool {
// Check if the character is a line break or NUL.
func is_breakz(b []byte, i int) bool {
//return is_break(b, i) || is_z(b, i)
- return ( // is_break:
- b[i] == '\r' || // CR (#xD)
+ return (
+ // is_break:
+ b[i] == '\r' || // CR (#xD)
b[i] == '\n' || // LF (#xA)
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
@@ -127,8 +150,9 @@ func is_breakz(b []byte, i int) bool {
// Check if the character is a line break, space, or NUL.
func is_spacez(b []byte, i int) bool {
//return is_space(b, i) || is_breakz(b, i)
- return ( // is_space:
- b[i] == ' ' ||
+ return (
+ // is_space:
+ b[i] == ' ' ||
// is_breakz:
b[i] == '\r' || // CR (#xD)
b[i] == '\n' || // LF (#xA)
@@ -141,8 +165,9 @@ func is_spacez(b []byte, i int) bool {
// Check if the character is a line break, space, tab, or NUL.
func is_blankz(b []byte, i int) bool {
//return is_blank(b, i) || is_breakz(b, i)
- return ( // is_blank:
- b[i] == ' ' || b[i] == '\t' ||
+ return (
+ // is_blank:
+ b[i] == ' ' || b[i] == '\t' ||
// is_breakz:
b[i] == '\r' || // CR (#xD)
b[i] == '\n' || // LF (#xA)
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 3aeaa3e0..99747b64 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1,3 +1,7 @@
+# github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1
+## explicit; go 1.16
+github.com/Azure/go-ansiterm
+github.com/Azure/go-ansiterm/winterm
# github.com/BurntSushi/toml v0.3.1
## explicit
github.com/BurntSushi/toml
@@ -16,7 +20,7 @@ github.com/acarl005/stripansi
# github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
## explicit
github.com/armon/go-socks5
-# github.com/blacknon/go-sshlib v0.1.5
+# github.com/blacknon/go-sshlib v0.1.7
## explicit; go 1.17
github.com/blacknon/go-sshlib
# github.com/blacknon/textcol v0.0.1
@@ -25,7 +29,6 @@ github.com/blacknon/textcol
# github.com/c-bata/go-prompt v0.2.5
## explicit; go 1.14
github.com/c-bata/go-prompt
-github.com/c-bata/go-prompt/completer
github.com/c-bata/go-prompt/internal/bisect
github.com/c-bata/go-prompt/internal/debug
github.com/c-bata/go-prompt/internal/strings
@@ -42,6 +45,9 @@ github.com/dustin/go-humanize
# github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
## explicit
github.com/kardianos/osext
+# github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
+## explicit
+github.com/kballard/go-shellquote
# github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8
## explicit
github.com/kevinburke/ssh_config
@@ -69,15 +75,19 @@ github.com/mattn/go-tty
## explicit; go 1.12
github.com/miekg/pkcs11
github.com/miekg/pkcs11/p11
-# github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e
-## explicit
+# github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6
+## explicit; go 1.13
+github.com/moby/term/windows
+# github.com/nsf/termbox-go v1.1.1
+## explicit; go 1.15
github.com/nsf/termbox-go
# github.com/pkg/errors v0.9.1
## explicit
github.com/pkg/errors
-# github.com/pkg/sftp v1.10.1
-## explicit; go 1.12
+# github.com/pkg/sftp v1.13.4
+## explicit; go 1.15
github.com/pkg/sftp
+github.com/pkg/sftp/internal/encoding/ssh/filexfer
# github.com/pkg/term v1.1.0
## explicit; go 1.14
github.com/pkg/term/termios
@@ -90,7 +100,7 @@ github.com/rivo/uniseg
# github.com/sevlyar/go-daemon v0.1.5
## explicit
github.com/sevlyar/go-daemon
-# github.com/stretchr/testify v1.5.1
+# github.com/stretchr/testify v1.7.0
## explicit; go 1.13
github.com/stretchr/testify/assert
# github.com/thales-e-security/pool v0.0.2
@@ -118,13 +128,14 @@ golang.org/x/crypto/internal/subtle
golang.org/x/crypto/ssh
golang.org/x/crypto/ssh/agent
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
+golang.org/x/crypto/ssh/knownhosts
golang.org/x/crypto/ssh/terminal
# golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d
## explicit; go 1.17
golang.org/x/net/context
golang.org/x/net/internal/socks
golang.org/x/net/proxy
-# golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e
+# golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7
## explicit; go 1.17
golang.org/x/sys/cpu
golang.org/x/sys/internal/unsafeheader
@@ -134,9 +145,9 @@ golang.org/x/sys/windows
# golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
## explicit; go 1.17
golang.org/x/term
-# gopkg.in/yaml.v2 v2.2.8
+# gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
## explicit
-gopkg.in/yaml.v2
+gopkg.in/yaml.v3
# mvdan.cc/sh v2.6.3+incompatible
## explicit
mvdan.cc/sh/syntax