vendor: update all dependencies to latest versions

This commit is contained in:
Nick Craig-Wood 2017-09-30 15:27:27 +01:00
parent 911d121bb9
commit b017fcfe9a
3048 changed files with 537057 additions and 189681 deletions

32
vendor/github.com/pkg/sftp/client.go generated vendored
View file

@ -14,12 +14,32 @@ import (
"golang.org/x/crypto/ssh"
)
// MaxPacket sets the maximum size of the payload.
func MaxPacket(size int) func(*Client) error {
// InternalInconsistency indicates the packets sent and the data queued to be
// written to the file don't match up. It is an unusual error and if you get it
// you should file a ticket.
var InternalInconsistency = errors.New("internal inconsistency")
// A ClientOption is a function which applies configuration to a Client.
type ClientOption func(*Client) error
// This is based on Openssh's max accepted size of 1<<18 - overhead
const maxMaxPacket = (1 << 18) - 1024
// MaxPacket sets the maximum size of the payload. The size param must be
// between 32768 (1<<15) and 261120 ((1 << 18) - 1024). The minimum size is
// given by the RFC, while the maximum size is a de-facto standard based on
// Openssh's SFTP server which won't accept packets much larger than that.
//
// Note if you aren't using Openssh's sftp server and get the error "failed to
// send packet header: EOF" when copying a large file try lowering this number.
func MaxPacket(size int) ClientOption {
return func(c *Client) error {
if size < 1<<15 {
return errors.Errorf("size must be greater or equal to 32k")
}
if size > maxMaxPacket {
return errors.Errorf("max packet size is too large (see docs)")
}
c.maxPacket = size
return nil
}
@ -27,7 +47,7 @@ func MaxPacket(size int) func(*Client) error {
// NewClient creates a new SFTP client on conn, using zero or more option
// functions.
func NewClient(conn *ssh.Client, opts ...func(*Client) error) (*Client, error) {
func NewClient(conn *ssh.Client, opts ...ClientOption) (*Client, error) {
s, err := conn.NewSession()
if err != nil {
return nil, err
@ -50,7 +70,7 @@ func NewClient(conn *ssh.Client, opts ...func(*Client) error) (*Client, error) {
// NewClientPipe creates a new SFTP client given a Reader and a WriteCloser.
// This can be used for connecting to an SFTP server over TCP/TLS or by using
// the system's ssh client program (e.g. via exec.Command).
func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...func(*Client) error) (*Client, error) {
func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Client, error) {
sftp := &Client{
clientConn: clientConn{
conn: conn{
@ -611,7 +631,7 @@ func (c *Client) Mkdir(path string) error {
// applyOptions applies options functions to the Client.
// If an error is encountered, option processing ceases.
func (c *Client) applyOptions(opts ...func(*Client) error) error {
func (c *Client) applyOptions(opts ...ClientOption) error {
for _, f := range opts {
if err := f(c); err != nil {
return err
@ -791,7 +811,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
if inFlight == 0 {
if firstErr.err == nil && len(pendingWrites) > 0 {
return copied, errors.New("internal inconsistency")
return copied, InternalInconsistency
}
break
}

View file

@ -104,14 +104,26 @@ func ExampleClient_Mkdir_parents() {
}
defer client.Close()
ssh_fx_failure := uint32(4)
sshFxFailure := uint32(4)
mkdirParents := func(client *sftp.Client, dir string) (err error) {
var parents string
if path.IsAbs(dir) {
// Otherwise, an absolute path given below would be turned in to a relative one
// by splitting on "/"
parents = "/"
}
for _, name := range strings.Split(dir, "/") {
if name == "" {
// Paths with double-/ in them should just move along
// this will also catch the case of the first character being a "/", i.e. an absolute path
continue
}
parents = path.Join(parents, name)
err = client.Mkdir(parents)
if status, ok := err.(*sftp.StatusError); ok {
if status.Code == ssh_fx_failure {
if status.Code == sshFxFailure {
var fi os.FileInfo
fi, err = client.Stat(parents)
if err == nil {

View file

@ -23,13 +23,13 @@ type packetManager struct {
working *sync.WaitGroup
}
func newPktMgr(sender packetSender) packetManager {
s := packetManager{
requests: make(chan requestPacket, sftpServerWorkerCount),
responses: make(chan responsePacket, sftpServerWorkerCount),
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),
incoming: make([]uint32, 0, SftpServerWorkerCount),
outgoing: make([]responsePacket, 0, SftpServerWorkerCount),
sender: sender,
working: &sync.WaitGroup{},
}
@ -39,19 +39,19 @@ func newPktMgr(sender packetSender) packetManager {
// register incoming packets to be handled
// send id of 0 for packets without id
func (s packetManager) incomingPacket(pkt requestPacket) {
func (s *packetManager) incomingPacket(pkt requestPacket) {
s.working.Add(1)
s.requests <- pkt // buffer == sftpServerWorkerCount
s.requests <- pkt // buffer == SftpServerWorkerCount
}
// register outgoing packets as being ready
func (s packetManager) readyPacket(pkt responsePacket) {
func (s *packetManager) readyPacket(pkt responsePacket) {
s.responses <- pkt
s.working.Done()
}
// shut down packetManager controller
func (s packetManager) close() {
func (s *packetManager) close() {
// pause until current packets are processed
s.working.Wait()
close(s.fini)
@ -63,15 +63,15 @@ func (s packetManager) close() {
// transfers.
func (s *packetManager) workerChan(runWorker func(requestChan)) requestChan {
rwChan := make(chan requestPacket, sftpServerWorkerCount)
for i := 0; i < sftpServerWorkerCount; i++ {
rwChan := make(chan requestPacket, SftpServerWorkerCount)
for i := 0; i < SftpServerWorkerCount; i++ {
runWorker(rwChan)
}
cmdChan := make(chan requestPacket)
runWorker(cmdChan)
pktChan := make(chan requestPacket, sftpServerWorkerCount)
pktChan := make(chan requestPacket, SftpServerWorkerCount)
go func() {
// start with cmdChan
curChan := cmdChan
@ -147,10 +147,10 @@ func (s *packetManager) maybeSendPackets() {
}
}
func outfilter(o []responsePacket) []uint32 {
res := make([]uint32, 0, len(o))
for _, v := range o {
res = append(res, v.id())
}
return res
}
//func outfilter(o []responsePacket) []uint32 {
// res := make([]uint32, 0, len(o))
// for _, v := range o {
// res = append(res, v.id())
// }
// return res
//}

View file

@ -11,7 +11,6 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"sync"
"time"
)
@ -26,7 +25,7 @@ func InMemHandler() Handlers {
}
// Handlers
func (fs *root) Fileread(r Request) (io.ReaderAt, error) {
func (fs *root) Fileread(r *Request) (io.ReaderAt, error) {
fs.filesLock.Lock()
defer fs.filesLock.Unlock()
file, err := fs.fetch(r.Filepath)
@ -42,7 +41,7 @@ func (fs *root) Fileread(r Request) (io.ReaderAt, error) {
return file.ReaderAt()
}
func (fs *root) Filewrite(r Request) (io.WriterAt, error) {
func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
fs.filesLock.Lock()
defer fs.filesLock.Unlock()
file, err := fs.fetch(r.Filepath)
@ -60,7 +59,7 @@ func (fs *root) Filewrite(r Request) (io.WriterAt, error) {
return file.WriterAt()
}
func (fs *root) Filecmd(r Request) error {
func (fs *root) Filecmd(r *Request) error {
fs.filesLock.Lock()
defer fs.filesLock.Unlock()
switch r.Method {
@ -101,20 +100,27 @@ func (fs *root) Filecmd(r Request) error {
return nil
}
func (fs *root) Fileinfo(r Request) ([]os.FileInfo, error) {
type listerat []os.FileInfo
// Modeled after strings.Reader's ReadAt() implementation
func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) {
var n int
if offset >= int64(len(f)) {
return 0, io.EOF
}
n = copy(ls, f[offset:])
if n < len(ls) {
return n, io.EOF
}
return n, nil
}
func (fs *root) Filelist(r *Request) (ListerAt, 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 {
@ -126,21 +132,13 @@ func (fs *root) Fileinfo(r Request) ([]os.FileInfo, error) {
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
return listerat(list), nil
case "Stat":
file, err := fs.fetch(r.Filepath)
if err != nil {
return nil, err
}
return []os.FileInfo{file}, nil
return listerat([]os.FileInfo{file}), nil
case "Readlink":
file, err := fs.fetch(r.Filepath)
if err != nil {
@ -152,7 +150,7 @@ func (fs *root) Fileinfo(r Request) ([]os.FileInfo, error) {
return nil, err
}
}
return []os.FileInfo{file}, nil
return listerat([]os.FileInfo{file}), nil
}
return nil, nil
}

View file

@ -10,21 +10,29 @@ import (
// FileReader should return an io.Reader for the filepath
type FileReader interface {
Fileread(Request) (io.ReaderAt, error)
Fileread(*Request) (io.ReaderAt, error)
}
// FileWriter should return an io.Writer for the filepath
type FileWriter interface {
Filewrite(Request) (io.WriterAt, error)
Filewrite(*Request) (io.WriterAt, error)
}
// FileCmder should return an error (rename, remove, setstate, etc.)
type FileCmder interface {
Filecmd(Request) error
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)
// FileLister should return file info interface and errors (readdir, stat)
type FileLister interface {
Filelist(*Request) (ListerAt, error)
}
// ListerAt does for file lists what io.ReaderAt does for files.
// ListAt should return the number of entries copied and an io.EOF
// error if at end of list. This is testable by comparing how many you
// copied to how many could be copied (eg. n < len(ls) below).
// The copy() builtin is best for the copying.
type ListerAt interface {
ListAt([]os.FileInfo, int64) (int, error)
}

View file

@ -3,9 +3,9 @@ package sftp
import (
"encoding"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"sync"
"syscall"
@ -21,14 +21,14 @@ type Handlers struct {
FileGet FileReader
FilePut FileWriter
FileCmd FileCmder
FileInfo FileInfoer
FileList FileLister
}
// RequestServer abstracts the sftp protocol with an http request-like protocol
type RequestServer struct {
*serverConn
Handlers Handlers
pktMgr packetManager
pktMgr *packetManager
openRequests map[string]Request
openRequestLock sync.RWMutex
handleCount int
@ -51,20 +51,22 @@ func NewRequestServer(rwc io.ReadWriteCloser, h Handlers) *RequestServer {
}
}
func (rs *RequestServer) nextRequest(r Request) string {
// Note that we are explicitly saving the Request as a value.
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
rs.openRequests[handle] = *r
return handle
}
func (rs *RequestServer) getRequest(handle string) (Request, bool) {
// Returns pointer to new copy of Request object
func (rs *RequestServer) getRequest(handle string) (*Request, bool) {
rs.openRequestLock.RLock()
defer rs.openRequestLock.RUnlock()
r, ok := rs.openRequests[handle]
return r, ok
return &r, ok
}
func (rs *RequestServer) closeRequest(handle string) {
@ -130,7 +132,7 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
rs.closeRequest(handle)
rpkt = statusFromError(pkt, nil)
case *sshFxpRealpathPacket:
rpkt = cleanPath(pkt)
rpkt = cleanPacketPath(pkt)
case isOpener:
handle := rs.nextRequest(requestFromPacket(pkt))
rpkt = sshFxpHandlePacket{pkt.id(), handle}
@ -142,7 +144,7 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
} else {
request = requestFromPacket(
&sshFxpStatPacket{ID: pkt.id(), Path: request.Filepath})
rpkt = rs.handle(request, pkt)
rpkt = request.call(rs.Handlers, pkt)
}
case *sshFxpFsetstatPacket:
handle := pkt.getHandle()
@ -154,20 +156,23 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
&sshFxpSetstatPacket{ID: pkt.id(), Path: request.Filepath,
Flags: pkt.Flags, Attrs: pkt.Attrs,
})
rpkt = rs.handle(request, pkt)
rpkt = request.call(rs.Handlers, pkt)
}
case hasHandle:
handle := pkt.getHandle()
request, ok := rs.getRequest(handle)
request.update(pkt)
if !ok {
uerr := request.updateMethod(pkt)
if !ok || uerr != nil {
if uerr == nil {
uerr = syscall.EBADF
}
rpkt = statusFromError(pkt, syscall.EBADF)
} else {
rpkt = rs.handle(request, pkt)
rpkt = request.call(rs.Handlers, pkt)
}
case hasPath:
request := requestFromPacket(pkt)
rpkt = rs.handle(request, pkt)
rpkt = request.call(rs.Handlers, pkt)
default:
return errors.Errorf("unexpected packet type %T", pkt)
}
@ -180,31 +185,24 @@ func (rs *RequestServer) packetWorker(pktChan chan requestPacket) error {
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)
func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket {
path := cleanPath(pkt.getPath())
return &sshFxpNamePacket{
ID: pkt.id(),
NameAttrs: []sshFxpNameAttr{{
Name: cleaned_path,
LongName: cleaned_path,
Name: path,
LongName: 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)
func cleanPath(path string) string {
cleanSlashPath := filepath.ToSlash(filepath.Clean(path))
if !strings.HasPrefix(cleanSlashPath, "/") {
return "/" + cleanSlashPath
}
return rpkt
return cleanSlashPath
}
// Wrap underlying connection methods to use packetManager
@ -220,12 +218,3 @@ func (rs *RequestServer) sendPacket(m encoding.BinaryMarshaler) error {
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
}

View file

@ -315,6 +315,7 @@ func TestRequestReadlink(t *testing.T) {
func TestRequestReaddir(t *testing.T) {
p := clientRequestServerPair(t)
MaxFilelist = 22 // make not divisible by our test amount (100)
defer p.Close()
for i := 0; i < 100; i++ {
fname := fmt.Sprintf("/foo_%02d", i)
@ -327,3 +328,20 @@ func TestRequestReaddir(t *testing.T) {
names := []string{di[18].Name(), di[81].Name()}
assert.Equal(t, []string{"foo_18", "foo_81"}, names)
}
func TestCleanPath(t *testing.T) {
assert.Equal(t, "/", cleanPath("/"))
assert.Equal(t, "/", cleanPath("//"))
assert.Equal(t, "/a", cleanPath("/a/"))
assert.Equal(t, "/a", cleanPath("a/"))
assert.Equal(t, "/a/b/c", cleanPath("/a//b//c/"))
// filepath.ToSlash does not touch \ as char on unix systems, so os.PathSeparator is used for windows compatible tests
bslash := string(os.PathSeparator)
assert.Equal(t, "/", cleanPath(bslash))
assert.Equal(t, "/", cleanPath(bslash+bslash))
assert.Equal(t, "/a", cleanPath(bslash+"a"+bslash))
assert.Equal(t, "/a", cleanPath("a"+bslash))
assert.Equal(t, "/a/b/c", cleanPath(bslash+"a"+bslash+bslash+"b"+bslash+bslash+"c"+bslash))
}

238
vendor/github.com/pkg/sftp/request.go generated vendored
View file

@ -11,6 +11,9 @@ import (
"github.com/pkg/errors"
)
// MaxFilelist is the max number of files to return in a readdir batch.
var MaxFilelist int64 = 100
// Request contains the data and state for the incoming service request.
type Request struct {
// Get, Put, Setstat, Stat, Rename, Remove
@ -20,72 +23,73 @@ type Request struct {
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
writerAt io.WriterAt
readerAt io.ReaderAt
listerAt ListerAt
lsoffset int64
}
type packet_data struct {
id uint32
_id uint32
data []byte
length uint32
offset int64
}
func (pd packet_data) id() uint32 {
return pd._id
}
// New Request initialized based on packet data
func requestFromPacket(pkt hasPath) Request {
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)
request.Target = cleanPath(p.Newpath)
case *sshFxpSymlinkPacket:
request.Target = filepath.Clean(p.Linkpath)
request.Target = cleanPath(p.Linkpath)
}
return request
}
func newRequest() *Request {
return &Request{state: &state{}, stateLock: &sync.RWMutex{}}
}
// 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{}
func NewRequest(method, path string) *Request {
request := newRequest()
request.Method = method
request.Filepath = cleanPath(path)
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) {
// Returns current offset for file list
func (r *Request) lsNext() int64 {
r.stateLock.RLock()
defer r.stateLock.RUnlock()
r.state.readdirToken = token
return r.state.lsoffset
}
// 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
// Increases next offset
func (r *Request) lsInc(offset int64) {
r.stateLock.Lock()
defer r.stateLock.Unlock()
r.state.lsoffset = r.state.lsoffset + offset
}
// manage file read/write state
func (r Request) setFileState(s interface{}) {
func (r *Request) setFileState(s interface{}) {
r.stateLock.Lock()
defer r.stateLock.Unlock()
switch s := s.(type) {
@ -93,38 +97,33 @@ func (r Request) setFileState(s interface{}) {
r.state.writerAt = s
case io.ReaderAt:
r.state.readerAt = s
case ListerAt:
r.state.listerAt = s
case int64:
r.state.lsoffset = s
}
}
func (r Request) getWriter() io.WriterAt {
func (r *Request) getWriter() io.WriterAt {
r.stateLock.RLock()
defer r.stateLock.RUnlock()
return r.state.writerAt
}
func (r Request) getReader() io.ReaderAt {
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) {
func (r *Request) getLister() ListerAt {
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
return r.state.listerAt
}
// Close reader/writer if possible
func (r Request) close() {
func (r *Request) close() {
rd := r.getReader()
if c, ok := rd.(io.Closer); ok {
c.Close()
@ -135,112 +134,130 @@ func (r Request) 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
func (r *Request) call(handlers Handlers, pkt requestPacket) responsePacket {
pd := packetData(pkt)
switch r.Method {
case "Get":
rpkt, err = fileget(handlers.FileGet, r)
return fileget(handlers.FileGet, r, pd)
case "Put": // add "Append" to this to handle append only file writes
rpkt, err = fileput(handlers.FilePut, r)
return fileput(handlers.FilePut, r, pd)
case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
rpkt, err = filecmd(handlers.FileCmd, r)
return filecmd(handlers.FileCmd, r, pd)
case "List", "Stat", "Readlink":
rpkt, err = fileinfo(handlers.FileInfo, r)
return filelist(handlers.FileList, r, pd)
default:
return rpkt, errors.Errorf("unexpected method: %s", r.Method)
return statusFromError(pkt,
errors.Errorf("unexpected method: %s", r.Method))
}
return rpkt, err
}
// file data for additional read/write packets
func packetData(p requestPacket) packet_data {
pd := packet_data{_id: p.id()}
switch p := p.(type) {
case *sshFxpReadPacket:
pd.length = p.Len
pd.offset = int64(p.Offset)
case *sshFxpWritePacket:
pd.data = p.Data
pd.length = p.Length
pd.offset = int64(p.Offset)
}
return pd
}
// wrap FileReader handler
func fileget(h FileReader, r Request) (responsePacket, error) {
func fileget(h FileReader, r *Request, pd packet_data) responsePacket {
var err error
reader := r.getReader()
if reader == nil {
reader, err = h.Fileread(r)
if err != nil {
return nil, err
return statusFromError(pd, err)
}
r.setFileState(reader)
}
pd := r.popPacket()
data := make([]byte, clamp(pd.length, maxTxPacket))
n, err := reader.ReadAt(data, pd.offset)
// only return EOF erro if no data left to read
if err != nil && (err != io.EOF || n == 0) {
return nil, err
return statusFromError(pd, err)
}
return &sshFxpDataPacket{
ID: pd.id,
ID: pd.id(),
Length: uint32(n),
Data: data[:n],
}, nil
}
}
// wrap FileWriter handler
func fileput(h FileWriter, r Request) (responsePacket, error) {
func fileput(h FileWriter, r *Request, pd packet_data) responsePacket {
var err error
writer := r.getWriter()
if writer == nil {
writer, err = h.Filewrite(r)
if err != nil {
return nil, err
return statusFromError(pd, err)
}
r.setFileState(writer)
}
pd := r.popPacket()
_, err = writer.WriteAt(pd.data, pd.offset)
if err != nil {
return nil, err
return statusFromError(pd, err)
}
return &sshFxpStatusPacket{
ID: pd.id,
ID: pd.id(),
StatusError: StatusError{
Code: ssh_FX_OK,
}}, nil
}}
}
// wrap FileCmder handler
func filecmd(h FileCmder, r Request) (responsePacket, error) {
func filecmd(h FileCmder, r *Request, pd packet_data) responsePacket {
err := h.Filecmd(r)
if err != nil {
return nil, err
return statusFromError(pd, err)
}
return &sshFxpStatusPacket{
ID: r.pkt_id,
ID: pd.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
// wrap FileLister handler
func filelist(h FileLister, r *Request, pd packet_data) responsePacket {
var err error
lister := r.getLister()
if lister == nil {
lister, err = h.Filelist(r)
if err != nil {
return statusFromError(pd, err)
}
r.setFileState(lister)
}
offset := r.lsNext()
finfo := make([]os.FileInfo, MaxFilelist)
n, err := lister.ListAt(finfo, offset)
r.lsInc(int64(n))
// ignore EOF as we only return it when there are no results
finfo = finfo[:n] // avoid need for nil tests below
switch r.Method {
case "List":
pd := r.popPacket()
dirname := path.Base(r.Filepath)
ret := &sshFxpNamePacket{ID: pd.id}
if err != nil && err != io.EOF {
return statusFromError(pd, err)
}
if n == 0 {
return statusFromError(pd, io.EOF)
}
dirname := filepath.ToSlash(path.Base(r.Filepath))
ret := &sshFxpNamePacket{ID: pd.id()}
for _, fi := range finfo {
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
Name: fi.Name(),
@ -248,63 +265,56 @@ func fileinfo(h FileInfoer, r Request) (responsePacket, error) {
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
return ret
case "Stat":
if len(finfo) == 0 {
if err != nil && err != io.EOF {
return statusFromError(pd, err)
}
if n == 0 {
err = &os.PathError{Op: "stat", Path: r.Filepath,
Err: syscall.ENOENT}
return nil, err
return statusFromError(pd, err)
}
return &sshFxpStatResponse{
ID: r.pkt_id,
ID: pd.id(),
info: finfo[0],
}, nil
}
case "Readlink":
if len(finfo) == 0 {
if err != nil && err != io.EOF {
return statusFromError(pd, err)
}
if n == 0 {
err = &os.PathError{Op: "readlink", Path: r.Filepath,
Err: syscall.ENOENT}
return nil, err
return statusFromError(pd, err)
}
filename := finfo[0].Name()
return &sshFxpNamePacket{
ID: r.pkt_id,
ID: pd.id(),
NameAttrs: []sshFxpNameAttr{{
Name: filename,
LongName: filename,
Attrs: emptyFileStat,
}},
}, nil
}
default:
err = errors.Errorf("unexpected method: %s", r.Method)
return statusFromError(pd, err)
}
return nil, err
}
// file data for additional read/write packets
func (r *Request) update(p hasHandle) error {
pd := packet_data{id: p.id()}
func (r *Request) updateMethod(p hasHandle) error {
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
}

View file

@ -18,28 +18,25 @@ type testHandler struct {
err error // dummy error, should be file related
}
func (t *testHandler) Fileread(r Request) (io.ReaderAt, error) {
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) {
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) Filecmd(r *Request) error {
return t.err
}
func (t *testHandler) Fileinfo(r Request) ([]os.FileInfo, error) {
func (t *testHandler) Filelist(r *Request) (ListerAt, error) {
if t.err != nil {
return nil, t.err
}
@ -51,7 +48,7 @@ func (t *testHandler) Fileinfo(r Request) ([]os.FileInfo, error) {
if err != nil {
return nil, err
}
return []os.FileInfo{fi}, nil
return listerat([]os.FileInfo{fi}), nil
}
// make sure len(fakefile) == len(filecontents)
@ -59,21 +56,15 @@ type fakefile [10]byte
var filecontents = []byte("file-data.")
func testRequest(method string) Request {
request := Request{
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
}
@ -98,7 +89,7 @@ func newTestHandlers() Handlers {
FileGet: handler,
FilePut: handler,
FileCmd: handler,
FileInfo: handler,
FileList: handler,
}
}
@ -120,15 +111,30 @@ func statusOk(t *testing.T, p interface{}) {
}
}
// fake/test packet
type fakePacket struct {
myid uint32
handle string
}
func (f fakePacket) id() uint32 {
return f.myid
}
func (f fakePacket) getHandle() string {
return f.handle
}
func (fakePacket) UnmarshalBinary(d []byte) error { return nil }
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))
pkt := &sshFxpReadPacket{uint32(i), "a", uint64(i * 5), 5}
rpkt := request.call(handlers, pkt)
dpkt := rpkt.(*sshFxpDataPacket)
assert.Equal(t, dpkt.id(), uint32(i))
assert.Equal(t, string(dpkt.Data), txt)
}
}
@ -136,26 +142,25 @@ func TestRequestGet(t *testing.T) {
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)
pkt := &sshFxpWritePacket{0, "a", 0, 5, []byte("file-")}
rpkt := request.call(handlers, pkt)
statusOk(t, rpkt)
pkt = &sshFxpWritePacket{1, "a", 5, 5, []byte("data.")}
rpkt = request.call(handlers, pkt)
statusOk(t, rpkt)
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)
pkt := fakePacket{myid: 1}
rpkt := request.call(handlers, pkt)
statusOk(t, rpkt)
handlers.returnError()
pkt, err = request.handle(handlers)
assert.Nil(t, pkt)
assert.Equal(t, err, errTest)
rpkt = request.call(handlers, pkt)
assert.Equal(t, rpkt, statusFromError(rpkt, errTest))
}
func TestRequestInfoList(t *testing.T) { testInfoMethod(t, "List") }
@ -163,9 +168,9 @@ 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)
pkt := fakePacket{myid: 1}
rpkt := request.call(handlers, pkt)
spkt, ok := rpkt.(*sshFxpStatResponse)
assert.True(t, ok)
assert.Equal(t, spkt.info.Name(), "request_test.go")
}
@ -173,9 +178,9 @@ func TestRequestInfoStat(t *testing.T) {
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)
pkt := fakePacket{myid: 1}
rpkt := request.call(handlers, pkt)
npkt, ok := rpkt.(*sshFxpNamePacket)
assert.True(t, ok)
assert.IsType(t, sshFxpNameAttr{}, npkt.NameAttrs[0])
assert.Equal(t, npkt.NameAttrs[0].Name, "request_test.go")

98
vendor/github.com/pkg/sftp/server.go generated vendored
View file

@ -18,7 +18,7 @@ import (
)
const (
sftpServerWorkerCount = 8
SftpServerWorkerCount = 8
)
// Server is an SSH File Transfer Protocol (sftp) server.
@ -29,7 +29,7 @@ type Server struct {
*serverConn
debugStream io.Writer
readOnly bool
pktMgr packetManager
pktMgr *packetManager
openFiles map[string]*os.File
openFilesLock sync.RWMutex
handleCount int
@ -230,8 +230,7 @@ func handlePacket(s *Server, p interface{}) error {
if err != nil {
return s.sendError(p, err)
}
f = filepath.Clean(f)
f = filepath.ToSlash(f) // make path more Unix like on windows servers
f = cleanPath(f)
return s.sendPacket(sshFxpNamePacket{
ID: p.ID,
NameAttrs: []sshFxpNameAttr{{
@ -555,6 +554,8 @@ func statusFromError(p ider, err error) sshFxpStatusPacket {
ret.StatusError.msg = err.Error()
if err == io.EOF {
ret.StatusError.Code = ssh_FX_EOF
} else if err == os.ErrNotExist {
ret.StatusError.Code = ssh_FX_NO_SUCH_FILE
} else if errno, ok := err.(syscall.Errno); ok {
ret.StatusError.Code = translateErrno(errno)
} else if pathError, ok := err.(*os.PathError); ok {
@ -573,3 +574,92 @@ func clamp(v, max uint32) uint32 {
}
return v
}
func runLsTypeWord(dirent os.FileInfo) string {
// find first character, the type char
// b Block special file.
// c Character special file.
// d Directory.
// l Symbolic link.
// s Socket link.
// p FIFO.
// - Regular file.
tc := '-'
mode := dirent.Mode()
if (mode & os.ModeDir) != 0 {
tc = 'd'
} else if (mode & os.ModeDevice) != 0 {
tc = 'b'
if (mode & os.ModeCharDevice) != 0 {
tc = 'c'
}
} else if (mode & os.ModeSymlink) != 0 {
tc = 'l'
} else if (mode & os.ModeSocket) != 0 {
tc = 's'
} else if (mode & os.ModeNamedPipe) != 0 {
tc = 'p'
}
// owner
orc := '-'
if (mode & 0400) != 0 {
orc = 'r'
}
owc := '-'
if (mode & 0200) != 0 {
owc = 'w'
}
oxc := '-'
ox := (mode & 0100) != 0
setuid := (mode & os.ModeSetuid) != 0
if ox && setuid {
oxc = 's'
} else if setuid {
oxc = 'S'
} else if ox {
oxc = 'x'
}
// group
grc := '-'
if (mode & 040) != 0 {
grc = 'r'
}
gwc := '-'
if (mode & 020) != 0 {
gwc = 'w'
}
gxc := '-'
gx := (mode & 010) != 0
setgid := (mode & os.ModeSetgid) != 0
if gx && setgid {
gxc = 's'
} else if setgid {
gxc = 'S'
} else if gx {
gxc = 'x'
}
// all / others
arc := '-'
if (mode & 04) != 0 {
arc = 'r'
}
awc := '-'
if (mode & 02) != 0 {
awc = 'w'
}
axc := '-'
ax := (mode & 01) != 0
sticky := (mode & os.ModeSticky) != 0
if ax && sticky {
axc = 't'
} else if sticky {
axc = 'T'
} else if ax {
axc = 'x'
}
return fmt.Sprintf("%c%c%c%c%c%c%c%c%c%c", tc, orc, owc, oxc, grc, gwc, gxc, arc, awc, axc)
}

View file

@ -6,7 +6,12 @@ package sftp
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
crand "crypto/rand"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"flag"
"fmt"
"io/ioutil"
@ -316,7 +321,7 @@ func (chsvr *sshSessionChannelServer) handleSubsystem(req *ssh.Request) error {
// 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")
t.Skip("skipping integration test")
}
listener, err := net.Listen("tcp", "127.0.0.1:0")
@ -357,17 +362,59 @@ func testServer(t *testing.T, useSubsystem bool, readonly bool) (net.Listener, s
return listener, host, port
}
func makeDummyKey() (string, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), crand.Reader)
if err != nil {
return "", fmt.Errorf("cannot generate key: %v", err)
}
der, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return "", fmt.Errorf("cannot marshal key: %v", err)
}
block := &pem.Block{Type: "EC PRIVATE KEY", Bytes: der}
f, err := ioutil.TempFile("", "sftp-test-key-")
if err != nil {
return "", fmt.Errorf("cannot create temp file: %v", err)
}
defer func() {
if f != nil {
_ = f.Close()
_ = os.Remove(f.Name())
}
}()
if err := pem.Encode(f, block); err != nil {
return "", fmt.Errorf("cannot write key: %v", err)
}
if err := f.Close(); err != nil {
return "", fmt.Errorf("error closing key file: %v", err)
}
path := f.Name()
f = nil
return path, nil
}
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")
}
// make a dummy key so we don't rely on ssh-agent
dummyKey, err := makeDummyKey()
if err != nil {
return "", err
}
defer os.Remove(dummyKey)
args := []string{
// "-vvvv",
"-b", "-",
"-o", "StrictHostKeyChecking=no",
"-o", "LogLevel=ERROR",
"-o", "UserKnownHostsFile /dev/null",
// do not trigger ssh-agent prompting
"-o", "IdentityFile=" + dummyKey,
"-o", "IdentitiesOnly=yes",
"-P", fmt.Sprintf("%d", port), fmt.Sprintf("%s:%s", host, path),
}
cmd := exec.Command(*testSftpClientBin, args...)
@ -378,7 +425,7 @@ func runSftpClient(t *testing.T, script string, path string, host string, port i
if err := cmd.Start(); err != nil {
return "", err
}
err := cmd.Wait()
err = cmd.Wait()
return string(stdout.Bytes()), err
}

View file

@ -4,9 +4,29 @@ package sftp
import (
"os"
"path"
"time"
"fmt"
)
func runLs(dirname string, dirent os.FileInfo) string {
return path.Join(dirname, dirent.Name())
typeword := runLsTypeWord(dirent)
numLinks := 1
if dirent.IsDir() {
numLinks = 0
}
username := "root"
groupname := "root"
mtime := dirent.ModTime()
monthStr := mtime.Month().String()[0:3]
day := mtime.Day()
year := mtime.Year()
now := time.Now()
isOld := mtime.Before(now.Add(-time.Hour * 24 * 365 / 2))
yearOrTime := fmt.Sprintf("%02d:%02d", mtime.Hour(), mtime.Minute())
if isOld {
yearOrTime = fmt.Sprintf("%d", year)
}
return fmt.Sprintf("%s %4d %-8s %-8s %8d %s %2d %5s %s", typeword, numLinks, username, groupname, dirent.Size(), monthStr, day, yearOrTime, dirent.Name())
}

View file

@ -1,11 +1,159 @@
package sftp
import (
"testing"
"os"
"regexp"
"time"
"io"
"sync"
"testing"
)
const (
typeDirectory = "d"
typeFile = "[^d]"
)
func TestRunLsWithExamplesDirectory(t *testing.T) {
path := "examples"
item, _ := os.Stat(path)
result := runLs(path, item)
runLsTestHelper(t, result, typeDirectory, path)
}
func TestRunLsWithLicensesFile(t *testing.T) {
path := "LICENSE"
item, _ := os.Stat(path)
result := runLs(path, item)
runLsTestHelper(t, result, typeFile, path)
}
/*
The format of the `longname' field is unspecified by this protocol.
It MUST be suitable for use in the output of a directory listing
command (in fact, the recommended operation for a directory listing
command is to simply display this data). However, clients SHOULD NOT
attempt to parse the longname field for file attributes; they SHOULD
use the attrs field instead.
The recommended format for the longname field is as follows:
-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer
1234567890 123 12345678 12345678 12345678 123456789012
Here, the first line is sample output, and the second field indicates
widths of the various fields. Fields are separated by spaces. The
first field lists file permissions for user, group, and others; the
second field is link count; the third field is the name of the user
who owns the file; the fourth field is the name of the group that
owns the file; the fifth field is the size of the file in bytes; the
sixth field (which actually may contain spaces, but is fixed to 12
characters) is the file modification time, and the seventh field is
the file name. Each field is specified to be a minimum of certain
number of character positions (indicated by the second line above),
but may also be longer if the data does not fit in the specified
length.
The SSH_FXP_ATTRS response has the following format:
uint32 id
ATTRS attrs
where `id' is the request identifier, and `attrs' is the returned
file attributes as described in Section ``File Attributes''.
*/
func runLsTestHelper(t *testing.T, result, expectedType, path string) {
// using regular expressions to make tests work on all systems
// a virtual file system (like afero) would be needed to mock valid filesystem checks
// expected layout is:
// drwxr-xr-x 8 501 20 272 Aug 9 19:46 examples
// permissions (len 10, "drwxr-xr-x")
got := result[0:10]
if ok, err := regexp.MatchString("^"+expectedType+"[rwx-]{9}$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): permission field mismatch, expected dir, got: %#v, err: %#v", path, got, err)
}
// space
got = result[10:11]
if ok, err := regexp.MatchString("^\\s$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): spacer 1 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
}
// link count (len 3, number)
got = result[12:15]
if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): link count field mismatch, got: %#v, err: %#v", path, got, err)
}
// spacer
got = result[15:16]
if ok, err := regexp.MatchString("^\\s$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): spacer 2 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
}
// username / uid (len 8, number or string)
got = result[16:24]
if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): username / uid mismatch, expected user, got: %#v, err: %#v", path, got, err)
}
// spacer
got = result[24:25]
if ok, err := regexp.MatchString("^\\s$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): spacer 3 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
}
// groupname / gid (len 8, number or string)
got = result[25:33]
if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): groupname / gid mismatch, expected group, got: %#v, err: %#v", path, got, err)
}
// spacer
got = result[33:34]
if ok, err := regexp.MatchString("^\\s$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): spacer 4 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
}
// filesize (len 8)
got = result[34:42]
if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): filesize field mismatch, expected size in bytes, got: %#v, err: %#v", path, got, err)
}
// spacer
got = result[42:43]
if ok, err := regexp.MatchString("^\\s$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): spacer 5 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
}
// mod time (len 12, e.g. Aug 9 19:46)
got = result[43:55]
layout := "Jan 2 15:04"
_, err := time.Parse(layout, got)
if err != nil {
layout = "Jan 2 2006"
_, err = time.Parse(layout, got)
}
if err != nil {
t.Errorf("runLs(%#v, *FileInfo): mod time field mismatch, expected date layout %s, got: %#v, err: %#v", path, layout, got, err)
}
// spacer
got = result[55:56]
if ok, err := regexp.MatchString("^\\s$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): spacer 6 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
}
// filename
got = result[56:]
if ok, err := regexp.MatchString("^"+path+"$", got); !ok {
t.Errorf("runLs(%#v, *FileInfo): name field mismatch, expected examples, got: %#v, err: %#v", path, got, err)
}
}
func clientServerPair(t *testing.T) (*Client, *Server) {
cr, sw := io.Pipe()
sr, cw := io.Pipe()

View file

@ -11,96 +11,7 @@ import (
"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 {
func runLsStatt(dirent os.FileInfo, statt *syscall.Stat_t) string {
// example from openssh sftp server:
// crw-rw-rw- 1 root wheel 0 Jul 31 20:52 ttyvd
// format:
@ -136,7 +47,7 @@ func runLs(dirname string, dirent os.FileInfo) string {
if dsys == nil {
} else if statt, ok := dsys.(*syscall.Stat_t); !ok {
} else {
return runLsStatt(dirname, dirent, statt)
return runLsStatt(dirent, statt)
}
return path.Join(dirname, dirent.Name())