Skip to content

Commit

Permalink
Merge pull request #46 from ScriptRock/sftpserver
Browse files Browse the repository at this point in the history
sftp server implementation
  • Loading branch information
marksheahan committed Sep 9, 2015
2 parents d2d0a43 + e4daa2d commit aee0f78
Show file tree
Hide file tree
Showing 13 changed files with 2,437 additions and 14 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.*.swo
.*.swp

server_standalone/server_standalone

91 changes: 91 additions & 0 deletions attrs.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,56 @@ func unmarshalAttrs(b []byte) (*FileStat, []byte) {
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

uid := uint32(0)
gid := uint32(0)
mtime := uint32(fi.ModTime().Unix())
atime := mtime

var flags uint32 = ssh_FILEXFER_ATTR_SIZE |
ssh_FILEXFER_ATTR_PERMISSIONS |
ssh_FILEXFER_ATTR_ACMODTIME

if statt, ok := fi.Sys().(*syscall.Stat_t); ok {
flags |= ssh_FILEXFER_ATTR_UIDGID
uid = statt.Uid
gid = statt.Gid
}

b = marshalUint32(b, flags) // flags
if flags&ssh_FILEXFER_ATTR_SIZE != 0 {
b = marshalUint64(b, uint64(fi.Size())) // size
}
if flags&ssh_FILEXFER_ATTR_UIDGID != 0 {
b = marshalUint32(b, uid)
b = marshalUint32(b, gid)
}
if flags&ssh_FILEXFER_ATTR_PERMISSIONS != 0 {
b = marshalUint32(b, fromFileMode(fi.Mode())) // permissions
}
if flags&ssh_FILEXFER_ATTR_ACMODTIME != 0 {
b = marshalUint32(b, atime)
b = marshalUint32(b, 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)
Expand Down Expand Up @@ -136,3 +186,44 @@ func toFileMode(mode uint32) os.FileMode {
}
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
}
54 changes: 54 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,34 @@ func (c *Client) opendir(path string) (string, error) {
}
}

// Stat returns a FileInfo structure describing the file specified by path 'p'.
// If 'p' is a symbolic link, the returned FileInfo structure describes the referent file.
func (c *Client) Stat(p string) (os.FileInfo, error) {
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpStatPacket{
Id: id,
Path: p,
})
if err != nil {
return nil, err
}
switch typ {
case ssh_FXP_ATTRS:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIdErr{id, sid}
}
attr, _ := unmarshalAttrs(data)
return fileInfoFromStat(attr, path.Base(p)), nil
case ssh_FXP_STATUS:
return nil, unmarshalStatus(id, data)
default:
return nil, unimplementedPacketErr(typ)
}
}

// Lstat returns a FileInfo structure describing the file specified by path 'p'.
// If 'p' is a symbolic link, the returned FileInfo structure describes the symbolic link.
func (c *Client) Lstat(p string) (os.FileInfo, error) {
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpLstatPacket{
Expand Down Expand Up @@ -312,6 +340,25 @@ func (c *Client) ReadLink(p string) (string, error) {
}
}

// Symlink creates a symbolic link at 'newname', pointing at target 'oldname'
func (c *Client) Symlink(oldname, newname string) error {
id := c.nextId()
typ, data, err := c.sendRequest(sshFxpSymlinkPacket{
Id: id,
Linkpath: newname,
Targetpath: oldname,
})
if err != nil {
return err
}
switch typ {
case ssh_FXP_STATUS:
return okOrErr(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
}
}

// setstat is a convience wrapper to allow for changing of various parts of the file descriptor.
func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {
id := c.nextId()
Expand Down Expand Up @@ -1071,6 +1118,13 @@ func unmarshalStatus(id uint32, data []byte) error {
}
}

func marshalStatus(b []byte, err StatusError) []byte {
b = marshalUint32(b, err.Code)
b = marshalString(b, err.msg)
b = marshalString(b, err.lang)
return b
}

// flags converts the flags passed to OpenFile into ssh flags.
// Unsupported flags are ignored.
func flags(f int) uint32 {
Expand Down
41 changes: 41 additions & 0 deletions client_integration_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package sftp

import (
"syscall"
"testing"
)

func TestClientStatVFS(t *testing.T) {
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")
}

if vfs.Bavail != s.Bavail {
t.Fatal("f_bavail does not match")
}
}
Loading

0 comments on commit aee0f78

Please sign in to comment.