add serverside StatVFS function, implemented for darwin and linux (#89)

This commit is contained in:
Mark Sheahan 2016-06-12 21:45:13 -07:00 committed by Dave Cheney
parent 8ceba579e7
commit 9ff4de5c31
8 changed files with 214 additions and 1 deletions

View File

@ -1,7 +1,9 @@
package sftp package sftp
import ( import (
"bytes"
"encoding" "encoding"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -11,7 +13,8 @@ import (
) )
var ( var (
errShortPacket = errors.New("packet too short") errShortPacket = errors.New("packet too short")
errUnknownExtendedPacket = errors.New("unknown extended packet")
) )
const ( const (
@ -832,3 +835,69 @@ func (p *StatVFS) TotalSpace() uint64 {
func (p *StatVFS) FreeSpace() uint64 { func (p *StatVFS) FreeSpace() uint64 {
return p.Frsize * p.Bfree return p.Frsize * p.Bfree
} }
// Convert to ssh_FXP_EXTENDED_REPLY packet binary format
func (p *StatVFS) MarshalBinary() ([]byte, error) {
buf := &bytes.Buffer{}
buf.Write([]byte{ssh_FXP_EXTENDED_REPLY})
if err := binary.Write(buf, binary.BigEndian, p); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
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
}

View File

@ -171,6 +171,8 @@ func (svr *Server) sftpServerWorker() error {
case ssh_FXP_SYMLINK: case ssh_FXP_SYMLINK:
pkt = &sshFxpSymlinkPacket{} pkt = &sshFxpSymlinkPacket{}
readonly = false readonly = false
case ssh_FXP_EXTENDED:
pkt = &sshFxpExtendedPacket{}
default: default:
return errors.Errorf("unhandled packet type: %s", p.pktType) return errors.Errorf("unhandled packet type: %s", p.pktType)
} }
@ -182,6 +184,8 @@ func (svr *Server) sftpServerWorker() error {
switch pkt := pkt.(type) { switch pkt := pkt.(type) {
case *sshFxpOpenPacket: case *sshFxpOpenPacket:
readonly = pkt.readonly() readonly = pkt.readonly()
case *sshFxpExtendedPacket:
readonly = pkt.SpecificPacket.readonly()
} }
// If server is operating read-only and a write operation is requested, // If server is operating read-only and a write operation is requested,

View File

@ -17,10 +17,12 @@ func main() {
var ( var (
readOnly bool readOnly bool
debugStderr bool debugStderr bool
debugLevel string
) )
flag.BoolVar(&readOnly, "R", false, "read-only server") flag.BoolVar(&readOnly, "R", false, "read-only server")
flag.BoolVar(&debugStderr, "e", false, "debug to stderr") flag.BoolVar(&debugStderr, "e", false, "debug to stderr")
flag.StringVar(&debugLevel, "l", "none", "debug level (ignored)")
flag.Parse() flag.Parse()
debugStream := ioutil.Discard debugStream := ioutil.Discard

21
server_statvfs_darwin.go Normal file
View 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
server_statvfs_impl.go Normal file
View File

@ -0,0 +1,25 @@
// +build darwin linux
// 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.ID, err))
}
retPkt, err := statvfsFromStatfst(stat)
if err != nil {
return svr.sendPacket(statusFromError(p.ID, err))
}
retPkt.ID = p.ID
return svr.sendPacket(retPkt)
}

21
server_statvfs_linux.go Normal file
View 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.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
Fsid: uint64(uint64(stat.Fsid.X__val[1])<<32 | uint64(stat.Fsid.X__val[0])), // endianness?
Flag: uint64(stat.Flags), // assuming POSIX?
Namemax: uint64(stat.Namelen),
}, nil
}

11
server_statvfs_stubs.go Normal file
View File

@ -0,0 +1,11 @@
// +build !darwin,!linux
package sftp
import (
"syscall"
)
func (p sshFxpExtendedPacketStatVFS) respond(svr *Server) error {
return syscall.ENOTSUP
}

60
server_test.go Normal file
View File

@ -0,0 +1,60 @@
package sftp
import (
"errors"
"testing"
)
var errClientRecvFinished = errors.New("client recv finished")
func clientServerPair(t *testing.T) (*Client, *Server) {
c, s := netPipe(t)
server, err := NewServer(s)
if err != nil {
t.Fatal(err)
}
go server.Serve()
client, err := NewClientPipe(c, c)
if err != nil {
t.Fatal(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, _ := clientServerPair(t)
defer client.Close()
badPacket := sshFxpTestBadExtendedPacket{client.nextID(), "thisDoesn'tExist", "foobar"}
_, _, err := client.sendRequest(badPacket)
if err != nil {
t.Log(err)
} else {
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)
}