mirror of
https://github.com/rclone/rclone
synced 2024-12-05 15:55:18 +01:00
50a3a96e27
Before this change the sftp handler returned a nil error for unknown operations which meant the server crashed when one was encountered. In particular the "Readlink" operations was causing problems. After this change the handler returns ErrSshFxOpUnsupported which signals to the remote end that we don't support that operation. See: https://forum.rclone.org/t/rclone-serve-sftp-not-working-in-windows/12209
155 lines
3.0 KiB
Go
155 lines
3.0 KiB
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":
|
|
node, err := v.Stat(r.Filepath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
attr := r.Attributes()
|
|
if attr.Mtime != 0 {
|
|
modTime := time.Unix(int64(attr.Mtime), 0)
|
|
err := node.SetModTime(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":
|
|
node, err := v.Stat(r.Filepath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = node.Remove()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case "Mkdir":
|
|
dir, leaf, err := v.StatParent(r.Filepath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = dir.Mkdir(leaf)
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|