Skip to content

Commit

Permalink
align fsetstat with openssh sftp-server
Browse files Browse the repository at this point in the history
  • Loading branch information
puellanivis committed Mar 5, 2025
1 parent 1d50cfb commit 3afbfaa
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 62 deletions.
23 changes: 15 additions & 8 deletions localfs/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,24 +136,31 @@ func (f *File) ReadDir(maxDataLen uint32) (entries []*sshfx.NameEntry, err error

// SetStat implements [sftp.SetStatFileHandler].
func (f *File) SetStat(attrs *sshfx.Attributes) (err error) {
if len(attrs.Extended) > 0 {
err = &sshfx.StatusPacket{
StatusCode: sshfx.StatusOpUnsupported,
ErrorMessage: "unsupported fsetstat: extended atributes",
}
}

if attrs.HasSize() {
sz := attrs.GetSize()
err = cmp.Or(err, f.Truncate(int64(sz)))
err = cmp.Or(f.Truncate(int64(sz)), err)
}

if attrs.HasPermissions() {
perm := attrs.GetPermissions()
err = cmp.Or(err, f.Chmod(fs.FileMode(perm.Perm())))
}

if attrs.HasUIDGID() {
uid, gid := attrs.GetUIDGID()
err = cmp.Or(err, f.Chown(int(uid), int(gid)))
err = cmp.Or(f.Chmod(fs.FileMode(perm.Perm())), err)
}

if attrs.HasACModTime() {
atime, mtime := attrs.GetACModTime()
err = cmp.Or(err, os.Chtimes(f.filename, time.Unix(int64(atime), 0), time.Unix(int64(mtime), 0)))
err = cmp.Or(os.Chtimes(f.filename, time.Unix(int64(atime), 0), time.Unix(int64(mtime), 0)), err)
}

if attrs.HasUIDGID() {
uid, gid := attrs.GetUIDGID()
err = cmp.Or(f.Chown(int(uid), int(gid)), err)
}

return err
Expand Down
79 changes: 25 additions & 54 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,30 +74,24 @@ type SetStatFileHandler interface {
SetStat(attrs *sshfx.Attributes) error
}

func noop() error {
return nil
}

// TruncateFileHandler is an extension interface for handling the truncate subfunction of an SSH_FXP_FSETSTAT request.
type TruncateFileHandler interface {
FileHandler
Truncate(size int64) error
}

func trunc(attr *sshfx.Attributes, f FileHandler) (func() error, error) {
func ftrunc(attr *sshfx.Attributes, f FileHandler) error {
if !attr.HasSize() {
return noop, nil
return nil
}

sz := attr.GetSize()

if truncater, ok := f.(TruncateFileHandler); ok {
return func() error {
return truncater.Truncate(int64(sz))
}, nil
return truncater.Truncate(int64(sz))
}

return nil, &sshfx.StatusPacket{
return &sshfx.StatusPacket{
StatusCode: sshfx.StatusOpUnsupported,
ErrorMessage: "unsupported fsetstat: ftruncate",
}
Expand All @@ -109,20 +103,18 @@ type ChownFileHandler interface {
Chown(uid, gid int) error
}

func chown(attr *sshfx.Attributes, f FileHandler) (func() error, error) {
func fchown(attr *sshfx.Attributes, f FileHandler) error {
if !attr.HasUIDGID() {
return noop, nil
return nil
}

uid, gid := attr.GetUIDGID()

if chowner, ok := f.(ChownFileHandler); ok {
return func() error {
return chowner.Chown(int(uid), int(gid))
}, nil
return chowner.Chown(int(uid), int(gid))
}

return nil, &sshfx.StatusPacket{
return &sshfx.StatusPacket{
StatusCode: sshfx.StatusOpUnsupported,
ErrorMessage: "unsupported fsetstat: fchown",
}
Expand All @@ -134,20 +126,18 @@ type ChmodFileHandler interface {
Chmod(mode fs.FileMode) error
}

func chmod(attr *sshfx.Attributes, f FileHandler) (func() error, error) {
func fchmod(attr *sshfx.Attributes, f FileHandler) error {
if !attr.HasPermissions() {
return noop, nil
return nil
}

mode := attr.GetPermissions()

if chmoder, ok := f.(ChmodFileHandler); ok {
return func() error {
return chmoder.Chmod(sshfx.ToGoFileMode(mode))
}, nil
return chmoder.Chmod(sshfx.ToGoFileMode(mode))
}

return nil, &sshfx.StatusPacket{
return &sshfx.StatusPacket{
StatusCode: sshfx.StatusOpUnsupported,
ErrorMessage: "unsupported fsetstat: fchmod",
}
Expand All @@ -159,20 +149,18 @@ type ChtimesFileHandler interface {
Chtimes(atime, mtime time.Time) error
}

func chtimes(attr *sshfx.Attributes, f FileHandler) (func() error, error) {
func fchtimes(attr *sshfx.Attributes, f FileHandler) error {
if !attr.HasACModTime() {
return noop, nil
return nil
}

atime, mtime := attr.GetACModTime()

if chtimeser, ok := f.(ChtimesFileHandler); ok {
return func() error {
return chtimeser.Chtimes(time.Unix(int64(atime), 0), time.Unix(int64(mtime), 0))
}, nil
return chtimeser.Chtimes(time.Unix(int64(atime), 0), time.Unix(int64(mtime), 0))
}

return nil, &sshfx.StatusPacket{
return &sshfx.StatusPacket{
StatusCode: sshfx.StatusOpUnsupported,
ErrorMessage: "unsupported fsetstat: fchtimes",
}
Expand Down Expand Up @@ -753,39 +741,22 @@ func (srv *Server) handle(req sshfx.Packet, hint []byte, maxDataLen uint32) (ssh
return nil, file.SetStat(&req.Attrs)
}

var err error

if len(req.Attrs.Extended) > 0 {
return nil, &sshfx.StatusPacket{
err = &sshfx.StatusPacket{
StatusCode: sshfx.StatusOpUnsupported,
ErrorMessage: "unsupported fsetstat: extended attributes",
}
}

trunc, err := trunc(&req.Attrs, file)
if err != nil {
return nil, err
}

chown, err := chown(&req.Attrs, file)
if err != nil {
return nil, err
}

chmod, err := chmod(&req.Attrs, file)
if err != nil {
return nil, err
}

chtimes, err := chtimes(&req.Attrs, file)
if err != nil {
return nil, err
}
// This is the order and behavior of operations for SSH_FXP_FSETSTAT in openssh's sftp-server.
err = cmp.Or(ftrunc(&req.Attrs, file), err)
err = cmp.Or(fchmod(&req.Attrs, file), err)
err = cmp.Or(fchtimes(&req.Attrs, file), err)
err = cmp.Or(fchown(&req.Attrs, file), err)

return nil, cmp.Or(
trunc(),
chown(),
chmod(),
chtimes(),
)
return nil, err
}
}

Expand Down

0 comments on commit 3afbfaa

Please sign in to comment.