package sftp // ssh_FXP_ATTRS support // see https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt#section-5 import ( "os" "time" ) const ( sshFileXferAttrSize = 0x00000001 sshFileXferAttrUIDGID = 0x00000002 sshFileXferAttrPermissions = 0x00000004 sshFileXferAttrACmodTime = 0x00000008 sshFileXferAttrExtended = 0x80000000 sshFileXferAttrAll = sshFileXferAttrSize | sshFileXferAttrUIDGID | sshFileXferAttrPermissions | sshFileXferAttrACmodTime | sshFileXferAttrExtended ) // fileInfo is an artificial type designed to satisfy os.FileInfo. type fileInfo struct { name string stat *FileStat } // Name returns the base name of the file. func (fi *fileInfo) Name() string { return fi.name } // Size returns the length in bytes for regular files; system-dependent for others. func (fi *fileInfo) Size() int64 { return int64(fi.stat.Size) } // Mode returns file mode bits. func (fi *fileInfo) Mode() os.FileMode { return fi.stat.FileMode() } // ModTime returns the last modification time of the file. func (fi *fileInfo) ModTime() time.Time { return fi.stat.ModTime() } // IsDir returns true if the file is a directory. func (fi *fileInfo) IsDir() bool { return fi.Mode().IsDir() } func (fi *fileInfo) Sys() interface{} { return fi.stat } // FileStat holds the original unmarshalled values from a call to READDIR or // *STAT. It is exported for the purposes of accessing the raw values via // os.FileInfo.Sys(). It is also used server side to store the unmarshalled // values for SetStat. type FileStat struct { Size uint64 Mode uint32 Mtime uint32 Atime uint32 UID uint32 GID uint32 Extended []StatExtended } // ModTime returns the Mtime SFTP file attribute converted to a time.Time func (fs *FileStat) ModTime() time.Time { return time.Unix(int64(fs.Mtime), 0) } // AccessTime returns the Atime SFTP file attribute converted to a time.Time func (fs *FileStat) AccessTime() time.Time { return time.Unix(int64(fs.Atime), 0) } // FileMode returns the Mode SFTP file attribute converted to an os.FileMode func (fs *FileStat) FileMode() os.FileMode { return toFileMode(fs.Mode) } // StatExtended contains additional, extended information for a FileStat. type StatExtended struct { ExtType string ExtData string } func fileInfoFromStat(stat *FileStat, name string) os.FileInfo { return &fileInfo{ name: name, stat: stat, } } // 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 var flags uint32 = sshFileXferAttrSize | sshFileXferAttrPermissions | sshFileXferAttrACmodTime fileStat := &FileStat{ Size: uint64(fi.Size()), Mode: fromFileMode(fi.Mode()), Mtime: uint32(mtime), Atime: uint32(atime), } // 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 }