2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-10 23:31:09 +00:00
restic/vendor/github.com/pkg/sftp/request.go

308 lines
7.2 KiB
Go
Raw Normal View History

2017-07-23 12:24:45 +00:00
package sftp
import (
"io"
"os"
"path"
"path/filepath"
"sync"
"syscall"
"github.com/pkg/errors"
)
2017-09-13 12:09:48 +00:00
// MaxFilelist is the max number of files to return in a readdir batch.
var MaxFilelist int64 = 100
2017-07-23 12:24:45 +00:00
// Request contains the data and state for the incoming service request.
type Request struct {
// Get, Put, Setstat, Stat, Rename, Remove
// Rmdir, Mkdir, List, Readlink, Symlink
Method string
Filepath string
Flags uint32
Attrs []byte // convert to sub-struct
Target string // for renames and sym-links
// reader/writer/readdir from handlers
stateLock sync.RWMutex
state state
2017-07-23 12:24:45 +00:00
}
type state struct {
2017-09-13 12:09:48 +00:00
writerAt io.WriterAt
readerAt io.ReaderAt
listerAt ListerAt
lsoffset int64
2017-07-23 12:24:45 +00:00
}
// New Request initialized based on packet data
2017-09-13 12:09:48 +00:00
func requestFromPacket(pkt hasPath) *Request {
2017-07-23 12:24:45 +00:00
method := requestMethod(pkt)
request := NewRequest(method, pkt.getPath())
switch p := pkt.(type) {
case *sshFxpOpenPacket:
request.Flags = p.Pflags
2017-07-23 12:24:45 +00:00
case *sshFxpSetstatPacket:
request.Flags = p.Flags
request.Attrs = p.Attrs.([]byte)
case *sshFxpRenamePacket:
2017-09-13 12:09:48 +00:00
request.Target = cleanPath(p.Newpath)
2017-07-23 12:24:45 +00:00
case *sshFxpSymlinkPacket:
2017-09-13 12:09:48 +00:00
request.Target = cleanPath(p.Linkpath)
2017-07-23 12:24:45 +00:00
}
return request
}
// NewRequest creates a new Request object.
2017-09-13 12:09:48 +00:00
func NewRequest(method, path string) *Request {
return &Request{Method: method, Filepath: cleanPath(path)}
2017-07-23 12:24:45 +00:00
}
2017-09-13 12:09:48 +00:00
// Returns current offset for file list
func (r *Request) lsNext() int64 {
2017-07-23 12:24:45 +00:00
r.stateLock.RLock()
defer r.stateLock.RUnlock()
2017-09-13 12:09:48 +00:00
return r.state.lsoffset
2017-07-23 12:24:45 +00:00
}
2017-09-13 12:09:48 +00:00
// Increases next offset
func (r *Request) lsInc(offset int64) {
r.stateLock.Lock()
defer r.stateLock.Unlock()
r.state.lsoffset = r.state.lsoffset + offset
2017-07-23 12:24:45 +00:00
}
// manage file read/write state
func (r *Request) setWriterState(wa io.WriterAt) {
2017-07-23 12:24:45 +00:00
r.stateLock.Lock()
defer r.stateLock.Unlock()
r.state.writerAt = wa
}
func (r *Request) setReaderState(ra io.ReaderAt) {
r.stateLock.Lock()
defer r.stateLock.Unlock()
r.state.readerAt = ra
}
func (r *Request) setListerState(la ListerAt) {
r.stateLock.Lock()
defer r.stateLock.Unlock()
r.state.listerAt = la
2017-07-23 12:24:45 +00:00
}
2017-09-13 12:09:48 +00:00
func (r *Request) getWriter() io.WriterAt {
2017-07-23 12:24:45 +00:00
r.stateLock.RLock()
defer r.stateLock.RUnlock()
return r.state.writerAt
}
2017-09-13 12:09:48 +00:00
func (r *Request) getReader() io.ReaderAt {
2017-07-23 12:24:45 +00:00
r.stateLock.RLock()
defer r.stateLock.RUnlock()
return r.state.readerAt
}
2017-09-13 12:09:48 +00:00
func (r *Request) getLister() ListerAt {
2017-07-23 12:24:45 +00:00
r.stateLock.RLock()
defer r.stateLock.RUnlock()
2017-09-13 12:09:48 +00:00
return r.state.listerAt
2017-07-23 12:24:45 +00:00
}
// Close reader/writer if possible
func (r *Request) close() error {
2017-07-23 12:24:45 +00:00
rd := r.getReader()
if c, ok := rd.(io.Closer); ok {
return c.Close()
2017-07-23 12:24:45 +00:00
}
wt := r.getWriter()
if c, ok := wt.(io.Closer); ok {
return c.Close()
2017-07-23 12:24:45 +00:00
}
return nil
2017-07-23 12:24:45 +00:00
}
// called from worker to handle packet/request
2017-09-13 12:09:48 +00:00
func (r *Request) call(handlers Handlers, pkt requestPacket) responsePacket {
2017-07-23 12:24:45 +00:00
switch r.Method {
case "Get":
return fileget(handlers.FileGet, r, pkt)
case "Put", "Open":
return fileput(handlers.FilePut, r, pkt)
2017-07-23 12:24:45 +00:00
case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
return filecmd(handlers.FileCmd, r, pkt)
2017-07-23 12:24:45 +00:00
case "List", "Stat", "Readlink":
return filelist(handlers.FileList, r, pkt)
2017-07-23 12:24:45 +00:00
default:
2017-09-13 12:09:48 +00:00
return statusFromError(pkt,
errors.Errorf("unexpected method: %s", r.Method))
2017-07-23 12:24:45 +00:00
}
2017-09-13 12:09:48 +00:00
}
// file data for additional read/write packets
func packetData(p requestPacket) (data []byte, offset int64, length uint32) {
2017-09-13 12:09:48 +00:00
switch p := p.(type) {
case *sshFxpReadPacket:
length = p.Len
offset = int64(p.Offset)
2017-09-13 12:09:48 +00:00
case *sshFxpWritePacket:
data = p.Data
length = p.Length
offset = int64(p.Offset)
2017-09-13 12:09:48 +00:00
}
return
2017-07-23 12:24:45 +00:00
}
// wrap FileReader handler
func fileget(h FileReader, r *Request, pkt requestPacket) responsePacket {
2017-07-23 12:24:45 +00:00
var err error
reader := r.getReader()
if reader == nil {
reader, err = h.Fileread(r)
if err != nil {
return statusFromError(pkt, err)
2017-07-23 12:24:45 +00:00
}
r.setReaderState(reader)
2017-07-23 12:24:45 +00:00
}
_, offset, length := packetData(pkt)
data := make([]byte, clamp(length, maxTxPacket))
n, err := reader.ReadAt(data, offset)
2017-09-13 12:09:48 +00:00
// only return EOF erro if no data left to read
2017-07-23 12:24:45 +00:00
if err != nil && (err != io.EOF || n == 0) {
return statusFromError(pkt, err)
2017-07-23 12:24:45 +00:00
}
return &sshFxpDataPacket{
ID: pkt.id(),
2017-07-23 12:24:45 +00:00
Length: uint32(n),
Data: data[:n],
2017-09-13 12:09:48 +00:00
}
2017-07-23 12:24:45 +00:00
}
// wrap FileWriter handler
func fileput(h FileWriter, r *Request, pkt requestPacket) responsePacket {
2017-07-23 12:24:45 +00:00
var err error
writer := r.getWriter()
if writer == nil {
writer, err = h.Filewrite(r)
if err != nil {
return statusFromError(pkt, err)
2017-07-23 12:24:45 +00:00
}
r.setWriterState(writer)
2017-07-23 12:24:45 +00:00
}
data, offset, _ := packetData(pkt)
_, err = writer.WriteAt(data, offset)
return statusFromError(pkt, err)
2017-07-23 12:24:45 +00:00
}
// wrap FileCmder handler
func filecmd(h FileCmder, r *Request, pkt requestPacket) responsePacket {
2017-07-23 12:24:45 +00:00
err := h.Filecmd(r)
return statusFromError(pkt, err)
2017-07-23 12:24:45 +00:00
}
2017-09-13 12:09:48 +00:00
// wrap FileLister handler
func filelist(h FileLister, r *Request, pkt requestPacket) responsePacket {
2017-09-13 12:09:48 +00:00
var err error
lister := r.getLister()
if lister == nil {
lister, err = h.Filelist(r)
if err != nil {
return statusFromError(pkt, err)
2017-09-13 12:09:48 +00:00
}
r.setListerState(lister)
2017-07-23 12:24:45 +00:00
}
2017-09-13 12:09:48 +00:00
offset := r.lsNext()
finfo := make([]os.FileInfo, MaxFilelist)
n, err := lister.ListAt(finfo, offset)
r.lsInc(int64(n))
// ignore EOF as we only return it when there are no results
finfo = finfo[:n] // avoid need for nil tests below
2017-07-23 12:24:45 +00:00
switch r.Method {
case "List":
2017-09-13 12:09:48 +00:00
if err != nil && err != io.EOF {
return statusFromError(pkt, err)
2017-09-13 12:09:48 +00:00
}
if n == 0 {
return statusFromError(pkt, io.EOF)
2017-09-13 12:09:48 +00:00
}
dirname := filepath.ToSlash(path.Base(r.Filepath))
ret := &sshFxpNamePacket{ID: pkt.id()}
2017-09-13 12:09:48 +00:00
2017-07-23 12:24:45 +00:00
for _, fi := range finfo {
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
Name: fi.Name(),
LongName: runLs(dirname, fi),
Attrs: []interface{}{fi},
})
}
2017-09-13 12:09:48 +00:00
return ret
2017-07-23 12:24:45 +00:00
case "Stat":
2017-09-13 12:09:48 +00:00
if err != nil && err != io.EOF {
return statusFromError(pkt, err)
2017-09-13 12:09:48 +00:00
}
if n == 0 {
2017-07-23 12:24:45 +00:00
err = &os.PathError{Op: "stat", Path: r.Filepath,
Err: syscall.ENOENT}
return statusFromError(pkt, err)
2017-07-23 12:24:45 +00:00
}
return &sshFxpStatResponse{
ID: pkt.id(),
2017-07-23 12:24:45 +00:00
info: finfo[0],
2017-09-13 12:09:48 +00:00
}
2017-07-23 12:24:45 +00:00
case "Readlink":
2017-09-13 12:09:48 +00:00
if err != nil && err != io.EOF {
return statusFromError(pkt, err)
2017-09-13 12:09:48 +00:00
}
if n == 0 {
2017-07-23 12:24:45 +00:00
err = &os.PathError{Op: "readlink", Path: r.Filepath,
Err: syscall.ENOENT}
return statusFromError(pkt, err)
2017-07-23 12:24:45 +00:00
}
filename := finfo[0].Name()
return &sshFxpNamePacket{
ID: pkt.id(),
2017-07-23 12:24:45 +00:00
NameAttrs: []sshFxpNameAttr{{
Name: filename,
LongName: filename,
Attrs: emptyFileStat,
}},
2017-09-13 12:09:48 +00:00
}
default:
err = errors.Errorf("unexpected method: %s", r.Method)
return statusFromError(pkt, err)
2017-07-23 12:24:45 +00:00
}
}
// init attributes of request object from packet data
func requestMethod(p requestPacket) (method string) {
switch p.(type) {
2017-07-23 12:24:45 +00:00
case *sshFxpReadPacket:
method = "Get"
2017-07-23 12:24:45 +00:00
case *sshFxpWritePacket:
method = "Put"
2017-07-23 12:24:45 +00:00
case *sshFxpReaddirPacket:
method = "List"
2017-07-23 12:24:45 +00:00
case *sshFxpOpenPacket, *sshFxpOpendirPacket:
method = "Open"
case *sshFxpSetstatPacket, *sshFxpFsetstatPacket:
2017-07-23 12:24:45 +00:00
method = "Setstat"
case *sshFxpRenamePacket:
method = "Rename"
case *sshFxpSymlinkPacket:
method = "Symlink"
case *sshFxpRemovePacket:
method = "Remove"
case *sshFxpStatPacket, *sshFxpLstatPacket, *sshFxpFstatPacket:
2017-07-23 12:24:45 +00:00
method = "Stat"
case *sshFxpRmdirPacket:
method = "Rmdir"
case *sshFxpReadlinkPacket:
method = "Readlink"
case *sshFxpMkdirPacket:
method = "Mkdir"
}
return method
}