diff --git a/attrs.go b/attrs.go index 542a450..4453b85 100644 --- a/attrs.go +++ b/attrs.go @@ -17,60 +17,90 @@ const ( ssh_FILEXFER_ATTR_EXTENDED = 0x80000000 ) -type attr struct { - name string - size uint64 - mode os.FileMode +// fileInfo is an artificial type designed to satisfy os.FileInfo. +type fileInfo struct { + name string + size int64 + mode os.FileMode mtime time.Time + sys interface{} } // Name returns the base name of the file. -func (a *attr) Name() string { return a.name } +func (a *fileInfo) Name() string { return a.name } // Size returns the length in bytes for regular files; system-dependent for others. -func (a *attr) Size() int64 { return int64(a.size) } +func (a *fileInfo) Size() int64 { return a.size } // Mode returns file mode bits. -func (a *attr) Mode() os.FileMode { return a.mode } +func (a *fileInfo) Mode() os.FileMode { return a.mode } // ModTime returns the last modification time of the file. -func (a *attr) ModTime() time.Time { return a.mtime } +func (a *fileInfo) ModTime() time.Time { return a.mtime } // IsDir returns true if the file is a directory. -func (a *attr) IsDir() bool { return a.Mode().IsDir() } +func (a *fileInfo) IsDir() bool { return a.Mode().IsDir() } -func (a *attr) Sys() interface{} { return a } +func (a *fileInfo) Sys() interface{} { return a.sys } -func unmarshalAttrs(b []byte) (*attr, []byte) { +// FileStat holds the original unmarshalled values from a call LSTAT. +type FileStat struct { + Size uint64 + Mode uint32 + Mtime uint32 + Atime uint32 + Uid uint32 + Gid uint32 + Extended []StatExtended +} + +type StatExtended struct { + ExtType string + ExtData string +} + +func fileInfoFromStat(st *FileStat, name string) os.FileInfo { + fs := &fileInfo{ + name: name, + size: int64(st.Size), + mode: toFileMode(st.Mode), + mtime: time.Unix(int64(st.Mtime), 0), + sys: st, + } + return fs +} + +func unmarshalAttrs(b []byte) (*FileStat, []byte) { flags, b := unmarshalUint32(b) - var a attr + var a FileStat if flags&ssh_FILEXFER_ATTR_SIZE == ssh_FILEXFER_ATTR_SIZE { - a.size, b = unmarshalUint64(b) + a.Size, b = unmarshalUint64(b) } if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID { - _, b = unmarshalUint32(b) // discarded + a.Uid, b = unmarshalUint32(b) } if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID { - _, b = unmarshalUint32(b) // discarded + a.Gid, b = unmarshalUint32(b) } if flags&ssh_FILEXFER_ATTR_PERMISSIONS == ssh_FILEXFER_ATTR_PERMISSIONS { - var mode uint32 - mode, b = unmarshalUint32(b) - a.mode = toFileMode(mode) + a.Mode, b = unmarshalUint32(b) } if flags&ssh_FILEXFER_ATTR_ACMODTIME == ssh_FILEXFER_ATTR_ACMODTIME { - var mtime uint32 - _, b = unmarshalUint32(b) // discarded - mtime, b = unmarshalUint32(b) - a.mtime = time.Unix(int64(mtime), 0) + a.Atime, b = unmarshalUint32(b) + a.Mtime, b = unmarshalUint32(b) } if flags&ssh_FILEXFER_ATTR_EXTENDED == ssh_FILEXFER_ATTR_EXTENDED { var count uint32 count, b = unmarshalUint32(b) + ext := make([]StatExtended,count,count) for i := uint32(0); i < count; i++ { - _, b = unmarshalString(b) - _, b = unmarshalString(b) + var typ string + var data string + typ, b = unmarshalString(b) + data, b = unmarshalString(b) + ext[i] = StatExtended{typ,data} } + a.Extended = ext } return &a, b } diff --git a/client.go b/client.go index 9db758d..2913531 100644 --- a/client.go +++ b/client.go @@ -139,13 +139,12 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) { var filename string filename, data = unmarshalString(data) _, data = unmarshalString(data) // discard longname - var attr *attr + var attr *FileStat attr, data = unmarshalAttrs(data) if filename == "." || filename == ".." { continue } - attr.name = path.Base(filename) - attrs = append(attrs, attr) + attrs = append(attrs, fileInfoFromStat(attr, path.Base(filename)) ) } case ssh_FXP_STATUS: // TODO(dfc) scope warning! @@ -216,8 +215,7 @@ func (c *Client) Lstat(p string) (os.FileInfo, error) { return nil, &unexpectedIdErr{id, sid} } attr, _ := unmarshalAttrs(data) - attr.name = path.Base(p) - return attr, nil + return fileInfoFromStat(attr, path.Base(p)), nil case ssh_FXP_STATUS: return nil, unmarshalStatus(id, data) default: @@ -405,7 +403,7 @@ func (c *Client) close(handle string) error { } } -func (c *Client) fstat(handle string) (*attr, error) { +func (c *Client) fstat(handle string) (*FileStat, error) { type packet struct { Type byte Id uint32 @@ -638,10 +636,10 @@ func (f *File) Read(b []byte) (int, error) { // error. func (f *File) Stat() (os.FileInfo, error) { fi, err := f.c.fstat(f.handle) - if err == nil { - fi.name = path.Base(f.path) + if err != nil { + return nil, err } - return fi, err + return fileInfoFromStat(fi, path.Base(f.path)), nil } // clamp writes to less than 32k