2016-07-19 03:58:57 +08:00
|
|
|
package sftp
|
|
|
|
|
2016-07-21 07:47:28 +08:00
|
|
|
// 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.
|
|
|
|
|
2016-07-19 03:58:57 +08:00
|
|
|
import (
|
2020-09-17 17:53:13 +08:00
|
|
|
"errors"
|
2016-07-19 03:58:57 +08:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
2020-09-03 01:21:12 +08:00
|
|
|
"path"
|
2017-07-11 07:43:58 +08:00
|
|
|
"sort"
|
2019-10-09 23:40:31 +08:00
|
|
|
"strings"
|
2016-07-30 06:57:06 +08:00
|
|
|
"sync"
|
2019-01-29 10:20:55 +08:00
|
|
|
"syscall"
|
2016-07-19 03:58:57 +08:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2018-05-12 05:26:11 +08:00
|
|
|
// InMemHandler returns a Hanlders object with the test handlers.
|
2016-07-19 03:58:57 +08:00
|
|
|
func InMemHandler() Handlers {
|
2016-07-19 06:53:13 +08:00
|
|
|
root := &root{
|
|
|
|
files: make(map[string]*memFile),
|
2016-07-19 03:58:57 +08:00
|
|
|
}
|
2016-07-19 06:53:13 +08:00
|
|
|
root.memFile = newMemFile("/", true)
|
|
|
|
return Handlers{root, root, root, root}
|
2016-07-19 03:58:57 +08:00
|
|
|
}
|
|
|
|
|
2018-05-12 05:26:11 +08:00
|
|
|
// Example Handlers
|
2017-08-13 15:22:00 +08:00
|
|
|
func (fs *root) Fileread(r *Request) (io.ReaderAt, error) {
|
2018-01-17 06:18:45 +08:00
|
|
|
if fs.mockErr != nil {
|
|
|
|
return nil, fs.mockErr
|
2018-01-08 10:29:18 +08:00
|
|
|
}
|
2019-02-05 05:22:46 +08:00
|
|
|
_ = r.WithContext(r.Context()) // initialize context for deadlock testing
|
2016-08-02 07:13:11 +08:00
|
|
|
fs.filesLock.Lock()
|
|
|
|
defer fs.filesLock.Unlock()
|
2016-07-19 06:53:13 +08:00
|
|
|
file, err := fs.fetch(r.Filepath)
|
2016-07-19 03:58:57 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if file.symlink != "" {
|
2016-07-19 06:53:13 +08:00
|
|
|
file, err = fs.fetch(file.symlink)
|
2016-07-19 03:58:57 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2016-07-30 06:57:06 +08:00
|
|
|
return file.ReaderAt()
|
2016-07-19 03:58:57 +08:00
|
|
|
}
|
|
|
|
|
2020-09-03 01:21:12 +08:00
|
|
|
func (fs *root) getFileForWrite(r *Request) (*memFile, error) {
|
2018-01-17 06:18:45 +08:00
|
|
|
if fs.mockErr != nil {
|
|
|
|
return nil, fs.mockErr
|
2018-01-08 10:29:18 +08:00
|
|
|
}
|
2019-02-05 05:22:46 +08:00
|
|
|
_ = r.WithContext(r.Context()) // initialize context for deadlock testing
|
2016-08-02 07:13:11 +08:00
|
|
|
fs.filesLock.Lock()
|
|
|
|
defer fs.filesLock.Unlock()
|
2016-07-19 06:53:13 +08:00
|
|
|
file, err := fs.fetch(r.Filepath)
|
2016-07-19 03:58:57 +08:00
|
|
|
if err == os.ErrNotExist {
|
2020-09-03 01:21:12 +08:00
|
|
|
dir, err := fs.fetch(path.Dir(r.Filepath))
|
2016-07-19 03:58:57 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-07-19 06:53:13 +08:00
|
|
|
if !dir.isdir {
|
|
|
|
return nil, os.ErrInvalid
|
|
|
|
}
|
|
|
|
file = newMemFile(r.Filepath, false)
|
|
|
|
fs.files[r.Filepath] = file
|
2016-07-19 03:58:57 +08:00
|
|
|
}
|
2020-09-03 01:21:12 +08:00
|
|
|
return file, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
|
|
|
|
file, err := fs.getFileForWrite(r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-07-30 06:57:06 +08:00
|
|
|
return file.WriterAt()
|
2016-07-19 03:58:57 +08:00
|
|
|
}
|
|
|
|
|
2020-08-30 16:40:22 +08:00
|
|
|
func (fs *root) OpenFile(r *Request) (WriterAtReaderAt, error) {
|
2020-09-03 01:21:12 +08:00
|
|
|
return fs.getFileForWrite(r)
|
2020-08-30 16:40:22 +08:00
|
|
|
}
|
|
|
|
|
2017-08-13 15:22:00 +08:00
|
|
|
func (fs *root) Filecmd(r *Request) error {
|
2018-01-17 06:18:45 +08:00
|
|
|
if fs.mockErr != nil {
|
|
|
|
return fs.mockErr
|
2018-01-08 10:29:18 +08:00
|
|
|
}
|
2019-02-05 05:22:46 +08:00
|
|
|
_ = r.WithContext(r.Context()) // initialize context for deadlock testing
|
2016-08-02 07:13:11 +08:00
|
|
|
fs.filesLock.Lock()
|
|
|
|
defer fs.filesLock.Unlock()
|
2016-07-19 03:58:57 +08:00
|
|
|
switch r.Method {
|
2017-04-25 12:36:09 +08:00
|
|
|
case "Setstat":
|
2020-08-22 15:51:12 +08:00
|
|
|
file, err := fs.fetch(r.Filepath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if r.AttrFlags().Size {
|
|
|
|
return file.Truncate(int64(r.Attributes().Size))
|
|
|
|
}
|
2016-07-19 03:58:57 +08:00
|
|
|
return nil
|
|
|
|
case "Rename":
|
2016-07-19 06:53:13 +08:00
|
|
|
file, err := fs.fetch(r.Filepath)
|
2016-07-19 03:58:57 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-23 07:16:41 +08:00
|
|
|
if _, ok := fs.files[r.Target]; ok {
|
2016-07-26 04:01:16 +08:00
|
|
|
return &os.LinkError{Op: "rename", Old: r.Filepath, New: r.Target,
|
|
|
|
Err: fmt.Errorf("dest file exists")}
|
2016-07-23 07:16:41 +08:00
|
|
|
}
|
2018-08-18 02:59:53 +08:00
|
|
|
file.name = r.Target
|
2016-07-19 06:53:13 +08:00
|
|
|
fs.files[r.Target] = file
|
|
|
|
delete(fs.files, r.Filepath)
|
2019-10-09 23:40:31 +08:00
|
|
|
|
|
|
|
if file.IsDir() {
|
|
|
|
for path, file := range fs.files {
|
|
|
|
if strings.HasPrefix(path, r.Filepath+"/") {
|
|
|
|
file.name = r.Target + path[len(r.Filepath):]
|
|
|
|
fs.files[r.Target+path[len(r.Filepath):]] = file
|
|
|
|
delete(fs.files, path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-07-19 03:58:57 +08:00
|
|
|
case "Rmdir", "Remove":
|
2020-09-03 01:26:46 +08:00
|
|
|
file, err := fs.fetch(path.Dir(r.Filepath))
|
2016-07-19 03:58:57 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-10-09 23:40:31 +08:00
|
|
|
|
|
|
|
if file.IsDir() {
|
|
|
|
for path := range fs.files {
|
|
|
|
if strings.HasPrefix(path, r.Filepath+"/") {
|
|
|
|
return &os.PathError{
|
|
|
|
Op: "remove",
|
|
|
|
Path: r.Filepath + "/",
|
|
|
|
Err: fmt.Errorf("directory is not empty"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-19 06:53:13 +08:00
|
|
|
delete(fs.files, r.Filepath)
|
2019-10-09 23:40:31 +08:00
|
|
|
|
2016-07-19 03:58:57 +08:00
|
|
|
case "Mkdir":
|
2020-09-03 01:26:46 +08:00
|
|
|
_, err := fs.fetch(path.Dir(r.Filepath))
|
2016-07-19 03:58:57 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-19 06:53:13 +08:00
|
|
|
fs.files[r.Filepath] = newMemFile(r.Filepath, true)
|
2019-05-25 03:23:18 +08:00
|
|
|
case "Link":
|
|
|
|
file, err := fs.fetch(r.Filepath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if file.IsDir() {
|
|
|
|
return fmt.Errorf("hard link not allowed for directory")
|
|
|
|
}
|
|
|
|
fs.files[r.Target] = file
|
2016-07-19 03:58:57 +08:00
|
|
|
case "Symlink":
|
2016-07-19 06:53:13 +08:00
|
|
|
_, err := fs.fetch(r.Filepath)
|
2016-07-19 03:58:57 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2016-07-19 06:53:13 +08:00
|
|
|
link := newMemFile(r.Target, false)
|
|
|
|
link.symlink = r.Filepath
|
|
|
|
fs.files[r.Target] = link
|
2016-07-19 03:58:57 +08:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-07-28 09:22:11 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-08-13 15:22:00 +08:00
|
|
|
func (fs *root) Filelist(r *Request) (ListerAt, error) {
|
2018-01-17 06:18:45 +08:00
|
|
|
if fs.mockErr != nil {
|
|
|
|
return nil, fs.mockErr
|
2018-01-08 10:29:18 +08:00
|
|
|
}
|
2019-02-05 05:22:46 +08:00
|
|
|
_ = r.WithContext(r.Context()) // initialize context for deadlock testing
|
2016-08-02 07:13:11 +08:00
|
|
|
fs.filesLock.Lock()
|
|
|
|
defer fs.filesLock.Unlock()
|
2017-07-28 09:22:11 +08:00
|
|
|
|
2019-01-29 10:20:55 +08:00
|
|
|
file, err := fs.fetch(r.Filepath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-07-19 03:58:57 +08:00
|
|
|
switch r.Method {
|
|
|
|
case "List":
|
2019-01-29 10:20:55 +08:00
|
|
|
if !file.IsDir() {
|
|
|
|
return nil, syscall.ENOTDIR
|
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
orderedNames := []string{}
|
|
|
|
for fn := range fs.files {
|
2020-09-03 01:26:46 +08:00
|
|
|
if path.Dir(fn) == r.Filepath {
|
2019-08-30 23:04:37 +08:00
|
|
|
orderedNames = append(orderedNames, fn)
|
2016-07-19 03:58:57 +08:00
|
|
|
}
|
|
|
|
}
|
2019-08-30 23:04:37 +08:00
|
|
|
sort.Strings(orderedNames)
|
|
|
|
list := make([]os.FileInfo, len(orderedNames))
|
|
|
|
for i, fn := range orderedNames {
|
2017-07-11 08:03:35 +08:00
|
|
|
list[i] = fs.files[fn]
|
|
|
|
}
|
2017-07-28 09:22:11 +08:00
|
|
|
return listerat(list), nil
|
2016-07-19 03:58:57 +08:00
|
|
|
case "Stat":
|
2020-09-06 14:26:58 +08:00
|
|
|
if file.symlink != "" {
|
|
|
|
file, err = fs.fetch(file.symlink)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2017-07-28 09:22:11 +08:00
|
|
|
return listerat([]os.FileInfo{file}), nil
|
2016-07-19 03:58:57 +08:00
|
|
|
case "Readlink":
|
2016-07-19 06:53:13 +08:00
|
|
|
if file.symlink != "" {
|
|
|
|
file, err = fs.fetch(file.symlink)
|
2016-07-19 03:58:57 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2017-07-28 09:22:11 +08:00
|
|
|
return listerat([]os.FileInfo{file}), nil
|
2016-07-19 03:58:57 +08:00
|
|
|
}
|
|
|
|
return nil, nil
|
|
|
|
}
|
2016-07-19 06:53:13 +08:00
|
|
|
|
2020-09-06 14:26:58 +08:00
|
|
|
// implements LstatFileLister interface
|
|
|
|
func (fs *root) Lstat(r *Request) (ListerAt, error) {
|
|
|
|
if fs.mockErr != nil {
|
|
|
|
return nil, fs.mockErr
|
|
|
|
}
|
|
|
|
_ = r.WithContext(r.Context()) // initialize context for deadlock testing
|
|
|
|
fs.filesLock.Lock()
|
|
|
|
defer fs.filesLock.Unlock()
|
|
|
|
|
|
|
|
file, err := fs.fetch(r.Filepath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return listerat([]os.FileInfo{file}), nil
|
|
|
|
}
|
|
|
|
|
2016-07-21 07:47:28 +08:00
|
|
|
// In memory file-system-y thing that the Hanlders live on
|
|
|
|
type root struct {
|
|
|
|
*memFile
|
2016-08-02 07:13:11 +08:00
|
|
|
files map[string]*memFile
|
|
|
|
filesLock sync.Mutex
|
2018-01-17 06:18:45 +08:00
|
|
|
mockErr error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set a mocked error that the next handler call will return.
|
|
|
|
// Set to nil to reset for no error.
|
|
|
|
func (fs *root) returnErr(err error) {
|
|
|
|
fs.mockErr = err
|
2016-07-21 07:47:28 +08:00
|
|
|
}
|
|
|
|
|
2016-07-26 02:42:18 +08:00
|
|
|
func (fs *root) fetch(path string) (*memFile, error) {
|
2016-07-21 07:47:28 +08:00
|
|
|
if path == "/" {
|
2016-07-26 02:42:18 +08:00
|
|
|
return fs.memFile, nil
|
2016-07-21 07:47:28 +08:00
|
|
|
}
|
2016-07-26 02:42:18 +08:00
|
|
|
if file, ok := fs.files[path]; ok {
|
2016-07-21 07:47:28 +08:00
|
|
|
return file, nil
|
|
|
|
}
|
|
|
|
return nil, os.ErrNotExist
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements os.FileInfo, Reader and Writer interfaces.
|
|
|
|
// These are the 3 interfaces necessary for the Handlers.
|
2019-09-12 14:17:32 +08:00
|
|
|
// Implements the optional interface TransferError.
|
2016-07-19 06:53:13 +08:00
|
|
|
type memFile struct {
|
2019-09-12 14:17:32 +08:00
|
|
|
name string
|
|
|
|
modtime time.Time
|
|
|
|
symlink string
|
|
|
|
isdir bool
|
|
|
|
transferError error
|
2020-09-17 17:53:13 +08:00
|
|
|
|
|
|
|
mu sync.RWMutex
|
|
|
|
content []byte
|
2016-07-19 06:53:13 +08:00
|
|
|
}
|
|
|
|
|
2016-07-21 07:47:28 +08:00
|
|
|
// factory to make sure modtime is set
|
2016-07-19 06:53:13 +08:00
|
|
|
func newMemFile(name string, isdir bool) *memFile {
|
|
|
|
return &memFile{
|
|
|
|
name: name,
|
|
|
|
modtime: time.Now(),
|
|
|
|
isdir: isdir,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 17:53:13 +08:00
|
|
|
// These are helper functions, they must be called while holding the memFile.mu mutex
|
|
|
|
func (f *memFile) size() int64 { return int64(len(f.content)) }
|
|
|
|
func (f *memFile) grow(n int64) { f.content = append(f.content, make([]byte, n)...) }
|
|
|
|
|
2016-07-19 06:53:13 +08:00
|
|
|
// Have memFile fulfill os.FileInfo interface
|
2020-09-03 01:26:46 +08:00
|
|
|
func (f *memFile) Name() string { return path.Base(f.name) }
|
2020-09-17 17:53:13 +08:00
|
|
|
func (f *memFile) Size() int64 {
|
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
|
|
|
return f.size()
|
|
|
|
}
|
2016-07-19 06:53:13 +08:00
|
|
|
func (f *memFile) Mode() os.FileMode {
|
|
|
|
if f.isdir {
|
2020-09-17 17:53:13 +08:00
|
|
|
return os.FileMode(0755) | os.ModeDir
|
2016-07-19 06:53:13 +08:00
|
|
|
}
|
2016-07-20 02:25:33 +08:00
|
|
|
if f.symlink != "" {
|
2020-09-17 17:53:13 +08:00
|
|
|
return os.FileMode(0777) | os.ModeSymlink
|
2016-07-20 02:25:33 +08:00
|
|
|
}
|
2020-09-17 17:53:13 +08:00
|
|
|
return os.FileMode(0644)
|
2016-07-19 06:53:13 +08:00
|
|
|
}
|
2016-09-14 05:05:32 +08:00
|
|
|
func (f *memFile) ModTime() time.Time { return f.modtime }
|
2016-07-19 06:53:13 +08:00
|
|
|
func (f *memFile) IsDir() bool { return f.isdir }
|
|
|
|
func (f *memFile) Sys() interface{} {
|
2017-02-25 12:06:51 +08:00
|
|
|
return fakeFileInfoSys()
|
2016-07-19 06:53:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read/Write
|
2016-07-30 06:57:06 +08:00
|
|
|
func (f *memFile) ReaderAt() (io.ReaderAt, error) {
|
2016-07-19 06:53:13 +08:00
|
|
|
if f.isdir {
|
|
|
|
return nil, os.ErrInvalid
|
|
|
|
}
|
2020-09-17 17:53:13 +08:00
|
|
|
|
|
|
|
return f, nil
|
|
|
|
}
|
|
|
|
func (f *memFile) ReadAt(b []byte, off int64) (int, error) {
|
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
|
|
|
if off < 0 {
|
|
|
|
return 0, errors.New("memFile.ReadAt: negative offset")
|
|
|
|
}
|
|
|
|
|
|
|
|
if off >= f.size() {
|
|
|
|
return 0, io.EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
n := copy(b, f.content[off:])
|
|
|
|
if n < len(b) {
|
|
|
|
return n, io.EOF
|
|
|
|
}
|
|
|
|
|
|
|
|
return n, nil
|
2016-07-19 06:53:13 +08:00
|
|
|
}
|
|
|
|
|
2016-07-30 06:57:06 +08:00
|
|
|
func (f *memFile) WriterAt() (io.WriterAt, error) {
|
2016-07-19 06:53:13 +08:00
|
|
|
if f.isdir {
|
|
|
|
return nil, os.ErrInvalid
|
|
|
|
}
|
2020-09-17 17:53:13 +08:00
|
|
|
|
2016-07-19 06:53:13 +08:00
|
|
|
return f, nil
|
|
|
|
}
|
2020-09-17 17:53:13 +08:00
|
|
|
func (f *memFile) WriteAt(b []byte, off int64) (int, error) {
|
2016-08-02 07:13:11 +08:00
|
|
|
// fmt.Println(string(p), off)
|
2016-08-02 05:09:42 +08:00
|
|
|
// mimic write delays, should be optional
|
2020-09-17 17:53:13 +08:00
|
|
|
time.Sleep(time.Microsecond * time.Duration(len(b)))
|
|
|
|
|
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
|
|
|
grow := int64(len(b)) + off - f.size()
|
|
|
|
if grow > 0 {
|
|
|
|
f.grow(grow)
|
2016-07-30 06:57:06 +08:00
|
|
|
}
|
2019-09-12 14:17:32 +08:00
|
|
|
|
2020-09-17 17:53:13 +08:00
|
|
|
return copy(f.content[off:], b), nil
|
2020-08-30 16:40:22 +08:00
|
|
|
}
|
|
|
|
|
2020-08-22 15:51:12 +08:00
|
|
|
func (f *memFile) Truncate(size int64) error {
|
2020-09-17 17:53:13 +08:00
|
|
|
f.mu.Lock()
|
|
|
|
defer f.mu.Unlock()
|
|
|
|
|
|
|
|
grow := size - f.size()
|
2020-08-22 15:51:12 +08:00
|
|
|
if grow <= 0 {
|
|
|
|
f.content = f.content[:size]
|
|
|
|
} else {
|
2020-09-17 17:53:13 +08:00
|
|
|
f.grow(grow)
|
2020-08-22 15:51:12 +08:00
|
|
|
}
|
2020-09-17 17:53:13 +08:00
|
|
|
|
2020-08-22 15:51:12 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-09-12 14:17:32 +08:00
|
|
|
func (f *memFile) TransferError(err error) {
|
|
|
|
f.transferError = err
|
|
|
|
}
|