mirror of
https://github.com/restic/restic.git
synced 2025-12-08 06:09:56 +00:00
Vendor dependencies with dep
This commit is contained in:
parent
df8a5792f1
commit
91edebf1fe
1691 changed files with 466360 additions and 0 deletions
24
vendor/github.com/pkg/errors/.gitignore
generated
vendored
Normal file
24
vendor/github.com/pkg/errors/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
11
vendor/github.com/pkg/errors/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/pkg/errors/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- 1.7.1
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
||||
23
vendor/github.com/pkg/errors/LICENSE
generated
vendored
Normal file
23
vendor/github.com/pkg/errors/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
52
vendor/github.com/pkg/errors/README.md
generated
vendored
Normal file
52
vendor/github.com/pkg/errors/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# errors [](https://travis-ci.org/pkg/errors) [](https://ci.appveyor.com/project/davecheney/errors/branch/master) [](http://godoc.org/github.com/pkg/errors) [](https://goreportcard.com/report/github.com/pkg/errors)
|
||||
|
||||
Package errors provides simple error handling primitives.
|
||||
|
||||
`go get github.com/pkg/errors`
|
||||
|
||||
The traditional error handling idiom in Go is roughly akin to
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||
|
||||
## Adding context to an error
|
||||
|
||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||
```go
|
||||
_, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read failed")
|
||||
}
|
||||
```
|
||||
## Retrieving the cause of an error
|
||||
|
||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||
```go
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
```
|
||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||
```go
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *MyError:
|
||||
// handle specifically
|
||||
default:
|
||||
// unknown error
|
||||
}
|
||||
```
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
|
||||
## Licence
|
||||
|
||||
BSD-2-Clause
|
||||
32
vendor/github.com/pkg/errors/appveyor.yml
generated
vendored
Normal file
32
vendor/github.com/pkg/errors/appveyor.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||
shallow_clone: true # for startup speed
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# http://www.appveyor.com/docs/installed-software
|
||||
install:
|
||||
# some helpful output for debugging builds
|
||||
- go version
|
||||
- go env
|
||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||
# but MSYS2 at C:\msys64 has mingw64
|
||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- set PATH=C:\gopath\bin;%PATH%
|
||||
- go test -v ./...
|
||||
|
||||
#artifacts:
|
||||
# - path: '%GOPATH%\bin\*.exe'
|
||||
deploy: off
|
||||
59
vendor/github.com/pkg/errors/bench_test.go
generated
vendored
Normal file
59
vendor/github.com/pkg/errors/bench_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// +build go1.7
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
stderrors "errors"
|
||||
)
|
||||
|
||||
func noErrors(at, depth int) error {
|
||||
if at >= depth {
|
||||
return stderrors.New("no error")
|
||||
}
|
||||
return noErrors(at+1, depth)
|
||||
}
|
||||
func yesErrors(at, depth int) error {
|
||||
if at >= depth {
|
||||
return New("ye error")
|
||||
}
|
||||
return yesErrors(at+1, depth)
|
||||
}
|
||||
|
||||
func BenchmarkErrors(b *testing.B) {
|
||||
var toperr error
|
||||
type run struct {
|
||||
stack int
|
||||
std bool
|
||||
}
|
||||
runs := []run{
|
||||
{10, false},
|
||||
{10, true},
|
||||
{100, false},
|
||||
{100, true},
|
||||
{1000, false},
|
||||
{1000, true},
|
||||
}
|
||||
for _, r := range runs {
|
||||
part := "pkg/errors"
|
||||
if r.std {
|
||||
part = "errors"
|
||||
}
|
||||
name := fmt.Sprintf("%s-stack-%d", part, r.stack)
|
||||
b.Run(name, func(b *testing.B) {
|
||||
var err error
|
||||
f := yesErrors
|
||||
if r.std {
|
||||
f = noErrors
|
||||
}
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err = f(0, r.stack)
|
||||
}
|
||||
b.StopTimer()
|
||||
toperr = err
|
||||
})
|
||||
}
|
||||
}
|
||||
269
vendor/github.com/pkg/errors/errors.go
generated
vendored
Normal file
269
vendor/github.com/pkg/errors/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// and the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||
// functions destructure errors.Wrap into its component operations of annotating
|
||||
// an error with a stack trace and an a message, respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error which does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// causer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface.
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// Where errors.StackTrace is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// stackTracer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is call, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
||||
226
vendor/github.com/pkg/errors/errors_test.go
generated
vendored
Normal file
226
vendor/github.com/pkg/errors/errors_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
err string
|
||||
want error
|
||||
}{
|
||||
{"", fmt.Errorf("")},
|
||||
{"foo", fmt.Errorf("foo")},
|
||||
{"foo", New("foo")},
|
||||
{"string with format specifiers: %v", errors.New("string with format specifiers: %v")},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := New(tt.err)
|
||||
if got.Error() != tt.want.Error() {
|
||||
t.Errorf("New.Error(): got: %q, want %q", got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapNil(t *testing.T) {
|
||||
got := Wrap(nil, "no error")
|
||||
if got != nil {
|
||||
t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrap(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
message string
|
||||
want string
|
||||
}{
|
||||
{io.EOF, "read error", "read error: EOF"},
|
||||
{Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := Wrap(tt.err, tt.message).Error()
|
||||
if got != tt.want {
|
||||
t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type nilError struct{}
|
||||
|
||||
func (nilError) Error() string { return "nil error" }
|
||||
|
||||
func TestCause(t *testing.T) {
|
||||
x := New("error")
|
||||
tests := []struct {
|
||||
err error
|
||||
want error
|
||||
}{{
|
||||
// nil error is nil
|
||||
err: nil,
|
||||
want: nil,
|
||||
}, {
|
||||
// explicit nil error is nil
|
||||
err: (error)(nil),
|
||||
want: nil,
|
||||
}, {
|
||||
// typed nil is nil
|
||||
err: (*nilError)(nil),
|
||||
want: (*nilError)(nil),
|
||||
}, {
|
||||
// uncaused error is unaffected
|
||||
err: io.EOF,
|
||||
want: io.EOF,
|
||||
}, {
|
||||
// caused error returns cause
|
||||
err: Wrap(io.EOF, "ignored"),
|
||||
want: io.EOF,
|
||||
}, {
|
||||
err: x, // return from errors.New
|
||||
want: x,
|
||||
}, {
|
||||
WithMessage(nil, "whoops"),
|
||||
nil,
|
||||
}, {
|
||||
WithMessage(io.EOF, "whoops"),
|
||||
io.EOF,
|
||||
}, {
|
||||
WithStack(nil),
|
||||
nil,
|
||||
}, {
|
||||
WithStack(io.EOF),
|
||||
io.EOF,
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
got := Cause(tt.err)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapfNil(t *testing.T) {
|
||||
got := Wrapf(nil, "no error")
|
||||
if got != nil {
|
||||
t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrapf(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
message string
|
||||
want string
|
||||
}{
|
||||
{io.EOF, "read error", "read error: EOF"},
|
||||
{Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"},
|
||||
{Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := Wrapf(tt.err, tt.message).Error()
|
||||
if got != tt.want {
|
||||
t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorf(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
want string
|
||||
}{
|
||||
{Errorf("read error without format specifiers"), "read error without format specifiers"},
|
||||
{Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := tt.err.Error()
|
||||
if got != tt.want {
|
||||
t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithStackNil(t *testing.T) {
|
||||
got := WithStack(nil)
|
||||
if got != nil {
|
||||
t.Errorf("WithStack(nil): got %#v, expected nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithStack(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
want string
|
||||
}{
|
||||
{io.EOF, "EOF"},
|
||||
{WithStack(io.EOF), "EOF"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := WithStack(tt.err).Error()
|
||||
if got != tt.want {
|
||||
t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithMessageNil(t *testing.T) {
|
||||
got := WithMessage(nil, "no error")
|
||||
if got != nil {
|
||||
t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithMessage(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
message string
|
||||
want string
|
||||
}{
|
||||
{io.EOF, "read error", "read error: EOF"},
|
||||
{WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := WithMessage(tt.err, tt.message).Error()
|
||||
if got != tt.want {
|
||||
t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// errors.New, etc values are not expected to be compared by value
|
||||
// but the change in errors#27 made them incomparable. Assert that
|
||||
// various kinds of errors have a functional equality operator, even
|
||||
// if the result of that equality is always false.
|
||||
func TestErrorEquality(t *testing.T) {
|
||||
vals := []error{
|
||||
nil,
|
||||
io.EOF,
|
||||
errors.New("EOF"),
|
||||
New("EOF"),
|
||||
Errorf("EOF"),
|
||||
Wrap(io.EOF, "EOF"),
|
||||
Wrapf(io.EOF, "EOF%d", 2),
|
||||
WithMessage(nil, "whoops"),
|
||||
WithMessage(io.EOF, "whoops"),
|
||||
WithStack(io.EOF),
|
||||
WithStack(nil),
|
||||
}
|
||||
|
||||
for i := range vals {
|
||||
for j := range vals {
|
||||
_ = vals[i] == vals[j] // mustn't panic
|
||||
}
|
||||
}
|
||||
}
|
||||
205
vendor/github.com/pkg/errors/example_test.go
generated
vendored
Normal file
205
vendor/github.com/pkg/errors/example_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package errors_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func ExampleNew() {
|
||||
err := errors.New("whoops")
|
||||
fmt.Println(err)
|
||||
|
||||
// Output: whoops
|
||||
}
|
||||
|
||||
func ExampleNew_printf() {
|
||||
err := errors.New("whoops")
|
||||
fmt.Printf("%+v", err)
|
||||
|
||||
// Example output:
|
||||
// whoops
|
||||
// github.com/pkg/errors_test.ExampleNew_printf
|
||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:17
|
||||
// testing.runExample
|
||||
// /home/dfc/go/src/testing/example.go:114
|
||||
// testing.RunExamples
|
||||
// /home/dfc/go/src/testing/example.go:38
|
||||
// testing.(*M).Run
|
||||
// /home/dfc/go/src/testing/testing.go:744
|
||||
// main.main
|
||||
// /github.com/pkg/errors/_test/_testmain.go:106
|
||||
// runtime.main
|
||||
// /home/dfc/go/src/runtime/proc.go:183
|
||||
// runtime.goexit
|
||||
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
||||
}
|
||||
|
||||
func ExampleWithMessage() {
|
||||
cause := errors.New("whoops")
|
||||
err := errors.WithMessage(cause, "oh noes")
|
||||
fmt.Println(err)
|
||||
|
||||
// Output: oh noes: whoops
|
||||
}
|
||||
|
||||
func ExampleWithStack() {
|
||||
cause := errors.New("whoops")
|
||||
err := errors.WithStack(cause)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output: whoops
|
||||
}
|
||||
|
||||
func ExampleWithStack_printf() {
|
||||
cause := errors.New("whoops")
|
||||
err := errors.WithStack(cause)
|
||||
fmt.Printf("%+v", err)
|
||||
|
||||
// Example Output:
|
||||
// whoops
|
||||
// github.com/pkg/errors_test.ExampleWithStack_printf
|
||||
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55
|
||||
// testing.runExample
|
||||
// /usr/lib/go/src/testing/example.go:114
|
||||
// testing.RunExamples
|
||||
// /usr/lib/go/src/testing/example.go:38
|
||||
// testing.(*M).Run
|
||||
// /usr/lib/go/src/testing/testing.go:744
|
||||
// main.main
|
||||
// github.com/pkg/errors/_test/_testmain.go:106
|
||||
// runtime.main
|
||||
// /usr/lib/go/src/runtime/proc.go:183
|
||||
// runtime.goexit
|
||||
// /usr/lib/go/src/runtime/asm_amd64.s:2086
|
||||
// github.com/pkg/errors_test.ExampleWithStack_printf
|
||||
// /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56
|
||||
// testing.runExample
|
||||
// /usr/lib/go/src/testing/example.go:114
|
||||
// testing.RunExamples
|
||||
// /usr/lib/go/src/testing/example.go:38
|
||||
// testing.(*M).Run
|
||||
// /usr/lib/go/src/testing/testing.go:744
|
||||
// main.main
|
||||
// github.com/pkg/errors/_test/_testmain.go:106
|
||||
// runtime.main
|
||||
// /usr/lib/go/src/runtime/proc.go:183
|
||||
// runtime.goexit
|
||||
// /usr/lib/go/src/runtime/asm_amd64.s:2086
|
||||
}
|
||||
|
||||
func ExampleWrap() {
|
||||
cause := errors.New("whoops")
|
||||
err := errors.Wrap(cause, "oh noes")
|
||||
fmt.Println(err)
|
||||
|
||||
// Output: oh noes: whoops
|
||||
}
|
||||
|
||||
func fn() error {
|
||||
e1 := errors.New("error")
|
||||
e2 := errors.Wrap(e1, "inner")
|
||||
e3 := errors.Wrap(e2, "middle")
|
||||
return errors.Wrap(e3, "outer")
|
||||
}
|
||||
|
||||
func ExampleCause() {
|
||||
err := fn()
|
||||
fmt.Println(err)
|
||||
fmt.Println(errors.Cause(err))
|
||||
|
||||
// Output: outer: middle: inner: error
|
||||
// error
|
||||
}
|
||||
|
||||
func ExampleWrap_extended() {
|
||||
err := fn()
|
||||
fmt.Printf("%+v\n", err)
|
||||
|
||||
// Example output:
|
||||
// error
|
||||
// github.com/pkg/errors_test.fn
|
||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:47
|
||||
// github.com/pkg/errors_test.ExampleCause_printf
|
||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:63
|
||||
// testing.runExample
|
||||
// /home/dfc/go/src/testing/example.go:114
|
||||
// testing.RunExamples
|
||||
// /home/dfc/go/src/testing/example.go:38
|
||||
// testing.(*M).Run
|
||||
// /home/dfc/go/src/testing/testing.go:744
|
||||
// main.main
|
||||
// /github.com/pkg/errors/_test/_testmain.go:104
|
||||
// runtime.main
|
||||
// /home/dfc/go/src/runtime/proc.go:183
|
||||
// runtime.goexit
|
||||
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
||||
// github.com/pkg/errors_test.fn
|
||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner
|
||||
// github.com/pkg/errors_test.fn
|
||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle
|
||||
// github.com/pkg/errors_test.fn
|
||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer
|
||||
}
|
||||
|
||||
func ExampleWrapf() {
|
||||
cause := errors.New("whoops")
|
||||
err := errors.Wrapf(cause, "oh noes #%d", 2)
|
||||
fmt.Println(err)
|
||||
|
||||
// Output: oh noes #2: whoops
|
||||
}
|
||||
|
||||
func ExampleErrorf_extended() {
|
||||
err := errors.Errorf("whoops: %s", "foo")
|
||||
fmt.Printf("%+v", err)
|
||||
|
||||
// Example output:
|
||||
// whoops: foo
|
||||
// github.com/pkg/errors_test.ExampleErrorf
|
||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:101
|
||||
// testing.runExample
|
||||
// /home/dfc/go/src/testing/example.go:114
|
||||
// testing.RunExamples
|
||||
// /home/dfc/go/src/testing/example.go:38
|
||||
// testing.(*M).Run
|
||||
// /home/dfc/go/src/testing/testing.go:744
|
||||
// main.main
|
||||
// /github.com/pkg/errors/_test/_testmain.go:102
|
||||
// runtime.main
|
||||
// /home/dfc/go/src/runtime/proc.go:183
|
||||
// runtime.goexit
|
||||
// /home/dfc/go/src/runtime/asm_amd64.s:2059
|
||||
}
|
||||
|
||||
func Example_stackTrace() {
|
||||
type stackTracer interface {
|
||||
StackTrace() errors.StackTrace
|
||||
}
|
||||
|
||||
err, ok := errors.Cause(fn()).(stackTracer)
|
||||
if !ok {
|
||||
panic("oops, err does not implement stackTracer")
|
||||
}
|
||||
|
||||
st := err.StackTrace()
|
||||
fmt.Printf("%+v", st[0:2]) // top two frames
|
||||
|
||||
// Example output:
|
||||
// github.com/pkg/errors_test.fn
|
||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:47
|
||||
// github.com/pkg/errors_test.Example_stackTrace
|
||||
// /home/dfc/src/github.com/pkg/errors/example_test.go:127
|
||||
}
|
||||
|
||||
func ExampleCause_printf() {
|
||||
err := errors.Wrap(func() error {
|
||||
return func() error {
|
||||
return errors.Errorf("hello %s", fmt.Sprintf("world"))
|
||||
}()
|
||||
}(), "failed")
|
||||
|
||||
fmt.Printf("%v", err)
|
||||
|
||||
// Output: failed: hello world
|
||||
}
|
||||
535
vendor/github.com/pkg/errors/format_test.go
generated
vendored
Normal file
535
vendor/github.com/pkg/errors/format_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,535 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFormatNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
error
|
||||
format string
|
||||
want string
|
||||
}{{
|
||||
New("error"),
|
||||
"%s",
|
||||
"error",
|
||||
}, {
|
||||
New("error"),
|
||||
"%v",
|
||||
"error",
|
||||
}, {
|
||||
New("error"),
|
||||
"%+v",
|
||||
"error\n" +
|
||||
"github.com/pkg/errors.TestFormatNew\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:26",
|
||||
}, {
|
||||
New("error"),
|
||||
"%q",
|
||||
`"error"`,
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatErrorf(t *testing.T) {
|
||||
tests := []struct {
|
||||
error
|
||||
format string
|
||||
want string
|
||||
}{{
|
||||
Errorf("%s", "error"),
|
||||
"%s",
|
||||
"error",
|
||||
}, {
|
||||
Errorf("%s", "error"),
|
||||
"%v",
|
||||
"error",
|
||||
}, {
|
||||
Errorf("%s", "error"),
|
||||
"%+v",
|
||||
"error\n" +
|
||||
"github.com/pkg/errors.TestFormatErrorf\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:56",
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatWrap(t *testing.T) {
|
||||
tests := []struct {
|
||||
error
|
||||
format string
|
||||
want string
|
||||
}{{
|
||||
Wrap(New("error"), "error2"),
|
||||
"%s",
|
||||
"error2: error",
|
||||
}, {
|
||||
Wrap(New("error"), "error2"),
|
||||
"%v",
|
||||
"error2: error",
|
||||
}, {
|
||||
Wrap(New("error"), "error2"),
|
||||
"%+v",
|
||||
"error\n" +
|
||||
"github.com/pkg/errors.TestFormatWrap\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:82",
|
||||
}, {
|
||||
Wrap(io.EOF, "error"),
|
||||
"%s",
|
||||
"error: EOF",
|
||||
}, {
|
||||
Wrap(io.EOF, "error"),
|
||||
"%v",
|
||||
"error: EOF",
|
||||
}, {
|
||||
Wrap(io.EOF, "error"),
|
||||
"%+v",
|
||||
"EOF\n" +
|
||||
"error\n" +
|
||||
"github.com/pkg/errors.TestFormatWrap\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:96",
|
||||
}, {
|
||||
Wrap(Wrap(io.EOF, "error1"), "error2"),
|
||||
"%+v",
|
||||
"EOF\n" +
|
||||
"error1\n" +
|
||||
"github.com/pkg/errors.TestFormatWrap\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:103\n",
|
||||
}, {
|
||||
Wrap(New("error with space"), "context"),
|
||||
"%q",
|
||||
`"context: error with space"`,
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatWrapf(t *testing.T) {
|
||||
tests := []struct {
|
||||
error
|
||||
format string
|
||||
want string
|
||||
}{{
|
||||
Wrapf(io.EOF, "error%d", 2),
|
||||
"%s",
|
||||
"error2: EOF",
|
||||
}, {
|
||||
Wrapf(io.EOF, "error%d", 2),
|
||||
"%v",
|
||||
"error2: EOF",
|
||||
}, {
|
||||
Wrapf(io.EOF, "error%d", 2),
|
||||
"%+v",
|
||||
"EOF\n" +
|
||||
"error2\n" +
|
||||
"github.com/pkg/errors.TestFormatWrapf\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:134",
|
||||
}, {
|
||||
Wrapf(New("error"), "error%d", 2),
|
||||
"%s",
|
||||
"error2: error",
|
||||
}, {
|
||||
Wrapf(New("error"), "error%d", 2),
|
||||
"%v",
|
||||
"error2: error",
|
||||
}, {
|
||||
Wrapf(New("error"), "error%d", 2),
|
||||
"%+v",
|
||||
"error\n" +
|
||||
"github.com/pkg/errors.TestFormatWrapf\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:149",
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
testFormatRegexp(t, i, tt.error, tt.format, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatWithStack(t *testing.T) {
|
||||
tests := []struct {
|
||||
error
|
||||
format string
|
||||
want []string
|
||||
}{{
|
||||
WithStack(io.EOF),
|
||||
"%s",
|
||||
[]string{"EOF"},
|
||||
}, {
|
||||
WithStack(io.EOF),
|
||||
"%v",
|
||||
[]string{"EOF"},
|
||||
}, {
|
||||
WithStack(io.EOF),
|
||||
"%+v",
|
||||
[]string{"EOF",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:175"},
|
||||
}, {
|
||||
WithStack(New("error")),
|
||||
"%s",
|
||||
[]string{"error"},
|
||||
}, {
|
||||
WithStack(New("error")),
|
||||
"%v",
|
||||
[]string{"error"},
|
||||
}, {
|
||||
WithStack(New("error")),
|
||||
"%+v",
|
||||
[]string{"error",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:189",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:189"},
|
||||
}, {
|
||||
WithStack(WithStack(io.EOF)),
|
||||
"%+v",
|
||||
[]string{"EOF",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:197",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:197"},
|
||||
}, {
|
||||
WithStack(WithStack(Wrapf(io.EOF, "message"))),
|
||||
"%+v",
|
||||
[]string{"EOF",
|
||||
"message",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:205",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:205",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:205"},
|
||||
}, {
|
||||
WithStack(Errorf("error%d", 1)),
|
||||
"%+v",
|
||||
[]string{"error1",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:216",
|
||||
"github.com/pkg/errors.TestFormatWithStack\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:216"},
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatWithMessage(t *testing.T) {
|
||||
tests := []struct {
|
||||
error
|
||||
format string
|
||||
want []string
|
||||
}{{
|
||||
WithMessage(New("error"), "error2"),
|
||||
"%s",
|
||||
[]string{"error2: error"},
|
||||
}, {
|
||||
WithMessage(New("error"), "error2"),
|
||||
"%v",
|
||||
[]string{"error2: error"},
|
||||
}, {
|
||||
WithMessage(New("error"), "error2"),
|
||||
"%+v",
|
||||
[]string{
|
||||
"error",
|
||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:244",
|
||||
"error2"},
|
||||
}, {
|
||||
WithMessage(io.EOF, "addition1"),
|
||||
"%s",
|
||||
[]string{"addition1: EOF"},
|
||||
}, {
|
||||
WithMessage(io.EOF, "addition1"),
|
||||
"%v",
|
||||
[]string{"addition1: EOF"},
|
||||
}, {
|
||||
WithMessage(io.EOF, "addition1"),
|
||||
"%+v",
|
||||
[]string{"EOF", "addition1"},
|
||||
}, {
|
||||
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
|
||||
"%v",
|
||||
[]string{"addition2: addition1: EOF"},
|
||||
}, {
|
||||
WithMessage(WithMessage(io.EOF, "addition1"), "addition2"),
|
||||
"%+v",
|
||||
[]string{"EOF", "addition1", "addition2"},
|
||||
}, {
|
||||
Wrap(WithMessage(io.EOF, "error1"), "error2"),
|
||||
"%+v",
|
||||
[]string{"EOF", "error1", "error2",
|
||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:272"},
|
||||
}, {
|
||||
WithMessage(Errorf("error%d", 1), "error2"),
|
||||
"%+v",
|
||||
[]string{"error1",
|
||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:278",
|
||||
"error2"},
|
||||
}, {
|
||||
WithMessage(WithStack(io.EOF), "error"),
|
||||
"%+v",
|
||||
[]string{
|
||||
"EOF",
|
||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:285",
|
||||
"error"},
|
||||
}, {
|
||||
WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"),
|
||||
"%+v",
|
||||
[]string{
|
||||
"EOF",
|
||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:293",
|
||||
"inside-error",
|
||||
"github.com/pkg/errors.TestFormatWithMessage\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:293",
|
||||
"outside-error"},
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatGeneric(t *testing.T) {
|
||||
starts := []struct {
|
||||
err error
|
||||
want []string
|
||||
}{
|
||||
{New("new-error"), []string{
|
||||
"new-error",
|
||||
"github.com/pkg/errors.TestFormatGeneric\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:315"},
|
||||
}, {Errorf("errorf-error"), []string{
|
||||
"errorf-error",
|
||||
"github.com/pkg/errors.TestFormatGeneric\n" +
|
||||
"\t.+/github.com/pkg/errors/format_test.go:319"},
|
||||
}, {errors.New("errors-new-error"), []string{
|
||||
"errors-new-error"},
|
||||
},
|
||||
}
|
||||
|
||||
wrappers := []wrapper{
|
||||
{
|
||||
func(err error) error { return WithMessage(err, "with-message") },
|
||||
[]string{"with-message"},
|
||||
}, {
|
||||
func(err error) error { return WithStack(err) },
|
||||
[]string{
|
||||
"github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" +
|
||||
".+/github.com/pkg/errors/format_test.go:333",
|
||||
},
|
||||
}, {
|
||||
func(err error) error { return Wrap(err, "wrap-error") },
|
||||
[]string{
|
||||
"wrap-error",
|
||||
"github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" +
|
||||
".+/github.com/pkg/errors/format_test.go:339",
|
||||
},
|
||||
}, {
|
||||
func(err error) error { return Wrapf(err, "wrapf-error%d", 1) },
|
||||
[]string{
|
||||
"wrapf-error1",
|
||||
"github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" +
|
||||
".+/github.com/pkg/errors/format_test.go:346",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for s := range starts {
|
||||
err := starts[s].err
|
||||
want := starts[s].want
|
||||
testFormatCompleteCompare(t, s, err, "%+v", want, false)
|
||||
testGenericRecursive(t, err, want, wrappers, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) {
|
||||
got := fmt.Sprintf(format, arg)
|
||||
gotLines := strings.SplitN(got, "\n", -1)
|
||||
wantLines := strings.SplitN(want, "\n", -1)
|
||||
|
||||
if len(wantLines) > len(gotLines) {
|
||||
t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want)
|
||||
return
|
||||
}
|
||||
|
||||
for i, w := range wantLines {
|
||||
match, err := regexp.MatchString(w, gotLines[i])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !match {
|
||||
t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stackLineR = regexp.MustCompile(`\.`)
|
||||
|
||||
// parseBlocks parses input into a slice, where:
|
||||
// - incase entry contains a newline, its a stacktrace
|
||||
// - incase entry contains no newline, its a solo line.
|
||||
//
|
||||
// Detecting stack boundaries only works incase the WithStack-calls are
|
||||
// to be found on the same line, thats why it is optionally here.
|
||||
//
|
||||
// Example use:
|
||||
//
|
||||
// for _, e := range blocks {
|
||||
// if strings.ContainsAny(e, "\n") {
|
||||
// // Match as stack
|
||||
// } else {
|
||||
// // Match as line
|
||||
// }
|
||||
// }
|
||||
//
|
||||
func parseBlocks(input string, detectStackboundaries bool) ([]string, error) {
|
||||
var blocks []string
|
||||
|
||||
stack := ""
|
||||
wasStack := false
|
||||
lines := map[string]bool{} // already found lines
|
||||
|
||||
for _, l := range strings.Split(input, "\n") {
|
||||
isStackLine := stackLineR.MatchString(l)
|
||||
|
||||
switch {
|
||||
case !isStackLine && wasStack:
|
||||
blocks = append(blocks, stack, l)
|
||||
stack = ""
|
||||
lines = map[string]bool{}
|
||||
case isStackLine:
|
||||
if wasStack {
|
||||
// Detecting two stacks after another, possible cause lines match in
|
||||
// our tests due to WithStack(WithStack(io.EOF)) on same line.
|
||||
if detectStackboundaries {
|
||||
if lines[l] {
|
||||
if len(stack) == 0 {
|
||||
return nil, errors.New("len of block must not be zero here")
|
||||
}
|
||||
|
||||
blocks = append(blocks, stack)
|
||||
stack = l
|
||||
lines = map[string]bool{l: true}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
stack = stack + "\n" + l
|
||||
} else {
|
||||
stack = l
|
||||
}
|
||||
lines[l] = true
|
||||
case !isStackLine && !wasStack:
|
||||
blocks = append(blocks, l)
|
||||
default:
|
||||
return nil, errors.New("must not happen")
|
||||
}
|
||||
|
||||
wasStack = isStackLine
|
||||
}
|
||||
|
||||
// Use up stack
|
||||
if stack != "" {
|
||||
blocks = append(blocks, stack)
|
||||
}
|
||||
return blocks, nil
|
||||
}
|
||||
|
||||
func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) {
|
||||
gotStr := fmt.Sprintf(format, arg)
|
||||
|
||||
got, err := parseBlocks(gotStr, detectStackBoundaries)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q",
|
||||
n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr)
|
||||
}
|
||||
|
||||
for i := range got {
|
||||
if strings.ContainsAny(want[i], "\n") {
|
||||
// Match as stack
|
||||
match, err := regexp.MatchString(want[i], got[i])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !match {
|
||||
t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n",
|
||||
n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want))
|
||||
}
|
||||
} else {
|
||||
// Match as message
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type wrapper struct {
|
||||
wrap func(err error) error
|
||||
want []string
|
||||
}
|
||||
|
||||
func prettyBlocks(blocks []string, prefix ...string) string {
|
||||
var out []string
|
||||
|
||||
for _, b := range blocks {
|
||||
out = append(out, fmt.Sprintf("%v", b))
|
||||
}
|
||||
|
||||
return " " + strings.Join(out, "\n ")
|
||||
}
|
||||
|
||||
func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) {
|
||||
if len(beforeWant) == 0 {
|
||||
panic("beforeWant must not be empty")
|
||||
}
|
||||
for _, w := range list {
|
||||
if len(w.want) == 0 {
|
||||
panic("want must not be empty")
|
||||
}
|
||||
|
||||
err := w.wrap(beforeErr)
|
||||
|
||||
// Copy required cause append(beforeWant, ..) modified beforeWant subtly.
|
||||
beforeCopy := make([]string, len(beforeWant))
|
||||
copy(beforeCopy, beforeWant)
|
||||
|
||||
beforeWant := beforeCopy
|
||||
last := len(beforeWant) - 1
|
||||
var want []string
|
||||
|
||||
// Merge two stacks behind each other.
|
||||
if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") {
|
||||
want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...)
|
||||
} else {
|
||||
want = append(beforeWant, w.want...)
|
||||
}
|
||||
|
||||
testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false)
|
||||
if maxDepth > 0 {
|
||||
testGenericRecursive(t, err, want, list, maxDepth-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
178
vendor/github.com/pkg/errors/stack.go
generated
vendored
Normal file
178
vendor/github.com/pkg/errors/stack.go
generated
vendored
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s path of source file relative to the compile time GOPATH
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
||||
|
||||
func trimGOPATH(name, file string) string {
|
||||
// Here we want to get the source file path relative to the compile time
|
||||
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||
// the import path, which does not include the GOPATH. Thus we can trim
|
||||
// segments from the beginning of the file path until the number of path
|
||||
// separators remaining is one more than the number of path separators in
|
||||
// the function name. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// file /home/user/src/pkg/sub/file.go
|
||||
// fn.Name() pkg/sub.Type.Method
|
||||
//
|
||||
// We want to produce:
|
||||
//
|
||||
// pkg/sub/file.go
|
||||
//
|
||||
// From this we can easily see that fn.Name() has one less path separator
|
||||
// than our desired output. We count separators from the end of the file
|
||||
// path until it finds two more than in the function name and then move
|
||||
// one character forward to preserve the initial path segment without a
|
||||
// leading separator.
|
||||
const sep = "/"
|
||||
goal := strings.Count(name, sep) + 2
|
||||
i := len(file)
|
||||
for n := 0; n < goal; n++ {
|
||||
i = strings.LastIndex(file[:i], sep)
|
||||
if i == -1 {
|
||||
// not enough separators found, set i so that the slice expression
|
||||
// below leaves file unmodified
|
||||
i = -len(sep)
|
||||
break
|
||||
}
|
||||
}
|
||||
// get back to 0 or trim the leading separator
|
||||
file = file[i+len(sep):]
|
||||
return file
|
||||
}
|
||||
292
vendor/github.com/pkg/errors/stack_test.go
generated
vendored
Normal file
292
vendor/github.com/pkg/errors/stack_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var initpc, _, _, _ = runtime.Caller(0)
|
||||
|
||||
func TestFrameLine(t *testing.T) {
|
||||
var tests = []struct {
|
||||
Frame
|
||||
want int
|
||||
}{{
|
||||
Frame(initpc),
|
||||
9,
|
||||
}, {
|
||||
func() Frame {
|
||||
var pc, _, _, _ = runtime.Caller(0)
|
||||
return Frame(pc)
|
||||
}(),
|
||||
20,
|
||||
}, {
|
||||
func() Frame {
|
||||
var pc, _, _, _ = runtime.Caller(1)
|
||||
return Frame(pc)
|
||||
}(),
|
||||
28,
|
||||
}, {
|
||||
Frame(0), // invalid PC
|
||||
0,
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := tt.Frame.line()
|
||||
want := tt.want
|
||||
if want != got {
|
||||
t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type X struct{}
|
||||
|
||||
func (x X) val() Frame {
|
||||
var pc, _, _, _ = runtime.Caller(0)
|
||||
return Frame(pc)
|
||||
}
|
||||
|
||||
func (x *X) ptr() Frame {
|
||||
var pc, _, _, _ = runtime.Caller(0)
|
||||
return Frame(pc)
|
||||
}
|
||||
|
||||
func TestFrameFormat(t *testing.T) {
|
||||
var tests = []struct {
|
||||
Frame
|
||||
format string
|
||||
want string
|
||||
}{{
|
||||
Frame(initpc),
|
||||
"%s",
|
||||
"stack_test.go",
|
||||
}, {
|
||||
Frame(initpc),
|
||||
"%+s",
|
||||
"github.com/pkg/errors.init\n" +
|
||||
"\t.+/github.com/pkg/errors/stack_test.go",
|
||||
}, {
|
||||
Frame(0),
|
||||
"%s",
|
||||
"unknown",
|
||||
}, {
|
||||
Frame(0),
|
||||
"%+s",
|
||||
"unknown",
|
||||
}, {
|
||||
Frame(initpc),
|
||||
"%d",
|
||||
"9",
|
||||
}, {
|
||||
Frame(0),
|
||||
"%d",
|
||||
"0",
|
||||
}, {
|
||||
Frame(initpc),
|
||||
"%n",
|
||||
"init",
|
||||
}, {
|
||||
func() Frame {
|
||||
var x X
|
||||
return x.ptr()
|
||||
}(),
|
||||
"%n",
|
||||
`\(\*X\).ptr`,
|
||||
}, {
|
||||
func() Frame {
|
||||
var x X
|
||||
return x.val()
|
||||
}(),
|
||||
"%n",
|
||||
"X.val",
|
||||
}, {
|
||||
Frame(0),
|
||||
"%n",
|
||||
"",
|
||||
}, {
|
||||
Frame(initpc),
|
||||
"%v",
|
||||
"stack_test.go:9",
|
||||
}, {
|
||||
Frame(initpc),
|
||||
"%+v",
|
||||
"github.com/pkg/errors.init\n" +
|
||||
"\t.+/github.com/pkg/errors/stack_test.go:9",
|
||||
}, {
|
||||
Frame(0),
|
||||
"%v",
|
||||
"unknown:0",
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
testFormatRegexp(t, i, tt.Frame, tt.format, tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFuncname(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, want string
|
||||
}{
|
||||
{"", ""},
|
||||
{"runtime.main", "main"},
|
||||
{"github.com/pkg/errors.funcname", "funcname"},
|
||||
{"funcname", "funcname"},
|
||||
{"io.copyBuffer", "copyBuffer"},
|
||||
{"main.(*R).Write", "(*R).Write"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := funcname(tt.name)
|
||||
want := tt.want
|
||||
if got != want {
|
||||
t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimGOPATH(t *testing.T) {
|
||||
var tests = []struct {
|
||||
Frame
|
||||
want string
|
||||
}{{
|
||||
Frame(initpc),
|
||||
"github.com/pkg/errors/stack_test.go",
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
pc := tt.Frame.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
file, _ := fn.FileLine(pc)
|
||||
got := trimGOPATH(fn.Name(), file)
|
||||
testFormatRegexp(t, i, got, "%s", tt.want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackTrace(t *testing.T) {
|
||||
tests := []struct {
|
||||
err error
|
||||
want []string
|
||||
}{{
|
||||
New("ooh"), []string{
|
||||
"github.com/pkg/errors.TestStackTrace\n" +
|
||||
"\t.+/github.com/pkg/errors/stack_test.go:172",
|
||||
},
|
||||
}, {
|
||||
Wrap(New("ooh"), "ahh"), []string{
|
||||
"github.com/pkg/errors.TestStackTrace\n" +
|
||||
"\t.+/github.com/pkg/errors/stack_test.go:177", // this is the stack of Wrap, not New
|
||||
},
|
||||
}, {
|
||||
Cause(Wrap(New("ooh"), "ahh")), []string{
|
||||
"github.com/pkg/errors.TestStackTrace\n" +
|
||||
"\t.+/github.com/pkg/errors/stack_test.go:182", // this is the stack of New
|
||||
},
|
||||
}, {
|
||||
func() error { return New("ooh") }(), []string{
|
||||
`github.com/pkg/errors.(func·009|TestStackTrace.func1)` +
|
||||
"\n\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New
|
||||
"github.com/pkg/errors.TestStackTrace\n" +
|
||||
"\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New's caller
|
||||
},
|
||||
}, {
|
||||
Cause(func() error {
|
||||
return func() error {
|
||||
return Errorf("hello %s", fmt.Sprintf("world"))
|
||||
}()
|
||||
}()), []string{
|
||||
`github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` +
|
||||
"\n\t.+/github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf
|
||||
`github.com/pkg/errors.(func·011|TestStackTrace.func2)` +
|
||||
"\n\t.+/github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller
|
||||
"github.com/pkg/errors.TestStackTrace\n" +
|
||||
"\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller
|
||||
},
|
||||
}}
|
||||
for i, tt := range tests {
|
||||
x, ok := tt.err.(interface {
|
||||
StackTrace() StackTrace
|
||||
})
|
||||
if !ok {
|
||||
t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err)
|
||||
continue
|
||||
}
|
||||
st := x.StackTrace()
|
||||
for j, want := range tt.want {
|
||||
testFormatRegexp(t, i, st[j], "%+v", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stackTrace() StackTrace {
|
||||
const depth = 8
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(1, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return st.StackTrace()
|
||||
}
|
||||
|
||||
func TestStackTraceFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
StackTrace
|
||||
format string
|
||||
want string
|
||||
}{{
|
||||
nil,
|
||||
"%s",
|
||||
`\[\]`,
|
||||
}, {
|
||||
nil,
|
||||
"%v",
|
||||
`\[\]`,
|
||||
}, {
|
||||
nil,
|
||||
"%+v",
|
||||
"",
|
||||
}, {
|
||||
nil,
|
||||
"%#v",
|
||||
`\[\]errors.Frame\(nil\)`,
|
||||
}, {
|
||||
make(StackTrace, 0),
|
||||
"%s",
|
||||
`\[\]`,
|
||||
}, {
|
||||
make(StackTrace, 0),
|
||||
"%v",
|
||||
`\[\]`,
|
||||
}, {
|
||||
make(StackTrace, 0),
|
||||
"%+v",
|
||||
"",
|
||||
}, {
|
||||
make(StackTrace, 0),
|
||||
"%#v",
|
||||
`\[\]errors.Frame{}`,
|
||||
}, {
|
||||
stackTrace()[:2],
|
||||
"%s",
|
||||
`\[stack_test.go stack_test.go\]`,
|
||||
}, {
|
||||
stackTrace()[:2],
|
||||
"%v",
|
||||
`\[stack_test.go:225 stack_test.go:272\]`,
|
||||
}, {
|
||||
stackTrace()[:2],
|
||||
"%+v",
|
||||
"\n" +
|
||||
"github.com/pkg/errors.stackTrace\n" +
|
||||
"\t.+/github.com/pkg/errors/stack_test.go:225\n" +
|
||||
"github.com/pkg/errors.TestStackTraceFormat\n" +
|
||||
"\t.+/github.com/pkg/errors/stack_test.go:276",
|
||||
}, {
|
||||
stackTrace()[:2],
|
||||
"%#v",
|
||||
`\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`,
|
||||
}}
|
||||
|
||||
for i, tt := range tests {
|
||||
testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want)
|
||||
}
|
||||
}
|
||||
11
vendor/github.com/pkg/profile/.travis.yml
generated
vendored
Normal file
11
vendor/github.com/pkg/profile/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
language: go
|
||||
go_import_path: github.com/pkg/profile
|
||||
go:
|
||||
- 1.4.3
|
||||
- 1.5.2
|
||||
- 1.6.3
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test github.com/pkg/profile
|
||||
- go test -race github.com/pkg/profile
|
||||
1
vendor/github.com/pkg/profile/AUTHORS
generated
vendored
Normal file
1
vendor/github.com/pkg/profile/AUTHORS
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Dave Cheney <dave@cheney.net>
|
||||
24
vendor/github.com/pkg/profile/LICENSE
generated
vendored
Normal file
24
vendor/github.com/pkg/profile/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2013 Dave Cheney. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
54
vendor/github.com/pkg/profile/README.md
generated
vendored
Normal file
54
vendor/github.com/pkg/profile/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
profile
|
||||
=======
|
||||
|
||||
Simple profiling support package for Go
|
||||
|
||||
[](https://travis-ci.org/pkg/profile) [](http://godoc.org/github.com/pkg/profile)
|
||||
|
||||
|
||||
installation
|
||||
------------
|
||||
|
||||
go get github.com/pkg/profile
|
||||
|
||||
usage
|
||||
-----
|
||||
|
||||
Enabling profiling in your application is as simple as one line at the top of your main function
|
||||
|
||||
```go
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
defer profile.Start().Stop()
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
options
|
||||
-------
|
||||
|
||||
What to profile is controlled by config value passed to profile.Start.
|
||||
By default CPU profiling is enabled.
|
||||
|
||||
```go
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
// p.Stop() must be called before the program exits to
|
||||
// ensure profiling information is written to disk.
|
||||
p := profile.Start(profile.MemProfile, profile.ProfilePath("."), profile.NoShutdownHook)
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Several convenience package level values are provided for cpu, memory, and block (contention) profiling.
|
||||
|
||||
For more complex options, consult the [documentation](http://godoc.org/github.com/pkg/profile).
|
||||
|
||||
contributing
|
||||
------------
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports.
|
||||
|
||||
Before proposing a change, please discuss it first by raising an issue.
|
||||
58
vendor/github.com/pkg/profile/example_test.go
generated
vendored
Normal file
58
vendor/github.com/pkg/profile/example_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package profile_test
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/profile"
|
||||
)
|
||||
|
||||
func ExampleStart() {
|
||||
// start a simple CPU profile and register
|
||||
// a defer to Stop (flush) the profiling data.
|
||||
defer profile.Start().Stop()
|
||||
}
|
||||
|
||||
func ExampleCPUProfile() {
|
||||
// CPU profiling is the default profiling mode, but you can specify it
|
||||
// explicitly for completeness.
|
||||
defer profile.Start(profile.CPUProfile).Stop()
|
||||
}
|
||||
|
||||
func ExampleMemProfile() {
|
||||
// use memory profiling, rather than the default cpu profiling.
|
||||
defer profile.Start(profile.MemProfile).Stop()
|
||||
}
|
||||
|
||||
func ExampleMemProfileRate() {
|
||||
// use memory profiling with custom rate.
|
||||
defer profile.Start(profile.MemProfileRate(2048)).Stop()
|
||||
}
|
||||
|
||||
func ExampleProfilePath() {
|
||||
// set the location that the profile will be written to
|
||||
defer profile.Start(profile.ProfilePath(os.Getenv("HOME"))).Stop()
|
||||
}
|
||||
|
||||
func ExampleNoShutdownHook() {
|
||||
// disable the automatic shutdown hook.
|
||||
defer profile.Start(profile.NoShutdownHook).Stop()
|
||||
}
|
||||
|
||||
func ExampleStart_withFlags() {
|
||||
// use the flags package to selectively enable profiling.
|
||||
mode := flag.String("profile.mode", "", "enable profiling mode, one of [cpu, mem, mutex, block]")
|
||||
flag.Parse()
|
||||
switch *mode {
|
||||
case "cpu":
|
||||
defer profile.Start(profile.CPUProfile).Stop()
|
||||
case "mem":
|
||||
defer profile.Start(profile.MemProfile).Stop()
|
||||
case "mutex":
|
||||
defer profile.Start(profile.MutexProfile).Stop()
|
||||
case "block":
|
||||
defer profile.Start(profile.BlockProfile).Stop()
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
13
vendor/github.com/pkg/profile/mutex.go
generated
vendored
Normal file
13
vendor/github.com/pkg/profile/mutex.go
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// +build go1.8
|
||||
|
||||
package profile
|
||||
|
||||
import "runtime"
|
||||
|
||||
func enableMutexProfile() {
|
||||
runtime.SetMutexProfileFraction(1)
|
||||
}
|
||||
|
||||
func disableMutexProfile() {
|
||||
runtime.SetMutexProfileFraction(0)
|
||||
}
|
||||
9
vendor/github.com/pkg/profile/mutex17.go
generated
vendored
Normal file
9
vendor/github.com/pkg/profile/mutex17.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// +build !go1.8
|
||||
|
||||
package profile
|
||||
|
||||
// mock mutex support for Go 1.7 and earlier.
|
||||
|
||||
func enableMutexProfile() {}
|
||||
|
||||
func disableMutexProfile() {}
|
||||
244
vendor/github.com/pkg/profile/profile.go
generated
vendored
Normal file
244
vendor/github.com/pkg/profile/profile.go
generated
vendored
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
// Package profile provides a simple way to manage runtime/pprof
|
||||
// profiling of your Go application.
|
||||
package profile
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
const (
|
||||
cpuMode = iota
|
||||
memMode
|
||||
mutexMode
|
||||
blockMode
|
||||
traceMode
|
||||
)
|
||||
|
||||
// Profile represents an active profiling session.
|
||||
type Profile struct {
|
||||
// quiet suppresses informational messages during profiling.
|
||||
quiet bool
|
||||
|
||||
// noShutdownHook controls whether the profiling package should
|
||||
// hook SIGINT to write profiles cleanly.
|
||||
noShutdownHook bool
|
||||
|
||||
// mode holds the type of profiling that will be made
|
||||
mode int
|
||||
|
||||
// path holds the base path where various profiling files are written.
|
||||
// If blank, the base path will be generated by ioutil.TempDir.
|
||||
path string
|
||||
|
||||
// memProfileRate holds the rate for the memory profile.
|
||||
memProfileRate int
|
||||
|
||||
// closer holds a cleanup function that run after each profile
|
||||
closer func()
|
||||
|
||||
// stopped records if a call to profile.Stop has been made
|
||||
stopped uint32
|
||||
}
|
||||
|
||||
// NoShutdownHook controls whether the profiling package should
|
||||
// hook SIGINT to write profiles cleanly.
|
||||
// Programs with more sophisticated signal handling should set
|
||||
// this to true and ensure the Stop() function returned from Start()
|
||||
// is called during shutdown.
|
||||
func NoShutdownHook(p *Profile) { p.noShutdownHook = true }
|
||||
|
||||
// Quiet suppresses informational messages during profiling.
|
||||
func Quiet(p *Profile) { p.quiet = true }
|
||||
|
||||
// CPUProfile enables cpu profiling.
|
||||
// It disables any previous profiling settings.
|
||||
func CPUProfile(p *Profile) { p.mode = cpuMode }
|
||||
|
||||
// DefaultMemProfileRate is the default memory profiling rate.
|
||||
// See also http://golang.org/pkg/runtime/#pkg-variables
|
||||
const DefaultMemProfileRate = 4096
|
||||
|
||||
// MemProfile enables memory profiling.
|
||||
// It disables any previous profiling settings.
|
||||
func MemProfile(p *Profile) {
|
||||
p.memProfileRate = DefaultMemProfileRate
|
||||
p.mode = memMode
|
||||
}
|
||||
|
||||
// MemProfileRate enables memory profiling at the preferred rate.
|
||||
// It disables any previous profiling settings.
|
||||
func MemProfileRate(rate int) func(*Profile) {
|
||||
return func(p *Profile) {
|
||||
p.memProfileRate = rate
|
||||
p.mode = memMode
|
||||
}
|
||||
}
|
||||
|
||||
// MutexProfile enables mutex profiling.
|
||||
// It disables any previous profiling settings.
|
||||
//
|
||||
// Mutex profiling is a no-op before go1.8.
|
||||
func MutexProfile(p *Profile) { p.mode = mutexMode }
|
||||
|
||||
// BlockProfile enables block (contention) profiling.
|
||||
// It disables any previous profiling settings.
|
||||
func BlockProfile(p *Profile) { p.mode = blockMode }
|
||||
|
||||
// Trace profile controls if execution tracing will be enabled. It disables any previous profiling settings.
|
||||
func TraceProfile(p *Profile) { p.mode = traceMode }
|
||||
|
||||
// ProfilePath controls the base path where various profiling
|
||||
// files are written. If blank, the base path will be generated
|
||||
// by ioutil.TempDir.
|
||||
func ProfilePath(path string) func(*Profile) {
|
||||
return func(p *Profile) {
|
||||
p.path = path
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the profile and flushes any unwritten data.
|
||||
func (p *Profile) Stop() {
|
||||
if !atomic.CompareAndSwapUint32(&p.stopped, 0, 1) {
|
||||
// someone has already called close
|
||||
return
|
||||
}
|
||||
p.closer()
|
||||
atomic.StoreUint32(&started, 0)
|
||||
}
|
||||
|
||||
// started is non zero if a profile is running.
|
||||
var started uint32
|
||||
|
||||
// Start starts a new profiling session.
|
||||
// The caller should call the Stop method on the value returned
|
||||
// to cleanly stop profiling.
|
||||
func Start(options ...func(*Profile)) interface {
|
||||
Stop()
|
||||
} {
|
||||
if !atomic.CompareAndSwapUint32(&started, 0, 1) {
|
||||
log.Fatal("profile: Start() already called")
|
||||
}
|
||||
|
||||
var prof Profile
|
||||
for _, option := range options {
|
||||
option(&prof)
|
||||
}
|
||||
|
||||
path, err := func() (string, error) {
|
||||
if p := prof.path; p != "" {
|
||||
return p, os.MkdirAll(p, 0777)
|
||||
}
|
||||
return ioutil.TempDir("", "profile")
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("profile: could not create initial output directory: %v", err)
|
||||
}
|
||||
|
||||
logf := func(format string, args ...interface{}) {
|
||||
if !prof.quiet {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
switch prof.mode {
|
||||
case cpuMode:
|
||||
fn := filepath.Join(path, "cpu.pprof")
|
||||
f, err := os.Create(fn)
|
||||
if err != nil {
|
||||
log.Fatalf("profile: could not create cpu profile %q: %v", fn, err)
|
||||
}
|
||||
logf("profile: cpu profiling enabled, %s", fn)
|
||||
pprof.StartCPUProfile(f)
|
||||
prof.closer = func() {
|
||||
pprof.StopCPUProfile()
|
||||
f.Close()
|
||||
logf("profile: cpu profiling disabled, %s", fn)
|
||||
}
|
||||
|
||||
case memMode:
|
||||
fn := filepath.Join(path, "mem.pprof")
|
||||
f, err := os.Create(fn)
|
||||
if err != nil {
|
||||
log.Fatalf("profile: could not create memory profile %q: %v", fn, err)
|
||||
}
|
||||
old := runtime.MemProfileRate
|
||||
runtime.MemProfileRate = prof.memProfileRate
|
||||
logf("profile: memory profiling enabled (rate %d), %s", runtime.MemProfileRate, fn)
|
||||
prof.closer = func() {
|
||||
pprof.Lookup("heap").WriteTo(f, 0)
|
||||
f.Close()
|
||||
runtime.MemProfileRate = old
|
||||
logf("profile: memory profiling disabled, %s", fn)
|
||||
}
|
||||
|
||||
case mutexMode:
|
||||
fn := filepath.Join(path, "mutex.pprof")
|
||||
f, err := os.Create(fn)
|
||||
if err != nil {
|
||||
log.Fatalf("profile: could not create mutex profile %q: %v", fn, err)
|
||||
}
|
||||
enableMutexProfile()
|
||||
logf("profile: mutex profiling enabled, %s", fn)
|
||||
prof.closer = func() {
|
||||
if mp := pprof.Lookup("mutex"); mp != nil {
|
||||
mp.WriteTo(f, 0)
|
||||
}
|
||||
f.Close()
|
||||
disableMutexProfile()
|
||||
logf("profile: mutex profiling disabled, %s", fn)
|
||||
}
|
||||
|
||||
case blockMode:
|
||||
fn := filepath.Join(path, "block.pprof")
|
||||
f, err := os.Create(fn)
|
||||
if err != nil {
|
||||
log.Fatalf("profile: could not create block profile %q: %v", fn, err)
|
||||
}
|
||||
runtime.SetBlockProfileRate(1)
|
||||
logf("profile: block profiling enabled, %s", fn)
|
||||
prof.closer = func() {
|
||||
pprof.Lookup("block").WriteTo(f, 0)
|
||||
f.Close()
|
||||
runtime.SetBlockProfileRate(0)
|
||||
logf("profile: block profiling disabled, %s", fn)
|
||||
}
|
||||
|
||||
case traceMode:
|
||||
fn := filepath.Join(path, "trace.out")
|
||||
f, err := os.Create(fn)
|
||||
if err != nil {
|
||||
log.Fatalf("profile: could not create trace output file %q: %v", fn, err)
|
||||
}
|
||||
if err := startTrace(f); err != nil {
|
||||
log.Fatalf("profile: could not start trace: %v", err)
|
||||
}
|
||||
logf("profile: trace enabled, %s", fn)
|
||||
prof.closer = func() {
|
||||
stopTrace()
|
||||
logf("profile: trace disabled, %s", fn)
|
||||
}
|
||||
}
|
||||
|
||||
if !prof.noShutdownHook {
|
||||
go func() {
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
<-c
|
||||
|
||||
log.Println("profile: caught interrupt, stopping profiles")
|
||||
prof.Stop()
|
||||
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
return &prof
|
||||
}
|
||||
330
vendor/github.com/pkg/profile/profile_test.go
generated
vendored
Normal file
330
vendor/github.com/pkg/profile/profile_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type checkFn func(t *testing.T, stdout, stderr []byte, err error)
|
||||
|
||||
func TestProfile(t *testing.T) {
|
||||
f, err := ioutil.TempFile("", "profile_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
var profileTests = []struct {
|
||||
name string
|
||||
code string
|
||||
checks []checkFn
|
||||
}{{
|
||||
name: "default profile (cpu)",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
defer profile.Start().Stop()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{
|
||||
NoStdout,
|
||||
Stderr("profile: cpu profiling enabled"),
|
||||
NoErr,
|
||||
},
|
||||
}, {
|
||||
name: "memory profile",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
defer profile.Start(profile.MemProfile).Stop()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{
|
||||
NoStdout,
|
||||
Stderr("profile: memory profiling enabled"),
|
||||
NoErr,
|
||||
},
|
||||
}, {
|
||||
name: "memory profile (rate 2048)",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
defer profile.Start(profile.MemProfileRate(2048)).Stop()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{
|
||||
NoStdout,
|
||||
Stderr("profile: memory profiling enabled (rate 2048)"),
|
||||
NoErr,
|
||||
},
|
||||
}, {
|
||||
name: "double start",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
profile.Start()
|
||||
profile.Start()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{
|
||||
NoStdout,
|
||||
Stderr("cpu profiling enabled", "profile: Start() already called"),
|
||||
Err,
|
||||
},
|
||||
}, {
|
||||
name: "block profile",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
defer profile.Start(profile.BlockProfile).Stop()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{
|
||||
NoStdout,
|
||||
Stderr("profile: block profiling enabled"),
|
||||
NoErr,
|
||||
},
|
||||
}, {
|
||||
name: "mutex profile",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
defer profile.Start(profile.MutexProfile).Stop()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{
|
||||
NoStdout,
|
||||
Stderr("profile: mutex profiling enabled"),
|
||||
NoErr,
|
||||
},
|
||||
}, {
|
||||
name: "profile path",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
defer profile.Start(profile.ProfilePath(".")).Stop()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{
|
||||
NoStdout,
|
||||
Stderr("profile: cpu profiling enabled, cpu.pprof"),
|
||||
NoErr,
|
||||
},
|
||||
}, {
|
||||
name: "profile path error",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
defer profile.Start(profile.ProfilePath("` + f.Name() + `")).Stop()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{
|
||||
NoStdout,
|
||||
Stderr("could not create initial output"),
|
||||
Err,
|
||||
},
|
||||
}, {
|
||||
name: "multiple profile sessions",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
profile.Start(profile.CPUProfile).Stop()
|
||||
profile.Start(profile.MemProfile).Stop()
|
||||
profile.Start(profile.BlockProfile).Stop()
|
||||
profile.Start(profile.CPUProfile).Stop()
|
||||
profile.Start(profile.MutexProfile).Stop()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{
|
||||
NoStdout,
|
||||
Stderr("profile: cpu profiling enabled",
|
||||
"profile: cpu profiling disabled",
|
||||
"profile: memory profiling enabled",
|
||||
"profile: memory profiling disabled",
|
||||
"profile: block profiling enabled",
|
||||
"profile: block profiling disabled",
|
||||
"profile: cpu profiling enabled",
|
||||
"profile: cpu profiling disabled",
|
||||
"profile: mutex profiling enabled",
|
||||
"profile: mutex profiling disabled"),
|
||||
NoErr,
|
||||
},
|
||||
}, {
|
||||
name: "profile quiet",
|
||||
code: `
|
||||
package main
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func main() {
|
||||
defer profile.Start(profile.Quiet).Stop()
|
||||
}
|
||||
`,
|
||||
checks: []checkFn{NoStdout, NoStderr, NoErr},
|
||||
}}
|
||||
for _, tt := range profileTests {
|
||||
t.Log(tt.name)
|
||||
stdout, stderr, err := runTest(t, tt.code)
|
||||
for _, f := range tt.checks {
|
||||
f(t, stdout, stderr, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NoStdout checks that stdout was blank.
|
||||
func NoStdout(t *testing.T, stdout, _ []byte, _ error) {
|
||||
if len := len(stdout); len > 0 {
|
||||
t.Errorf("stdout: wanted 0 bytes, got %d", len)
|
||||
}
|
||||
}
|
||||
|
||||
// Stderr verifies that the given lines match the output from stderr
|
||||
func Stderr(lines ...string) checkFn {
|
||||
return func(t *testing.T, _, stderr []byte, _ error) {
|
||||
r := bytes.NewReader(stderr)
|
||||
if !validateOutput(r, lines) {
|
||||
t.Errorf("stderr: wanted '%s', got '%s'", lines, stderr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NoStderr checks that stderr was blank.
|
||||
func NoStderr(t *testing.T, _, stderr []byte, _ error) {
|
||||
if len := len(stderr); len > 0 {
|
||||
t.Errorf("stderr: wanted 0 bytes, got %d", len)
|
||||
}
|
||||
}
|
||||
|
||||
// Err checks that there was an error returned
|
||||
func Err(t *testing.T, _, _ []byte, err error) {
|
||||
if err == nil {
|
||||
t.Errorf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
// NoErr checks that err was nil
|
||||
func NoErr(t *testing.T, _, _ []byte, err error) {
|
||||
if err != nil {
|
||||
t.Errorf("error: expected nil, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// validatedOutput validates the given slice of lines against data from the given reader.
|
||||
func validateOutput(r io.Reader, want []string) bool {
|
||||
s := bufio.NewScanner(r)
|
||||
for _, line := range want {
|
||||
if !s.Scan() || !strings.Contains(s.Text(), line) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var validateOutputTests = []struct {
|
||||
input string
|
||||
lines []string
|
||||
want bool
|
||||
}{{
|
||||
input: "",
|
||||
want: true,
|
||||
}, {
|
||||
input: `profile: yes
|
||||
`,
|
||||
want: true,
|
||||
}, {
|
||||
input: `profile: yes
|
||||
`,
|
||||
lines: []string{"profile: yes"},
|
||||
want: true,
|
||||
}, {
|
||||
input: `profile: yes
|
||||
profile: no
|
||||
`,
|
||||
lines: []string{"profile: yes"},
|
||||
want: true,
|
||||
}, {
|
||||
input: `profile: yes
|
||||
profile: no
|
||||
`,
|
||||
lines: []string{"profile: yes", "profile: no"},
|
||||
want: true,
|
||||
}, {
|
||||
input: `profile: yes
|
||||
profile: no
|
||||
`,
|
||||
lines: []string{"profile: no"},
|
||||
want: false,
|
||||
}}
|
||||
|
||||
func TestValidateOutput(t *testing.T) {
|
||||
for _, tt := range validateOutputTests {
|
||||
r := strings.NewReader(tt.input)
|
||||
got := validateOutput(r, tt.lines)
|
||||
if tt.want != got {
|
||||
t.Errorf("validateOutput(%q, %q), want %v, got %v", tt.input, tt.lines, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runTest executes the go program supplied and returns the contents of stdout,
|
||||
// stderr, and an error which may contain status information about the result
|
||||
// of the program.
|
||||
func runTest(t *testing.T, code string) ([]byte, []byte, error) {
|
||||
chk := func(err error) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
gopath, err := ioutil.TempDir("", "profile-gopath")
|
||||
chk(err)
|
||||
defer os.RemoveAll(gopath)
|
||||
|
||||
srcdir := filepath.Join(gopath, "src")
|
||||
err = os.Mkdir(srcdir, 0755)
|
||||
chk(err)
|
||||
src := filepath.Join(srcdir, "main.go")
|
||||
err = ioutil.WriteFile(src, []byte(code), 0644)
|
||||
chk(err)
|
||||
|
||||
cmd := exec.Command("go", "run", src)
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
err = cmd.Run()
|
||||
return stdout.Bytes(), stderr.Bytes(), err
|
||||
}
|
||||
8
vendor/github.com/pkg/profile/trace.go
generated
vendored
Normal file
8
vendor/github.com/pkg/profile/trace.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// +build go1.7
|
||||
|
||||
package profile
|
||||
|
||||
import "runtime/trace"
|
||||
|
||||
var startTrace = trace.Start
|
||||
var stopTrace = trace.Stop
|
||||
10
vendor/github.com/pkg/profile/trace16.go
generated
vendored
Normal file
10
vendor/github.com/pkg/profile/trace16.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// +build !go1.7
|
||||
|
||||
package profile
|
||||
|
||||
import "io"
|
||||
|
||||
// mock trace support for Go 1.6 and earlier.
|
||||
|
||||
func startTrace(w io.Writer) error { return nil }
|
||||
func stopTrace() {}
|
||||
8
vendor/github.com/pkg/profile/trace_test.go
generated
vendored
Normal file
8
vendor/github.com/pkg/profile/trace_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package profile_test
|
||||
|
||||
import "github.com/pkg/profile"
|
||||
|
||||
func ExampleTraceProfile() {
|
||||
// use execution tracing, rather than the default cpu profiling.
|
||||
defer profile.Start(profile.TraceProfile).Stop()
|
||||
}
|
||||
7
vendor/github.com/pkg/sftp/.gitignore
generated
vendored
Normal file
7
vendor/github.com/pkg/sftp/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.*.swo
|
||||
.*.swp
|
||||
|
||||
server_standalone/server_standalone
|
||||
|
||||
examples/*/id_rsa
|
||||
examples/*/id_rsa.pub
|
||||
37
vendor/github.com/pkg/sftp/.travis.yml
generated
vendored
Normal file
37
vendor/github.com/pkg/sftp/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
language: go
|
||||
go_import_path: github.com/pkg/sftp
|
||||
|
||||
# current and previous stable releases, and tip
|
||||
go:
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- tip
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
go: 1.7.x
|
||||
- os: osx
|
||||
go: tip
|
||||
|
||||
sudo: false
|
||||
|
||||
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 ./...
|
||||
3
vendor/github.com/pkg/sftp/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/github.com/pkg/sftp/CONTRIBUTORS
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Dave Cheney <dave@cheney.net>
|
||||
Saulius Gurklys <s4uliu5@gmail.com>
|
||||
John Eikenberry <jae@zhar.net>
|
||||
9
vendor/github.com/pkg/sftp/LICENSE
generated
vendored
Normal file
9
vendor/github.com/pkg/sftp/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
Copyright (c) 2013, Dave Cheney
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
44
vendor/github.com/pkg/sftp/README.md
generated
vendored
Normal file
44
vendor/github.com/pkg/sftp/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
sftp
|
||||
----
|
||||
|
||||
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.
|
||||
|
||||
[](https://travis-ci.org/pkg/sftp) [](http://godoc.org/github.com/pkg/sftp)
|
||||
|
||||
usage and examples
|
||||
------------------
|
||||
|
||||
See [godoc.org/github.com/pkg/sftp](http://godoc.org/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.
|
||||
|
||||
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.
|
||||
|
||||
contributing
|
||||
------------
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports.
|
||||
|
||||
Before proposing a large change, first please discuss your change by raising an
|
||||
issue.
|
||||
|
||||
For API/code bugs, please include a small, self contained code example to
|
||||
reproduce the issue. For pull requests, remember test coverage.
|
||||
|
||||
We try to handle issues and pull requests with a 0 open philosophy. That means
|
||||
we will try to address the submission as soon as possible and will work toward
|
||||
a resolution. If progress can no longer be made (eg. unreproducible bug) or
|
||||
stops (eg. unresponsive submitter), we will close the bug.
|
||||
|
||||
Thanks.
|
||||
237
vendor/github.com/pkg/sftp/attrs.go
generated
vendored
Normal file
237
vendor/github.com/pkg/sftp/attrs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
package sftp
|
||||
|
||||
// ssh_FXP_ATTRS support
|
||||
// see http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
// 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 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 }
|
||||
|
||||
// Mode returns file mode bits.
|
||||
func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
|
||||
|
||||
// ModTime returns the last modification time of the file.
|
||||
func (fi *fileInfo) ModTime() time.Time { return fi.mtime }
|
||||
|
||||
// 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 }
|
||||
|
||||
// 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 os.FileInfo.Sys()
|
||||
type FileStat struct {
|
||||
Size uint64
|
||||
Mode uint32
|
||||
Mtime uint32
|
||||
Atime uint32
|
||||
UID uint32
|
||||
GID uint32
|
||||
Extended []StatExtended
|
||||
}
|
||||
|
||||
// StatExtended contains additional, extended information for a FileStat.
|
||||
type StatExtended struct {
|
||||
ExtType string
|
||||
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,
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
fileStat := FileStat{
|
||||
Size: uint64(fi.Size()),
|
||||
Mode: fromFileMode(fi.Mode()),
|
||||
Mtime: uint32(mtime),
|
||||
Atime: uint32(atime),
|
||||
}
|
||||
|
||||
// os specific file stat decoding
|
||||
fileStatFromInfoOs(fi, &flags, &fileStat)
|
||||
|
||||
return flags, fileStat
|
||||
}
|
||||
|
||||
func unmarshalAttrs(b []byte) (*FileStat, []byte) {
|
||||
flags, b := unmarshalUint32(b)
|
||||
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, 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
|
||||
}
|
||||
11
vendor/github.com/pkg/sftp/attrs_stubs.go
generated
vendored
Normal file
11
vendor/github.com/pkg/sftp/attrs_stubs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// +build !cgo,!plan9 windows android
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func fileStatFromInfoOs(fi os.FileInfo, flags *uint32, fileStat *FileStat) {
|
||||
// todo
|
||||
}
|
||||
45
vendor/github.com/pkg/sftp/attrs_test.go
generated
vendored
Normal file
45
vendor/github.com/pkg/sftp/attrs_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ensure that attrs implemenst os.FileInfo
|
||||
var _ os.FileInfo = new(fileInfo)
|
||||
|
||||
var unmarshalAttrsTests = []struct {
|
||||
b []byte
|
||||
want *fileInfo
|
||||
rest []byte
|
||||
}{
|
||||
{marshal(nil, struct{ Flags uint32 }{}), &fileInfo{mtime: time.Unix(int64(0), 0)}, nil},
|
||||
{marshal(nil, struct {
|
||||
Flags uint32
|
||||
Size uint64
|
||||
}{ssh_FILEXFER_ATTR_SIZE, 20}), &fileInfo{size: 20, mtime: time.Unix(int64(0), 0)}, nil},
|
||||
{marshal(nil, struct {
|
||||
Flags uint32
|
||||
Size uint64
|
||||
Permissions uint32
|
||||
}{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil},
|
||||
{marshal(nil, struct {
|
||||
Flags uint32
|
||||
Size uint64
|
||||
UID, GID, Permissions uint32
|
||||
}{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 1000, 1000, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalAttrs(t *testing.T) {
|
||||
for _, tt := range unmarshalAttrsTests {
|
||||
stat, rest := unmarshalAttrs(tt.b)
|
||||
got := fileInfoFromStat(stat, "")
|
||||
tt.want.sys = got.Sys()
|
||||
if !reflect.DeepEqual(got, tt.want) || !bytes.Equal(tt.rest, rest) {
|
||||
t.Errorf("unmarshalAttrs(%#v): want %#v, %#v, got: %#v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
17
vendor/github.com/pkg/sftp/attrs_unix.go
generated
vendored
Normal file
17
vendor/github.com/pkg/sftp/attrs_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
|
||||
// +build cgo
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func fileStatFromInfoOs(fi os.FileInfo, flags *uint32, fileStat *FileStat) {
|
||||
if statt, ok := fi.Sys().(*syscall.Stat_t); ok {
|
||||
*flags |= ssh_FILEXFER_ATTR_UIDGID
|
||||
fileStat.UID = statt.Uid
|
||||
fileStat.GID = statt.Gid
|
||||
}
|
||||
}
|
||||
1131
vendor/github.com/pkg/sftp/client.go
generated
vendored
Normal file
1131
vendor/github.com/pkg/sftp/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
42
vendor/github.com/pkg/sftp/client_integration_darwin_test.go
generated
vendored
Normal file
42
vendor/github.com/pkg/sftp/client_integration_darwin_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const sftpServer = "/usr/libexec/sftp-server"
|
||||
|
||||
func TestClientStatVFS(t *testing.T) {
|
||||
if *testServerImpl {
|
||||
t.Skipf("go server does not support FXP_EXTENDED")
|
||||
}
|
||||
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
vfs, err := sftp.StatVFS("/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// get system stats
|
||||
s := syscall.Statfs_t{}
|
||||
err = syscall.Statfs("/", &s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check some stats
|
||||
if vfs.Files != uint64(s.Files) {
|
||||
t.Fatal("fr_size does not match")
|
||||
}
|
||||
|
||||
if vfs.Bfree != uint64(s.Bfree) {
|
||||
t.Fatal("f_bsize does not match")
|
||||
}
|
||||
|
||||
if vfs.Favail != uint64(s.Ffree) {
|
||||
t.Fatal("f_namemax does not match")
|
||||
}
|
||||
}
|
||||
42
vendor/github.com/pkg/sftp/client_integration_linux_test.go
generated
vendored
Normal file
42
vendor/github.com/pkg/sftp/client_integration_linux_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const sftpServer = "/usr/lib/openssh/sftp-server"
|
||||
|
||||
func TestClientStatVFS(t *testing.T) {
|
||||
if *testServerImpl {
|
||||
t.Skipf("go server does not support FXP_EXTENDED")
|
||||
}
|
||||
sftp, cmd := testClient(t, READWRITE, NO_DELAY)
|
||||
defer cmd.Wait()
|
||||
defer sftp.Close()
|
||||
|
||||
vfs, err := sftp.StatVFS("/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// get system stats
|
||||
s := syscall.Statfs_t{}
|
||||
err = syscall.Statfs("/", &s)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// check some stats
|
||||
if vfs.Frsize != uint64(s.Frsize) {
|
||||
t.Fatalf("fr_size does not match, expected: %v, got: %v", s.Frsize, vfs.Frsize)
|
||||
}
|
||||
|
||||
if vfs.Bsize != uint64(s.Bsize) {
|
||||
t.Fatalf("f_bsize does not match, expected: %v, got: %v", s.Bsize, vfs.Bsize)
|
||||
}
|
||||
|
||||
if vfs.Namemax != uint64(s.Namelen) {
|
||||
t.Fatalf("f_namemax does not match, expected: %v, got: %v", s.Namelen, vfs.Namemax)
|
||||
}
|
||||
}
|
||||
2167
vendor/github.com/pkg/sftp/client_integration_test.go
generated
vendored
Normal file
2167
vendor/github.com/pkg/sftp/client_integration_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
147
vendor/github.com/pkg/sftp/client_test.go
generated
vendored
Normal file
147
vendor/github.com/pkg/sftp/client_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kr/fs"
|
||||
)
|
||||
|
||||
// assert that *Client implements fs.FileSystem
|
||||
var _ fs.FileSystem = new(Client)
|
||||
|
||||
// assert that *File implements io.ReadWriteCloser
|
||||
var _ io.ReadWriteCloser = new(File)
|
||||
|
||||
func TestNormaliseError(t *testing.T) {
|
||||
var (
|
||||
ok = &StatusError{Code: ssh_FX_OK}
|
||||
eof = &StatusError{Code: ssh_FX_EOF}
|
||||
fail = &StatusError{Code: ssh_FX_FAILURE}
|
||||
noSuchFile = &StatusError{Code: ssh_FX_NO_SUCH_FILE}
|
||||
foo = errors.New("foo")
|
||||
)
|
||||
|
||||
var tests = []struct {
|
||||
desc string
|
||||
err error
|
||||
want error
|
||||
}{
|
||||
{
|
||||
desc: "nil error",
|
||||
},
|
||||
{
|
||||
desc: "not *StatusError",
|
||||
err: foo,
|
||||
want: foo,
|
||||
},
|
||||
{
|
||||
desc: "*StatusError with ssh_FX_EOF",
|
||||
err: eof,
|
||||
want: io.EOF,
|
||||
},
|
||||
{
|
||||
desc: "*StatusError with ssh_FX_NO_SUCH_FILE",
|
||||
err: noSuchFile,
|
||||
want: os.ErrNotExist,
|
||||
},
|
||||
{
|
||||
desc: "*StatusError with ssh_FX_OK",
|
||||
err: ok,
|
||||
},
|
||||
{
|
||||
desc: "*StatusError with ssh_FX_FAILURE",
|
||||
err: fail,
|
||||
want: fail,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := normaliseError(tt.err)
|
||||
if got != tt.want {
|
||||
t.Errorf("normaliseError(%#v), test %q\n- want: %#v\n- got: %#v",
|
||||
tt.err, tt.desc, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var flagsTests = []struct {
|
||||
flags int
|
||||
want uint32
|
||||
}{
|
||||
{os.O_RDONLY, ssh_FXF_READ},
|
||||
{os.O_WRONLY, ssh_FXF_WRITE},
|
||||
{os.O_RDWR, ssh_FXF_READ | ssh_FXF_WRITE},
|
||||
{os.O_RDWR | os.O_CREATE | os.O_TRUNC, ssh_FXF_READ | ssh_FXF_WRITE | ssh_FXF_CREAT | ssh_FXF_TRUNC},
|
||||
{os.O_WRONLY | os.O_APPEND, ssh_FXF_WRITE | ssh_FXF_APPEND},
|
||||
}
|
||||
|
||||
func TestFlags(t *testing.T) {
|
||||
for i, tt := range flagsTests {
|
||||
got := flags(tt.flags)
|
||||
if got != tt.want {
|
||||
t.Errorf("test %v: flags(%x): want: %x, got: %x", i, tt.flags, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalStatus(t *testing.T) {
|
||||
requestID := uint32(1)
|
||||
|
||||
id := marshalUint32([]byte{}, requestID)
|
||||
idCode := marshalUint32(id, ssh_FX_FAILURE)
|
||||
idCodeMsg := marshalString(idCode, "err msg")
|
||||
idCodeMsgLang := marshalString(idCodeMsg, "lang tag")
|
||||
|
||||
var tests = []struct {
|
||||
desc string
|
||||
reqID uint32
|
||||
status []byte
|
||||
want error
|
||||
}{
|
||||
{
|
||||
desc: "well-formed status",
|
||||
reqID: 1,
|
||||
status: idCodeMsgLang,
|
||||
want: &StatusError{
|
||||
Code: ssh_FX_FAILURE,
|
||||
msg: "err msg",
|
||||
lang: "lang tag",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing error message and language tag",
|
||||
reqID: 1,
|
||||
status: idCode,
|
||||
want: &StatusError{
|
||||
Code: ssh_FX_FAILURE,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing language tag",
|
||||
reqID: 1,
|
||||
status: idCodeMsg,
|
||||
want: &StatusError{
|
||||
Code: ssh_FX_FAILURE,
|
||||
msg: "err msg",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "request identifier mismatch",
|
||||
reqID: 2,
|
||||
status: idCodeMsgLang,
|
||||
want: &unexpectedIDErr{2, requestID},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := unmarshalStatus(tt.reqID, tt.status)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("unmarshalStatus(%v, %v), test %q\n- want: %#v\n- got: %#v",
|
||||
requestID, tt.status, tt.desc, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
133
vendor/github.com/pkg/sftp/conn.go
generated
vendored
Normal file
133
vendor/github.com/pkg/sftp/conn.go
generated
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// conn implements a bidirectional channel on which client and server
|
||||
// connections are multiplexed.
|
||||
type conn struct {
|
||||
io.Reader
|
||||
io.WriteCloser
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
type clientConn struct {
|
||||
conn
|
||||
wg sync.WaitGroup
|
||||
sync.Mutex // protects inflight
|
||||
inflight map[uint32]chan<- result // outstanding requests
|
||||
}
|
||||
|
||||
// Close closes the SFTP session.
|
||||
func (c *clientConn) Close() error {
|
||||
defer c.wg.Wait()
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
func (c *clientConn) loop() {
|
||||
defer c.wg.Done()
|
||||
err := c.recv()
|
||||
if err != nil {
|
||||
c.broadcastErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}()
|
||||
for {
|
||||
typ, data, err := c.recvPacket()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sid, _ := unmarshalUint32(data)
|
||||
c.Lock()
|
||||
ch, ok := c.inflight[sid]
|
||||
delete(c.inflight, sid)
|
||||
c.Unlock()
|
||||
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)
|
||||
}
|
||||
ch <- result{typ: typ, data: data}
|
||||
}
|
||||
}
|
||||
|
||||
// result captures the result of receiving the a packet from the server
|
||||
type result struct {
|
||||
typ byte
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
|
||||
type idmarshaler interface {
|
||||
id() uint32
|
||||
encoding.BinaryMarshaler
|
||||
}
|
||||
|
||||
func (c *clientConn) sendPacket(p idmarshaler) (byte, []byte, error) {
|
||||
ch := make(chan result, 1)
|
||||
c.dispatchRequest(ch, p)
|
||||
s := <-ch
|
||||
return s.typ, s.data, s.err
|
||||
}
|
||||
|
||||
func (c *clientConn) dispatchRequest(ch chan<- result, p idmarshaler) {
|
||||
c.Lock()
|
||||
c.inflight[p.id()] = ch
|
||||
c.Unlock()
|
||||
if err := c.conn.sendPacket(p); err != nil {
|
||||
c.Lock()
|
||||
delete(c.inflight, p.id())
|
||||
c.Unlock()
|
||||
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}
|
||||
}
|
||||
}
|
||||
|
||||
type serverConn struct {
|
||||
conn
|
||||
}
|
||||
|
||||
func (s *serverConn) sendError(p ider, err error) error {
|
||||
return s.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
9
vendor/github.com/pkg/sftp/debug.go
generated
vendored
Normal file
9
vendor/github.com/pkg/sftp/debug.go
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// +build debug
|
||||
|
||||
package sftp
|
||||
|
||||
import "log"
|
||||
|
||||
func debug(fmt string, args ...interface{}) {
|
||||
log.Printf(fmt, args...)
|
||||
}
|
||||
135
vendor/github.com/pkg/sftp/example_test.go
generated
vendored
Normal file
135
vendor/github.com/pkg/sftp/example_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package sftp_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
var conn *ssh.Client
|
||||
|
||||
// open an SFTP session over an existing ssh connection.
|
||||
sftp, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer sftp.Close()
|
||||
|
||||
// walk a directory
|
||||
w := sftp.Walk("/home/user")
|
||||
for w.Step() {
|
||||
if w.Err() != nil {
|
||||
continue
|
||||
}
|
||||
log.Println(w.Path())
|
||||
}
|
||||
|
||||
// leave your mark
|
||||
f, err := sftp.Create("hello.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err := f.Write([]byte("Hello world!")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// check it's there
|
||||
fi, err := sftp.Lstat("hello.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println(fi)
|
||||
}
|
||||
|
||||
func ExampleNewClientPipe() {
|
||||
// Connect to a remote host and request the sftp subsystem via the 'ssh'
|
||||
// command. This assumes that passwordless login is correctly configured.
|
||||
cmd := exec.Command("ssh", "example.com", "-s", "sftp")
|
||||
|
||||
// send errors from ssh to stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// get stdin and stdout
|
||||
wr, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
rd, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// start the process
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer cmd.Wait()
|
||||
|
||||
// open the SFTP session
|
||||
client, err := sftp.NewClientPipe(rd, wr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// read a directory
|
||||
list, err := client.ReadDir("/")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// print contents
|
||||
for _, item := range list {
|
||||
fmt.Println(item.Name())
|
||||
}
|
||||
|
||||
// close the connection
|
||||
client.Close()
|
||||
}
|
||||
|
||||
func ExampleClient_Mkdir_parents() {
|
||||
// Example of mimicing 'mkdir --parents'; I.E. recursively create
|
||||
// directoryies and don't error if any directories already exists.
|
||||
var conn *ssh.Client
|
||||
|
||||
client, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ssh_fx_failure := uint32(4)
|
||||
mkdirParents := func(client *sftp.Client, dir string) (err error) {
|
||||
var parents string
|
||||
for _, name := range strings.Split(dir, "/") {
|
||||
parents = path.Join(parents, name)
|
||||
err = client.Mkdir(parents)
|
||||
if status, ok := err.(*sftp.StatusError); ok {
|
||||
if status.Code == ssh_fx_failure {
|
||||
var fi os.FileInfo
|
||||
fi, err = client.Stat(parents)
|
||||
if err == nil {
|
||||
if !fi.IsDir() {
|
||||
return fmt.Errorf("File exists: %s", parents)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = mkdirParents(client, "/tmp/foo/bar")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
78
vendor/github.com/pkg/sftp/examples/buffered-read-benchmark/main.go
generated
vendored
Normal file
78
vendor/github.com/pkg/sftp/examples/buffered-read-benchmark/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// buffered-read-benchmark benchmarks the peformance of reading
|
||||
// from /dev/zero on the server to a []byte on the client via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
SIZE = flag.Int("s", 1<<15, "set max packet size")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
r, err := c.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
const size = 1e9
|
||||
|
||||
log.Printf("reading %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := io.ReadFull(r, make([]byte, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("read %v bytes in %s", size, time.Since(t1))
|
||||
}
|
||||
84
vendor/github.com/pkg/sftp/examples/buffered-write-benchmark/main.go
generated
vendored
Normal file
84
vendor/github.com/pkg/sftp/examples/buffered-write-benchmark/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// buffered-write-benchmark benchmarks the peformance of writing
|
||||
// a single large []byte on the client to /dev/null on the server via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
SIZE = flag.Int("s", 1<<15, "set max packet size")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
w, err := c.OpenFile("/dev/null", syscall.O_WRONLY)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
f, err := os.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
const size = 1e9
|
||||
|
||||
log.Printf("writing %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := w.Write(make([]byte, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("wrote %v bytes in %s", size, time.Since(t1))
|
||||
}
|
||||
131
vendor/github.com/pkg/sftp/examples/request-server/main.go
generated
vendored
Normal file
131
vendor/github.com/pkg/sftp/examples/request-server/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// An example SFTP server implementation using the golang SSH package.
|
||||
// Serves the whole filesystem visible to the user, and has a hard-coded username and password,
|
||||
// so not for real use!
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Based on example server code from golang.org/x/crypto/ssh and server_standalone
|
||||
func main() {
|
||||
|
||||
var (
|
||||
readOnly bool
|
||||
debugStderr bool
|
||||
)
|
||||
|
||||
flag.BoolVar(&readOnly, "R", false, "read-only server")
|
||||
flag.BoolVar(&debugStderr, "e", false, "debug to stderr")
|
||||
flag.Parse()
|
||||
|
||||
debugStream := ioutil.Discard
|
||||
if debugStderr {
|
||||
debugStream = os.Stderr
|
||||
}
|
||||
|
||||
// An SSH server is represented by a ServerConfig, which holds
|
||||
// certificate details and handles authentication of ServerConns.
|
||||
config := &ssh.ServerConfig{
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
// Should use constant-time compare (or better, salt+hash) in
|
||||
// a production setting.
|
||||
fmt.Fprintf(debugStream, "Login: %s\n", c.User())
|
||||
if c.User() == "testuser" && string(pass) == "tiger" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
},
|
||||
}
|
||||
|
||||
privateBytes, err := ioutil.ReadFile("id_rsa")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load private key", err)
|
||||
}
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse private key", err)
|
||||
}
|
||||
|
||||
config.AddHostKey(private)
|
||||
|
||||
// Once a ServerConfig has been configured, connections can be
|
||||
// accepted.
|
||||
listener, err := net.Listen("tcp", "0.0.0.0:2022")
|
||||
if err != nil {
|
||||
log.Fatal("failed to listen for connection", err)
|
||||
}
|
||||
fmt.Printf("Listening on %v\n", listener.Addr())
|
||||
|
||||
nConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("failed to accept incoming connection", err)
|
||||
}
|
||||
|
||||
// Before use, a handshake must be performed on the incoming net.Conn.
|
||||
sconn, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
||||
if err != nil {
|
||||
log.Fatal("failed to handshake", err)
|
||||
}
|
||||
log.Println("login detected:", sconn.User())
|
||||
fmt.Fprintf(debugStream, "SSH server established\n")
|
||||
|
||||
// The incoming Request channel must be serviced.
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
// Service the incoming Channel channel.
|
||||
for newChannel := range chans {
|
||||
// Channels have a type, depending on the application level
|
||||
// protocol intended. In the case of an SFTP session, this is "subsystem"
|
||||
// with a payload string of "<length=4>sftp"
|
||||
fmt.Fprintf(debugStream, "Incoming channel: %s\n", newChannel.ChannelType())
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
fmt.Fprintf(debugStream, "Unknown channel type: %s\n", newChannel.ChannelType())
|
||||
continue
|
||||
}
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("could not accept channel.", err)
|
||||
}
|
||||
fmt.Fprintf(debugStream, "Channel accepted\n")
|
||||
|
||||
// Sessions have out-of-band requests such as "shell",
|
||||
// "pty-req" and "env". Here we handle only the
|
||||
// "subsystem" request.
|
||||
go func(in <-chan *ssh.Request) {
|
||||
for req := range in {
|
||||
fmt.Fprintf(debugStream, "Request: %v\n", req.Type)
|
||||
ok := false
|
||||
switch req.Type {
|
||||
case "subsystem":
|
||||
fmt.Fprintf(debugStream, "Subsystem: %s\n", req.Payload[4:])
|
||||
if string(req.Payload[4:]) == "sftp" {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(debugStream, " - accepted: %v\n", ok)
|
||||
req.Reply(ok, nil)
|
||||
}
|
||||
}(requests)
|
||||
|
||||
root := sftp.InMemHandler()
|
||||
server := sftp.NewRequestServer(channel, root)
|
||||
if err := server.Serve(); err == io.EOF {
|
||||
server.Close()
|
||||
log.Print("sftp client exited session.")
|
||||
} else if err != nil {
|
||||
log.Fatal("sftp server completed with error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
12
vendor/github.com/pkg/sftp/examples/sftp-server/README.md
generated
vendored
Normal file
12
vendor/github.com/pkg/sftp/examples/sftp-server/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
Example SFTP server implementation
|
||||
===
|
||||
|
||||
In order to use this example you will need an RSA key.
|
||||
|
||||
On linux-like systems with openssh installed, you can use the command:
|
||||
|
||||
```
|
||||
ssh-keygen -t rsa -f id_rsa
|
||||
```
|
||||
|
||||
Then you will be able to run the sftp-server command in the current directory.
|
||||
147
vendor/github.com/pkg/sftp/examples/sftp-server/main.go
generated
vendored
Normal file
147
vendor/github.com/pkg/sftp/examples/sftp-server/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
// An example SFTP server implementation using the golang SSH package.
|
||||
// Serves the whole filesystem visible to the user, and has a hard-coded username and password,
|
||||
// so not for real use!
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// Based on example server code from golang.org/x/crypto/ssh and server_standalone
|
||||
func main() {
|
||||
|
||||
var (
|
||||
readOnly bool
|
||||
debugStderr bool
|
||||
)
|
||||
|
||||
flag.BoolVar(&readOnly, "R", false, "read-only server")
|
||||
flag.BoolVar(&debugStderr, "e", false, "debug to stderr")
|
||||
flag.Parse()
|
||||
|
||||
debugStream := ioutil.Discard
|
||||
if debugStderr {
|
||||
debugStream = os.Stderr
|
||||
}
|
||||
|
||||
// An SSH server is represented by a ServerConfig, which holds
|
||||
// certificate details and handles authentication of ServerConns.
|
||||
config := &ssh.ServerConfig{
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
// Should use constant-time compare (or better, salt+hash) in
|
||||
// a production setting.
|
||||
fmt.Fprintf(debugStream, "Login: %s\n", c.User())
|
||||
if c.User() == "testuser" && string(pass) == "tiger" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
},
|
||||
}
|
||||
|
||||
privateBytes, err := ioutil.ReadFile("id_rsa")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load private key", err)
|
||||
}
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse private key", err)
|
||||
}
|
||||
|
||||
config.AddHostKey(private)
|
||||
|
||||
// Once a ServerConfig has been configured, connections can be
|
||||
// accepted.
|
||||
listener, err := net.Listen("tcp", "0.0.0.0:2022")
|
||||
if err != nil {
|
||||
log.Fatal("failed to listen for connection", err)
|
||||
}
|
||||
fmt.Printf("Listening on %v\n", listener.Addr())
|
||||
|
||||
nConn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("failed to accept incoming connection", err)
|
||||
}
|
||||
|
||||
// Before use, a handshake must be performed on the incoming
|
||||
// net.Conn.
|
||||
_, chans, reqs, err := ssh.NewServerConn(nConn, config)
|
||||
if err != nil {
|
||||
log.Fatal("failed to handshake", err)
|
||||
}
|
||||
fmt.Fprintf(debugStream, "SSH server established\n")
|
||||
|
||||
// The incoming Request channel must be serviced.
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
// Service the incoming Channel channel.
|
||||
for newChannel := range chans {
|
||||
// Channels have a type, depending on the application level
|
||||
// protocol intended. In the case of an SFTP session, this is "subsystem"
|
||||
// with a payload string of "<length=4>sftp"
|
||||
fmt.Fprintf(debugStream, "Incoming channel: %s\n", newChannel.ChannelType())
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
fmt.Fprintf(debugStream, "Unknown channel type: %s\n", newChannel.ChannelType())
|
||||
continue
|
||||
}
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
log.Fatal("could not accept channel.", err)
|
||||
}
|
||||
fmt.Fprintf(debugStream, "Channel accepted\n")
|
||||
|
||||
// Sessions have out-of-band requests such as "shell",
|
||||
// "pty-req" and "env". Here we handle only the
|
||||
// "subsystem" request.
|
||||
go func(in <-chan *ssh.Request) {
|
||||
for req := range in {
|
||||
fmt.Fprintf(debugStream, "Request: %v\n", req.Type)
|
||||
ok := false
|
||||
switch req.Type {
|
||||
case "subsystem":
|
||||
fmt.Fprintf(debugStream, "Subsystem: %s\n", req.Payload[4:])
|
||||
if string(req.Payload[4:]) == "sftp" {
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(debugStream, " - accepted: %v\n", ok)
|
||||
req.Reply(ok, nil)
|
||||
}
|
||||
}(requests)
|
||||
|
||||
serverOptions := []sftp.ServerOption{
|
||||
sftp.WithDebug(debugStream),
|
||||
}
|
||||
|
||||
if readOnly {
|
||||
serverOptions = append(serverOptions, sftp.ReadOnly())
|
||||
fmt.Fprintf(debugStream, "Read-only server\n")
|
||||
} else {
|
||||
fmt.Fprintf(debugStream, "Read write server\n")
|
||||
}
|
||||
|
||||
server, err := sftp.NewServer(
|
||||
channel,
|
||||
serverOptions...,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := server.Serve(); err == io.EOF {
|
||||
server.Close()
|
||||
log.Print("sftp client exited session.")
|
||||
} else if err != nil {
|
||||
log.Fatal("sftp server completed with error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
85
vendor/github.com/pkg/sftp/examples/streaming-read-benchmark/main.go
generated
vendored
Normal file
85
vendor/github.com/pkg/sftp/examples/streaming-read-benchmark/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// streaming-read-benchmark benchmarks the peformance of reading
|
||||
// from /dev/zero on the server to /dev/null on the client via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
SIZE = flag.Int("s", 1<<15, "set max packet size")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
r, err := c.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
w, err := os.OpenFile("/dev/null", syscall.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
const size int64 = 1e9
|
||||
|
||||
log.Printf("reading %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := io.Copy(w, io.LimitReader(r, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("read %v bytes in %s", size, time.Since(t1))
|
||||
}
|
||||
85
vendor/github.com/pkg/sftp/examples/streaming-write-benchmark/main.go
generated
vendored
Normal file
85
vendor/github.com/pkg/sftp/examples/streaming-write-benchmark/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// streaming-write-benchmark benchmarks the peformance of writing
|
||||
// from /dev/zero on the client to /dev/null on the server via io.Copy.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
var (
|
||||
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||
PORT = flag.Int("port", 22, "ssh server port")
|
||||
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||
SIZE = flag.Int("s", 1<<15, "set max packet size")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var auths []ssh.AuthMethod
|
||||
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||
|
||||
}
|
||||
if *PASS != "" {
|
||||
auths = append(auths, ssh.Password(*PASS))
|
||||
}
|
||||
|
||||
config := ssh.ClientConfig{
|
||||
User: *USER,
|
||||
Auth: auths,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||
conn, err := ssh.Dial("tcp", addr, &config)
|
||||
if err != nil {
|
||||
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
c, err := sftp.NewClient(conn, sftp.MaxPacket(*SIZE))
|
||||
if err != nil {
|
||||
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
w, err := c.OpenFile("/dev/null", syscall.O_WRONLY)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
f, err := os.Open("/dev/zero")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
const size int64 = 1e9
|
||||
|
||||
log.Printf("writing %v bytes", size)
|
||||
t1 := time.Now()
|
||||
n, err := io.Copy(w, io.LimitReader(f, size))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if n != size {
|
||||
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||
}
|
||||
log.Printf("wrote %v bytes in %s", size, time.Since(t1))
|
||||
}
|
||||
295
vendor/github.com/pkg/sftp/match.go
generated
vendored
Normal file
295
vendor/github.com/pkg/sftp/match.go
generated
vendored
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ErrBadPattern indicates a globbing pattern was malformed.
|
||||
var ErrBadPattern = path.ErrBadPattern
|
||||
|
||||
// Unix separator
|
||||
const separator = "/"
|
||||
|
||||
// Match reports whether name matches the shell file name pattern.
|
||||
// The pattern syntax is:
|
||||
//
|
||||
// pattern:
|
||||
// { term }
|
||||
// term:
|
||||
// '*' matches any sequence of non-Separator characters
|
||||
// '?' matches any single non-Separator character
|
||||
// '[' [ '^' ] { character-range } ']'
|
||||
// character class (must be non-empty)
|
||||
// c matches character c (c != '*', '?', '\\', '[')
|
||||
// '\\' c matches character c
|
||||
//
|
||||
// character-range:
|
||||
// c matches character c (c != '\\', '-', ']')
|
||||
// '\\' c matches character c
|
||||
// lo '-' hi matches character c for lo <= c <= hi
|
||||
//
|
||||
// Match requires pattern to match all of name, not just a substring.
|
||||
// The only possible returned error is ErrBadPattern, when pattern
|
||||
// is malformed.
|
||||
//
|
||||
//
|
||||
func Match(pattern, name string) (matched bool, err error) {
|
||||
return path.Match(pattern, name)
|
||||
}
|
||||
|
||||
// detect if byte(char) is path separator
|
||||
func isPathSeparator(c byte) bool {
|
||||
return string(c) == "/"
|
||||
}
|
||||
|
||||
// scanChunk gets the next segment of pattern, which is a non-star string
|
||||
// possibly preceded by a star.
|
||||
func scanChunk(pattern string) (star bool, chunk, rest string) {
|
||||
for len(pattern) > 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:]
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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:]
|
||||
}
|
||||
|
||||
// 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 '/').
|
||||
//
|
||||
// Glob ignores file system errors such as I/O errors reading directories.
|
||||
// The only possible returned error is ErrBadPattern, when pattern
|
||||
// is malformed.
|
||||
func (c *Client) Glob(pattern string) (matches []string, err error) {
|
||||
if !hasMeta(pattern) {
|
||||
file, err := c.Lstat(pattern)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
dir, _ := Split(pattern)
|
||||
dir = cleanGlobPath(dir)
|
||||
return []string{Join(dir, file.Name())}, nil
|
||||
}
|
||||
|
||||
dir, file := Split(pattern)
|
||||
dir = cleanGlobPath(dir)
|
||||
|
||||
if !hasMeta(dir) {
|
||||
return c.glob(dir, file, nil)
|
||||
}
|
||||
|
||||
// Prevent infinite recursion. See issue 15879.
|
||||
if dir == pattern {
|
||||
return nil, ErrBadPattern
|
||||
}
|
||||
|
||||
var m []string
|
||||
m, err = c.Glob(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, d := range m {
|
||||
matches, err = c.glob(d, file, matches)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// cleanGlobPath prepares path for glob matching.
|
||||
func cleanGlobPath(path string) string {
|
||||
switch path {
|
||||
case "":
|
||||
return "."
|
||||
case string(separator):
|
||||
// do nothing to the path
|
||||
return path
|
||||
default:
|
||||
return path[0 : len(path)-1] // chop off trailing separator
|
||||
}
|
||||
}
|
||||
|
||||
// glob searches for files matching pattern in the directory dir
|
||||
// and appends them to matches. If the directory cannot be
|
||||
// opened, it returns the existing matches. New matches are
|
||||
// added in lexicographical order.
|
||||
func (c *Client) glob(dir, pattern string, matches []string) (m []string, e error) {
|
||||
m = matches
|
||||
fi, err := c.Stat(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !fi.IsDir() {
|
||||
return
|
||||
}
|
||||
names, err := c.ReadDir(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//sort.Strings(names)
|
||||
|
||||
for _, n := range names {
|
||||
matched, err := Match(pattern, n.Name())
|
||||
if err != nil {
|
||||
return m, err
|
||||
}
|
||||
if matched {
|
||||
m = append(m, Join(dir, n.Name()))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Join joins any number of path elements into a single path, adding
|
||||
// a Separator if necessary.
|
||||
// all empty strings are ignored.
|
||||
func Join(elem ...string) string {
|
||||
return path.Join(elem...)
|
||||
}
|
||||
|
||||
// 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, "*?[")
|
||||
}
|
||||
5
vendor/github.com/pkg/sftp/other_test.go
generated
vendored
Normal file
5
vendor/github.com/pkg/sftp/other_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// +build !linux,!darwin
|
||||
|
||||
package sftp
|
||||
|
||||
const sftpServer = "/usr/bin/false" // unsupported
|
||||
156
vendor/github.com/pkg/sftp/packet-manager.go
generated
vendored
Normal file
156
vendor/github.com/pkg/sftp/packet-manager.go
generated
vendored
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// The goal of the packetManager is to keep the outgoing packets in the same
|
||||
// order as the incoming. This is due to some sftp clients requiring this
|
||||
// behavior (eg. winscp).
|
||||
|
||||
type packetSender interface {
|
||||
sendPacket(encoding.BinaryMarshaler) error
|
||||
}
|
||||
|
||||
type packetManager struct {
|
||||
requests chan requestPacket
|
||||
responses chan responsePacket
|
||||
fini chan struct{}
|
||||
incoming requestPacketIDs
|
||||
outgoing responsePackets
|
||||
sender packetSender // connection object
|
||||
working *sync.WaitGroup
|
||||
}
|
||||
|
||||
func newPktMgr(sender packetSender) packetManager {
|
||||
s := packetManager{
|
||||
requests: make(chan requestPacket, sftpServerWorkerCount),
|
||||
responses: make(chan responsePacket, sftpServerWorkerCount),
|
||||
fini: make(chan struct{}),
|
||||
incoming: make([]uint32, 0, sftpServerWorkerCount),
|
||||
outgoing: make([]responsePacket, 0, sftpServerWorkerCount),
|
||||
sender: sender,
|
||||
working: &sync.WaitGroup{},
|
||||
}
|
||||
go s.controller()
|
||||
return s
|
||||
}
|
||||
|
||||
// register incoming packets to be handled
|
||||
// send id of 0 for packets without id
|
||||
func (s packetManager) incomingPacket(pkt requestPacket) {
|
||||
s.working.Add(1)
|
||||
s.requests <- pkt // buffer == sftpServerWorkerCount
|
||||
}
|
||||
|
||||
// register outgoing packets as being ready
|
||||
func (s packetManager) readyPacket(pkt responsePacket) {
|
||||
s.responses <- pkt
|
||||
s.working.Done()
|
||||
}
|
||||
|
||||
// shut down packetManager controller
|
||||
func (s packetManager) close() {
|
||||
// pause until current packets are processed
|
||||
s.working.Wait()
|
||||
close(s.fini)
|
||||
}
|
||||
|
||||
// Passed a worker function, returns a channel for incoming packets.
|
||||
// The goal is to process packets in the order they are received as is
|
||||
// requires by section 7 of the RFC, while maximizing throughput of file
|
||||
// transfers.
|
||||
func (s *packetManager) workerChan(runWorker func(requestChan)) requestChan {
|
||||
|
||||
rwChan := make(chan requestPacket, sftpServerWorkerCount)
|
||||
for i := 0; i < sftpServerWorkerCount; i++ {
|
||||
runWorker(rwChan)
|
||||
}
|
||||
|
||||
cmdChan := make(chan requestPacket)
|
||||
runWorker(cmdChan)
|
||||
|
||||
pktChan := make(chan requestPacket, sftpServerWorkerCount)
|
||||
go func() {
|
||||
// start with cmdChan
|
||||
curChan := cmdChan
|
||||
for pkt := range pktChan {
|
||||
// on file open packet, switch to rwChan
|
||||
switch pkt.(type) {
|
||||
case *sshFxpOpenPacket:
|
||||
curChan = rwChan
|
||||
// on file close packet, switch back to cmdChan
|
||||
// after waiting for any reads/writes to finish
|
||||
case *sshFxpClosePacket:
|
||||
// wait for rwChan to finish
|
||||
s.working.Wait()
|
||||
// stop using rwChan
|
||||
curChan = cmdChan
|
||||
}
|
||||
s.incomingPacket(pkt)
|
||||
curChan <- pkt
|
||||
}
|
||||
close(rwChan)
|
||||
close(cmdChan)
|
||||
s.close()
|
||||
}()
|
||||
|
||||
return pktChan
|
||||
}
|
||||
|
||||
// process packets
|
||||
func (s *packetManager) controller() {
|
||||
for {
|
||||
select {
|
||||
case pkt := <-s.requests:
|
||||
debug("incoming id: %v", pkt.id())
|
||||
s.incoming = append(s.incoming, pkt.id())
|
||||
if len(s.incoming) > 1 {
|
||||
s.incoming.Sort()
|
||||
}
|
||||
case pkt := <-s.responses:
|
||||
debug("outgoing pkt: %v", pkt.id())
|
||||
s.outgoing = append(s.outgoing, pkt)
|
||||
if len(s.outgoing) > 1 {
|
||||
s.outgoing.Sort()
|
||||
}
|
||||
case <-s.fini:
|
||||
return
|
||||
}
|
||||
s.maybeSendPackets()
|
||||
}
|
||||
}
|
||||
|
||||
// send as many packets as are ready
|
||||
func (s *packetManager) maybeSendPackets() {
|
||||
for {
|
||||
if len(s.outgoing) == 0 || len(s.incoming) == 0 {
|
||||
debug("break! -- outgoing: %v; incoming: %v",
|
||||
len(s.outgoing), len(s.incoming))
|
||||
break
|
||||
}
|
||||
out := s.outgoing[0]
|
||||
in := s.incoming[0]
|
||||
// debug("incoming: %v", s.incoming)
|
||||
// debug("outgoing: %v", outfilter(s.outgoing))
|
||||
if in == out.id() {
|
||||
s.sender.sendPacket(out)
|
||||
// pop off heads
|
||||
copy(s.incoming, s.incoming[1:]) // shift left
|
||||
s.incoming = s.incoming[:len(s.incoming)-1] // remove last
|
||||
copy(s.outgoing, s.outgoing[1:]) // shift left
|
||||
s.outgoing = s.outgoing[:len(s.outgoing)-1] // remove last
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func outfilter(o []responsePacket) []uint32 {
|
||||
res := make([]uint32, 0, len(o))
|
||||
for _, v := range o {
|
||||
res = append(res, v.id())
|
||||
}
|
||||
return res
|
||||
}
|
||||
21
vendor/github.com/pkg/sftp/packet-manager_go1.8.go
generated
vendored
Normal file
21
vendor/github.com/pkg/sftp/packet-manager_go1.8.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// +build go1.8
|
||||
|
||||
package sftp
|
||||
|
||||
import "sort"
|
||||
|
||||
type responsePackets []responsePacket
|
||||
|
||||
func (r responsePackets) Sort() {
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i].id() < r[j].id()
|
||||
})
|
||||
}
|
||||
|
||||
type requestPacketIDs []uint32
|
||||
|
||||
func (r requestPacketIDs) Sort() {
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i] < r[j]
|
||||
})
|
||||
}
|
||||
21
vendor/github.com/pkg/sftp/packet-manager_legacy.go
generated
vendored
Normal file
21
vendor/github.com/pkg/sftp/packet-manager_legacy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// +build !go1.8
|
||||
|
||||
package sftp
|
||||
|
||||
import "sort"
|
||||
|
||||
// for sorting/ordering outgoing
|
||||
type responsePackets []responsePacket
|
||||
|
||||
func (r responsePackets) Len() int { return len(r) }
|
||||
func (r responsePackets) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r responsePackets) Less(i, j int) bool { return r[i].id() < r[j].id() }
|
||||
func (r responsePackets) Sort() { sort.Sort(r) }
|
||||
|
||||
// for sorting/ordering incoming
|
||||
type requestPacketIDs []uint32
|
||||
|
||||
func (r requestPacketIDs) Len() int { return len(r) }
|
||||
func (r requestPacketIDs) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r requestPacketIDs) Less(i, j int) bool { return r[i] < r[j] }
|
||||
func (r requestPacketIDs) Sort() { sort.Sort(r) }
|
||||
154
vendor/github.com/pkg/sftp/packet-manager_test.go
generated
vendored
Normal file
154
vendor/github.com/pkg/sftp/packet-manager_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type _testSender struct {
|
||||
sent chan encoding.BinaryMarshaler
|
||||
}
|
||||
|
||||
func newTestSender() *_testSender {
|
||||
return &_testSender{make(chan encoding.BinaryMarshaler)}
|
||||
}
|
||||
|
||||
func (s _testSender) sendPacket(p encoding.BinaryMarshaler) error {
|
||||
s.sent <- p
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakepacket uint32
|
||||
|
||||
func (fakepacket) MarshalBinary() ([]byte, error) {
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func (fakepacket) UnmarshalBinary([]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fakepacket) id() uint32 {
|
||||
return uint32(f)
|
||||
}
|
||||
|
||||
type pair struct {
|
||||
in fakepacket
|
||||
out fakepacket
|
||||
}
|
||||
|
||||
// basic test
|
||||
var ttable1 = []pair{
|
||||
pair{fakepacket(0), fakepacket(0)},
|
||||
pair{fakepacket(1), fakepacket(1)},
|
||||
pair{fakepacket(2), fakepacket(2)},
|
||||
pair{fakepacket(3), fakepacket(3)},
|
||||
}
|
||||
|
||||
// outgoing packets out of order
|
||||
var ttable2 = []pair{
|
||||
pair{fakepacket(0), fakepacket(0)},
|
||||
pair{fakepacket(1), fakepacket(4)},
|
||||
pair{fakepacket(2), fakepacket(1)},
|
||||
pair{fakepacket(3), fakepacket(3)},
|
||||
pair{fakepacket(4), fakepacket(2)},
|
||||
}
|
||||
|
||||
// incoming packets out of order
|
||||
var ttable3 = []pair{
|
||||
pair{fakepacket(2), fakepacket(0)},
|
||||
pair{fakepacket(1), fakepacket(1)},
|
||||
pair{fakepacket(3), fakepacket(2)},
|
||||
pair{fakepacket(0), fakepacket(3)},
|
||||
}
|
||||
|
||||
var tables = [][]pair{ttable1, ttable2, ttable3}
|
||||
|
||||
func TestPacketManager(t *testing.T) {
|
||||
sender := newTestSender()
|
||||
s := newPktMgr(sender)
|
||||
|
||||
for i := range tables {
|
||||
table := tables[i]
|
||||
for _, p := range table {
|
||||
s.incomingPacket(p.in)
|
||||
}
|
||||
for _, p := range table {
|
||||
s.readyPacket(p.out)
|
||||
}
|
||||
for i := 0; i < len(table); i++ {
|
||||
pkt := <-sender.sent
|
||||
id := pkt.(fakepacket).id()
|
||||
assert.Equal(t, id, uint32(i))
|
||||
}
|
||||
}
|
||||
s.close()
|
||||
}
|
||||
|
||||
func (p sshFxpRemovePacket) String() string {
|
||||
return fmt.Sprintf("RmPct:%d", p.ID)
|
||||
}
|
||||
func (p sshFxpOpenPacket) String() string {
|
||||
return fmt.Sprintf("OpPct:%d", p.ID)
|
||||
}
|
||||
func (p sshFxpWritePacket) String() string {
|
||||
return fmt.Sprintf("WrPct:%d", p.ID)
|
||||
}
|
||||
func (p sshFxpClosePacket) String() string {
|
||||
return fmt.Sprintf("ClPct:%d", p.ID)
|
||||
}
|
||||
|
||||
// Test what happens when the pool processes a close packet on a file that it
|
||||
// is still reading from.
|
||||
func TestCloseOutOfOrder(t *testing.T) {
|
||||
packets := []requestPacket{
|
||||
&sshFxpRemovePacket{ID: 0, Filename: "foo"},
|
||||
&sshFxpOpenPacket{ID: 1},
|
||||
&sshFxpWritePacket{ID: 2, Handle: "foo"},
|
||||
&sshFxpWritePacket{ID: 3, Handle: "foo"},
|
||||
&sshFxpWritePacket{ID: 4, Handle: "foo"},
|
||||
&sshFxpWritePacket{ID: 5, Handle: "foo"},
|
||||
&sshFxpClosePacket{ID: 6, Handle: "foo"},
|
||||
&sshFxpRemovePacket{ID: 7, Filename: "foo"},
|
||||
}
|
||||
|
||||
recvChan := make(chan requestPacket, len(packets)+1)
|
||||
sender := newTestSender()
|
||||
pktMgr := newPktMgr(sender)
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(packets))
|
||||
runWorker := func(ch requestChan) {
|
||||
go func() {
|
||||
for pkt := range ch {
|
||||
if _, ok := pkt.(*sshFxpWritePacket); ok {
|
||||
// sleep to cause writes to come after close/remove
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
pktMgr.working.Done()
|
||||
recvChan <- pkt
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
}
|
||||
pktChan := pktMgr.workerChan(runWorker)
|
||||
for _, p := range packets {
|
||||
pktChan <- p
|
||||
}
|
||||
wg.Wait()
|
||||
close(recvChan)
|
||||
received := []requestPacket{}
|
||||
for p := range recvChan {
|
||||
received = append(received, p)
|
||||
}
|
||||
if received[len(received)-2].id() != packets[len(packets)-2].id() {
|
||||
t.Fatal("Packets processed out of order1:", received, packets)
|
||||
}
|
||||
if received[len(received)-1].id() != packets[len(packets)-1].id() {
|
||||
t.Fatal("Packets processed out of order2:", received, packets)
|
||||
}
|
||||
}
|
||||
141
vendor/github.com/pkg/sftp/packet-typing.go
generated
vendored
Normal file
141
vendor/github.com/pkg/sftp/packet-typing.go
generated
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// all incoming packets
|
||||
type requestPacket interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
id() uint32
|
||||
}
|
||||
|
||||
type requestChan chan requestPacket
|
||||
|
||||
type responsePacket interface {
|
||||
encoding.BinaryMarshaler
|
||||
id() uint32
|
||||
}
|
||||
|
||||
// interfaces to group types
|
||||
type hasPath interface {
|
||||
requestPacket
|
||||
getPath() string
|
||||
}
|
||||
|
||||
type hasHandle interface {
|
||||
requestPacket
|
||||
getHandle() string
|
||||
}
|
||||
|
||||
type isOpener interface {
|
||||
hasPath
|
||||
isOpener()
|
||||
}
|
||||
|
||||
type notReadOnly interface {
|
||||
notReadOnly()
|
||||
}
|
||||
|
||||
//// 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 }
|
||||
|
||||
// Openers implement hasPath and isOpener
|
||||
func (p sshFxpOpendirPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpOpendirPacket) isOpener() {}
|
||||
func (p sshFxpOpenPacket) getPath() string { return p.Path }
|
||||
func (p sshFxpOpenPacket) isOpener() {}
|
||||
|
||||
// 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 }
|
||||
|
||||
// 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() {}
|
||||
|
||||
// this has a handle, but is only used for close
|
||||
func (p sshFxpClosePacket) getHandle() string { return p.Handle }
|
||||
|
||||
// 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 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:
|
||||
pkt = &sshFxInitPacket{}
|
||||
case ssh_FXP_LSTAT:
|
||||
pkt = &sshFxpLstatPacket{}
|
||||
case ssh_FXP_OPEN:
|
||||
pkt = &sshFxpOpenPacket{}
|
||||
case ssh_FXP_CLOSE:
|
||||
pkt = &sshFxpClosePacket{}
|
||||
case ssh_FXP_READ:
|
||||
pkt = &sshFxpReadPacket{}
|
||||
case ssh_FXP_WRITE:
|
||||
pkt = &sshFxpWritePacket{}
|
||||
case ssh_FXP_FSTAT:
|
||||
pkt = &sshFxpFstatPacket{}
|
||||
case ssh_FXP_SETSTAT:
|
||||
pkt = &sshFxpSetstatPacket{}
|
||||
case ssh_FXP_FSETSTAT:
|
||||
pkt = &sshFxpFsetstatPacket{}
|
||||
case ssh_FXP_OPENDIR:
|
||||
pkt = &sshFxpOpendirPacket{}
|
||||
case ssh_FXP_READDIR:
|
||||
pkt = &sshFxpReaddirPacket{}
|
||||
case ssh_FXP_REMOVE:
|
||||
pkt = &sshFxpRemovePacket{}
|
||||
case ssh_FXP_MKDIR:
|
||||
pkt = &sshFxpMkdirPacket{}
|
||||
case ssh_FXP_RMDIR:
|
||||
pkt = &sshFxpRmdirPacket{}
|
||||
case ssh_FXP_REALPATH:
|
||||
pkt = &sshFxpRealpathPacket{}
|
||||
case ssh_FXP_STAT:
|
||||
pkt = &sshFxpStatPacket{}
|
||||
case ssh_FXP_RENAME:
|
||||
pkt = &sshFxpRenamePacket{}
|
||||
case ssh_FXP_READLINK:
|
||||
pkt = &sshFxpReadlinkPacket{}
|
||||
case ssh_FXP_SYMLINK:
|
||||
pkt = &sshFxpSymlinkPacket{}
|
||||
case ssh_FXP_EXTENDED:
|
||||
pkt = &sshFxpExtendedPacket{}
|
||||
default:
|
||||
return nil, errors.Errorf("unhandled packet type: %s", p.pktType)
|
||||
}
|
||||
if err := pkt.UnmarshalBinary(p.pktBytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pkt, nil
|
||||
}
|
||||
898
vendor/github.com/pkg/sftp/packet.go
generated
vendored
Normal file
898
vendor/github.com/pkg/sftp/packet.go
generated
vendored
Normal file
|
|
@ -0,0 +1,898 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
errShortPacket = errors.New("packet too short")
|
||||
errUnknownExtendedPacket = errors.New("unknown extended packet")
|
||||
)
|
||||
|
||||
const (
|
||||
debugDumpTxPacket = false
|
||||
debugDumpRxPacket = false
|
||||
debugDumpTxPacketBytes = false
|
||||
debugDumpRxPacketBytes = false
|
||||
)
|
||||
|
||||
func marshalUint32(b []byte, v uint32) []byte {
|
||||
return append(b, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||
}
|
||||
|
||||
func marshalUint64(b []byte, v uint64) []byte {
|
||||
return marshalUint32(marshalUint32(b, uint32(v>>32)), uint32(v))
|
||||
}
|
||||
|
||||
func marshalString(b []byte, v string) []byte {
|
||||
return append(marshalUint32(b, uint32(len(v))), v...)
|
||||
}
|
||||
|
||||
func marshal(b []byte, v interface{}) []byte {
|
||||
if v == nil {
|
||||
return b
|
||||
}
|
||||
switch v := v.(type) {
|
||||
case uint8:
|
||||
return append(b, v)
|
||||
case uint32:
|
||||
return marshalUint32(b, v)
|
||||
case uint64:
|
||||
return marshalUint64(b, v)
|
||||
case string:
|
||||
return marshalString(b, v)
|
||||
case os.FileInfo:
|
||||
return marshalFileInfo(b, v)
|
||||
default:
|
||||
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()))
|
||||
}
|
||||
return b
|
||||
case reflect.Slice:
|
||||
for i, n := 0, d.Len(); i < n; i++ {
|
||||
b = append(marshal(b, d.Index(i).Interface()))
|
||||
}
|
||||
return b
|
||||
default:
|
||||
panic(fmt.Sprintf("marshal(%#v): cannot handle type %T", v, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unmarshalUint32(b []byte) (uint32, []byte) {
|
||||
v := uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
|
||||
return v, b[4:]
|
||||
}
|
||||
|
||||
func unmarshalUint32Safe(b []byte) (uint32, []byte, error) {
|
||||
var v uint32
|
||||
if len(b) < 4 {
|
||||
return 0, nil, errShortPacket
|
||||
}
|
||||
v, b = unmarshalUint32(b)
|
||||
return v, b, nil
|
||||
}
|
||||
|
||||
func unmarshalUint64(b []byte) (uint64, []byte) {
|
||||
h, b := unmarshalUint32(b)
|
||||
l, b := unmarshalUint32(b)
|
||||
return uint64(h)<<32 | uint64(l), b
|
||||
}
|
||||
|
||||
func unmarshalUint64Safe(b []byte) (uint64, []byte, error) {
|
||||
var v uint64
|
||||
if len(b) < 8 {
|
||||
return 0, nil, errShortPacket
|
||||
}
|
||||
v, b = unmarshalUint64(b)
|
||||
return v, b, nil
|
||||
}
|
||||
|
||||
func unmarshalString(b []byte) (string, []byte) {
|
||||
n, b := unmarshalUint32(b)
|
||||
return string(b[:n]), b[n:]
|
||||
}
|
||||
|
||||
func unmarshalStringSafe(b []byte) (string, []byte, error) {
|
||||
n, b, err := unmarshalUint32Safe(b)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if int64(n) > int64(len(b)) {
|
||||
return "", nil, errShortPacket
|
||||
}
|
||||
return string(b[:n]), b[n:], nil
|
||||
}
|
||||
|
||||
// sendPacket marshals p according to RFC 4234.
|
||||
func sendPacket(w io.Writer, m encoding.BinaryMarshaler) error {
|
||||
bb, err := m.MarshalBinary()
|
||||
if err != nil {
|
||||
return errors.Errorf("binary marshaller failed: %v", err)
|
||||
}
|
||||
if debugDumpTxPacketBytes {
|
||||
debug("send packet: %s %d bytes %x", fxp(bb[0]), len(bb), bb[1:])
|
||||
} else if debugDumpTxPacket {
|
||||
debug("send packet: %s %d bytes", fxp(bb[0]), len(bb))
|
||||
}
|
||||
l := uint32(len(bb))
|
||||
hdr := []byte{byte(l >> 24), byte(l >> 16), byte(l >> 8), byte(l)}
|
||||
_, err = w.Write(hdr)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to send packet header: %v", err)
|
||||
}
|
||||
_, err = w.Write(bb)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to send packet body: %v", 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 {
|
||||
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)
|
||||
return 0, nil, err
|
||||
}
|
||||
if debugDumpRxPacketBytes {
|
||||
debug("recv packet: %s %d bytes %x", fxp(b[0]), l, b[1:])
|
||||
} else if debugDumpRxPacket {
|
||||
debug("recv packet: %s %d bytes", fxp(b[0]), l)
|
||||
}
|
||||
return b[0], b[1:], nil
|
||||
}
|
||||
|
||||
type extensionPair struct {
|
||||
Name string
|
||||
Data string
|
||||
}
|
||||
|
||||
func unmarshalExtensionPair(b []byte) (extensionPair, []byte, error) {
|
||||
var ep extensionPair
|
||||
var err error
|
||||
ep.Name, b, err = unmarshalStringSafe(b)
|
||||
if err != nil {
|
||||
return ep, b, err
|
||||
}
|
||||
ep.Data, b, err = unmarshalStringSafe(b)
|
||||
return ep, b, err
|
||||
}
|
||||
|
||||
// Here starts the definition of packets along with their MarshalBinary
|
||||
// implementations.
|
||||
// Manually writing the marshalling logic wins us a lot of time and
|
||||
// allocation.
|
||||
|
||||
type sshFxInitPacket struct {
|
||||
Version uint32
|
||||
Extensions []extensionPair
|
||||
}
|
||||
|
||||
func (p sshFxInitPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 // byte + uint32
|
||||
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 = marshalUint32(b, p.Version)
|
||||
for _, e := range p.Extensions {
|
||||
b = marshalString(b, e.Name)
|
||||
b = marshalString(b, e.Data)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *sshFxInitPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.Version, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
for len(b) > 0 {
|
||||
var ep extensionPair
|
||||
ep, b, err = unmarshalExtensionPair(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Extensions = append(p.Extensions, ep)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxVersionPacket struct {
|
||||
Version uint32
|
||||
Extensions []struct {
|
||||
Name, Data string
|
||||
}
|
||||
}
|
||||
|
||||
func (p sshFxVersionPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 // byte + uint32
|
||||
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 = 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
|
||||
4 + len(str)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, packetType)
|
||||
b = marshalUint32(b, id)
|
||||
b = marshalString(b, str)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func unmarshalIDString(b []byte, id *uint32, str *string) error {
|
||||
var err error
|
||||
*id, b, err = unmarshalUint32Safe(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*str, b, err = unmarshalStringSafe(b)
|
||||
return err
|
||||
}
|
||||
|
||||
type sshFxpReaddirPacket struct {
|
||||
ID uint32
|
||||
Handle string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Handle)
|
||||
}
|
||||
|
||||
type sshFxpOpendirPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Path)
|
||||
}
|
||||
|
||||
type sshFxpLstatPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Path)
|
||||
}
|
||||
|
||||
type sshFxpStatPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Path)
|
||||
}
|
||||
|
||||
type sshFxpFstatPacket struct {
|
||||
ID uint32
|
||||
Handle string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Handle)
|
||||
}
|
||||
|
||||
type sshFxpClosePacket struct {
|
||||
ID uint32
|
||||
Handle string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Handle)
|
||||
}
|
||||
|
||||
type sshFxpRemovePacket struct {
|
||||
ID uint32
|
||||
Filename string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Filename)
|
||||
}
|
||||
|
||||
type sshFxpRmdirPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Path)
|
||||
}
|
||||
|
||||
type sshFxpSymlinkPacket struct {
|
||||
ID uint32
|
||||
Targetpath string
|
||||
Linkpath string
|
||||
}
|
||||
|
||||
func (p sshFxpSymlinkPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpSymlinkPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Targetpath) +
|
||||
4 + len(p.Linkpath)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_SYMLINK)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Targetpath)
|
||||
b = marshalString(b, p.Linkpath)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *sshFxpSymlinkPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Targetpath, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Linkpath, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxpReadlinkPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Path)
|
||||
}
|
||||
|
||||
type sshFxpRealpathPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
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) UnmarshalBinary(b []byte) error {
|
||||
return unmarshalIDString(b, &p.ID, &p.Path)
|
||||
}
|
||||
|
||||
type sshFxpNameAttr struct {
|
||||
Name string
|
||||
LongName string
|
||||
Attrs []interface{}
|
||||
}
|
||||
|
||||
func (p sshFxpNameAttr) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{}
|
||||
b = marshalString(b, p.Name)
|
||||
b = marshalString(b, p.LongName)
|
||||
for _, attr := range p.Attrs {
|
||||
b = marshal(b, attr)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpNamePacket struct {
|
||||
ID uint32
|
||||
NameAttrs []sshFxpNameAttr
|
||||
}
|
||||
|
||||
func (p sshFxpNamePacket) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{}
|
||||
b = append(b, ssh_FXP_NAME)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalUint32(b, uint32(len(p.NameAttrs)))
|
||||
for _, na := range p.NameAttrs {
|
||||
ab, err := na.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b = append(b, ab...)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpOpenPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
Pflags uint32
|
||||
Flags uint32 // ignored
|
||||
}
|
||||
|
||||
func (p sshFxpOpenPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpOpenPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 +
|
||||
4 + len(p.Path) +
|
||||
4 + 4
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_OPEN)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Path)
|
||||
b = marshalUint32(b, p.Pflags)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *sshFxpOpenPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Path, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Pflags, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Flags, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxpReadPacket struct {
|
||||
ID uint32
|
||||
Handle string
|
||||
Offset uint64
|
||||
Len uint32
|
||||
}
|
||||
|
||||
func (p sshFxpReadPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpReadPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Handle) +
|
||||
8 + 4 // uint64 + uint32
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_READ)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Handle)
|
||||
b = marshalUint64(b, p.Offset)
|
||||
b = marshalUint32(b, p.Len)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *sshFxpReadPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Handle, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Offset, b, err = unmarshalUint64Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Len, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxpRenamePacket struct {
|
||||
ID uint32
|
||||
Oldpath string
|
||||
Newpath string
|
||||
}
|
||||
|
||||
func (p sshFxpRenamePacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpRenamePacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Oldpath) +
|
||||
4 + len(p.Newpath)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_RENAME)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Oldpath)
|
||||
b = marshalString(b, p.Newpath)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *sshFxpRenamePacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Oldpath, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Newpath, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxpWritePacket struct {
|
||||
ID uint32
|
||||
Handle string
|
||||
Offset uint64
|
||||
Length uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (p sshFxpWritePacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpWritePacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Handle) +
|
||||
8 + 4 + // uint64 + uint32
|
||||
len(p.Data)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_WRITE)
|
||||
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
|
||||
}
|
||||
|
||||
func (p *sshFxpWritePacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Handle, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Offset, b, err = unmarshalUint64Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Length, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if uint32(len(b)) < p.Length {
|
||||
return errShortPacket
|
||||
}
|
||||
|
||||
p.Data = append([]byte{}, b[:p.Length]...)
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxpMkdirPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
Flags uint32 // ignored
|
||||
}
|
||||
|
||||
func (p sshFxpMkdirPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpMkdirPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Path) +
|
||||
4 // uint32
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_MKDIR)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Path)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *sshFxpMkdirPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Path, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Flags, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxpSetstatPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
Flags uint32
|
||||
Attrs interface{}
|
||||
}
|
||||
|
||||
type sshFxpFsetstatPacket struct {
|
||||
ID uint32
|
||||
Handle string
|
||||
Flags uint32
|
||||
Attrs interface{}
|
||||
}
|
||||
|
||||
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
|
||||
4 + len(p.Path) +
|
||||
4 // uint32 + uint64
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_SETSTAT)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Path)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
b = marshal(b, p.Attrs)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p sshFxpFsetstatPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + // type(byte) + uint32
|
||||
4 + len(p.Handle) +
|
||||
4 // uint32 + uint64
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_FSETSTAT)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Handle)
|
||||
b = marshalUint32(b, p.Flags)
|
||||
b = marshal(b, p.Attrs)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *sshFxpSetstatPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Path, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Flags, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Attrs = b
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *sshFxpFsetstatPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Handle, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Flags, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Attrs = b
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxpHandlePacket struct {
|
||||
ID uint32
|
||||
Handle string
|
||||
}
|
||||
|
||||
func (p sshFxpHandlePacket) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{ssh_FXP_HANDLE}
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Handle)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpStatusPacket struct {
|
||||
ID uint32
|
||||
StatusError
|
||||
}
|
||||
|
||||
func (p sshFxpStatusPacket) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{ssh_FXP_STATUS}
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalStatus(b, p.StatusError)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type sshFxpDataPacket struct {
|
||||
ID uint32
|
||||
Length uint32
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func (p sshFxpDataPacket) MarshalBinary() ([]byte, error) {
|
||||
b := []byte{ssh_FXP_DATA}
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalUint32(b, p.Length)
|
||||
b = append(b, p.Data[:p.Length]...)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (p *sshFxpDataPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.Length, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if uint32(len(b)) < p.Length {
|
||||
return errors.New("truncated packet")
|
||||
}
|
||||
|
||||
p.Data = make([]byte, p.Length)
|
||||
copy(p.Data, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
type sshFxpStatvfsPacket struct {
|
||||
ID uint32
|
||||
Path string
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_EXTENDED)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, "statvfs@openssh.com")
|
||||
b = marshalString(b, p.Path)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// A StatVFS contains statistics about a filesystem.
|
||||
type StatVFS struct {
|
||||
ID uint32
|
||||
Bsize uint64 /* file system block size */
|
||||
Frsize uint64 /* fundamental fs block size */
|
||||
Blocks uint64 /* number of blocks (unit f_frsize) */
|
||||
Bfree uint64 /* free blocks in file system */
|
||||
Bavail uint64 /* free blocks for non-root */
|
||||
Files uint64 /* total file inodes */
|
||||
Ffree uint64 /* free file inodes */
|
||||
Favail uint64 /* free file inodes for to non-root */
|
||||
Fsid uint64 /* file system id */
|
||||
Flag uint64 /* bit mask of f_flag values */
|
||||
Namemax uint64 /* maximum filename length */
|
||||
}
|
||||
|
||||
// TotalSpace calculates the amount of total space in a filesystem.
|
||||
func (p *StatVFS) TotalSpace() uint64 {
|
||||
return p.Frsize * p.Blocks
|
||||
}
|
||||
|
||||
// FreeSpace calculates the amount of free space in a filesystem.
|
||||
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) {
|
||||
var buf bytes.Buffer
|
||||
buf.Write([]byte{ssh_FXP_EXTENDED_REPLY})
|
||||
err := binary.Write(&buf, binary.BigEndian, p)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
type sshFxpExtendedPacket struct {
|
||||
ID uint32
|
||||
ExtendedRequest string
|
||||
SpecificPacket interface {
|
||||
serverRespondablePacket
|
||||
readonly() bool
|
||||
}
|
||||
}
|
||||
|
||||
func (p sshFxpExtendedPacket) id() uint32 { return p.ID }
|
||||
func (p sshFxpExtendedPacket) readonly() bool { return p.SpecificPacket.readonly() }
|
||||
|
||||
func (p sshFxpExtendedPacket) respond(svr *Server) error {
|
||||
return p.SpecificPacket.respond(svr)
|
||||
}
|
||||
|
||||
func (p *sshFxpExtendedPacket) UnmarshalBinary(b []byte) error {
|
||||
var err error
|
||||
bOrig := b
|
||||
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
return err
|
||||
} else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// specific unmarshalling
|
||||
switch p.ExtendedRequest {
|
||||
case "statvfs@openssh.com":
|
||||
p.SpecificPacket = &sshFxpExtendedPacketStatVFS{}
|
||||
default:
|
||||
return errUnknownExtendedPacket
|
||||
}
|
||||
|
||||
return p.SpecificPacket.UnmarshalBinary(bOrig)
|
||||
}
|
||||
|
||||
type sshFxpExtendedPacketStatVFS struct {
|
||||
ID uint32
|
||||
ExtendedRequest string
|
||||
Path string
|
||||
}
|
||||
|
||||
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 {
|
||||
return err
|
||||
} else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
} else if p.Path, b, err = unmarshalStringSafe(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
345
vendor/github.com/pkg/sftp/packet_test.go
generated
vendored
Normal file
345
vendor/github.com/pkg/sftp/packet_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var marshalUint32Tests = []struct {
|
||||
v uint32
|
||||
want []byte
|
||||
}{
|
||||
{1, []byte{0, 0, 0, 1}},
|
||||
{256, []byte{0, 0, 1, 0}},
|
||||
{^uint32(0), []byte{255, 255, 255, 255}},
|
||||
}
|
||||
|
||||
func TestMarshalUint32(t *testing.T) {
|
||||
for _, tt := range marshalUint32Tests {
|
||||
got := marshalUint32(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshalUint32(%d): want %v, got %v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalUint64Tests = []struct {
|
||||
v uint64
|
||||
want []byte
|
||||
}{
|
||||
{1, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}},
|
||||
{256, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0}},
|
||||
{^uint64(0), []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||
{1 << 32, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
|
||||
}
|
||||
|
||||
func TestMarshalUint64(t *testing.T) {
|
||||
for _, tt := range marshalUint64Tests {
|
||||
got := marshalUint64(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshalUint64(%d): want %#v, got %#v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalStringTests = []struct {
|
||||
v string
|
||||
want []byte
|
||||
}{
|
||||
{"", []byte{0, 0, 0, 0}},
|
||||
{"/foo", []byte{0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f}},
|
||||
}
|
||||
|
||||
func TestMarshalString(t *testing.T) {
|
||||
for _, tt := range marshalStringTests {
|
||||
got := marshalString(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshalString(%q): want %#v, got %#v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var marshalTests = []struct {
|
||||
v interface{}
|
||||
want []byte
|
||||
}{
|
||||
{uint8(1), []byte{1}},
|
||||
{byte(1), []byte{1}},
|
||||
{uint32(1), []byte{0, 0, 0, 1}},
|
||||
{uint64(1), []byte{0, 0, 0, 0, 0, 0, 0, 1}},
|
||||
{"foo", []byte{0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f}},
|
||||
{[]uint32{1, 2, 3, 4}, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x4}},
|
||||
}
|
||||
|
||||
func TestMarshal(t *testing.T) {
|
||||
for _, tt := range marshalTests {
|
||||
got := marshal(nil, tt.v)
|
||||
if !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("marshal(%v): want %#v, got %#v", tt.v, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalUint32Tests = []struct {
|
||||
b []byte
|
||||
want uint32
|
||||
rest []byte
|
||||
}{
|
||||
{[]byte{0, 0, 0, 0}, 0, nil},
|
||||
{[]byte{0, 0, 1, 0}, 256, nil},
|
||||
{[]byte{255, 0, 0, 255}, 4278190335, nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalUint32(t *testing.T) {
|
||||
for _, tt := range unmarshalUint32Tests {
|
||||
got, rest := unmarshalUint32(tt.b)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("unmarshalUint32(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalUint64Tests = []struct {
|
||||
b []byte
|
||||
want uint64
|
||||
rest []byte
|
||||
}{
|
||||
{[]byte{0, 0, 0, 0, 0, 0, 0, 0}, 0, nil},
|
||||
{[]byte{0, 0, 0, 0, 0, 0, 1, 0}, 256, nil},
|
||||
{[]byte{255, 0, 0, 0, 0, 0, 0, 255}, 18374686479671623935, nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalUint64(t *testing.T) {
|
||||
for _, tt := range unmarshalUint64Tests {
|
||||
got, rest := unmarshalUint64(tt.b)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("unmarshalUint64(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unmarshalStringTests = []struct {
|
||||
b []byte
|
||||
want string
|
||||
rest []byte
|
||||
}{
|
||||
{marshalString(nil, ""), "", nil},
|
||||
{marshalString(nil, "blah"), "blah", nil},
|
||||
}
|
||||
|
||||
func TestUnmarshalString(t *testing.T) {
|
||||
for _, tt := range unmarshalStringTests {
|
||||
got, rest := unmarshalString(tt.b)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("unmarshalUint64(%v): want %q, %#v, got %q, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sendPacketTests = []struct {
|
||||
p encoding.BinaryMarshaler
|
||||
want []byte
|
||||
}{
|
||||
{sshFxInitPacket{
|
||||
Version: 3,
|
||||
Extensions: []extensionPair{
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
},
|
||||
}, []byte{0x0, 0x0, 0x0, 0x26, 0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}},
|
||||
|
||||
{sshFxpOpenPacket{
|
||||
ID: 1,
|
||||
Path: "/foo",
|
||||
Pflags: flags(os.O_RDONLY),
|
||||
}, []byte{0x0, 0x0, 0x0, 0x15, 0x3, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
|
||||
|
||||
{sshFxpWritePacket{
|
||||
ID: 124,
|
||||
Handle: "foo",
|
||||
Offset: 13,
|
||||
Length: uint32(len([]byte("bar"))),
|
||||
Data: []byte("bar"),
|
||||
}, []byte{0x0, 0x0, 0x0, 0x1b, 0x6, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x3, 0x62, 0x61, 0x72}},
|
||||
|
||||
{sshFxpSetstatPacket{
|
||||
ID: 31,
|
||||
Path: "/bar",
|
||||
Flags: flags(os.O_WRONLY),
|
||||
Attrs: struct {
|
||||
UID uint32
|
||||
GID uint32
|
||||
}{1000, 100},
|
||||
}, []byte{0x0, 0x0, 0x0, 0x19, 0x9, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x62, 0x61, 0x72, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x3, 0xe8, 0x0, 0x0, 0x0, 0x64}},
|
||||
}
|
||||
|
||||
func TestSendPacket(t *testing.T) {
|
||||
for _, tt := range sendPacketTests {
|
||||
var w bytes.Buffer
|
||||
sendPacket(&w, tt.p)
|
||||
if got := w.Bytes(); !bytes.Equal(tt.want, got) {
|
||||
t.Errorf("sendPacket(%v): want %#v, got %#v", tt.p, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sp(p encoding.BinaryMarshaler) []byte {
|
||||
var w bytes.Buffer
|
||||
sendPacket(&w, p)
|
||||
return w.Bytes()
|
||||
}
|
||||
|
||||
var recvPacketTests = []struct {
|
||||
b []byte
|
||||
want uint8
|
||||
rest []byte
|
||||
}{
|
||||
{sp(sshFxInitPacket{
|
||||
Version: 3,
|
||||
Extensions: []extensionPair{
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
},
|
||||
}), ssh_FXP_INIT, []byte{0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}},
|
||||
}
|
||||
|
||||
func TestRecvPacket(t *testing.T) {
|
||||
for _, tt := range recvPacketTests {
|
||||
r := bytes.NewReader(tt.b)
|
||||
got, rest, _ := recvPacket(r)
|
||||
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||
t.Errorf("recvPacket(%#v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHFxpOpenPacketreadonly(t *testing.T) {
|
||||
var tests = []struct {
|
||||
pflags uint32
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
pflags: ssh_FXF_READ,
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
pflags: ssh_FXF_WRITE,
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
pflags: ssh_FXF_READ | ssh_FXF_WRITE,
|
||||
ok: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
p := &sshFxpOpenPacket{
|
||||
Pflags: tt.pflags,
|
||||
}
|
||||
|
||||
if want, got := tt.ok, p.readonly(); want != got {
|
||||
t.Errorf("unexpected value for p.readonly(): want: %v, got: %v",
|
||||
want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHFxpOpenPackethasPflags(t *testing.T) {
|
||||
var tests = []struct {
|
||||
desc string
|
||||
haveFlags uint32
|
||||
testFlags []uint32
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
desc: "have read, test against write",
|
||||
haveFlags: ssh_FXF_READ,
|
||||
testFlags: []uint32{ssh_FXF_WRITE},
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
desc: "have write, test against read",
|
||||
haveFlags: ssh_FXF_WRITE,
|
||||
testFlags: []uint32{ssh_FXF_READ},
|
||||
ok: false,
|
||||
},
|
||||
{
|
||||
desc: "have read+write, test against read",
|
||||
haveFlags: ssh_FXF_READ | ssh_FXF_WRITE,
|
||||
testFlags: []uint32{ssh_FXF_READ},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
desc: "have read+write, test against write",
|
||||
haveFlags: ssh_FXF_READ | ssh_FXF_WRITE,
|
||||
testFlags: []uint32{ssh_FXF_WRITE},
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
desc: "have read+write, test against read+write",
|
||||
haveFlags: ssh_FXF_READ | ssh_FXF_WRITE,
|
||||
testFlags: []uint32{ssh_FXF_READ, ssh_FXF_WRITE},
|
||||
ok: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Log(tt.desc)
|
||||
|
||||
p := &sshFxpOpenPacket{
|
||||
Pflags: tt.haveFlags,
|
||||
}
|
||||
|
||||
if want, got := tt.ok, p.hasPflags(tt.testFlags...); want != got {
|
||||
t.Errorf("unexpected value for p.hasPflags(%#v): want: %v, got: %v",
|
||||
tt.testFlags, want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalInit(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxInitPacket{
|
||||
Version: 3,
|
||||
Extensions: []extensionPair{
|
||||
{"posix-rename@openssh.com", "1"},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalOpen(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxpOpenPacket{
|
||||
ID: 1,
|
||||
Path: "/home/test/some/random/path",
|
||||
Pflags: flags(os.O_RDONLY),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalWriteWorstCase(b *testing.B) {
|
||||
data := make([]byte, 32*1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxpWritePacket{
|
||||
ID: 1,
|
||||
Handle: "someopaquehandle",
|
||||
Offset: 0,
|
||||
Length: uint32(len(data)),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalWrite1k(b *testing.B) {
|
||||
data := make([]byte, 1024)
|
||||
for i := 0; i < b.N; i++ {
|
||||
sp(sshFxpWritePacket{
|
||||
ID: 1,
|
||||
Handle: "someopaquehandle",
|
||||
Offset: 0,
|
||||
Length: uint32(len(data)),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
}
|
||||
5
vendor/github.com/pkg/sftp/release.go
generated
vendored
Normal file
5
vendor/github.com/pkg/sftp/release.go
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// +build !debug
|
||||
|
||||
package sftp
|
||||
|
||||
func debug(fmt string, args ...interface{}) {}
|
||||
244
vendor/github.com/pkg/sftp/request-example.go
generated
vendored
Normal file
244
vendor/github.com/pkg/sftp/request-example.go
generated
vendored
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
package sftp
|
||||
|
||||
// This serves as an example of how to implement the request server handler as
|
||||
// well as a dummy backend for testing. It implements an in-memory backend that
|
||||
// works as a very simple filesystem with simple flat key-value lookup system.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InMemHandler returns a Hanlders object with the test handlers
|
||||
func InMemHandler() Handlers {
|
||||
root := &root{
|
||||
files: make(map[string]*memFile),
|
||||
}
|
||||
root.memFile = newMemFile("/", true)
|
||||
return Handlers{root, root, root, root}
|
||||
}
|
||||
|
||||
// Handlers
|
||||
func (fs *root) Fileread(r Request) (io.ReaderAt, error) {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.symlink != "" {
|
||||
file, err = fs.fetch(file.symlink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return file.ReaderAt()
|
||||
}
|
||||
|
||||
func (fs *root) Filewrite(r Request) (io.WriterAt, error) {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err == os.ErrNotExist {
|
||||
dir, err := fs.fetch(filepath.Dir(r.Filepath))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !dir.isdir {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
file = newMemFile(r.Filepath, false)
|
||||
fs.files[r.Filepath] = file
|
||||
}
|
||||
return file.WriterAt()
|
||||
}
|
||||
|
||||
func (fs *root) Filecmd(r Request) error {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
switch r.Method {
|
||||
case "Setstat":
|
||||
return nil
|
||||
case "Rename":
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
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")}
|
||||
}
|
||||
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
|
||||
}
|
||||
delete(fs.files, r.Filepath)
|
||||
case "Mkdir":
|
||||
_, err := fs.fetch(filepath.Dir(r.Filepath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fs.files[r.Filepath] = newMemFile(r.Filepath, true)
|
||||
case "Symlink":
|
||||
_, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
link := newMemFile(r.Target, false)
|
||||
link.symlink = r.Filepath
|
||||
fs.files[r.Target] = link
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fs *root) Fileinfo(r Request) ([]os.FileInfo, error) {
|
||||
fs.filesLock.Lock()
|
||||
defer fs.filesLock.Unlock()
|
||||
switch r.Method {
|
||||
case "List":
|
||||
var err error
|
||||
batch_size := 10
|
||||
current_offset := 0
|
||||
if token := r.LsNext(); token != "" {
|
||||
current_offset, err = strconv.Atoi(token)
|
||||
if err != nil {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
}
|
||||
ordered_names := []string{}
|
||||
for fn, _ := range fs.files {
|
||||
if filepath.Dir(fn) == r.Filepath {
|
||||
ordered_names = append(ordered_names, fn)
|
||||
}
|
||||
}
|
||||
sort.Sort(sort.StringSlice(ordered_names))
|
||||
list := make([]os.FileInfo, len(ordered_names))
|
||||
for i, fn := range ordered_names {
|
||||
list[i] = fs.files[fn]
|
||||
}
|
||||
if len(list) < current_offset {
|
||||
return nil, io.EOF
|
||||
}
|
||||
new_offset := current_offset + batch_size
|
||||
if new_offset > len(list) {
|
||||
new_offset = len(list)
|
||||
}
|
||||
r.LsSave(strconv.Itoa(new_offset))
|
||||
return list[current_offset:new_offset], nil
|
||||
case "Stat":
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []os.FileInfo{file}, nil
|
||||
case "Readlink":
|
||||
file, err := fs.fetch(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if file.symlink != "" {
|
||||
file, err = fs.fetch(file.symlink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return []os.FileInfo{file}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// In memory file-system-y thing that the Hanlders live on
|
||||
type root struct {
|
||||
*memFile
|
||||
files map[string]*memFile
|
||||
filesLock sync.Mutex
|
||||
}
|
||||
|
||||
func (fs *root) fetch(path string) (*memFile, error) {
|
||||
if path == "/" {
|
||||
return fs.memFile, nil
|
||||
}
|
||||
if file, ok := fs.files[path]; ok {
|
||||
return file, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// Implements os.FileInfo, Reader and Writer interfaces.
|
||||
// These are the 3 interfaces necessary for the Handlers.
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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) Mode() os.FileMode {
|
||||
ret := os.FileMode(0644)
|
||||
if f.isdir {
|
||||
ret = os.FileMode(0755) | os.ModeDir
|
||||
}
|
||||
if f.symlink != "" {
|
||||
ret = os.FileMode(0777) | os.ModeSymlink
|
||||
}
|
||||
return ret
|
||||
}
|
||||
func (f *memFile) ModTime() time.Time { return f.modtime }
|
||||
func (f *memFile) IsDir() bool { return f.isdir }
|
||||
func (f *memFile) Sys() interface{} {
|
||||
return fakeFileInfoSys()
|
||||
}
|
||||
|
||||
// Read/Write
|
||||
func (f *memFile) ReaderAt() (io.ReaderAt, error) {
|
||||
if f.isdir {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return bytes.NewReader(f.content), nil
|
||||
}
|
||||
|
||||
func (f *memFile) WriterAt() (io.WriterAt, error) {
|
||||
if f.isdir {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
func (f *memFile) WriteAt(p []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
|
||||
}
|
||||
30
vendor/github.com/pkg/sftp/request-interfaces.go
generated
vendored
Normal file
30
vendor/github.com/pkg/sftp/request-interfaces.go
generated
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Interfaces are differentiated based on required returned values.
|
||||
// All input arguments are to be pulled from Request (the only arg).
|
||||
|
||||
// FileReader should return an io.Reader for the filepath
|
||||
type FileReader interface {
|
||||
Fileread(Request) (io.ReaderAt, error)
|
||||
}
|
||||
|
||||
// FileWriter should return an io.Writer for the filepath
|
||||
type FileWriter interface {
|
||||
Filewrite(Request) (io.WriterAt, error)
|
||||
}
|
||||
|
||||
// FileCmder should return an error (rename, remove, setstate, etc.)
|
||||
type FileCmder interface {
|
||||
Filecmd(Request) error
|
||||
}
|
||||
|
||||
// FileInfoer should return file listing info and errors (readdir, stat)
|
||||
// note stat requests would return a list of 1
|
||||
type FileInfoer interface {
|
||||
Fileinfo(Request) ([]os.FileInfo, error)
|
||||
}
|
||||
48
vendor/github.com/pkg/sftp/request-readme.md
generated
vendored
Normal file
48
vendor/github.com/pkg/sftp/request-readme.md
generated
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Request Based SFTP API
|
||||
|
||||
The request based API allows for custom backends in a way similar to the http
|
||||
package. In order to create a backend you need to implement 4 handler
|
||||
interfaces; one for reading, one for writing, one for misc commands and one for
|
||||
listing files. Each has 1 required method and in each case those methods take
|
||||
the Request as the only parameter and they each return something different.
|
||||
These 4 interfaces are enough to handle all the SFTP traffic in a simplified
|
||||
manner.
|
||||
|
||||
The Request structure has 5 public fields which you will deal with.
|
||||
|
||||
- Method (string) - string name of incoming call
|
||||
- Filepath (string) - path of file to act on
|
||||
- Attrs ([]byte) - byte string of file attribute data
|
||||
- Target (string) - target path for renames and sym-links
|
||||
|
||||
Below are the methods and a brief description of what they need to do.
|
||||
|
||||
### Fileread(*Request) (io.Reader, error)
|
||||
|
||||
Handler for "Get" method and returns an io.Reader for the file which the server
|
||||
then sends to the client.
|
||||
|
||||
### Filewrite(*Request) (io.Writer, error)
|
||||
|
||||
Handler for "Put" method and returns an io.Writer for the file which the server
|
||||
then writes the uploaded file to.
|
||||
|
||||
### Filecmd(*Request) error
|
||||
|
||||
Handles "SetStat", "Rename", "Rmdir", "Mkdir" and "Symlink" methods. Makes the
|
||||
appropriate changes and returns nil for success or an filesystem like error
|
||||
(eg. os.ErrNotExist).
|
||||
|
||||
### Fileinfo(*Request) ([]os.FileInfo, error)
|
||||
|
||||
Handles "List", "Stat", "Readlink" methods. Gathers/creates FileInfo structs
|
||||
with the data on the files and returns in a list (list of 1 for Stat and
|
||||
Readlink).
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
- Add support for API users to see trace/debugging info of what is going on
|
||||
inside SFTP server.
|
||||
- Consider adding support for SFTP file append only mode.
|
||||
|
||||
231
vendor/github.com/pkg/sftp/request-server.go
generated
vendored
Normal file
231
vendor/github.com/pkg/sftp/request-server.go
generated
vendored
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var maxTxPacket uint32 = 1 << 15
|
||||
|
||||
type handleHandler func(string) string
|
||||
|
||||
// Handlers contains the 4 SFTP server request handlers.
|
||||
type Handlers struct {
|
||||
FileGet FileReader
|
||||
FilePut FileWriter
|
||||
FileCmd FileCmder
|
||||
FileInfo FileInfoer
|
||||
}
|
||||
|
||||
// RequestServer abstracts the sftp protocol with an http request-like protocol
|
||||
type RequestServer struct {
|
||||
*serverConn
|
||||
Handlers Handlers
|
||||
pktMgr packetManager
|
||||
openRequests map[string]Request
|
||||
openRequestLock sync.RWMutex
|
||||
handleCount int
|
||||
}
|
||||
|
||||
// NewRequestServer creates/allocates/returns new RequestServer.
|
||||
// Normally there there will be one server per user-session.
|
||||
func NewRequestServer(rwc io.ReadWriteCloser, h Handlers) *RequestServer {
|
||||
svrConn := &serverConn{
|
||||
conn: conn{
|
||||
Reader: rwc,
|
||||
WriteCloser: rwc,
|
||||
},
|
||||
}
|
||||
return &RequestServer{
|
||||
serverConn: svrConn,
|
||||
Handlers: h,
|
||||
pktMgr: newPktMgr(svrConn),
|
||||
openRequests: make(map[string]Request),
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *RequestServer) nextRequest(r Request) string {
|
||||
rs.openRequestLock.Lock()
|
||||
defer rs.openRequestLock.Unlock()
|
||||
rs.handleCount++
|
||||
handle := strconv.Itoa(rs.handleCount)
|
||||
rs.openRequests[handle] = r
|
||||
return handle
|
||||
}
|
||||
|
||||
func (rs *RequestServer) getRequest(handle string) (Request, bool) {
|
||||
rs.openRequestLock.RLock()
|
||||
defer rs.openRequestLock.RUnlock()
|
||||
r, ok := rs.openRequests[handle]
|
||||
return r, ok
|
||||
}
|
||||
|
||||
func (rs *RequestServer) closeRequest(handle string) {
|
||||
rs.openRequestLock.Lock()
|
||||
defer rs.openRequestLock.Unlock()
|
||||
if r, ok := rs.openRequests[handle]; ok {
|
||||
r.close()
|
||||
delete(rs.openRequests, handle)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var wg sync.WaitGroup
|
||||
runWorker := func(ch requestChan) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := rs.packetWorker(ch); err != nil {
|
||||
rs.conn.Close() // shuts down recvPacket
|
||||
}
|
||||
}()
|
||||
}
|
||||
pktChan := rs.pktMgr.workerChan(runWorker)
|
||||
|
||||
var err error
|
||||
var pkt requestPacket
|
||||
var pktType uint8
|
||||
var pktBytes []byte
|
||||
for {
|
||||
pktType, pktBytes, err = rs.recvPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
|
||||
if err != nil {
|
||||
debug("makePacket err: %v", err)
|
||||
rs.conn.Close() // shuts down recvPacket
|
||||
break
|
||||
}
|
||||
|
||||
pktChan <- pkt
|
||||
}
|
||||
|
||||
close(pktChan) // shuts down sftpServerWorkers
|
||||
wg.Wait() // wait for all workers to exit
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
|
||||
for pkt := range pktChan {
|
||||
var rpkt responsePacket
|
||||
switch pkt := pkt.(type) {
|
||||
case *sshFxInitPacket:
|
||||
rpkt = sshFxVersionPacket{sftpProtocolVersion, nil}
|
||||
case *sshFxpClosePacket:
|
||||
handle := pkt.getHandle()
|
||||
rs.closeRequest(handle)
|
||||
rpkt = statusFromError(pkt, nil)
|
||||
case *sshFxpRealpathPacket:
|
||||
rpkt = cleanPath(pkt)
|
||||
case isOpener:
|
||||
handle := rs.nextRequest(requestFromPacket(pkt))
|
||||
rpkt = sshFxpHandlePacket{pkt.id(), handle}
|
||||
case *sshFxpFstatPacket:
|
||||
handle := pkt.getHandle()
|
||||
request, ok := rs.getRequest(handle)
|
||||
if !ok {
|
||||
rpkt = statusFromError(pkt, syscall.EBADF)
|
||||
} else {
|
||||
request = requestFromPacket(
|
||||
&sshFxpStatPacket{ID: pkt.id(), Path: request.Filepath})
|
||||
rpkt = rs.handle(request, pkt)
|
||||
}
|
||||
case *sshFxpFsetstatPacket:
|
||||
handle := pkt.getHandle()
|
||||
request, ok := rs.getRequest(handle)
|
||||
if !ok {
|
||||
rpkt = statusFromError(pkt, syscall.EBADF)
|
||||
} else {
|
||||
request = requestFromPacket(
|
||||
&sshFxpSetstatPacket{ID: pkt.id(), Path: request.Filepath,
|
||||
Flags: pkt.Flags, Attrs: pkt.Attrs,
|
||||
})
|
||||
rpkt = rs.handle(request, pkt)
|
||||
}
|
||||
case hasHandle:
|
||||
handle := pkt.getHandle()
|
||||
request, ok := rs.getRequest(handle)
|
||||
request.update(pkt)
|
||||
if !ok {
|
||||
rpkt = statusFromError(pkt, syscall.EBADF)
|
||||
} else {
|
||||
rpkt = rs.handle(request, pkt)
|
||||
}
|
||||
case hasPath:
|
||||
request := requestFromPacket(pkt)
|
||||
rpkt = rs.handle(request, pkt)
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", pkt)
|
||||
}
|
||||
|
||||
err := rs.sendPacket(rpkt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanPath(pkt *sshFxpRealpathPacket) responsePacket {
|
||||
path := pkt.getPath()
|
||||
if !filepath.IsAbs(path) {
|
||||
path = "/" + path
|
||||
} // all paths are absolute
|
||||
|
||||
cleaned_path := filepath.Clean(path)
|
||||
return &sshFxpNamePacket{
|
||||
ID: pkt.id(),
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: cleaned_path,
|
||||
LongName: cleaned_path,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func (rs *RequestServer) handle(request Request, pkt requestPacket) responsePacket {
|
||||
// fmt.Println("Request Method: ", request.Method)
|
||||
rpkt, err := request.handle(rs.Handlers)
|
||||
if err != nil {
|
||||
err = errorAdapter(err)
|
||||
rpkt = statusFromError(pkt, err)
|
||||
}
|
||||
return rpkt
|
||||
}
|
||||
|
||||
// Wrap underlying connection methods to use packetManager
|
||||
func (rs *RequestServer) sendPacket(m encoding.BinaryMarshaler) error {
|
||||
if pkt, ok := m.(responsePacket); ok {
|
||||
rs.pktMgr.readyPacket(pkt)
|
||||
} else {
|
||||
return errors.Errorf("unexpected packet type %T", m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *RequestServer) sendError(p ider, err error) error {
|
||||
return rs.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
|
||||
// os.ErrNotExist should convert to ssh_FX_NO_SUCH_FILE, but is not recognized
|
||||
// by statusFromError. So we convert to syscall.ENOENT which it does.
|
||||
func errorAdapter(err error) error {
|
||||
if err == os.ErrNotExist {
|
||||
return syscall.ENOENT
|
||||
}
|
||||
return err
|
||||
}
|
||||
329
vendor/github.com/pkg/sftp/request-server_test.go
generated
vendored
Normal file
329
vendor/github.com/pkg/sftp/request-server_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
type csPair struct {
|
||||
cli *Client
|
||||
svr *RequestServer
|
||||
}
|
||||
|
||||
// these must be closed in order, else client.Close will hang
|
||||
func (cs csPair) Close() {
|
||||
cs.svr.Close()
|
||||
cs.cli.Close()
|
||||
os.Remove(sock)
|
||||
}
|
||||
|
||||
func (cs csPair) testHandler() *root {
|
||||
return cs.svr.Handlers.FileGet.(*root)
|
||||
}
|
||||
|
||||
const sock = "/tmp/rstest.sock"
|
||||
|
||||
func clientRequestServerPair(t *testing.T) *csPair {
|
||||
ready := make(chan bool)
|
||||
os.Remove(sock) // either this or signal handling
|
||||
var server *RequestServer
|
||||
go func() {
|
||||
l, err := net.Listen("unix", sock)
|
||||
if err != nil {
|
||||
// neither assert nor t.Fatal reliably exit before Accept errors
|
||||
panic(err)
|
||||
}
|
||||
ready <- true
|
||||
fd, err := l.Accept()
|
||||
assert.Nil(t, err)
|
||||
handlers := InMemHandler()
|
||||
server = NewRequestServer(fd, handlers)
|
||||
server.Serve()
|
||||
}()
|
||||
<-ready
|
||||
defer os.Remove(sock)
|
||||
c, err := net.Dial("unix", sock)
|
||||
assert.Nil(t, err)
|
||||
client, err := NewClientPipe(c, c)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v\n", err)
|
||||
}
|
||||
return &csPair{client, server}
|
||||
}
|
||||
|
||||
// after adding logging, maybe check log to make sure packet handling
|
||||
// was split over more than one worker
|
||||
func TestRequestSplitWrite(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
w, err := p.cli.Create("/foo")
|
||||
assert.Nil(t, err)
|
||||
p.cli.maxPacket = 3 // force it to send in small chunks
|
||||
contents := "one two three four five six seven eight nine ten"
|
||||
w.Write([]byte(contents))
|
||||
w.Close()
|
||||
r := p.testHandler()
|
||||
f, _ := r.fetch("/foo")
|
||||
assert.Equal(t, contents, string(f.content))
|
||||
}
|
||||
|
||||
func TestRequestCache(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
foo := NewRequest("", "foo")
|
||||
bar := NewRequest("", "bar")
|
||||
fh := p.svr.nextRequest(foo)
|
||||
bh := p.svr.nextRequest(bar)
|
||||
assert.Len(t, p.svr.openRequests, 2)
|
||||
_foo, ok := p.svr.getRequest(fh)
|
||||
assert.Equal(t, foo, _foo)
|
||||
assert.True(t, ok)
|
||||
_, ok = p.svr.getRequest("zed")
|
||||
assert.False(t, ok)
|
||||
p.svr.closeRequest(fh)
|
||||
p.svr.closeRequest(bh)
|
||||
assert.Len(t, p.svr.openRequests, 0)
|
||||
}
|
||||
|
||||
func TestRequestCacheState(t *testing.T) {
|
||||
// test operation that uses open/close
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, p.svr.openRequests, 0)
|
||||
// test operation that doesn't open/close
|
||||
err = p.cli.Remove("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, p.svr.openRequests, 0)
|
||||
}
|
||||
|
||||
func putTestFile(cli *Client, path, content string) (int, error) {
|
||||
w, err := cli.Create(path)
|
||||
if err == nil {
|
||||
defer w.Close()
|
||||
return w.Write([]byte(content))
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func TestRequestWrite(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
n, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 5, n)
|
||||
r := p.testHandler()
|
||||
f, err := r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, f.isdir)
|
||||
assert.Equal(t, f.content, []byte("hello"))
|
||||
}
|
||||
|
||||
// needs fail check
|
||||
func TestRequestFilename(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
f, err := r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, f.Name(), "foo")
|
||||
}
|
||||
|
||||
func TestRequestRead(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
rf, err := p.cli.Open("/foo")
|
||||
assert.Nil(t, err)
|
||||
defer rf.Close()
|
||||
contents := make([]byte, 5)
|
||||
n, err := rf.Read(contents)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
assert.Equal(t, 5, n)
|
||||
assert.Equal(t, "hello", string(contents[0:5]))
|
||||
}
|
||||
|
||||
func TestRequestReadFail(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
rf, err := p.cli.Open("/foo")
|
||||
assert.Nil(t, err)
|
||||
contents := make([]byte, 5)
|
||||
n, err := rf.Read(contents)
|
||||
assert.Equal(t, n, 0)
|
||||
assert.Exactly(t, os.ErrNotExist, err)
|
||||
}
|
||||
|
||||
func TestRequestOpen(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
fh, err := p.cli.Open("foo")
|
||||
assert.Nil(t, err)
|
||||
err = fh.Close()
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestRequestMkdir(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
err := p.cli.Mkdir("/foo")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
f, err := r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, f.isdir)
|
||||
}
|
||||
|
||||
func TestRequestRemove(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
_, err = r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Remove("/foo")
|
||||
assert.Nil(t, err)
|
||||
_, err = r.fetch("/foo")
|
||||
assert.Equal(t, err, os.ErrNotExist)
|
||||
}
|
||||
|
||||
func TestRequestRename(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
_, err = r.fetch("/foo")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Rename("/foo", "/bar")
|
||||
assert.Nil(t, err)
|
||||
_, err = r.fetch("/bar")
|
||||
assert.Nil(t, err)
|
||||
_, err = r.fetch("/foo")
|
||||
assert.Equal(t, err, os.ErrNotExist)
|
||||
}
|
||||
|
||||
func TestRequestRenameFail(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
_, err = putTestFile(p.cli, "/bar", "goodbye")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Rename("/foo", "/bar")
|
||||
assert.IsType(t, &StatusError{}, err)
|
||||
}
|
||||
|
||||
func TestRequestStat(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
fi, err := p.cli.Stat("/foo")
|
||||
assert.Equal(t, fi.Name(), "foo")
|
||||
assert.Equal(t, fi.Size(), int64(5))
|
||||
assert.Equal(t, fi.Mode(), os.FileMode(0644))
|
||||
assert.NoError(t, testOsSys(fi.Sys()))
|
||||
}
|
||||
|
||||
// NOTE: Setstat is a noop in the request server tests, but we want to test
|
||||
// that is does nothing without crapping out.
|
||||
func TestRequestSetstat(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
mode := os.FileMode(0644)
|
||||
err = p.cli.Chmod("/foo", mode)
|
||||
assert.Nil(t, err)
|
||||
fi, err := p.cli.Stat("/foo")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, fi.Name(), "foo")
|
||||
assert.Equal(t, fi.Size(), int64(5))
|
||||
assert.Equal(t, fi.Mode(), os.FileMode(0644))
|
||||
assert.NoError(t, testOsSys(fi.Sys()))
|
||||
}
|
||||
|
||||
func TestRequestFstat(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
fp, err := p.cli.Open("/foo")
|
||||
assert.Nil(t, err)
|
||||
fi, err := fp.Stat()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, fi.Name(), "foo")
|
||||
assert.Equal(t, fi.Size(), int64(5))
|
||||
assert.Equal(t, fi.Mode(), os.FileMode(0644))
|
||||
assert.NoError(t, testOsSys(fi.Sys()))
|
||||
}
|
||||
|
||||
func TestRequestStatFail(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
fi, err := p.cli.Stat("/foo")
|
||||
assert.Nil(t, fi)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestRequestSymlink(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Symlink("/foo", "/bar")
|
||||
assert.Nil(t, err)
|
||||
r := p.testHandler()
|
||||
fi, err := r.fetch("/bar")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, fi.Mode()&os.ModeSymlink == os.ModeSymlink)
|
||||
}
|
||||
|
||||
func TestRequestSymlinkFail(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
err := p.cli.Symlink("/foo", "/bar")
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
func TestRequestReadlink(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
_, err := putTestFile(p.cli, "/foo", "hello")
|
||||
assert.Nil(t, err)
|
||||
err = p.cli.Symlink("/foo", "/bar")
|
||||
assert.Nil(t, err)
|
||||
rl, err := p.cli.ReadLink("/bar")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "foo", rl)
|
||||
}
|
||||
|
||||
func TestRequestReaddir(t *testing.T) {
|
||||
p := clientRequestServerPair(t)
|
||||
defer p.Close()
|
||||
for i := 0; i < 100; i++ {
|
||||
fname := fmt.Sprintf("/foo_%02d", i)
|
||||
_, err := putTestFile(p.cli, fname, fname)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
di, err := p.cli.ReadDir("/")
|
||||
assert.Nil(t, err)
|
||||
assert.Len(t, di, 100)
|
||||
names := []string{di[18].Name(), di[81].Name()}
|
||||
assert.Equal(t, []string{"foo_18", "foo_81"}, names)
|
||||
}
|
||||
23
vendor/github.com/pkg/sftp/request-unix.go
generated
vendored
Normal file
23
vendor/github.com/pkg/sftp/request-unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// +build !windows
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func fakeFileInfoSys() interface{} {
|
||||
return &syscall.Stat_t{Uid: 65534, Gid: 65534}
|
||||
}
|
||||
|
||||
func testOsSys(sys interface{}) error {
|
||||
fstat := sys.(*FileStat)
|
||||
if fstat.UID != uint32(65534) {
|
||||
return errors.New("Uid failed to match.")
|
||||
}
|
||||
if fstat.GID != uint32(65534) {
|
||||
return errors.New("Gid failed to match:")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
334
vendor/github.com/pkg/sftp/request.go
generated
vendored
Normal file
334
vendor/github.com/pkg/sftp/request.go
generated
vendored
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// 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
|
||||
Method string
|
||||
Filepath string
|
||||
Flags uint32
|
||||
Attrs []byte // convert to sub-struct
|
||||
Target string // for renames and sym-links
|
||||
// packet data
|
||||
pkt_id uint32
|
||||
packets chan packet_data
|
||||
// reader/writer/readdir from handlers
|
||||
stateLock *sync.RWMutex
|
||||
state *state
|
||||
}
|
||||
|
||||
type state struct {
|
||||
writerAt io.WriterAt
|
||||
readerAt io.ReaderAt
|
||||
endofdir bool // in case handler doesn't use EOF on file list
|
||||
readdirToken string
|
||||
}
|
||||
|
||||
type packet_data struct {
|
||||
id uint32
|
||||
data []byte
|
||||
length uint32
|
||||
offset int64
|
||||
}
|
||||
|
||||
// New Request initialized based on packet data
|
||||
func requestFromPacket(pkt hasPath) Request {
|
||||
method := requestMethod(pkt)
|
||||
request := NewRequest(method, pkt.getPath())
|
||||
request.pkt_id = pkt.id()
|
||||
switch p := pkt.(type) {
|
||||
case *sshFxpSetstatPacket:
|
||||
request.Flags = p.Flags
|
||||
request.Attrs = p.Attrs.([]byte)
|
||||
case *sshFxpRenamePacket:
|
||||
request.Target = filepath.Clean(p.Newpath)
|
||||
case *sshFxpSymlinkPacket:
|
||||
request.Target = filepath.Clean(p.Linkpath)
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
// NewRequest creates a new Request object.
|
||||
func NewRequest(method, path string) Request {
|
||||
request := Request{Method: method, Filepath: filepath.Clean(path)}
|
||||
request.packets = make(chan packet_data, sftpServerWorkerCount)
|
||||
request.state = &state{}
|
||||
request.stateLock = &sync.RWMutex{}
|
||||
return request
|
||||
}
|
||||
|
||||
// LsSave takes a token to keep track of file list batches. Openssh uses a
|
||||
// batch size of 100, so I suggest sticking close to that.
|
||||
func (r Request) LsSave(token string) {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
r.state.readdirToken = token
|
||||
}
|
||||
|
||||
// LsNext should return the token from the previous call to know which batch
|
||||
// to return next.
|
||||
func (r Request) LsNext() string {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.readdirToken
|
||||
}
|
||||
|
||||
// manage file read/write state
|
||||
func (r Request) setFileState(s interface{}) {
|
||||
r.stateLock.Lock()
|
||||
defer r.stateLock.Unlock()
|
||||
switch s := s.(type) {
|
||||
case io.WriterAt:
|
||||
r.state.writerAt = s
|
||||
case io.ReaderAt:
|
||||
r.state.readerAt = s
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (r Request) getWriter() io.WriterAt {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.writerAt
|
||||
}
|
||||
|
||||
func (r Request) getReader() io.ReaderAt {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.readerAt
|
||||
}
|
||||
|
||||
// For backwards compatibility. The Handler didn't have batch handling at
|
||||
// first, and just always assumed 1 batch. This preserves that behavior.
|
||||
func (r Request) setEOD(eod bool) {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
r.state.endofdir = eod
|
||||
}
|
||||
|
||||
func (r Request) getEOD() bool {
|
||||
r.stateLock.RLock()
|
||||
defer r.stateLock.RUnlock()
|
||||
return r.state.endofdir
|
||||
}
|
||||
|
||||
// Close reader/writer if possible
|
||||
func (r Request) close() {
|
||||
rd := r.getReader()
|
||||
if c, ok := rd.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
wt := r.getWriter()
|
||||
if c, ok := wt.(io.Closer); ok {
|
||||
c.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// push packet_data into fifo
|
||||
func (r Request) pushPacket(pd packet_data) {
|
||||
r.packets <- pd
|
||||
}
|
||||
|
||||
// pop packet_data into fifo
|
||||
func (r *Request) popPacket() packet_data {
|
||||
return <-r.packets
|
||||
}
|
||||
|
||||
// called from worker to handle packet/request
|
||||
func (r Request) handle(handlers Handlers) (responsePacket, error) {
|
||||
var err error
|
||||
var rpkt responsePacket
|
||||
switch r.Method {
|
||||
case "Get":
|
||||
rpkt, err = fileget(handlers.FileGet, r)
|
||||
case "Put": // add "Append" to this to handle append only file writes
|
||||
rpkt, err = fileput(handlers.FilePut, r)
|
||||
case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
|
||||
rpkt, err = filecmd(handlers.FileCmd, r)
|
||||
case "List", "Stat", "Readlink":
|
||||
rpkt, err = fileinfo(handlers.FileInfo, r)
|
||||
default:
|
||||
return rpkt, errors.Errorf("unexpected method: %s", r.Method)
|
||||
}
|
||||
return rpkt, err
|
||||
}
|
||||
|
||||
// wrap FileReader handler
|
||||
func fileget(h FileReader, r Request) (responsePacket, error) {
|
||||
var err error
|
||||
reader := r.getReader()
|
||||
if reader == nil {
|
||||
reader, err = h.Fileread(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.setFileState(reader)
|
||||
}
|
||||
|
||||
pd := r.popPacket()
|
||||
data := make([]byte, clamp(pd.length, maxTxPacket))
|
||||
n, err := reader.ReadAt(data, pd.offset)
|
||||
if err != nil && (err != io.EOF || n == 0) {
|
||||
return nil, err
|
||||
}
|
||||
return &sshFxpDataPacket{
|
||||
ID: pd.id,
|
||||
Length: uint32(n),
|
||||
Data: data[:n],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// wrap FileWriter handler
|
||||
func fileput(h FileWriter, r Request) (responsePacket, error) {
|
||||
var err error
|
||||
writer := r.getWriter()
|
||||
if writer == nil {
|
||||
writer, err = h.Filewrite(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.setFileState(writer)
|
||||
}
|
||||
|
||||
pd := r.popPacket()
|
||||
_, err = writer.WriteAt(pd.data, pd.offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sshFxpStatusPacket{
|
||||
ID: pd.id,
|
||||
StatusError: StatusError{
|
||||
Code: ssh_FX_OK,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// wrap FileCmder handler
|
||||
func filecmd(h FileCmder, r Request) (responsePacket, error) {
|
||||
err := h.Filecmd(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sshFxpStatusPacket{
|
||||
ID: r.pkt_id,
|
||||
StatusError: StatusError{
|
||||
Code: ssh_FX_OK,
|
||||
}}, nil
|
||||
}
|
||||
|
||||
// wrap FileInfoer handler
|
||||
func fileinfo(h FileInfoer, r Request) (responsePacket, error) {
|
||||
if r.getEOD() {
|
||||
return nil, io.EOF
|
||||
}
|
||||
finfo, err := h.Fileinfo(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "List":
|
||||
pd := r.popPacket()
|
||||
dirname := path.Base(r.Filepath)
|
||||
ret := &sshFxpNamePacket{ID: pd.id}
|
||||
for _, fi := range finfo {
|
||||
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
|
||||
Name: fi.Name(),
|
||||
LongName: runLs(dirname, fi),
|
||||
Attrs: []interface{}{fi},
|
||||
})
|
||||
}
|
||||
// No entries means we should return EOF as the Handler didn't.
|
||||
if len(finfo) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
// If files are returned but no token is set, return EOF next call.
|
||||
if r.LsNext() == "" {
|
||||
r.setEOD(true)
|
||||
}
|
||||
return ret, nil
|
||||
case "Stat":
|
||||
if len(finfo) == 0 {
|
||||
err = &os.PathError{Op: "stat", Path: r.Filepath,
|
||||
Err: syscall.ENOENT}
|
||||
return nil, err
|
||||
}
|
||||
return &sshFxpStatResponse{
|
||||
ID: r.pkt_id,
|
||||
info: finfo[0],
|
||||
}, nil
|
||||
case "Readlink":
|
||||
if len(finfo) == 0 {
|
||||
err = &os.PathError{Op: "readlink", Path: r.Filepath,
|
||||
Err: syscall.ENOENT}
|
||||
return nil, err
|
||||
}
|
||||
filename := finfo[0].Name()
|
||||
return &sshFxpNamePacket{
|
||||
ID: r.pkt_id,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: filename,
|
||||
LongName: filename,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// file data for additional read/write packets
|
||||
func (r *Request) update(p hasHandle) error {
|
||||
pd := packet_data{id: p.id()}
|
||||
switch p := p.(type) {
|
||||
case *sshFxpReadPacket:
|
||||
r.Method = "Get"
|
||||
pd.length = p.Len
|
||||
pd.offset = int64(p.Offset)
|
||||
case *sshFxpWritePacket:
|
||||
r.Method = "Put"
|
||||
pd.data = p.Data
|
||||
pd.length = p.Length
|
||||
pd.offset = int64(p.Offset)
|
||||
case *sshFxpReaddirPacket:
|
||||
r.Method = "List"
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", p)
|
||||
}
|
||||
r.pushPacket(pd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// init attributes of request object from packet data
|
||||
func requestMethod(p hasPath) (method string) {
|
||||
switch p.(type) {
|
||||
case *sshFxpOpenPacket, *sshFxpOpendirPacket:
|
||||
method = "Open"
|
||||
case *sshFxpSetstatPacket:
|
||||
method = "Setstat"
|
||||
case *sshFxpRenamePacket:
|
||||
method = "Rename"
|
||||
case *sshFxpSymlinkPacket:
|
||||
method = "Symlink"
|
||||
case *sshFxpRemovePacket:
|
||||
method = "Remove"
|
||||
case *sshFxpStatPacket, *sshFxpLstatPacket:
|
||||
method = "Stat"
|
||||
case *sshFxpRmdirPacket:
|
||||
method = "Rmdir"
|
||||
case *sshFxpReadlinkPacket:
|
||||
method = "Readlink"
|
||||
case *sshFxpMkdirPacket:
|
||||
method = "Mkdir"
|
||||
}
|
||||
return method
|
||||
}
|
||||
182
vendor/github.com/pkg/sftp/request_test.go
generated
vendored
Normal file
182
vendor/github.com/pkg/sftp/request_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testHandler struct {
|
||||
filecontents []byte // dummy contents
|
||||
output io.WriterAt // dummy file out
|
||||
err error // dummy error, should be file related
|
||||
}
|
||||
|
||||
func (t *testHandler) Fileread(r Request) (io.ReaderAt, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
return bytes.NewReader(t.filecontents), nil
|
||||
}
|
||||
|
||||
func (t *testHandler) Filewrite(r Request) (io.WriterAt, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
return io.WriterAt(t.output), nil
|
||||
}
|
||||
|
||||
func (t *testHandler) Filecmd(r Request) error {
|
||||
if t.err != nil {
|
||||
return t.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *testHandler) Fileinfo(r Request) ([]os.FileInfo, error) {
|
||||
if t.err != nil {
|
||||
return nil, t.err
|
||||
}
|
||||
f, err := os.Open(r.Filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []os.FileInfo{fi}, nil
|
||||
}
|
||||
|
||||
// make sure len(fakefile) == len(filecontents)
|
||||
type fakefile [10]byte
|
||||
|
||||
var filecontents = []byte("file-data.")
|
||||
|
||||
func testRequest(method string) Request {
|
||||
request := Request{
|
||||
Filepath: "./request_test.go",
|
||||
Method: method,
|
||||
Attrs: []byte("foo"),
|
||||
Target: "foo",
|
||||
packets: make(chan packet_data, sftpServerWorkerCount),
|
||||
state: &state{},
|
||||
stateLock: &sync.RWMutex{},
|
||||
}
|
||||
for _, p := range []packet_data{
|
||||
packet_data{id: 1, data: filecontents[:5], length: 5},
|
||||
packet_data{id: 2, data: filecontents[5:], length: 5, offset: 5}} {
|
||||
request.packets <- p
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
func (ff *fakefile) WriteAt(p []byte, off int64) (int, error) {
|
||||
n := copy(ff[off:], p)
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (ff fakefile) string() string {
|
||||
b := make([]byte, len(ff))
|
||||
copy(b, ff[:])
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func newTestHandlers() Handlers {
|
||||
handler := &testHandler{
|
||||
filecontents: filecontents,
|
||||
output: &fakefile{},
|
||||
err: nil,
|
||||
}
|
||||
return Handlers{
|
||||
FileGet: handler,
|
||||
FilePut: handler,
|
||||
FileCmd: handler,
|
||||
FileInfo: handler,
|
||||
}
|
||||
}
|
||||
|
||||
func (h Handlers) getOutString() string {
|
||||
handler := h.FilePut.(*testHandler)
|
||||
return handler.output.(*fakefile).string()
|
||||
}
|
||||
|
||||
var errTest = errors.New("test error")
|
||||
|
||||
func (h *Handlers) returnError() {
|
||||
handler := h.FilePut.(*testHandler)
|
||||
handler.err = errTest
|
||||
}
|
||||
|
||||
func statusOk(t *testing.T, p interface{}) {
|
||||
if pkt, ok := p.(*sshFxpStatusPacket); ok {
|
||||
assert.Equal(t, pkt.StatusError.Code, uint32(ssh_FX_OK))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestGet(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Get")
|
||||
// req.length is 5, so we test reads in 5 byte chunks
|
||||
for i, txt := range []string{"file-", "data."} {
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
dpkt := pkt.(*sshFxpDataPacket)
|
||||
assert.Equal(t, dpkt.id(), uint32(i+1))
|
||||
assert.Equal(t, string(dpkt.Data), txt)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequestPut(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Put")
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
statusOk(t, pkt)
|
||||
pkt, err = request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
statusOk(t, pkt)
|
||||
assert.Equal(t, "file-data.", handlers.getOutString())
|
||||
}
|
||||
|
||||
func TestRequestCmdr(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Mkdir")
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
statusOk(t, pkt)
|
||||
|
||||
handlers.returnError()
|
||||
pkt, err = request.handle(handlers)
|
||||
assert.Nil(t, pkt)
|
||||
assert.Equal(t, err, errTest)
|
||||
}
|
||||
|
||||
func TestRequestInfoList(t *testing.T) { testInfoMethod(t, "List") }
|
||||
func TestRequestInfoReadlink(t *testing.T) { testInfoMethod(t, "Readlink") }
|
||||
func TestRequestInfoStat(t *testing.T) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest("Stat")
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
spkt, ok := pkt.(*sshFxpStatResponse)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, spkt.info.Name(), "request_test.go")
|
||||
}
|
||||
|
||||
func testInfoMethod(t *testing.T, method string) {
|
||||
handlers := newTestHandlers()
|
||||
request := testRequest(method)
|
||||
pkt, err := request.handle(handlers)
|
||||
assert.Nil(t, err)
|
||||
npkt, ok := pkt.(*sshFxpNamePacket)
|
||||
assert.True(t, ok)
|
||||
assert.IsType(t, sshFxpNameAttr{}, npkt.NameAttrs[0])
|
||||
assert.Equal(t, npkt.NameAttrs[0].Name, "request_test.go")
|
||||
}
|
||||
11
vendor/github.com/pkg/sftp/request_windows.go
generated
vendored
Normal file
11
vendor/github.com/pkg/sftp/request_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package sftp
|
||||
|
||||
import "syscall"
|
||||
|
||||
func fakeFileInfoSys() interface{} {
|
||||
return syscall.Win32FileAttributeData{}
|
||||
}
|
||||
|
||||
func testOsSys(sys interface{}) error {
|
||||
return nil
|
||||
}
|
||||
575
vendor/github.com/pkg/sftp/server.go
generated
vendored
Normal file
575
vendor/github.com/pkg/sftp/server.go
generated
vendored
Normal file
|
|
@ -0,0 +1,575 @@
|
|||
package sftp
|
||||
|
||||
// sftp server counterpart
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
sftpServerWorkerCount = 8
|
||||
)
|
||||
|
||||
// Server is an SSH File Transfer Protocol (sftp) server.
|
||||
// This is intended to provide the sftp subsystem to an ssh server daemon.
|
||||
// This implementation currently supports most of sftp server protocol version 3,
|
||||
// as specified at http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
||||
type Server struct {
|
||||
*serverConn
|
||||
debugStream io.Writer
|
||||
readOnly bool
|
||||
pktMgr packetManager
|
||||
openFiles map[string]*os.File
|
||||
openFilesLock sync.RWMutex
|
||||
handleCount int
|
||||
maxTxPacket uint32
|
||||
}
|
||||
|
||||
func (svr *Server) nextHandle(f *os.File) string {
|
||||
svr.openFilesLock.Lock()
|
||||
defer svr.openFilesLock.Unlock()
|
||||
svr.handleCount++
|
||||
handle := strconv.Itoa(svr.handleCount)
|
||||
svr.openFiles[handle] = f
|
||||
return handle
|
||||
}
|
||||
|
||||
func (svr *Server) closeHandle(handle string) error {
|
||||
svr.openFilesLock.Lock()
|
||||
defer svr.openFilesLock.Unlock()
|
||||
if f, ok := svr.openFiles[handle]; ok {
|
||||
delete(svr.openFiles, handle)
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
return syscall.EBADF
|
||||
}
|
||||
|
||||
func (svr *Server) getHandle(handle string) (*os.File, bool) {
|
||||
svr.openFilesLock.RLock()
|
||||
defer svr.openFilesLock.RUnlock()
|
||||
f, ok := svr.openFiles[handle]
|
||||
return f, ok
|
||||
}
|
||||
|
||||
type serverRespondablePacket interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
id() uint32
|
||||
respond(svr *Server) error
|
||||
}
|
||||
|
||||
// NewServer creates a new Server instance around the provided streams, serving
|
||||
// content from the root of the filesystem. Optionally, ServerOption
|
||||
// functions may be specified to further configure the Server.
|
||||
//
|
||||
// A subsequent call to Serve() is required to begin serving files over SFTP.
|
||||
func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) {
|
||||
svrConn := &serverConn{
|
||||
conn: conn{
|
||||
Reader: rwc,
|
||||
WriteCloser: rwc,
|
||||
},
|
||||
}
|
||||
s := &Server{
|
||||
serverConn: svrConn,
|
||||
debugStream: ioutil.Discard,
|
||||
pktMgr: newPktMgr(svrConn),
|
||||
openFiles: make(map[string]*os.File),
|
||||
maxTxPacket: 1 << 15,
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
if err := o(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// A ServerOption is a function which applies configuration to a Server.
|
||||
type ServerOption func(*Server) error
|
||||
|
||||
// WithDebug enables Server debugging output to the supplied io.Writer.
|
||||
func WithDebug(w io.Writer) ServerOption {
|
||||
return func(s *Server) error {
|
||||
s.debugStream = w
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReadOnly configures a Server to serve files in read-only mode.
|
||||
func ReadOnly() ServerOption {
|
||||
return func(s *Server) error {
|
||||
s.readOnly = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type rxPacket struct {
|
||||
pktType fxp
|
||||
pktBytes []byte
|
||||
}
|
||||
|
||||
// Up to N parallel servers
|
||||
func (svr *Server) sftpServerWorker(pktChan chan requestPacket) error {
|
||||
for pkt := range pktChan {
|
||||
|
||||
// readonly checks
|
||||
readonly := true
|
||||
switch pkt := pkt.(type) {
|
||||
case notReadOnly:
|
||||
readonly = false
|
||||
case *sshFxpOpenPacket:
|
||||
readonly = pkt.readonly()
|
||||
case *sshFxpExtendedPacket:
|
||||
readonly = pkt.SpecificPacket.readonly()
|
||||
}
|
||||
|
||||
// If server is operating read-only and a write operation is requested,
|
||||
// return permission denied
|
||||
if !readonly && svr.readOnly {
|
||||
if err := svr.sendError(pkt, syscall.EPERM); err != nil {
|
||||
return errors.Wrap(err, "failed to send read only packet response")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := handlePacket(svr, pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePacket(s *Server, p interface{}) error {
|
||||
switch p := p.(type) {
|
||||
case *sshFxInitPacket:
|
||||
return s.sendPacket(sshFxVersionPacket{sftpProtocolVersion, nil})
|
||||
case *sshFxpStatPacket:
|
||||
// stat the requested file
|
||||
info, err := os.Stat(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
return s.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
case *sshFxpLstatPacket:
|
||||
// stat the requested file
|
||||
info, err := os.Lstat(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
return s.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
case *sshFxpFstatPacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return s.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
|
||||
return s.sendPacket(sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
})
|
||||
case *sshFxpMkdirPacket:
|
||||
// TODO FIXME: ignore flags field
|
||||
err := os.Mkdir(p.Path, 0755)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpRmdirPacket:
|
||||
err := os.Remove(p.Path)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpRemovePacket:
|
||||
err := os.Remove(p.Filename)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpRenamePacket:
|
||||
err := os.Rename(p.Oldpath, p.Newpath)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpSymlinkPacket:
|
||||
err := os.Symlink(p.Targetpath, p.Linkpath)
|
||||
return s.sendError(p, err)
|
||||
case *sshFxpClosePacket:
|
||||
return s.sendError(p, s.closeHandle(p.Handle))
|
||||
case *sshFxpReadlinkPacket:
|
||||
f, err := os.Readlink(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
|
||||
return s.sendPacket(sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
})
|
||||
|
||||
case *sshFxpRealpathPacket:
|
||||
f, err := filepath.Abs(p.Path)
|
||||
if err != nil {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
f = filepath.Clean(f)
|
||||
f = filepath.ToSlash(f) // make path more Unix like on windows servers
|
||||
return s.sendPacket(sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []sshFxpNameAttr{{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
}},
|
||||
})
|
||||
case *sshFxpOpendirPacket:
|
||||
return sshFxpOpenPacket{
|
||||
ID: p.ID,
|
||||
Path: p.Path,
|
||||
Pflags: ssh_FXF_READ,
|
||||
}.respond(s)
|
||||
case *sshFxpReadPacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return s.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
data := make([]byte, clamp(p.Len, s.maxTxPacket))
|
||||
n, err := f.ReadAt(data, int64(p.Offset))
|
||||
if err != nil && (err != io.EOF || n == 0) {
|
||||
return s.sendError(p, err)
|
||||
}
|
||||
return s.sendPacket(sshFxpDataPacket{
|
||||
ID: p.ID,
|
||||
Length: uint32(n),
|
||||
Data: data[:n],
|
||||
})
|
||||
case *sshFxpWritePacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return s.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
_, err := f.WriteAt(p.Data, int64(p.Offset))
|
||||
return s.sendError(p, err)
|
||||
case serverRespondablePacket:
|
||||
err := p.respond(s)
|
||||
return errors.Wrap(err, "pkt.respond failed")
|
||||
default:
|
||||
return errors.Errorf("unexpected packet type %T", p)
|
||||
}
|
||||
}
|
||||
|
||||
// Serve serves SFTP connections until the streams stop or the SFTP subsystem
|
||||
// is stopped.
|
||||
func (svr *Server) Serve() error {
|
||||
var wg sync.WaitGroup
|
||||
runWorker := func(ch requestChan) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := svr.sftpServerWorker(ch); err != nil {
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
}
|
||||
}()
|
||||
}
|
||||
pktChan := svr.pktMgr.workerChan(runWorker)
|
||||
|
||||
var err error
|
||||
var pkt requestPacket
|
||||
var pktType uint8
|
||||
var pktBytes []byte
|
||||
for {
|
||||
pktType, pktBytes, err = svr.recvPacket()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
|
||||
if err != nil {
|
||||
debug("makePacket err: %v", err)
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
break
|
||||
}
|
||||
|
||||
pktChan <- pkt
|
||||
}
|
||||
|
||||
close(pktChan) // shuts down sftpServerWorkers
|
||||
wg.Wait() // wait for all workers to exit
|
||||
|
||||
// close any still-open files
|
||||
for handle, file := range svr.openFiles {
|
||||
fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name())
|
||||
file.Close()
|
||||
}
|
||||
return err // error from recvPacket
|
||||
}
|
||||
|
||||
// Wrap underlying connection methods to use packetManager
|
||||
func (svr *Server) sendPacket(m encoding.BinaryMarshaler) error {
|
||||
if pkt, ok := m.(responsePacket); ok {
|
||||
svr.pktMgr.readyPacket(pkt)
|
||||
} else {
|
||||
return errors.Errorf("unexpected packet type %T", m)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svr *Server) sendError(p ider, err error) error {
|
||||
return svr.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
|
||||
type ider interface {
|
||||
id() uint32
|
||||
}
|
||||
|
||||
// The init packet has no ID, so we just return a zero-value ID
|
||||
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}
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalFileInfo(b, p.info)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
var emptyFileStat = []interface{}{uint32(0)}
|
||||
|
||||
func (p sshFxpOpenPacket) readonly() bool {
|
||||
return !p.hasPflags(ssh_FXF_WRITE)
|
||||
}
|
||||
|
||||
func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
|
||||
for _, f := range flags {
|
||||
if p.Pflags&f == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p sshFxpOpenPacket) respond(svr *Server) error {
|
||||
var osFlags int
|
||||
if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) {
|
||||
osFlags |= os.O_RDWR
|
||||
} else if p.hasPflags(ssh_FXF_WRITE) {
|
||||
osFlags |= os.O_WRONLY
|
||||
} else if p.hasPflags(ssh_FXF_READ) {
|
||||
osFlags |= os.O_RDONLY
|
||||
} else {
|
||||
// how are they opening?
|
||||
return svr.sendError(p, syscall.EINVAL)
|
||||
}
|
||||
|
||||
if p.hasPflags(ssh_FXF_APPEND) {
|
||||
osFlags |= os.O_APPEND
|
||||
}
|
||||
if p.hasPflags(ssh_FXF_CREAT) {
|
||||
osFlags |= os.O_CREATE
|
||||
}
|
||||
if p.hasPflags(ssh_FXF_TRUNC) {
|
||||
osFlags |= os.O_TRUNC
|
||||
}
|
||||
if p.hasPflags(ssh_FXF_EXCL) {
|
||||
osFlags |= os.O_EXCL
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(p.Path, osFlags, 0644)
|
||||
if err != nil {
|
||||
return svr.sendError(p, err)
|
||||
}
|
||||
|
||||
handle := svr.nextHandle(f)
|
||||
return svr.sendPacket(sshFxpHandlePacket{p.ID, handle})
|
||||
}
|
||||
|
||||
func (p sshFxpReaddirPacket) respond(svr *Server) error {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return svr.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
dirname := f.Name()
|
||||
dirents, err := f.Readdir(128)
|
||||
if err != nil {
|
||||
return svr.sendError(p, err)
|
||||
}
|
||||
|
||||
ret := sshFxpNamePacket{ID: p.ID}
|
||||
for _, dirent := range dirents {
|
||||
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
|
||||
Name: dirent.Name(),
|
||||
LongName: runLs(dirname, dirent),
|
||||
Attrs: []interface{}{dirent},
|
||||
})
|
||||
}
|
||||
return svr.sendPacket(ret)
|
||||
}
|
||||
|
||||
func (p sshFxpSetstatPacket) respond(svr *Server) error {
|
||||
// additional unmarshalling is required for each possibility here
|
||||
b := p.Attrs.([]byte)
|
||||
var err error
|
||||
|
||||
debug("setstat name \"%s\"", p.Path)
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 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 {
|
||||
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 {
|
||||
var atime uint32
|
||||
var mtime uint32
|
||||
if atime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else {
|
||||
atimeT := time.Unix(int64(atime), 0)
|
||||
mtimeT := time.Unix(int64(mtime), 0)
|
||||
err = os.Chtimes(p.Path, atimeT, mtimeT)
|
||||
}
|
||||
}
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
|
||||
var uid uint32
|
||||
var gid uint32
|
||||
if uid, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else if gid, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else {
|
||||
err = os.Chown(p.Path, int(uid), int(gid))
|
||||
}
|
||||
}
|
||||
|
||||
return svr.sendError(p, err)
|
||||
}
|
||||
|
||||
func (p sshFxpFsetstatPacket) respond(svr *Server) error {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return svr.sendError(p, syscall.EBADF)
|
||||
}
|
||||
|
||||
// additional unmarshalling is required for each possibility here
|
||||
b := p.Attrs.([]byte)
|
||||
var err error
|
||||
|
||||
debug("fsetstat name \"%s\"", f.Name())
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 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 {
|
||||
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 {
|
||||
var atime uint32
|
||||
var mtime uint32
|
||||
if atime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else {
|
||||
atimeT := time.Unix(int64(atime), 0)
|
||||
mtimeT := time.Unix(int64(mtime), 0)
|
||||
err = os.Chtimes(f.Name(), atimeT, mtimeT)
|
||||
}
|
||||
}
|
||||
if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
|
||||
var uid uint32
|
||||
var gid uint32
|
||||
if uid, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else if gid, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else {
|
||||
err = f.Chown(int(uid), int(gid))
|
||||
}
|
||||
}
|
||||
|
||||
return svr.sendError(p, 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(),
|
||||
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,
|
||||
},
|
||||
}
|
||||
if err != nil {
|
||||
debug("statusFromError: error is %T %#v", err, err)
|
||||
ret.StatusError.Code = ssh_FX_FAILURE
|
||||
ret.StatusError.msg = err.Error()
|
||||
if err == io.EOF {
|
||||
ret.StatusError.Code = ssh_FX_EOF
|
||||
} else if errno, ok := err.(syscall.Errno); ok {
|
||||
ret.StatusError.Code = translateErrno(errno)
|
||||
} else if pathError, ok := err.(*os.PathError); ok {
|
||||
debug("statusFromError: error is %T %#v", pathError.Err, pathError.Err)
|
||||
if errno, ok := pathError.Err.(syscall.Errno); ok {
|
||||
ret.StatusError.Code = translateErrno(errno)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func clamp(v, max uint32) uint32 {
|
||||
if v > max {
|
||||
return max
|
||||
}
|
||||
return v
|
||||
}
|
||||
671
vendor/github.com/pkg/sftp/server_integration_test.go
generated
vendored
Normal file
671
vendor/github.com/pkg/sftp/server_integration_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,671 @@
|
|||
package sftp
|
||||
|
||||
// sftp server integration tests
|
||||
// enable with -integration
|
||||
// example invokation (darwin): gofmt -w `find . -name \*.go` && (cd server_standalone/ ; go build -tags debug) && go test -tags debug github.com/pkg/sftp -integration -v -sftp /usr/libexec/sftp-server -run ServerCompareSubsystems
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kr/fs"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
var testSftpClientBin = flag.String("sftp_client", "/usr/bin/sftp", "location of the sftp client binary")
|
||||
var sshServerDebugStream = ioutil.Discard
|
||||
var sftpServerDebugStream = ioutil.Discard
|
||||
var sftpClientDebugStream = ioutil.Discard
|
||||
|
||||
const (
|
||||
GOLANG_SFTP = true
|
||||
OPENSSH_SFTP = false
|
||||
)
|
||||
|
||||
var (
|
||||
hostPrivateKeySigner ssh.Signer
|
||||
privKey = []byte(`
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEArhp7SqFnXVZAgWREL9Ogs+miy4IU/m0vmdkoK6M97G9NX/Pj
|
||||
wf8I/3/ynxmcArbt8Rc4JgkjT2uxx/NqR0yN42N1PjO5Czu0dms1PSqcKIJdeUBV
|
||||
7gdrKSm9Co4d2vwfQp5mg47eG4w63pz7Drk9+VIyi9YiYH4bve7WnGDswn4ycvYZ
|
||||
slV5kKnjlfCdPig+g5P7yQYud0cDWVwyA0+kxvL6H3Ip+Fu8rLDZn4/P1WlFAIuc
|
||||
PAf4uEKDGGmC2URowi5eesYR7f6GN/HnBs2776laNlAVXZUmYTUfOGagwLsEkx8x
|
||||
XdNqntfbs2MOOoK+myJrNtcB9pCrM0H6um19uQIDAQABAoIBABkWr9WdVKvalgkP
|
||||
TdQmhu3mKRNyd1wCl+1voZ5IM9Ayac/98UAvZDiNU4Uhx52MhtVLJ0gz4Oa8+i16
|
||||
IkKMAZZW6ro/8dZwkBzQbieWUFJ2Fso2PyvB3etcnGU8/Yhk9IxBDzy+BbuqhYE2
|
||||
1ebVQtz+v1HvVZzaD11bYYm/Xd7Y28QREVfFen30Q/v3dv7dOteDE/RgDS8Czz7w
|
||||
jMW32Q8JL5grz7zPkMK39BLXsTcSYcaasT2ParROhGJZDmbgd3l33zKCVc1zcj9B
|
||||
SA47QljGd09Tys958WWHgtj2o7bp9v1Ufs4LnyKgzrB80WX1ovaSQKvd5THTLchO
|
||||
kLIhUAECgYEA2doGXy9wMBmTn/hjiVvggR1aKiBwUpnB87Hn5xCMgoECVhFZlT6l
|
||||
WmZe7R2klbtG1aYlw+y+uzHhoVDAJW9AUSV8qoDUwbRXvBVlp+In5wIqJ+VjfivK
|
||||
zgIfzomL5NvDz37cvPmzqIeySTowEfbQyq7CUQSoDtE9H97E2wWZhDkCgYEAzJdJ
|
||||
k+NSFoTkHhfD3L0xCDHpRV3gvaOeew8524fVtVUq53X8m91ng4AX1r74dCUYwwiF
|
||||
gqTtSSJfx2iH1xKnNq28M9uKg7wOrCKrRqNPnYUO3LehZEC7rwUr26z4iJDHjjoB
|
||||
uBcS7nw0LJ+0Zeg1IF+aIdZGV3MrAKnrzWPixYECgYBsffX6ZWebrMEmQ89eUtFF
|
||||
u9ZxcGI/4K8ErC7vlgBD5ffB4TYZ627xzFWuBLs4jmHCeNIJ9tct5rOVYN+wRO1k
|
||||
/CRPzYUnSqb+1jEgILL6istvvv+DkE+ZtNkeRMXUndWwel94BWsBnUKe0UmrSJ3G
|
||||
sq23J3iCmJW2T3z+DpXbkQKBgQCK+LUVDNPE0i42NsRnm+fDfkvLP7Kafpr3Umdl
|
||||
tMY474o+QYn+wg0/aPJIf9463rwMNyyhirBX/k57IIktUdFdtfPicd2MEGETElWv
|
||||
nN1GzYxD50Rs2f/jKisZhEwqT9YNyV9DkgDdGGdEbJNYqbv0qpwDIg8T9foe8E1p
|
||||
bdErgQKBgAt290I3L316cdxIQTkJh1DlScN/unFffITwu127WMr28Jt3mq3cZpuM
|
||||
Aecey/eEKCj+Rlas5NDYKsB18QIuAw+qqWyq0LAKLiAvP1965Rkc4PLScl3MgJtO
|
||||
QYa37FK0p8NcDeUuF86zXBVutwS5nJLchHhKfd590ks57OROtm29
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
hostPrivateKeySigner, err = ssh.ParsePrivateKey(privKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func keyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
permissions := &ssh.Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
}
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func pwAuth(conn ssh.ConnMetadata, pw []byte) (*ssh.Permissions, error) {
|
||||
permissions := &ssh.Permissions{
|
||||
CriticalOptions: map[string]string{},
|
||||
Extensions: map[string]string{},
|
||||
}
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func basicServerConfig() *ssh.ServerConfig {
|
||||
config := ssh.ServerConfig{
|
||||
Config: ssh.Config{
|
||||
MACs: []string{"hmac-sha1"},
|
||||
},
|
||||
PasswordCallback: pwAuth,
|
||||
PublicKeyCallback: keyAuth,
|
||||
}
|
||||
config.AddHostKey(hostPrivateKeySigner)
|
||||
return &config
|
||||
}
|
||||
|
||||
type sshServer struct {
|
||||
useSubsystem bool
|
||||
conn net.Conn
|
||||
config *ssh.ServerConfig
|
||||
sshConn *ssh.ServerConn
|
||||
newChans <-chan ssh.NewChannel
|
||||
newReqs <-chan *ssh.Request
|
||||
}
|
||||
|
||||
func sshServerFromConn(conn net.Conn, useSubsystem bool, config *ssh.ServerConfig) (*sshServer, error) {
|
||||
// From a standard TCP connection to an encrypted SSH connection
|
||||
sshConn, newChans, newReqs, err := ssh.NewServerConn(conn, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svr := &sshServer{useSubsystem, conn, config, sshConn, newChans, newReqs}
|
||||
svr.listenChannels()
|
||||
return svr, nil
|
||||
}
|
||||
|
||||
func (svr *sshServer) Wait() error {
|
||||
return svr.sshConn.Wait()
|
||||
}
|
||||
|
||||
func (svr *sshServer) Close() error {
|
||||
return svr.sshConn.Close()
|
||||
}
|
||||
|
||||
func (svr *sshServer) listenChannels() {
|
||||
go func() {
|
||||
for chanReq := range svr.newChans {
|
||||
go svr.handleChanReq(chanReq)
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
for req := range svr.newReqs {
|
||||
go svr.handleReq(req)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (svr *sshServer) handleReq(req *ssh.Request) {
|
||||
switch req.Type {
|
||||
default:
|
||||
rejectRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
type sshChannelServer struct {
|
||||
svr *sshServer
|
||||
chanReq ssh.NewChannel
|
||||
ch ssh.Channel
|
||||
newReqs <-chan *ssh.Request
|
||||
}
|
||||
|
||||
type sshSessionChannelServer struct {
|
||||
*sshChannelServer
|
||||
env []string
|
||||
}
|
||||
|
||||
func (svr *sshServer) handleChanReq(chanReq ssh.NewChannel) {
|
||||
fmt.Fprintf(sshServerDebugStream, "channel request: %v, extra: '%v'\n", chanReq.ChannelType(), hex.EncodeToString(chanReq.ExtraData()))
|
||||
switch chanReq.ChannelType() {
|
||||
case "session":
|
||||
if ch, reqs, err := chanReq.Accept(); err != nil {
|
||||
fmt.Fprintf(sshServerDebugStream, "fail to accept channel request: %v\n", err)
|
||||
chanReq.Reject(ssh.ResourceShortage, "channel accept failure")
|
||||
} else {
|
||||
chsvr := &sshSessionChannelServer{
|
||||
sshChannelServer: &sshChannelServer{svr, chanReq, ch, reqs},
|
||||
env: append([]string{}, os.Environ()...),
|
||||
}
|
||||
chsvr.handle()
|
||||
}
|
||||
default:
|
||||
chanReq.Reject(ssh.UnknownChannelType, "channel type is not a session")
|
||||
}
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handle() {
|
||||
// should maybe do something here...
|
||||
go chsvr.handleReqs()
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handleReqs() {
|
||||
for req := range chsvr.newReqs {
|
||||
chsvr.handleReq(req)
|
||||
}
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh server session channel complete\n")
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handleReq(req *ssh.Request) {
|
||||
switch req.Type {
|
||||
case "env":
|
||||
chsvr.handleEnv(req)
|
||||
case "subsystem":
|
||||
chsvr.handleSubsystem(req)
|
||||
default:
|
||||
rejectRequest(req)
|
||||
}
|
||||
}
|
||||
|
||||
func rejectRequest(req *ssh.Request) error {
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh rejecting request, type: %s\n", req.Type)
|
||||
err := req.Reply(false, []byte{})
|
||||
if err != nil {
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh request reply had error: %v\n", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func rejectRequestUnmarshalError(req *ssh.Request, s interface{}, err error) error {
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh request unmarshaling error, type '%T': %v\n", s, err)
|
||||
rejectRequest(req)
|
||||
return err
|
||||
}
|
||||
|
||||
// env request form:
|
||||
type sshEnvRequest struct {
|
||||
Envvar string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handleEnv(req *ssh.Request) error {
|
||||
envReq := &sshEnvRequest{}
|
||||
if err := ssh.Unmarshal(req.Payload, envReq); err != nil {
|
||||
return rejectRequestUnmarshalError(req, envReq, err)
|
||||
}
|
||||
req.Reply(true, nil)
|
||||
|
||||
found := false
|
||||
for i, envstr := range chsvr.env {
|
||||
if strings.HasPrefix(envstr, envReq.Envvar+"=") {
|
||||
found = true
|
||||
chsvr.env[i] = envReq.Envvar + "=" + envReq.Value
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
chsvr.env = append(chsvr.env, envReq.Envvar+"="+envReq.Value)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Payload: int: command size, string: command
|
||||
type sshSubsystemRequest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type sshSubsystemExitStatus struct {
|
||||
Status uint32
|
||||
}
|
||||
|
||||
func (chsvr *sshSessionChannelServer) handleSubsystem(req *ssh.Request) error {
|
||||
defer func() {
|
||||
err1 := chsvr.ch.CloseWrite()
|
||||
err2 := chsvr.ch.Close()
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh server subsystem request complete, err: %v %v\n", err1, err2)
|
||||
}()
|
||||
|
||||
subsystemReq := &sshSubsystemRequest{}
|
||||
if err := ssh.Unmarshal(req.Payload, subsystemReq); err != nil {
|
||||
return rejectRequestUnmarshalError(req, subsystemReq, err)
|
||||
}
|
||||
|
||||
// reply to the ssh client
|
||||
|
||||
// no idea if this is actually correct spec-wise.
|
||||
// just enough for an sftp server to start.
|
||||
if subsystemReq.Name != "sftp" {
|
||||
return req.Reply(false, nil)
|
||||
}
|
||||
|
||||
req.Reply(true, nil)
|
||||
|
||||
if !chsvr.svr.useSubsystem {
|
||||
// use the openssh sftp server backend; this is to test the ssh code, not the sftp code,
|
||||
// or is used for comparison between our sftp subsystem and the openssh sftp subsystem
|
||||
cmd := exec.Command(*testSftp, "-e", "-l", "DEBUG") // log to stderr
|
||||
cmd.Stdin = chsvr.ch
|
||||
cmd.Stdout = chsvr.ch
|
||||
cmd.Stderr = sftpServerDebugStream
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.Wait()
|
||||
}
|
||||
|
||||
sftpServer, err := NewServer(
|
||||
chsvr.ch,
|
||||
WithDebug(sftpServerDebugStream),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// wait for the session to close
|
||||
runErr := sftpServer.Serve()
|
||||
exitStatus := uint32(1)
|
||||
if runErr == nil {
|
||||
exitStatus = uint32(0)
|
||||
}
|
||||
|
||||
_, exitStatusErr := chsvr.ch.SendRequest("exit-status", false, ssh.Marshal(sshSubsystemExitStatus{exitStatus}))
|
||||
return exitStatusErr
|
||||
}
|
||||
|
||||
// starts an ssh server to test. returns: host string and port
|
||||
func testServer(t *testing.T, useSubsystem bool, readonly bool) (net.Listener, string, int) {
|
||||
if !*testIntegration {
|
||||
t.Skip("skipping intergration test")
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
host, portStr, err := net.SplitHostPort(listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh server socket closed: %v\n", err)
|
||||
break
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
sshSvr, err := sshServerFromConn(conn, useSubsystem, basicServerConfig())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
err = sshSvr.Wait()
|
||||
fmt.Fprintf(sshServerDebugStream, "ssh server finished, err: %v\n", err)
|
||||
}()
|
||||
}
|
||||
}()
|
||||
|
||||
return listener, host, port
|
||||
}
|
||||
|
||||
func runSftpClient(t *testing.T, script string, path string, host string, port int) (string, error) {
|
||||
// if sftp client binary is unavailable, skip test
|
||||
if _, err := os.Stat(*testSftpClientBin); err != nil {
|
||||
t.Skip("sftp client binary unavailable")
|
||||
}
|
||||
args := []string{
|
||||
// "-vvvv",
|
||||
"-b", "-",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "LogLevel=ERROR",
|
||||
"-o", "UserKnownHostsFile /dev/null",
|
||||
"-P", fmt.Sprintf("%d", port), fmt.Sprintf("%s:%s", host, path),
|
||||
}
|
||||
cmd := exec.Command(*testSftpClientBin, args...)
|
||||
var stdout bytes.Buffer
|
||||
cmd.Stdin = bytes.NewBufferString(script)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = sftpClientDebugStream
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
err := cmd.Wait()
|
||||
return string(stdout.Bytes()), err
|
||||
}
|
||||
|
||||
func TestServerCompareSubsystems(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
listenerOp, hostOp, portOp := testServer(t, OPENSSH_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
defer listenerOp.Close()
|
||||
|
||||
script := `
|
||||
ls /
|
||||
ls -l /
|
||||
ls /dev/
|
||||
ls -l /dev/
|
||||
ls -l /etc/
|
||||
ls -l /bin/
|
||||
ls -l /usr/bin/
|
||||
`
|
||||
outputGo, err := runSftpClient(t, script, "/", hostGo, portGo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
outputOp, err := runSftpClient(t, script, "/", hostOp, portOp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
newlineRegex := regexp.MustCompile(`\r*\n`)
|
||||
spaceRegex := regexp.MustCompile(`\s+`)
|
||||
outputGoLines := newlineRegex.Split(outputGo, -1)
|
||||
outputOpLines := newlineRegex.Split(outputOp, -1)
|
||||
|
||||
for i, goLine := range outputGoLines {
|
||||
if i > len(outputOpLines) {
|
||||
t.Fatalf("output line count differs")
|
||||
}
|
||||
opLine := outputOpLines[i]
|
||||
bad := false
|
||||
if goLine != opLine {
|
||||
goWords := spaceRegex.Split(goLine, -1)
|
||||
opWords := spaceRegex.Split(opLine, -1)
|
||||
// allow words[2] and [3] to be different as these are users & groups
|
||||
// also allow words[1] to differ as the link count for directories like
|
||||
// proc is unstable during testing as processes are created/destroyed.
|
||||
for j, goWord := range goWords {
|
||||
if j > len(opWords) {
|
||||
bad = true
|
||||
}
|
||||
opWord := opWords[j]
|
||||
if goWord != opWord && j != 1 && j != 2 && j != 3 {
|
||||
bad = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if bad {
|
||||
t.Errorf("outputs differ, go:\n%v\nopenssh:\n%v\n", goLine, opLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rng = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
|
||||
func randData(length int) []byte {
|
||||
data := make([]byte, length)
|
||||
for i := 0; i < length; i++ {
|
||||
data[i] = byte(rng.Uint32())
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func randName() string {
|
||||
return "sftp." + hex.EncodeToString(randData(16))
|
||||
}
|
||||
|
||||
func TestServerMkdirRmdir(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
tmpDir := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
// mkdir remote
|
||||
if _, err := runSftpClient(t, "mkdir "+tmpDir, "/", hostGo, portGo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// directory should now exist
|
||||
if _, err := os.Stat(tmpDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// now remove the directory
|
||||
if _, err := runSftpClient(t, "rmdir "+tmpDir, "/", hostGo, portGo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(tmpDir); err == nil {
|
||||
t.Fatal("should have error after deleting the directory")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerSymlink(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
link := "/tmp/" + randName()
|
||||
defer os.RemoveAll(link)
|
||||
|
||||
// now create a symbolic link within the new directory
|
||||
if output, err := runSftpClient(t, "symlink /bin/sh "+link, "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("failed: %v %v", err, string(output))
|
||||
}
|
||||
|
||||
// symlink should now exist
|
||||
if stat, err := os.Lstat(link); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if (stat.Mode() & os.ModeSymlink) != os.ModeSymlink {
|
||||
t.Fatalf("is not a symlink: %v", stat.Mode())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPut(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
tmpFileLocal := "/tmp/" + randName()
|
||||
tmpFileRemote := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpFileLocal)
|
||||
defer os.RemoveAll(tmpFileRemote)
|
||||
|
||||
t.Logf("put: local %v remote %v", tmpFileLocal, tmpFileRemote)
|
||||
|
||||
// create a file with random contents. This will be the local file pushed to the server
|
||||
tmpFileLocalData := randData(10 * 1024 * 1024)
|
||||
if err := ioutil.WriteFile(tmpFileLocal, tmpFileLocalData, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// sftp the file to the server
|
||||
if output, err := runSftpClient(t, "put "+tmpFileLocal+" "+tmpFileRemote, "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
|
||||
}
|
||||
|
||||
// tmpFile2 should now exist, with the same contents
|
||||
if tmpFileRemoteData, err := ioutil.ReadFile(tmpFileRemote); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if string(tmpFileLocalData) != string(tmpFileRemoteData) {
|
||||
t.Fatal("contents of file incorrect after put")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerGet(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
tmpFileLocal := "/tmp/" + randName()
|
||||
tmpFileRemote := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpFileLocal)
|
||||
defer os.RemoveAll(tmpFileRemote)
|
||||
|
||||
t.Logf("get: local %v remote %v", tmpFileLocal, tmpFileRemote)
|
||||
|
||||
// create a file with random contents. This will be the remote file pulled from the server
|
||||
tmpFileRemoteData := randData(10 * 1024 * 1024)
|
||||
if err := ioutil.WriteFile(tmpFileRemote, tmpFileRemoteData, 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// sftp the file to the server
|
||||
if output, err := runSftpClient(t, "get "+tmpFileRemote+" "+tmpFileLocal, "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
|
||||
}
|
||||
|
||||
// tmpFile2 should now exist, with the same contents
|
||||
if tmpFileLocalData, err := ioutil.ReadFile(tmpFileLocal); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if string(tmpFileLocalData) != string(tmpFileRemoteData) {
|
||||
t.Fatal("contents of file incorrect after put")
|
||||
}
|
||||
}
|
||||
|
||||
func compareDirectoriesRecursive(t *testing.T, aroot, broot string) {
|
||||
walker := fs.Walk(aroot)
|
||||
for walker.Step() {
|
||||
if err := walker.Err(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// find paths
|
||||
aPath := walker.Path()
|
||||
aRel, err := filepath.Rel(aroot, aPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not find relative path for %v: %v", aPath, err)
|
||||
}
|
||||
bPath := path.Join(broot, aRel)
|
||||
|
||||
if aRel == "." {
|
||||
continue
|
||||
}
|
||||
|
||||
//t.Logf("comparing: %v a: %v b %v", aRel, aPath, bPath)
|
||||
|
||||
// if a is a link, the sftp recursive copy won't have copied it. ignore
|
||||
aLink, err := os.Lstat(aPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not lstat %v: %v", aPath, err)
|
||||
}
|
||||
if aLink.Mode()&os.ModeSymlink != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// stat the files
|
||||
aFile, err := os.Stat(aPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not stat %v: %v", aPath, err)
|
||||
}
|
||||
bFile, err := os.Stat(bPath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not stat %v: %v", bPath, err)
|
||||
}
|
||||
|
||||
// compare stats, with some leniency for the timestamp
|
||||
if aFile.Mode() != bFile.Mode() {
|
||||
t.Fatalf("modes different for %v: %v vs %v", aRel, aFile.Mode(), bFile.Mode())
|
||||
}
|
||||
if !aFile.IsDir() {
|
||||
if aFile.Size() != bFile.Size() {
|
||||
t.Fatalf("sizes different for %v: %v vs %v", aRel, aFile.Size(), bFile.Size())
|
||||
}
|
||||
}
|
||||
timeDiff := aFile.ModTime().Sub(bFile.ModTime())
|
||||
if timeDiff > time.Second || timeDiff < -time.Second {
|
||||
t.Fatalf("mtimes different for %v: %v vs %v", aRel, aFile.ModTime(), bFile.ModTime())
|
||||
}
|
||||
|
||||
// compare contents
|
||||
if !aFile.IsDir() {
|
||||
if aContents, err := ioutil.ReadFile(aPath); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if bContents, err := ioutil.ReadFile(bPath); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if string(aContents) != string(bContents) {
|
||||
t.Fatalf("contents different for %v", aRel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPutRecursive(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
dirLocal, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpDirRemote := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpDirRemote)
|
||||
|
||||
t.Logf("put recursive: local %v remote %v", dirLocal, tmpDirRemote)
|
||||
|
||||
// push this directory (source code etc) recursively to the server
|
||||
if output, err := runSftpClient(t, "mkdir "+tmpDirRemote+"\r\nput -r -P "+dirLocal+"/ "+tmpDirRemote+"/", "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
|
||||
}
|
||||
|
||||
compareDirectoriesRecursive(t, dirLocal, path.Join(tmpDirRemote, path.Base(dirLocal)))
|
||||
}
|
||||
|
||||
func TestServerGetRecursive(t *testing.T) {
|
||||
listenerGo, hostGo, portGo := testServer(t, GOLANG_SFTP, READONLY)
|
||||
defer listenerGo.Close()
|
||||
|
||||
dirRemote, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpDirLocal := "/tmp/" + randName()
|
||||
defer os.RemoveAll(tmpDirLocal)
|
||||
|
||||
t.Logf("get recursive: local %v remote %v", tmpDirLocal, dirRemote)
|
||||
|
||||
// pull this directory (source code etc) recursively from the server
|
||||
if output, err := runSftpClient(t, "lmkdir "+tmpDirLocal+"\r\nget -r -P "+dirRemote+"/ "+tmpDirLocal+"/", "/", hostGo, portGo); err != nil {
|
||||
t.Fatalf("runSftpClient failed: %v, output\n%v\n", err, output)
|
||||
}
|
||||
|
||||
compareDirectoriesRecursive(t, dirRemote, path.Join(tmpDirLocal, path.Base(dirRemote)))
|
||||
}
|
||||
52
vendor/github.com/pkg/sftp/server_standalone/main.go
generated
vendored
Normal file
52
vendor/github.com/pkg/sftp/server_standalone/main.go
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package main
|
||||
|
||||
// small wrapper around sftp server that allows it to be used as a separate process subsystem call by the ssh server.
|
||||
// in practice this will statically link; however this allows unit testing from the sftp client.
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/sftp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
readOnly bool
|
||||
debugStderr bool
|
||||
debugLevel string
|
||||
options []sftp.ServerOption
|
||||
)
|
||||
|
||||
flag.BoolVar(&readOnly, "R", false, "read-only server")
|
||||
flag.BoolVar(&debugStderr, "e", false, "debug to stderr")
|
||||
flag.StringVar(&debugLevel, "l", "none", "debug level (ignored)")
|
||||
flag.Parse()
|
||||
|
||||
debugStream := ioutil.Discard
|
||||
if debugStderr {
|
||||
debugStream = os.Stderr
|
||||
}
|
||||
options = append(options, sftp.WithDebug(debugStream))
|
||||
|
||||
if readOnly {
|
||||
options = append(options, sftp.ReadOnly())
|
||||
}
|
||||
|
||||
svr, _ := sftp.NewServer(
|
||||
struct {
|
||||
io.Reader
|
||||
io.WriteCloser
|
||||
}{os.Stdin,
|
||||
os.Stdout,
|
||||
},
|
||||
options...,
|
||||
)
|
||||
if err := svr.Serve(); err != nil {
|
||||
fmt.Fprintf(debugStream, "sftp server completed with error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
21
vendor/github.com/pkg/sftp/server_statvfs_darwin.go
generated
vendored
Normal file
21
vendor/github.com/pkg/sftp/server_statvfs_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func statvfsFromStatfst(stat *syscall.Statfs_t) (*StatVFS, error) {
|
||||
return &StatVFS{
|
||||
Bsize: uint64(stat.Bsize),
|
||||
Frsize: uint64(stat.Bsize), // fragment size is a linux thing; use block size here
|
||||
Blocks: stat.Blocks,
|
||||
Bfree: stat.Bfree,
|
||||
Bavail: stat.Bavail,
|
||||
Files: stat.Files,
|
||||
Ffree: stat.Ffree,
|
||||
Favail: stat.Ffree, // not sure how to calculate Favail
|
||||
Fsid: uint64(uint64(stat.Fsid.Val[1])<<32 | uint64(stat.Fsid.Val[0])), // endianness?
|
||||
Flag: uint64(stat.Flags), // assuming POSIX?
|
||||
Namemax: 1024, // man 2 statfs shows: #define MAXPATHLEN 1024
|
||||
}, nil
|
||||
}
|
||||
25
vendor/github.com/pkg/sftp/server_statvfs_impl.go
generated
vendored
Normal file
25
vendor/github.com/pkg/sftp/server_statvfs_impl.go
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// +build darwin linux,!gccgo
|
||||
|
||||
// fill in statvfs structure with OS specific values
|
||||
// Statfs_t is different per-kernel, and only exists on some unixes (not Solaris for instance)
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) error {
|
||||
stat := &syscall.Statfs_t{}
|
||||
if err := syscall.Statfs(p.Path, stat); err != nil {
|
||||
return svr.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
|
||||
retPkt, err := statvfsFromStatfst(stat)
|
||||
if err != nil {
|
||||
return svr.sendPacket(statusFromError(p, err))
|
||||
}
|
||||
retPkt.ID = p.ID
|
||||
|
||||
return svr.sendPacket(retPkt)
|
||||
}
|
||||
22
vendor/github.com/pkg/sftp/server_statvfs_linux.go
generated
vendored
Normal file
22
vendor/github.com/pkg/sftp/server_statvfs_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// +build !gccgo,linux
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func statvfsFromStatfst(stat *syscall.Statfs_t) (*StatVFS, error) {
|
||||
return &StatVFS{
|
||||
Bsize: uint64(stat.Bsize),
|
||||
Frsize: uint64(stat.Frsize),
|
||||
Blocks: stat.Blocks,
|
||||
Bfree: stat.Bfree,
|
||||
Bavail: stat.Bavail,
|
||||
Files: stat.Files,
|
||||
Ffree: stat.Ffree,
|
||||
Favail: stat.Ffree, // not sure how to calculate Favail
|
||||
Flag: uint64(stat.Flags), // assuming POSIX?
|
||||
Namemax: uint64(stat.Namelen),
|
||||
}, nil
|
||||
}
|
||||
11
vendor/github.com/pkg/sftp/server_statvfs_stubs.go
generated
vendored
Normal file
11
vendor/github.com/pkg/sftp/server_statvfs_stubs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// +build !darwin,!linux gccgo
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) error {
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
12
vendor/github.com/pkg/sftp/server_stubs.go
generated
vendored
Normal file
12
vendor/github.com/pkg/sftp/server_stubs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// +build !cgo,!plan9 windows android
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
func runLs(dirname string, dirent os.FileInfo) string {
|
||||
return path.Join(dirname, dirent.Name())
|
||||
}
|
||||
95
vendor/github.com/pkg/sftp/server_test.go
generated
vendored
Normal file
95
vendor/github.com/pkg/sftp/server_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package sftp
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func clientServerPair(t *testing.T) (*Client, *Server) {
|
||||
cr, sw := io.Pipe()
|
||||
sr, cw := io.Pipe()
|
||||
server, err := NewServer(struct {
|
||||
io.Reader
|
||||
io.WriteCloser
|
||||
}{sr, sw})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go server.Serve()
|
||||
client, err := NewClientPipe(cr, cw)
|
||||
if err != nil {
|
||||
t.Fatalf("%+v\n", err)
|
||||
}
|
||||
return client, server
|
||||
}
|
||||
|
||||
type sshFxpTestBadExtendedPacket struct {
|
||||
ID uint32
|
||||
Extension string
|
||||
Data string
|
||||
}
|
||||
|
||||
func (p sshFxpTestBadExtendedPacket) id() uint32 { return p.ID }
|
||||
|
||||
func (p sshFxpTestBadExtendedPacket) MarshalBinary() ([]byte, error) {
|
||||
l := 1 + 4 + 4 + // type(byte) + uint32 + uint32
|
||||
len(p.Extension) +
|
||||
len(p.Data)
|
||||
|
||||
b := make([]byte, 0, l)
|
||||
b = append(b, ssh_FXP_EXTENDED)
|
||||
b = marshalUint32(b, p.ID)
|
||||
b = marshalString(b, p.Extension)
|
||||
b = marshalString(b, p.Data)
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// test that errors are sent back when we request an invalid extended packet operation
|
||||
func TestInvalidExtendedPacket(t *testing.T) {
|
||||
client, server := clientServerPair(t)
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
badPacket := sshFxpTestBadExtendedPacket{client.nextID(), "thisDoesn'tExist", "foobar"}
|
||||
_, _, err := client.clientConn.sendPacket(badPacket)
|
||||
if err == nil {
|
||||
t.Fatal("expected error from bad packet")
|
||||
}
|
||||
|
||||
// try to stat a file; the client should have shut down.
|
||||
filePath := "/etc/passwd"
|
||||
_, err = client.Stat(filePath)
|
||||
if err == nil {
|
||||
t.Fatal("expected error from closed connection")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// test that server handles concurrent requests correctly
|
||||
func TestConcurrentRequests(t *testing.T) {
|
||||
client, server := clientServerPair(t)
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
|
||||
concurrency := 2
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrency)
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
for j := 0; j < 1024; j++ {
|
||||
f, err := client.Open("/etc/passwd")
|
||||
if err != nil {
|
||||
t.Errorf("failed to open file: %v", err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Errorf("failed t close file: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
143
vendor/github.com/pkg/sftp/server_unix.go
generated
vendored
Normal file
143
vendor/github.com/pkg/sftp/server_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
// +build darwin dragonfly freebsd !android,linux netbsd openbsd solaris
|
||||
// +build cgo
|
||||
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func runLsStatt(dirname string, 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(dirname, dirent, statt)
|
||||
}
|
||||
|
||||
return path.Join(dirname, dirent.Name())
|
||||
}
|
||||
217
vendor/github.com/pkg/sftp/sftp.go
generated
vendored
Normal file
217
vendor/github.com/pkg/sftp/sftp.go
generated
vendored
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
// Package sftp implements the SSH File Transfer Protocol as described in
|
||||
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
type fxp uint8
|
||||
|
||||
func (f fxp) String() string {
|
||||
switch f {
|
||||
case ssh_FXP_INIT:
|
||||
return "SSH_FXP_INIT"
|
||||
case ssh_FXP_VERSION:
|
||||
return "SSH_FXP_VERSION"
|
||||
case ssh_FXP_OPEN:
|
||||
return "SSH_FXP_OPEN"
|
||||
case ssh_FXP_CLOSE:
|
||||
return "SSH_FXP_CLOSE"
|
||||
case ssh_FXP_READ:
|
||||
return "SSH_FXP_READ"
|
||||
case ssh_FXP_WRITE:
|
||||
return "SSH_FXP_WRITE"
|
||||
case ssh_FXP_LSTAT:
|
||||
return "SSH_FXP_LSTAT"
|
||||
case ssh_FXP_FSTAT:
|
||||
return "SSH_FXP_FSTAT"
|
||||
case ssh_FXP_SETSTAT:
|
||||
return "SSH_FXP_SETSTAT"
|
||||
case ssh_FXP_FSETSTAT:
|
||||
return "SSH_FXP_FSETSTAT"
|
||||
case ssh_FXP_OPENDIR:
|
||||
return "SSH_FXP_OPENDIR"
|
||||
case ssh_FXP_READDIR:
|
||||
return "SSH_FXP_READDIR"
|
||||
case ssh_FXP_REMOVE:
|
||||
return "SSH_FXP_REMOVE"
|
||||
case ssh_FXP_MKDIR:
|
||||
return "SSH_FXP_MKDIR"
|
||||
case ssh_FXP_RMDIR:
|
||||
return "SSH_FXP_RMDIR"
|
||||
case ssh_FXP_REALPATH:
|
||||
return "SSH_FXP_REALPATH"
|
||||
case ssh_FXP_STAT:
|
||||
return "SSH_FXP_STAT"
|
||||
case ssh_FXP_RENAME:
|
||||
return "SSH_FXP_RENAME"
|
||||
case ssh_FXP_READLINK:
|
||||
return "SSH_FXP_READLINK"
|
||||
case ssh_FXP_SYMLINK:
|
||||
return "SSH_FXP_SYMLINK"
|
||||
case ssh_FXP_STATUS:
|
||||
return "SSH_FXP_STATUS"
|
||||
case ssh_FXP_HANDLE:
|
||||
return "SSH_FXP_HANDLE"
|
||||
case ssh_FXP_DATA:
|
||||
return "SSH_FXP_DATA"
|
||||
case ssh_FXP_NAME:
|
||||
return "SSH_FXP_NAME"
|
||||
case ssh_FXP_ATTRS:
|
||||
return "SSH_FXP_ATTRS"
|
||||
case ssh_FXP_EXTENDED:
|
||||
return "SSH_FXP_EXTENDED"
|
||||
case ssh_FXP_EXTENDED_REPLY:
|
||||
return "SSH_FXP_EXTENDED_REPLY"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type fx uint8
|
||||
|
||||
func (f fx) String() string {
|
||||
switch f {
|
||||
case ssh_FX_OK:
|
||||
return "SSH_FX_OK"
|
||||
case ssh_FX_EOF:
|
||||
return "SSH_FX_EOF"
|
||||
case ssh_FX_NO_SUCH_FILE:
|
||||
return "SSH_FX_NO_SUCH_FILE"
|
||||
case ssh_FX_PERMISSION_DENIED:
|
||||
return "SSH_FX_PERMISSION_DENIED"
|
||||
case ssh_FX_FAILURE:
|
||||
return "SSH_FX_FAILURE"
|
||||
case ssh_FX_BAD_MESSAGE:
|
||||
return "SSH_FX_BAD_MESSAGE"
|
||||
case ssh_FX_NO_CONNECTION:
|
||||
return "SSH_FX_NO_CONNECTION"
|
||||
case ssh_FX_CONNECTION_LOST:
|
||||
return "SSH_FX_CONNECTION_LOST"
|
||||
case ssh_FX_OP_UNSUPPORTED:
|
||||
return "SSH_FX_OP_UNSUPPORTED"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
type unexpectedPacketErr struct {
|
||||
want, got uint8
|
||||
}
|
||||
|
||||
func (u *unexpectedPacketErr) Error() string {
|
||||
return fmt.Sprintf("sftp: unexpected packet: want %v, got %v", fxp(u.want), fxp(u.got))
|
||||
}
|
||||
|
||||
func unimplementedPacketErr(u uint8) error {
|
||||
return errors.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)
|
||||
}
|
||||
|
||||
func unimplementedSeekWhence(whence int) error {
|
||||
return errors.Errorf("sftp: unimplemented seek whence %v", whence)
|
||||
}
|
||||
|
||||
func unexpectedCount(want, got uint32) error {
|
||||
return errors.Errorf("sftp: unexpected count: want %v, got %v", want, got)
|
||||
}
|
||||
|
||||
type unexpectedVersionErr struct{ want, got uint32 }
|
||||
|
||||
func (u *unexpectedVersionErr) Error() string {
|
||||
return fmt.Sprintf("sftp: unexpected server version: want %v, got %v", u.want, u.got)
|
||||
}
|
||||
|
||||
// A StatusError is returned when an SFTP operation fails, and provides
|
||||
// additional information about the failure.
|
||||
type StatusError struct {
|
||||
Code uint32
|
||||
msg, lang string
|
||||
}
|
||||
|
||||
func (s *StatusError) Error() string { return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code)) }
|
||||
26
vendor/github.com/pkg/xattr/.gitignore
generated
vendored
Normal file
26
vendor/github.com/pkg/xattr/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
.DS_Store
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
|
||||
*.swp
|
||||
22
vendor/github.com/pkg/xattr/.travis.yml
generated
vendored
Normal file
22
vendor/github.com/pkg/xattr/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.8
|
||||
- tip
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
install:
|
||||
- go version
|
||||
- export GOBIN="$GOPATH/bin"
|
||||
- export PATH="$PATH:$GOBIN"
|
||||
- go get golang.org/x/tools/cmd/goimports
|
||||
- go build
|
||||
|
||||
script:
|
||||
- go vet
|
||||
- go test -v -race
|
||||
- diff <(GOPATH="$PWD:$PWD/vendor" goimports -d .) <(printf "")
|
||||
25
vendor/github.com/pkg/xattr/LICENSE
generated
vendored
Normal file
25
vendor/github.com/pkg/xattr/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Copyright (c) 2012 Dave Cheney. All rights reserved.
|
||||
Copyright (c) 2014 Kuba Podgórski. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
34
vendor/github.com/pkg/xattr/README.md
generated
vendored
Normal file
34
vendor/github.com/pkg/xattr/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[](http://godoc.org/github.com/pkg/xattr)
|
||||
[](https://goreportcard.com/report/github.com/pkg/xattr)
|
||||
[](https://travis-ci.org/pkg/xattr)
|
||||
|
||||
xattr
|
||||
=====
|
||||
Extended attribute support for Go (linux + darwin + freebsd).
|
||||
|
||||
"Extended attributes are name:value pairs associated permanently with files and directories, similar to the environment strings associated with a process. An attribute may be defined or undefined. If it is defined, its value may be empty or non-empty." [See more...](https://en.wikipedia.org/wiki/Extended_file_attributes)
|
||||
|
||||
|
||||
### Example
|
||||
```
|
||||
const path = "/tmp/myfile"
|
||||
const prefix = "user."
|
||||
|
||||
if err := xattr.Set(path, prefix+"test", []byte("test-attr-value")); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var list []string
|
||||
if list, err = xattr.List(path); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var data []byte
|
||||
if data, err = xattr.Get(path, prefix+"test"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = xattr.Remove(path, prefix+"test"); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
39
vendor/github.com/pkg/xattr/syscall_darwin.go
generated
vendored
Normal file
39
vendor/github.com/pkg/xattr/syscall_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// +build darwin
|
||||
|
||||
package xattr
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func getxattr(path string, name string, value *byte, size int, pos int, options int) (int, error) {
|
||||
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_GETXATTR, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(unsafe.Pointer(syscall.StringBytePtr(name))), uintptr(unsafe.Pointer(value)), uintptr(size), uintptr(pos), uintptr(options))
|
||||
if e1 != syscall.Errno(0) {
|
||||
return int(r0), e1
|
||||
}
|
||||
return int(r0), nil
|
||||
}
|
||||
|
||||
func listxattr(path string, namebuf *byte, size int, options int) (int, error) {
|
||||
r0, _, e1 := syscall.Syscall6(syscall.SYS_LISTXATTR, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(unsafe.Pointer(namebuf)), uintptr(size), uintptr(options), 0, 0)
|
||||
if e1 != syscall.Errno(0) {
|
||||
return int(r0), e1
|
||||
}
|
||||
return int(r0), nil
|
||||
}
|
||||
|
||||
func setxattr(path string, name string, value *byte, size int, pos int, options int) error {
|
||||
if _, _, e1 := syscall.Syscall6(syscall.SYS_SETXATTR, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(unsafe.Pointer(syscall.StringBytePtr(name))), uintptr(unsafe.Pointer(value)), uintptr(size), uintptr(pos), uintptr(options)); e1 != syscall.Errno(0) {
|
||||
return e1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removexattr(path string, name string, options int) error {
|
||||
if _, _, e1 := syscall.Syscall(syscall.SYS_REMOVEXATTR, uintptr(unsafe.Pointer(syscall.StringBytePtr(path))), uintptr(unsafe.Pointer(syscall.StringBytePtr(name))), uintptr(options)); e1 != syscall.Errno(0) {
|
||||
return e1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
91
vendor/github.com/pkg/xattr/syscall_freebsd.go
generated
vendored
Normal file
91
vendor/github.com/pkg/xattr/syscall_freebsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// +build freebsd
|
||||
|
||||
package xattr
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
ssize_t
|
||||
extattr_get_file(const char *path, int attrnamespace,
|
||||
const char *attrname, void *data, size_t nbytes);
|
||||
|
||||
ssize_t
|
||||
extattr_set_file(const char *path, int attrnamespace,
|
||||
const char *attrname, const void *data, size_t nbytes);
|
||||
|
||||
int
|
||||
extattr_delete_file(const char *path, int attrnamespace,
|
||||
const char *attrname);
|
||||
|
||||
ssize_t
|
||||
extattr_list_file(const char *path, int attrnamespace, void *data,
|
||||
size_t nbytes);
|
||||
*/
|
||||
|
||||
func extattr_get_file(path string, attrnamespace int, attrname string, data *byte, nbytes int) (int, error) {
|
||||
r, _, e := syscall.Syscall6(
|
||||
syscall.SYS_EXTATTR_GET_FILE,
|
||||
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
|
||||
uintptr(attrnamespace),
|
||||
uintptr(unsafe.Pointer(syscall.StringBytePtr(attrname))),
|
||||
uintptr(unsafe.Pointer(data)),
|
||||
uintptr(nbytes),
|
||||
0,
|
||||
)
|
||||
var err error
|
||||
if e != 0 {
|
||||
err = e
|
||||
}
|
||||
return int(r), err
|
||||
}
|
||||
|
||||
func extattr_set_file(path string, attrnamespace int, attrname string, data *byte, nbytes int) (int, error) {
|
||||
r, _, e := syscall.Syscall6(
|
||||
syscall.SYS_EXTATTR_SET_FILE,
|
||||
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
|
||||
uintptr(attrnamespace),
|
||||
uintptr(unsafe.Pointer(syscall.StringBytePtr(attrname))),
|
||||
uintptr(unsafe.Pointer(data)),
|
||||
uintptr(nbytes),
|
||||
0,
|
||||
)
|
||||
var err error
|
||||
if e != 0 {
|
||||
err = e
|
||||
}
|
||||
return int(r), err
|
||||
}
|
||||
|
||||
func extattr_delete_file(path string, attrnamespace int, attrname string) error {
|
||||
_, _, e := syscall.Syscall(
|
||||
syscall.SYS_EXTATTR_DELETE_FILE,
|
||||
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
|
||||
uintptr(attrnamespace),
|
||||
uintptr(unsafe.Pointer(syscall.StringBytePtr(attrname))),
|
||||
)
|
||||
var err error
|
||||
if e != 0 {
|
||||
err = e
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func extattr_list_file(path string, attrnamespace int, data *byte, nbytes int) (int, error) {
|
||||
r, _, e := syscall.Syscall6(
|
||||
syscall.SYS_EXTATTR_LIST_FILE,
|
||||
uintptr(unsafe.Pointer(syscall.StringBytePtr(path))),
|
||||
uintptr(attrnamespace),
|
||||
uintptr(unsafe.Pointer(data)),
|
||||
uintptr(nbytes),
|
||||
0,
|
||||
0,
|
||||
)
|
||||
var err error
|
||||
if e != 0 {
|
||||
err = e
|
||||
}
|
||||
return int(r), err
|
||||
}
|
||||
32
vendor/github.com/pkg/xattr/xattr.go
generated
vendored
Normal file
32
vendor/github.com/pkg/xattr/xattr.go
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
Package xattr provides support for extended attributes on linux, darwin and freebsd.
|
||||
Extended attributes are name:value pairs associated permanently with files and directories,
|
||||
similar to the environment strings associated with a process.
|
||||
An attribute may be defined or undefined. If it is defined, its value may be empty or non-empty.
|
||||
More details you can find here: https://en.wikipedia.org/wiki/Extended_file_attributes
|
||||
*/
|
||||
package xattr
|
||||
|
||||
// Error records an error and the operation, file path and attribute that caused it.
|
||||
type Error struct {
|
||||
Op string
|
||||
Path string
|
||||
Name string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Op + " " + e.Path + " " + e.Name + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// nullTermToStrings converts an array of NULL terminated UTF-8 strings to a []string.
|
||||
func nullTermToStrings(buf []byte) (result []string) {
|
||||
offset := 0
|
||||
for index, b := range buf {
|
||||
if b == 0 {
|
||||
result = append(result, string(buf[offset:index]))
|
||||
offset = index + 1
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
64
vendor/github.com/pkg/xattr/xattr_darwin.go
generated
vendored
Normal file
64
vendor/github.com/pkg/xattr/xattr_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// +build darwin
|
||||
|
||||
package xattr
|
||||
|
||||
// Get retrieves extended attribute data associated with path.
|
||||
func Get(path, name string) ([]byte, error) {
|
||||
// find size.
|
||||
size, err := getxattr(path, name, nil, 0, 0, 0)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.Get", path, name, err}
|
||||
}
|
||||
if size > 0 {
|
||||
buf := make([]byte, size)
|
||||
// Read into buffer of that size.
|
||||
read, err := getxattr(path, name, &buf[0], size, 0, 0)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.Get", path, name, err}
|
||||
}
|
||||
return buf[:read], nil
|
||||
}
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
// List retrieves a list of names of extended attributes associated
|
||||
// with the given path in the file system.
|
||||
func List(path string) ([]string, error) {
|
||||
// find size.
|
||||
size, err := listxattr(path, nil, 0, 0)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.List", path, "", err}
|
||||
}
|
||||
if size > 0 {
|
||||
|
||||
buf := make([]byte, size)
|
||||
// Read into buffer of that size.
|
||||
read, err := listxattr(path, &buf[0], size, 0)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.List", path, "", err}
|
||||
}
|
||||
return nullTermToStrings(buf[:read]), nil
|
||||
}
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// Set associates name and data together as an attribute of path.
|
||||
func Set(path, name string, data []byte) error {
|
||||
var dataval *byte = nil
|
||||
datalen := len(data)
|
||||
if datalen > 0 {
|
||||
dataval = &data[0]
|
||||
}
|
||||
if err := setxattr(path, name, dataval, datalen, 0, 0); err != nil {
|
||||
return &Error{"xattr.Set", path, name, err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes the attribute associated with the given path.
|
||||
func Remove(path, name string) error {
|
||||
if err := removexattr(path, name, 0); err != nil {
|
||||
return &Error{"xattr.Remove", path, name, err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
90
vendor/github.com/pkg/xattr/xattr_freebsd.go
generated
vendored
Normal file
90
vendor/github.com/pkg/xattr/xattr_freebsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// +build freebsd
|
||||
|
||||
package xattr
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
EXTATTR_NAMESPACE_USER = 1
|
||||
)
|
||||
|
||||
// Get retrieves extended attribute data associated with path.
|
||||
func Get(path, name string) ([]byte, error) {
|
||||
// find size.
|
||||
size, err := extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, nil, 0)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.Get", path, name, err}
|
||||
}
|
||||
if size > 0 {
|
||||
buf := make([]byte, size)
|
||||
// Read into buffer of that size.
|
||||
read, err := extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, &buf[0], size)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.Get", path, name, err}
|
||||
}
|
||||
return buf[:read], nil
|
||||
}
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
// List retrieves a list of names of extended attributes associated
|
||||
// with the given path in the file system.
|
||||
func List(path string) ([]string, error) {
|
||||
// find size.
|
||||
size, err := extattr_list_file(path, EXTATTR_NAMESPACE_USER, nil, 0)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.List", path, "", err}
|
||||
}
|
||||
if size > 0 {
|
||||
buf := make([]byte, size)
|
||||
// Read into buffer of that size.
|
||||
read, err := extattr_list_file(path, EXTATTR_NAMESPACE_USER, &buf[0], size)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.List", path, "", err}
|
||||
}
|
||||
return attrListToStrings(buf[:read]), nil
|
||||
}
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// Set associates name and data together as an attribute of path.
|
||||
func Set(path, name string, data []byte) error {
|
||||
var dataval *byte = nil
|
||||
datalen := len(data)
|
||||
if datalen > 0 {
|
||||
dataval = &data[0]
|
||||
}
|
||||
written, err := extattr_set_file(path, EXTATTR_NAMESPACE_USER, name, dataval, datalen)
|
||||
if err != nil {
|
||||
return &Error{"xattr.Set", path, name, err}
|
||||
}
|
||||
if written != datalen {
|
||||
return &Error{"xattr.Set", path, name, syscall.E2BIG}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes the attribute associated with the given path.
|
||||
func Remove(path, name string) error {
|
||||
if err := extattr_delete_file(path, EXTATTR_NAMESPACE_USER, name); err != nil {
|
||||
return &Error{"xattr.Remove", path, name, err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// attrListToStrings converts a sequnce of attribute name entries to a []string.
|
||||
// Each entry consists of a single byte containing the length
|
||||
// of the attribute name, followed by the attribute name.
|
||||
// The name is _not_ terminated by NUL.
|
||||
func attrListToStrings(buf []byte) []string {
|
||||
var result []string
|
||||
index := 0
|
||||
for index < len(buf) {
|
||||
next := index + 1 + int(buf[index])
|
||||
result = append(result, string(buf[index+1:next]))
|
||||
index = next
|
||||
}
|
||||
return result
|
||||
}
|
||||
61
vendor/github.com/pkg/xattr/xattr_linux.go
generated
vendored
Normal file
61
vendor/github.com/pkg/xattr/xattr_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// +build linux
|
||||
|
||||
package xattr
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Get retrieves extended attribute data associated with path.
|
||||
func Get(path, name string) ([]byte, error) {
|
||||
// find size.
|
||||
size, err := syscall.Getxattr(path, name, nil)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.Get", path, name, err}
|
||||
}
|
||||
if size > 0 {
|
||||
data := make([]byte, size)
|
||||
// Read into buffer of that size.
|
||||
read, err := syscall.Getxattr(path, name, data)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.Get", path, name, err}
|
||||
}
|
||||
return data[:read], nil
|
||||
}
|
||||
return []byte{}, nil
|
||||
}
|
||||
|
||||
// List retrieves a list of names of extended attributes associated
|
||||
// with the given path in the file system.
|
||||
func List(path string) ([]string, error) {
|
||||
// find size.
|
||||
size, err := syscall.Listxattr(path, nil)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.List", path, "", err}
|
||||
}
|
||||
if size > 0 {
|
||||
buf := make([]byte, size)
|
||||
// Read into buffer of that size.
|
||||
read, err := syscall.Listxattr(path, buf)
|
||||
if err != nil {
|
||||
return nil, &Error{"xattr.List", path, "", err}
|
||||
}
|
||||
return nullTermToStrings(buf[:read]), nil
|
||||
}
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// Set associates name and data together as an attribute of path.
|
||||
func Set(path, name string, data []byte) error {
|
||||
if err := syscall.Setxattr(path, name, data, 0); err != nil {
|
||||
return &Error{"xattr.Set", path, name, err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes the attribute associated
|
||||
// with the given path.
|
||||
func Remove(path, name string) error {
|
||||
if err := syscall.Removexattr(path, name); err != nil {
|
||||
return &Error{"xattr.Remove", path, name, err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
86
vendor/github.com/pkg/xattr/xattr_test.go
generated
vendored
Normal file
86
vendor/github.com/pkg/xattr/xattr_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// +build linux darwin freebsd
|
||||
|
||||
package xattr
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const UserPrefix = "user."
|
||||
|
||||
func Test(t *testing.T) {
|
||||
tmp, err := ioutil.TempFile("", "")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
err = Set(tmp.Name(), UserPrefix+"test", []byte("test-attr-value"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
list, err := List(tmp.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, name := range list {
|
||||
if name == UserPrefix+"test" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("Listxattr did not return test attribute")
|
||||
}
|
||||
|
||||
var data []byte
|
||||
data, err = Get(tmp.Name(), UserPrefix+"test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
value := string(data)
|
||||
t.Log(value)
|
||||
if "test-attr-value" != value {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
err = Remove(tmp.Name(), UserPrefix+"test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoData(t *testing.T) {
|
||||
tmp, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
err = Set(tmp.Name(), UserPrefix+"test", []byte{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
list, err := List(tmp.Name())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, name := range list {
|
||||
if name == UserPrefix+"test" {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("Listxattr did not return test attribute")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue