rclone/cmd/serve/sftp/handler.go

148 lines
2.9 KiB
Go

//go:build !plan9
package sftp
import (
"io"
"os"
"syscall"
"time"
"github.com/pkg/sftp"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/vfs"
)
// vfsHandler converts the VFS to be served by SFTP
type vfsHandler struct {
*vfs.VFS
}
// vfsHandler returns a Handlers object with the test handlers.
func newVFSHandler(vfs *vfs.VFS) sftp.Handlers {
v := vfsHandler{VFS: vfs}
return sftp.Handlers{
FileGet: v,
FilePut: v,
FileCmd: v,
FileList: v,
}
}
func (v vfsHandler) Fileread(r *sftp.Request) (io.ReaderAt, error) {
file, err := v.OpenFile(r.Filepath, os.O_RDONLY, 0777)
if err != nil {
return nil, err
}
return file, nil
}
func (v vfsHandler) Filewrite(r *sftp.Request) (io.WriterAt, error) {
file, err := v.OpenFile(r.Filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
return nil, err
}
return file, nil
}
func (v vfsHandler) Filecmd(r *sftp.Request) error {
switch r.Method {
case "Setstat":
attr := r.Attributes()
if attr.Mtime != 0 {
modTime := time.Unix(int64(attr.Mtime), 0)
err := v.Chtimes(r.Filepath, modTime, modTime)
if err != nil {
return err
}
}
return nil
case "Rename":
err := v.Rename(r.Filepath, r.Target)
if err != nil {
return err
}
case "Rmdir", "Remove":
err := v.Remove(r.Filepath)
if err != nil {
return err
}
case "Mkdir":
err := v.Mkdir(r.Filepath, 0777)
if err != nil {
return err
}
case "Symlink":
// FIXME
// _, err := v.fetch(r.Filepath)
// if err != nil {
// return err
// }
// link := newMemFile(r.Target, false)
// link.symlink = r.Filepath
// v.files[r.Target] = link
return sftp.ErrSshFxOpUnsupported
case "Link":
return sftp.ErrSshFxOpUnsupported
default:
return sftp.ErrSshFxOpUnsupported
}
return nil
}
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
}
func (v vfsHandler) Filelist(r *sftp.Request) (l sftp.ListerAt, err error) {
var node vfs.Node
var handle vfs.Handle
switch r.Method {
case "List":
node, err = v.Stat(r.Filepath)
if err != nil {
return nil, err
}
if !node.IsDir() {
return nil, syscall.ENOTDIR
}
handle, err = node.Open(os.O_RDONLY)
if err != nil {
return nil, err
}
defer fs.CheckClose(handle, &err)
fis, err := handle.Readdir(-1)
if err != nil {
return nil, err
}
return listerat(fis), nil
case "Stat":
node, err = v.Stat(r.Filepath)
if err != nil {
return nil, err
}
return listerat([]os.FileInfo{node}), nil
case "Readlink":
// FIXME
// if file.symlink != "" {
// file, err = v.fetch(file.symlink)
// if err != nil {
// return nil, err
// }
// }
// return listerat([]os.FileInfo{file}), nil
}
return nil, sftp.ErrSshFxOpUnsupported
}