github.com/artpar/rclone@v1.67.3/cmd/serve/sftp/handler.go (about) 1 //go:build !plan9 2 3 package sftp 4 5 import ( 6 "io" 7 "os" 8 "syscall" 9 "time" 10 11 "github.com/artpar/rclone/fs" 12 "github.com/artpar/rclone/vfs" 13 "github.com/pkg/sftp" 14 ) 15 16 // vfsHandler converts the VFS to be served by SFTP 17 type vfsHandler struct { 18 *vfs.VFS 19 } 20 21 // vfsHandler returns a Handlers object with the test handlers. 22 func newVFSHandler(vfs *vfs.VFS) sftp.Handlers { 23 v := vfsHandler{VFS: vfs} 24 return sftp.Handlers{ 25 FileGet: v, 26 FilePut: v, 27 FileCmd: v, 28 FileList: v, 29 } 30 } 31 32 func (v vfsHandler) Fileread(r *sftp.Request) (io.ReaderAt, error) { 33 file, err := v.OpenFile(r.Filepath, os.O_RDONLY, 0777) 34 if err != nil { 35 return nil, err 36 } 37 return file, nil 38 } 39 40 func (v vfsHandler) Filewrite(r *sftp.Request) (io.WriterAt, error) { 41 file, err := v.OpenFile(r.Filepath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777) 42 if err != nil { 43 return nil, err 44 } 45 return file, nil 46 } 47 48 func (v vfsHandler) Filecmd(r *sftp.Request) error { 49 switch r.Method { 50 case "Setstat": 51 attr := r.Attributes() 52 if attr.Mtime != 0 { 53 modTime := time.Unix(int64(attr.Mtime), 0) 54 err := v.Chtimes(r.Filepath, modTime, modTime) 55 if err != nil { 56 return err 57 } 58 } 59 return nil 60 case "Rename": 61 err := v.Rename(r.Filepath, r.Target) 62 if err != nil { 63 return err 64 } 65 case "Rmdir", "Remove": 66 err := v.Remove(r.Filepath) 67 if err != nil { 68 return err 69 } 70 case "Mkdir": 71 err := v.Mkdir(r.Filepath, 0777) 72 if err != nil { 73 return err 74 } 75 case "Symlink": 76 // FIXME 77 // _, err := v.fetch(r.Filepath) 78 // if err != nil { 79 // return err 80 // } 81 // link := newMemFile(r.Target, false) 82 // link.symlink = r.Filepath 83 // v.files[r.Target] = link 84 return sftp.ErrSshFxOpUnsupported 85 case "Link": 86 return sftp.ErrSshFxOpUnsupported 87 default: 88 return sftp.ErrSshFxOpUnsupported 89 } 90 return nil 91 } 92 93 type listerat []os.FileInfo 94 95 // Modeled after strings.Reader's ReadAt() implementation 96 func (f listerat) ListAt(ls []os.FileInfo, offset int64) (int, error) { 97 var n int 98 if offset >= int64(len(f)) { 99 return 0, io.EOF 100 } 101 n = copy(ls, f[offset:]) 102 if n < len(ls) { 103 return n, io.EOF 104 } 105 return n, nil 106 } 107 108 func (v vfsHandler) Filelist(r *sftp.Request) (l sftp.ListerAt, err error) { 109 var node vfs.Node 110 var handle vfs.Handle 111 switch r.Method { 112 case "List": 113 node, err = v.Stat(r.Filepath) 114 if err != nil { 115 return nil, err 116 } 117 if !node.IsDir() { 118 return nil, syscall.ENOTDIR 119 } 120 handle, err = node.Open(os.O_RDONLY) 121 if err != nil { 122 return nil, err 123 } 124 defer fs.CheckClose(handle, &err) 125 fis, err := handle.Readdir(-1) 126 if err != nil { 127 return nil, err 128 } 129 return listerat(fis), nil 130 case "Stat": 131 node, err = v.Stat(r.Filepath) 132 if err != nil { 133 return nil, err 134 } 135 return listerat([]os.FileInfo{node}), nil 136 case "Readlink": 137 // FIXME 138 // if file.symlink != "" { 139 // file, err = v.fetch(file.symlink) 140 // if err != nil { 141 // return nil, err 142 // } 143 // } 144 // return listerat([]os.FileInfo{file}), nil 145 } 146 return nil, sftp.ErrSshFxOpUnsupported 147 }