1package sftp
2
3// ssh_FXP_ATTRS support
4// see http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5
5
6import (
7	"os"
8	"time"
9)
10
11const (
12	sshFileXferAttrSize        = 0x00000001
13	sshFileXferAttrUIDGID      = 0x00000002
14	sshFileXferAttrPermissions = 0x00000004
15	sshFileXferAttrACmodTime   = 0x00000008
16	sshFileXferAttrExtented    = 0x80000000
17
18	sshFileXferAttrAll = sshFileXferAttrSize | sshFileXferAttrUIDGID | sshFileXferAttrPermissions |
19		sshFileXferAttrACmodTime | sshFileXferAttrExtented
20)
21
22// fileInfo is an artificial type designed to satisfy os.FileInfo.
23type fileInfo struct {
24	name  string
25	size  int64
26	mode  os.FileMode
27	mtime time.Time
28	sys   interface{}
29}
30
31// Name returns the base name of the file.
32func (fi *fileInfo) Name() string { return fi.name }
33
34// Size returns the length in bytes for regular files; system-dependent for others.
35func (fi *fileInfo) Size() int64 { return fi.size }
36
37// Mode returns file mode bits.
38func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
39
40// ModTime returns the last modification time of the file.
41func (fi *fileInfo) ModTime() time.Time { return fi.mtime }
42
43// IsDir returns true if the file is a directory.
44func (fi *fileInfo) IsDir() bool { return fi.Mode().IsDir() }
45
46func (fi *fileInfo) Sys() interface{} { return fi.sys }
47
48// FileStat holds the original unmarshalled values from a call to READDIR or
49// *STAT. It is exported for the purposes of accessing the raw values via
50// os.FileInfo.Sys(). It is also used server side to store the unmarshalled
51// values for SetStat.
52type FileStat struct {
53	Size     uint64
54	Mode     uint32
55	Mtime    uint32
56	Atime    uint32
57	UID      uint32
58	GID      uint32
59	Extended []StatExtended
60}
61
62// StatExtended contains additional, extended information for a FileStat.
63type StatExtended struct {
64	ExtType string
65	ExtData string
66}
67
68func fileInfoFromStat(st *FileStat, name string) os.FileInfo {
69	fs := &fileInfo{
70		name:  name,
71		size:  int64(st.Size),
72		mode:  toFileMode(st.Mode),
73		mtime: time.Unix(int64(st.Mtime), 0),
74		sys:   st,
75	}
76	return fs
77}
78
79func fileStatFromInfo(fi os.FileInfo) (uint32, FileStat) {
80	mtime := fi.ModTime().Unix()
81	atime := mtime
82	var flags uint32 = sshFileXferAttrSize |
83		sshFileXferAttrPermissions |
84		sshFileXferAttrACmodTime
85
86	fileStat := FileStat{
87		Size:  uint64(fi.Size()),
88		Mode:  fromFileMode(fi.Mode()),
89		Mtime: uint32(mtime),
90		Atime: uint32(atime),
91	}
92
93	// os specific file stat decoding
94	fileStatFromInfoOs(fi, &flags, &fileStat)
95
96	return flags, fileStat
97}
98
99func unmarshalAttrs(b []byte) (*FileStat, []byte) {
100	flags, b := unmarshalUint32(b)
101	return getFileStat(flags, b)
102}
103
104func getFileStat(flags uint32, b []byte) (*FileStat, []byte) {
105	var fs FileStat
106	if flags&sshFileXferAttrSize == sshFileXferAttrSize {
107		fs.Size, b, _ = unmarshalUint64Safe(b)
108	}
109	if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
110		fs.UID, b, _ = unmarshalUint32Safe(b)
111	}
112	if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
113		fs.GID, b, _ = unmarshalUint32Safe(b)
114	}
115	if flags&sshFileXferAttrPermissions == sshFileXferAttrPermissions {
116		fs.Mode, b, _ = unmarshalUint32Safe(b)
117	}
118	if flags&sshFileXferAttrACmodTime == sshFileXferAttrACmodTime {
119		fs.Atime, b, _ = unmarshalUint32Safe(b)
120		fs.Mtime, b, _ = unmarshalUint32Safe(b)
121	}
122	if flags&sshFileXferAttrExtented == sshFileXferAttrExtented {
123		var count uint32
124		count, b, _ = unmarshalUint32Safe(b)
125		ext := make([]StatExtended, count)
126		for i := uint32(0); i < count; i++ {
127			var typ string
128			var data string
129			typ, b, _ = unmarshalStringSafe(b)
130			data, b, _ = unmarshalStringSafe(b)
131			ext[i] = StatExtended{typ, data}
132		}
133		fs.Extended = ext
134	}
135	return &fs, b
136}
137
138func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
139	// attributes variable struct, and also variable per protocol version
140	// spec version 3 attributes:
141	// uint32   flags
142	// uint64   size           present only if flag SSH_FILEXFER_ATTR_SIZE
143	// uint32   uid            present only if flag SSH_FILEXFER_ATTR_UIDGID
144	// uint32   gid            present only if flag SSH_FILEXFER_ATTR_UIDGID
145	// uint32   permissions    present only if flag SSH_FILEXFER_ATTR_PERMISSIONS
146	// uint32   atime          present only if flag SSH_FILEXFER_ACMODTIME
147	// uint32   mtime          present only if flag SSH_FILEXFER_ACMODTIME
148	// uint32   extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED
149	// string   extended_type
150	// string   extended_data
151	// ...      more extended data (extended_type - extended_data pairs),
152	// 	   so that number of pairs equals extended_count
153
154	flags, fileStat := fileStatFromInfo(fi)
155
156	b = marshalUint32(b, flags)
157	if flags&sshFileXferAttrSize != 0 {
158		b = marshalUint64(b, fileStat.Size)
159	}
160	if flags&sshFileXferAttrUIDGID != 0 {
161		b = marshalUint32(b, fileStat.UID)
162		b = marshalUint32(b, fileStat.GID)
163	}
164	if flags&sshFileXferAttrPermissions != 0 {
165		b = marshalUint32(b, fileStat.Mode)
166	}
167	if flags&sshFileXferAttrACmodTime != 0 {
168		b = marshalUint32(b, fileStat.Atime)
169		b = marshalUint32(b, fileStat.Mtime)
170	}
171
172	return b
173}
174