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" "sort" "strconv" "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": 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 { ordered_names = append(ordered_names, fn) } } sort.Sort(sort.StringSlice(ordered_names)) list := make([]os.FileInfo, len(ordered_names)) 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 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 }