10000 request server: add support for SSH_FXP_FSETSTAT · etherscan-io/sftp@a011842 · GitHub
[go: up one dir, main page]

Skip to content

Commit a011842

Browse files
committed
request server: add support for SSH_FXP_FSETSTAT
we need to add a case for this packet inside the packet worker otherwise it will be handled in hasHandle case and it will become a "Put" request. Client side if a Truncate request is called on the open file we should send a FSETSTAT packet, the request is on the handle, and not a SETSTAT packet that should be used for paths and not for handle.
1 parent 2c44234 commit a011842

File tree

4 files changed

+96
-6
lines changed

4 files changed

+96
-6
lines changed

client.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,25 @@ func (c *Client) Symlink(oldname, newname string) error {
432432
}
433433
}
434434

435+
func (c *Client) setfstat(handle string, flags uint32, attrs interface{}) error {
436+
id := c.nextID()
437+
typ, data, err := c.sendPacket(sshFxpFsetstatPacket{
438+
ID: id,
439+
Handle: handle,
440+
Flags: flags,
441+
Attrs: attrs,
442+
})
443+
if err != nil {
444+
return err
445+
}
446+
switch typ {
447+
case sshFxpStatus:
448+
return normaliseError(unmarshalStatus(id, data))
449+
default:
450+
return unimplementedPacketErr(typ)
451+
}
452+
}
453+
435454
// setstat is a convience wrapper to allow for changing of various parts of the file descriptor.
436455
func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {
437456
id := c.nextID()
@@ -817,7 +836,7 @@ type File struct {
817836
path string
818837
handle string
819838

820-
mu sync.Mutex
839+
mu sync.Mutex
821840
offset uint64 // current offset within remote file
822841
}
823842

@@ -845,13 +864,13 @@ func (f *File) Read(b []byte) (int, error) {
845864
f.mu.Lock()
846865
defer f.mu.Unlock()
847866

848-
r, err := f.ReadAt(b, int64( f.offset ))
867+
r, err := f.ReadAt(b, int64(f.offset))
849868
f.offset += uint64(r)
850869
return r, err
851870
}
852871

853-
// ReadAt reads up to len(b) byte from the File at a given offset `off`. It returns
854-
// the number of bytes read and an error, if any. ReadAt follows io.ReaderAt semantics,
872+
// ReadAt reads up to len(b) byte from the File at a given offset `off`. It returns
873+
// the number of bytes read and an error, if any. ReadAt follows io.ReaderAt semantics,
855874
// so the file offset is not altered during the read.
856875
func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
857876
// Split the read into multiple maxPacket sized concurrent reads
@@ -860,7 +879,7 @@ func (f *File) ReadAt(b []byte, off int64) (n int, err error) {
860879
// overlapping round trip times.
861880
inFlight := 0
862881
desiredInFlight := 1
863-
offset := uint64( off )
882+
offset := uint64(off)
864883
// maxConcurrentRequests buffer to deal with broadcastErr() floods
865884
// also must have a buffer of max value of (desiredInFlight - inFlight)
866885
ch := make(chan result, f.c.maxConcurrentRequests+1)
@@ -1280,8 +1299,17 @@ func (f *File) Chmod(mode os.FileMode) error {
12801299
// that if the size is less than its current size it will be truncated to fit,
12811300
// the SFTP protocol does not specify what behavior the server should do when setting
12821301
// size greater than the current size.
1302+
// We send a SSH_FXP_FSETSTAT here since we have a file handle
12831303
func (f *File) Truncate(size int64) error {
1284-
return f.c.Truncate(f.path, size)
1304+
err := f.c.setfstat(f.handle, sshFileXferAttrSize, uint64(size))
1305+
if err == nil {
1306+
// reset the offset for future writes
1307+
f.mu.Lock()
1308+
defer f.mu.Unlock()
1309+
1310+
f.offset = uint64(size)
1311+
}
1312+
return err
12851313
}
12861314

12871315
func min(a, b int) int {

request-example.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ func (fs *root) Filecmd(r *Request) error {
7878
defer fs.filesLock.Unlock()
7979
switch r.Method {
8080
case "Setstat":
81+
file, err := fs.fetch(r.Filepath)
82+
if err != nil {
83+
return err
84+
}
85+
if r.AttrFlags().Size {
86+
return file.Truncate(int64(r.Attributes().Size))
87+
}
8188
return nil
8289
case "Rename":
8390
file, err := fs.fetch(r.Filepath)
@@ -302,6 +309,18 @@ func (f *memFile) WriteAt(p []byte, off int64) (int, error) {
302309
return len(p), nil
303310
}
304311

312+
func (f *memFile) Truncate(size int64) error {
313+
f.contentLock.Lock()
314+
defer f.contentLock.Unlock()
315+
grow := size - int64(len(f.content))
316+
if grow <= 0 {
317+
f.content = f.content[:size]
318+
} else {
319+
f.content = append(f.content, make([]byte, grow)...)
320+
}
321+
return nil
322+
}
323+
305324
func (f *memFile) TransferError(err error) {
306325
f.transferError = err
307326
}

request-server.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,15 @@ func (rs *RequestServer) packetWorker(
214214
request = NewRequest("Stat", request.Filepath)
215215
rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
216216
}
217+
case *sshFxpFsetstatPacket:
218+
handle := pkt.getHandle()
219+
request, ok := rs.getRequest(handle)
220+
if !ok {
221+
rpkt = statusFromError(pkt, syscall.EBADF)
222+
} else {
223+
request = NewRequest("Setstat", request.Filepath)
224+
rpkt = request.call(rs.Handlers, pkt, rs.pktMgr.alloc, orderID)
225+
}
217226
case *sshFxpExtendedPacketPosixRename:
218227
request := NewRequest("Rename", pkt.Oldpath)
219228
request.Target = pkt.Newpath

request-server_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,40 @@ func TestRequestFstat(t *testing.T) {
345345
checkRequestServerAllocator(t, p)
346346
}
347347

348+
func TestRequestFsetstat(t *testing.T) {
349+
p := clientRequestServerPair(t)
350+
defer p.Close()
351+
_, err := putTestFile(p.cli, "/foo", "hello")
352+
assert.Nil(t, err)
353+
fp, err := p.cli.OpenFile("/foo", os.O_WRONLY)
354+
assert.Nil(t, err)
355+
err = fp.Truncate(2)
356+
if assert.NoError(t, err) {
357+
fi, err := fp.Stat()
358+
if assert.NoError(t, err) {
359+
assert.Equal(t, fi.Name(), "foo")
360+
assert.Equal(t, fi.Size(), int64(2))
361+
}
362+
}
363+
// we expect the truncate size (2) as offset for this write
364+
n, err := fp.Write([]byte("hello"))
365+
assert.NoError(t, err)
366+
assert.Equal(t, 5, n)
367+
err = fp.Close()
368+
assert.NoError(t, err)
369+
rf, err := p.cli.Open("/foo")
370+
assert.Nil(t, err)
371+
defer rf.Close()
372+
contents := make([]byte, 20)
373+
n, err = rf.Read(contents)
374+
if err != nil && err != io.EOF {
375+
t.Fatalf("err: %v", err)
376+
}
377+
assert.Equal(t, 2+5, n)
378+
assert.Equal(t, "hehello", string(contents[0:n]))
379+
checkRequestServerAllocator(t, p)
380+
}
381+
348382
func TestRequestStatFail(t *testing.T) {
349383
p := clientRequestServerPair(t)
350384
defer p.Close()

0 commit comments

Comments
 (0)
0