| 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" |
| "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": |
| list := []os.FileInfo{} |
| for fn, fi := range fs.files { |
| if filepath.Dir(fn) == r.Filepath { |
| list = append(list, fi) |
| } |
| } |
| return list, 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 |
| } |