From c632ce6285b74e776d5dcfaec897a2a97f93d5bd Mon Sep 17 00:00:00 2001 From: Peter Verraedt Date: Wed, 15 Mar 2023 13:21:45 +0100 Subject: [PATCH] Marshal extended data if file info supports it If the file information returned by ListAt supports the FileInfoExtendedData interface, use it to retrieve extended data and pass it back to the client. In similar fashion, we add the FileInfoUidGid interface to allow for implementations to return uid and gid data, even if the os lacks support in syscall.Stat_t. Signed-off-by: Peter Verraedt --- attrs.go | 31 +++++++++++++++++++++++++++++++ ls_formatting.go | 7 +++++++ packet.go | 9 +++++++++ request-interfaces.go | 18 +++++++++++++----- 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/attrs.go b/attrs.go index 2bb2d576..894d080e 100644 --- a/attrs.go +++ b/attrs.go @@ -69,6 +69,20 @@ func fileInfoFromStat(stat *FileStat, name string) os.FileInfo { } } +// FileInfoUidGid extends os.FileInfo and adds callbacks for Uid and Gid retrieval, +// as an alternative to *syscall.Stat_t objects on unix systems. +type FileInfoUidGid interface { + os.FileInfo + Uid() uint32 + Gid() uint32 +} + +// FileInfoUidGid extends os.FileInfo and adds a callbacks for extended data retrieval. +type FileInfoExtendedData interface { + os.FileInfo + Extended() []StatExtended +} + func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) { mtime := fi.ModTime().Unix() atime := mtime @@ -86,5 +100,22 @@ func fileStatFromInfo(fi os.FileInfo) (uint32, *FileStat) { // os specific file stat decoding fileStatFromInfoOs(fi, &flags, fileStat) + // The call above will include the sshFileXferAttrUIDGID in case + // the os.FileInfo can be casted to *syscall.Stat_t on unix. + // If fi implements FileInfoUidGid, retrieve Uid, Gid from it instead. + if fiExt, ok := fi.(FileInfoUidGid); ok { + flags |= sshFileXferAttrUIDGID + fileStat.UID = fiExt.Uid() + fileStat.GID = fiExt.Gid() + } + + // if fi implements FileInfoExtendedData, retrieve extended data from it + if fiExt, ok := fi.(FileInfoExtendedData); ok { + fileStat.Extended = fiExt.Extended() + if len(fileStat.Extended) > 0 { + flags |= sshFileXferAttrExtended + } + } + return flags, fileStat } diff --git a/ls_formatting.go b/ls_formatting.go index e083e22a..19271ad7 100644 --- a/ls_formatting.go +++ b/ls_formatting.go @@ -60,6 +60,13 @@ func runLs(idLookup NameLookupFileLister, dirent os.FileInfo) string { uid = lsFormatID(sys.UID) gid = lsFormatID(sys.GID) default: + if fiExt, ok := dirent.(FileInfoUidGid); ok { + uid = lsFormatID(fiExt.Uid()) + gid = lsFormatID(fiExt.Gid()) + + break + } + numLinks, uid, gid = lsLinksUIDGID(dirent) } diff --git a/packet.go b/packet.go index d89ad997..b06d0b95 100644 --- a/packet.go +++ b/packet.go @@ -71,6 +71,15 @@ func marshalFileInfo(b []byte, fi os.FileInfo) []byte { b = marshalUint32(b, fileStat.Mtime) } + if flags&sshFileXferAttrExtended != 0 { + b = marshalUint32(b, uint32(len(fileStat.Extended))) + + for _, attr := range fileStat.Extended { + b = marshalString(b, attr.ExtType) + b = marshalString(b, attr.ExtData) + } + } + return b } diff --git a/request-interfaces.go b/request-interfaces.go index 75c6c393..2090e316 100644 --- a/request-interfaces.go +++ b/request-interfaces.go @@ -75,7 +75,7 @@ type StatVFSFileCmder interface { // Note in cases of an error, the error text will be sent to the client. // Called for Methods: List, Stat, Readlink // -// Since Filelist returns an os.FileInfo, this can make it non-ideal for impelmenting Readlink. +// Since Filelist returns an os.FileInfo, this can make it non-ideal for implementing Readlink. // This is because the Name receiver method defined by that interface defines that it should only return the base name. // However, Readlink is required to be capable of returning essentially any arbitrary valid path relative or absolute. // In order to implement this more expressive requirement, implement [ReadlinkFileLister] which will then be used instead. @@ -131,11 +131,19 @@ type NameLookupFileLister interface { LookupGroupName(string) string } -// ListerAt does for file lists what io.ReaderAt does for files. -// ListAt should return the number of entries copied and an io.EOF -// error if at end of list. This is testable by comparing how many you -// copied to how many could be copied (eg. n < len(ls) below). +// ListerAt does for file lists what io.ReaderAt does for files, i.e. a []os.FileInfo buffer is passed to the ListAt function +// and the entries that are populated in the buffer will be passed to the client. +// +// ListAt should return the number of entries copied and an io.EOF error if at end of list. +// This is testable by comparing how many you copied to how many could be copied (eg. n < len(ls) below). // The copy() builtin is best for the copying. +// +// Uid and gid information will on unix systems be retrieved from [os.FileInfo.Sys] +// if this function returns a [syscall.Stat_t] when called on a populated entry. +// Alternatively, if the entry implements [FileInfoUidGid], it will be used for uid and gid information. +// +// If a populated entry implements [FileInfoExtendedData], extended attributes will also be returned to the client. +// // Note in cases of an error, the error text will be sent to the client. type ListerAt interface { ListAt([]os.FileInfo, int64) (int, error)