1 package compactext4
2
3 import (
4 "bytes"
5 "encoding/binary"
6 "fmt"
7 "io"
8 "os"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/Microsoft/hcsshim/ext4/internal/format"
14 "github.com/Microsoft/hcsshim/internal/memory"
15 )
16
17 type testFile struct {
18 Path string
19 File *File
20 Data []byte
21 DataSize int64
22 Link string
23 ExpectError bool
24 }
25
26 var (
27 data []byte
28 name string
29 )
30
31 func init() {
32 data = make([]byte, BlockSize*2)
33 for i := range data {
34 data[i] = uint8(i)
35 }
36
37 nameb := make([]byte, 300)
38 for i := range nameb {
39 nameb[i] = byte('0' + i%10)
40 }
41 name = string(nameb)
42 }
43
44 type largeData struct {
45 pos int64
46 }
47
48 func (d *largeData) Read(b []byte) (int, error) {
49 p := d.pos
50 var pb [8]byte
51 for i := range b {
52 binary.LittleEndian.PutUint64(pb[:], uint64(p+int64(i)))
53 b[i] = pb[i%8]
54 }
55 d.pos += int64(len(b))
56 return len(b), nil
57 }
58
59 func (tf *testFile) Reader() io.Reader {
60 if tf.DataSize != 0 {
61 return io.LimitReader(&largeData{}, tf.DataSize)
62 }
63 return bytes.NewReader(tf.Data)
64 }
65
66 func createTestFile(t *testing.T, w *Writer, tf testFile) {
67 t.Helper()
68 var err error
69 if tf.File != nil {
70 tf.File.Size = int64(len(tf.Data))
71 if tf.File.Size == 0 {
72 tf.File.Size = tf.DataSize
73 }
74 err = w.Create(tf.Path, tf.File)
75 } else {
76 err = w.Link(tf.Link, tf.Path)
77 }
78 if tf.ExpectError && err == nil {
79 t.Errorf("%s: expected error", tf.Path)
80 } else if !tf.ExpectError && err != nil {
81 t.Error(err)
82 } else {
83 _, err := io.Copy(w, tf.Reader())
84 if err != nil {
85 t.Error(err)
86 }
87 }
88 }
89
90 func expectedMode(f *File) uint16 {
91 switch f.Mode & format.TypeMask {
92 case 0:
93 return f.Mode | S_IFREG
94 case S_IFLNK:
95 return f.Mode | 0777
96 default:
97 return f.Mode
98 }
99 }
100
101 func expectedSize(f *File) int64 {
102 switch f.Mode & format.TypeMask {
103 case 0, S_IFREG:
104 return f.Size
105 case S_IFLNK:
106 return int64(len(f.Linkname))
107 default:
108 return 0
109 }
110 }
111
112 func xattrsEqual(x1, x2 map[string][]byte) bool {
113 if len(x1) != len(x2) {
114 return false
115 }
116 for name, value := range x1 {
117 if !bytes.Equal(x2[name], value) {
118 return false
119 }
120 }
121 return true
122 }
123
124 func fileEqual(f1, f2 *File) bool {
125 return f1.Linkname == f2.Linkname &&
126 expectedSize(f1) == expectedSize(f2) &&
127 expectedMode(f1) == expectedMode(f2) &&
128 f1.Uid == f2.Uid &&
129 f1.Gid == f2.Gid &&
130 f1.Atime.Equal(f2.Atime) &&
131 f1.Ctime.Equal(f2.Ctime) &&
132 f1.Mtime.Equal(f2.Mtime) &&
133 f1.Crtime.Equal(f2.Crtime) &&
134 f1.Devmajor == f2.Devmajor &&
135 f1.Devminor == f2.Devminor &&
136 xattrsEqual(f1.Xattrs, f2.Xattrs)
137 }
138
139 func runTestsOnFiles(t *testing.T, testFiles []testFile, opts ...Option) {
140 t.Helper()
141 image := "testfs.img"
142 imagef, err := os.Create(image)
143 if err != nil {
144 t.Fatal(err)
145 }
146 defer os.Remove(image)
147 defer imagef.Close()
148
149 w := NewWriter(imagef, opts...)
150 for _, tf := range testFiles {
151 createTestFile(t, w, tf)
152 if !tf.ExpectError && tf.File != nil {
153 f, err := w.Stat(tf.Path)
154 if err != nil {
155 if !strings.Contains(err.Error(), "cannot retrieve") {
156 t.Error(err)
157 }
158 } else if !fileEqual(f, tf.File) {
159 t.Errorf("%s: stat mismatch: %#v %#v", tf.Path, tf.File, f)
160 }
161 }
162 }
163
164 if t.Failed() {
165 return
166 }
167
168 if err := w.Close(); err != nil {
169 t.Fatal(err)
170 }
171
172 fsck(t, image)
173
174 mountPath := "testmnt"
175
176 if mountImage(t, image, mountPath) {
177 defer unmountImage(t, mountPath)
178 validated := make(map[string]*testFile)
179 for i := range testFiles {
180 tf := testFiles[len(testFiles)-i-1]
181 if validated[tf.Link] != nil {
182
183
184 for j := range testFiles[:len(testFiles)-i-1] {
185 otf := testFiles[j]
186 if otf.Path == tf.Link && !otf.ExpectError {
187 tf = otf
188 break
189 }
190 }
191 }
192 if !tf.ExpectError && validated[tf.Path] == nil {
193 verifyTestFile(t, mountPath, tf)
194 validated[tf.Path] = &tf
195 }
196 }
197 }
198 }
199
200 func TestBasic(t *testing.T) {
201 now := time.Now()
202 testFiles := []testFile{
203 {Path: "empty", File: &File{Mode: 0644}},
204 {Path: "small", File: &File{Mode: 0644}, Data: data[:40]},
205 {Path: "time", File: &File{Atime: now, Ctime: now.Add(time.Second), Mtime: now.Add(time.Hour)}},
206 {Path: "block_1", File: &File{Mode: 0644}, Data: data[:BlockSize]},
207 {Path: "block_2", File: &File{Mode: 0644}, Data: data[:BlockSize*2]},
208 {Path: "symlink", File: &File{Linkname: "block_1", Mode: format.S_IFLNK}},
209 {Path: "symlink_59", File: &File{Linkname: name[:59], Mode: format.S_IFLNK}},
210 {Path: "symlink_60", File: &File{Linkname: name[:60], Mode: format.S_IFLNK}},
211 {Path: "symlink_120", File: &File{Linkname: name[:120], Mode: format.S_IFLNK}},
212 {Path: "symlink_300", File: &File{Linkname: name[:300], Mode: format.S_IFLNK}},
213 {Path: "dir", File: &File{Mode: format.S_IFDIR | 0755}},
214 {Path: "dir/fifo", File: &File{Mode: format.S_IFIFO}},
215 {Path: "dir/sock", File: &File{Mode: format.S_IFSOCK}},
216 {Path: "dir/blk", File: &File{Mode: format.S_IFBLK, Devmajor: 0x5678, Devminor: 0x1234}},
217 {Path: "dir/chr", File: &File{Mode: format.S_IFCHR, Devmajor: 0x5678, Devminor: 0x1234}},
218 {Path: "dir/hard_link", Link: "small"},
219 }
220
221 runTestsOnFiles(t, testFiles)
222 }
223
224 func TestLargeDirectory(t *testing.T) {
225 testFiles := []testFile{
226 {Path: "bigdir", File: &File{Mode: format.S_IFDIR | 0755}},
227 }
228 for i := 0; i < 50000; i++ {
229 testFiles = append(testFiles, testFile{
230 Path: fmt.Sprintf("bigdir/%d", i), File: &File{Mode: 0644},
231 })
232 }
233
234 runTestsOnFiles(t, testFiles)
235 }
236
237 func TestInlineData(t *testing.T) {
238 testFiles := []testFile{
239 {Path: "inline_30", File: &File{Mode: 0644}, Data: data[:30]},
240 {Path: "inline_60", File: &File{Mode: 0644}, Data: data[:60]},
241 {Path: "inline_120", File: &File{Mode: 0644}, Data: data[:120]},
242 {Path: "inline_full", File: &File{Mode: 0644}, Data: data[:inlineDataSize]},
243 {Path: "block_min", File: &File{Mode: 0644}, Data: data[:inlineDataSize+1]},
244 }
245
246 runTestsOnFiles(t, testFiles, InlineData)
247 }
248
249 func TestXattrs(t *testing.T) {
250 testFiles := []testFile{
251 {Path: "withsmallxattrs",
252 File: &File{
253 Mode: format.S_IFREG | 0644,
254 Xattrs: map[string][]byte{
255 "user.foo": []byte("test"),
256 "user.bar": []byte("test2"),
257 },
258 },
259 },
260 {Path: "withlargexattrs",
261 File: &File{
262 Mode: format.S_IFREG | 0644,
263 Xattrs: map[string][]byte{
264 "user.foo": data[:100],
265 "user.bar": data[:50],
266 },
267 },
268 },
269 }
270 runTestsOnFiles(t, testFiles)
271 }
272
273 func TestReplace(t *testing.T) {
274 testFiles := []testFile{
275 {Path: "lost+found", ExpectError: true, File: &File{}},
276 {Path: "lost+found", File: &File{Mode: format.S_IFDIR | 0777}},
277
278 {Path: "dir", File: &File{Mode: format.S_IFDIR | 0777}},
279 {Path: "dir/file", File: &File{}},
280 {Path: "dir", File: &File{Mode: format.S_IFDIR | 0700}},
281
282 {Path: "file", File: &File{}},
283 {Path: "file", File: &File{Mode: 0600}},
284 {Path: "file2", File: &File{}},
285 {Path: "link", Link: "file2"},
286 {Path: "file2", File: &File{Mode: 0600}},
287
288 {Path: "nolinks", File: &File{}},
289 {Path: "nolinks", ExpectError: true, Link: "file"},
290
291 {Path: "onelink", File: &File{}},
292 {Path: "onelink2", Link: "onelink"},
293 {Path: "onelink", Link: "file"},
294
295 {Path: "", ExpectError: true, File: &File{}},
296 {Path: "", ExpectError: true, Link: "file"},
297 {Path: "", File: &File{Mode: format.S_IFDIR | 0777}},
298
299 {Path: "smallxattr", File: &File{Xattrs: map[string][]byte{"user.foo": data[:4]}}},
300 {Path: "smallxattr", File: &File{Xattrs: map[string][]byte{"user.foo": data[:8]}}},
301
302 {Path: "smallxattr_delete", File: &File{Xattrs: map[string][]byte{"user.foo": data[:4]}}},
303 {Path: "smallxattr_delete", File: &File{}},
304
305 {Path: "largexattr", File: &File{Xattrs: map[string][]byte{"user.small": data[:8], "user.foo": data[:200]}}},
306 {Path: "largexattr", File: &File{Xattrs: map[string][]byte{"user.small": data[:12], "user.foo": data[:400]}}},
307
308 {Path: "largexattr", File: &File{Xattrs: map[string][]byte{"user.foo": data[:200]}}},
309 {Path: "largexattr_delete", File: &File{}},
310 }
311 runTestsOnFiles(t, testFiles)
312 }
313
314 func TestTime(t *testing.T) {
315 now := time.Now()
316 now2 := fsTimeToTime(timeToFsTime(now))
317 if now.UnixNano() != now2.UnixNano() {
318 t.Fatalf("%s != %s", now, now2)
319 }
320 }
321
322 func TestLargeFile(t *testing.T) {
323 testFiles := []testFile{
324 {Path: "small", File: &File{}, DataSize: memory.MiB},
325 {Path: "medium", File: &File{}, DataSize: 200 * memory.MiB},
326 {Path: "large", File: &File{}, DataSize: 600 * memory.MiB},
327 }
328 runTestsOnFiles(t, testFiles)
329 }
330
331 func TestFileLinkLimit(t *testing.T) {
332 testFiles := []testFile{
333 {Path: "file", File: &File{}},
334 }
335 for i := 0; i < format.MaxLinks; i++ {
336 testFiles = append(testFiles, testFile{Path: fmt.Sprintf("link%d", i), Link: "file"})
337 }
338 testFiles[len(testFiles)-1].ExpectError = true
339 runTestsOnFiles(t, testFiles)
340 }
341
342 func TestDirLinkLimit(t *testing.T) {
343 testFiles := []testFile{
344 {Path: "dir", File: &File{Mode: S_IFDIR}},
345 }
346 for i := 0; i < format.MaxLinks-1; i++ {
347 testFiles = append(testFiles, testFile{Path: fmt.Sprintf("dir/%d", i), File: &File{Mode: S_IFDIR}})
348 }
349 testFiles[len(testFiles)-1].ExpectError = true
350 runTestsOnFiles(t, testFiles)
351 }
352
353 func TestLargeDisk(t *testing.T) {
354 testFiles := []testFile{
355 {Path: "file", File: &File{}},
356 }
357 runTestsOnFiles(t, testFiles, MaximumDiskSize(maxMaxDiskSize))
358 }
359
View as plain text