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..b54fa35b 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. @@ -136,6 +136,13 @@ type NameLookupFileLister interface { // 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. +// ListerAt provides entries that satisfy the os.FileInfo interface. +// Uid and gid information will on unix systems be retrieved from +// [os.FileInfo.Sys] if this function returns a [syscall.Stat_t]. +// Alternatively, if the entry implements [FileInfoUidGid], it will be +// used for uid and gid information. +// If the entry implements [FileInfoExtended], 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)