1
2
3
4 package compactext4
5
6 import (
7 "bytes"
8 "fmt"
9 "io"
10 "os"
11 "os/exec"
12 "path"
13 "syscall"
14 "testing"
15 "time"
16 "unsafe"
17
18 "github.com/Microsoft/hcsshim/ext4/internal/format"
19 )
20
21 func timeEqual(ts syscall.Timespec, t time.Time) bool {
22 sec, nsec := t.Unix(), t.Nanosecond()
23 if t.IsZero() {
24 sec, nsec = 0, 0
25 }
26 return ts.Sec == sec && int(ts.Nsec) == nsec
27 }
28
29 func expectedDevice(f *File) uint64 {
30 return uint64(f.Devminor&0xff | f.Devmajor<<8 | (f.Devminor&0xffffff00)<<12)
31 }
32
33 func llistxattr(path string, b []byte) (int, error) {
34 pathp, err := syscall.BytePtrFromString(path)
35 if err != nil {
36 return 0, &os.PathError{Path: path, Op: "llistxattr", Err: err}
37 }
38 var p unsafe.Pointer
39 if len(b) > 0 {
40 p = unsafe.Pointer(&b[0])
41 }
42 r, _, e := syscall.Syscall(syscall.SYS_LLISTXATTR, uintptr(unsafe.Pointer(pathp)), uintptr(p), uintptr(len(b)))
43 if e != 0 {
44 return 0, &os.PathError{Path: path, Op: "llistxattr", Err: syscall.Errno(e)}
45 }
46 return int(r), nil
47 }
48
49 func lgetxattr(path string, name string, b []byte) (int, error) {
50 pathp, err := syscall.BytePtrFromString(path)
51 if err != nil {
52 return 0, &os.PathError{Path: path, Op: "llistxattr", Err: err}
53 }
54 namep, err := syscall.BytePtrFromString(name)
55 if err != nil {
56 return 0, &os.PathError{Path: path, Op: "llistxattr", Err: err}
57 }
58 var p unsafe.Pointer
59 if len(b) > 0 {
60 p = unsafe.Pointer(&b[0])
61 }
62 r, _, e := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathp)), uintptr(unsafe.Pointer(namep)), uintptr(p), uintptr(len(b)), 0, 0)
63 if e != 0 {
64 return 0, &os.PathError{Path: path, Op: "lgetxattr", Err: syscall.Errno(e)}
65 }
66 return int(r), nil
67 }
68
69 func readXattrs(path string) (map[string][]byte, error) {
70 xattrs := make(map[string][]byte)
71 var buf [4096]byte
72 var buf2 [4096]byte
73 b := buf[:]
74 n, err := llistxattr(path, b)
75 if err != nil {
76 return nil, err
77 }
78 b = b[:n]
79 for len(b) != 0 {
80 nn := bytes.IndexByte(b, 0)
81 name := string(b[:nn])
82 b = b[nn+1:]
83 vn, err := lgetxattr(path, name, buf2[:])
84 if err != nil {
85 return nil, err
86 }
87 value := buf2[:vn]
88 xattrs[name] = value
89 }
90 return xattrs, nil
91 }
92
93 func streamEqual(r1, r2 io.Reader) (bool, error) {
94 var b [4096]byte
95 var b2 [4096]byte
96 for {
97 n, err := r1.Read(b[:])
98 if n == 0 {
99 if err == io.EOF {
100 break
101 }
102 if err == nil {
103 continue
104 }
105 return false, err
106 }
107 _, err = io.ReadFull(r2, b2[:n])
108 if err == io.EOF || err == io.ErrUnexpectedEOF {
109 return false, nil
110 }
111 if err != nil {
112 return false, err
113 }
114 if !bytes.Equal(b[n:], b2[n:]) {
115 return false, nil
116 }
117 }
118
119 _, err := r2.Read(b[:1])
120 if err == nil {
121 return false, nil
122 }
123 if err != io.EOF {
124 return false, err
125 }
126 return true, nil
127 }
128
129 func verifyTestFile(t *testing.T, mountPath string, tf testFile) {
130 t.Helper()
131 name := path.Join(mountPath, tf.Path)
132 fi, err := os.Lstat(name)
133 if err != nil {
134 t.Error(err)
135 return
136 }
137 st := fi.Sys().(*syscall.Stat_t)
138 if tf.File != nil {
139 if st.Mode != uint32(expectedMode(tf.File)) ||
140 st.Uid != tf.File.Uid ||
141 st.Gid != tf.File.Gid ||
142 (!fi.IsDir() && st.Size != expectedSize(tf.File)) ||
143 st.Rdev != expectedDevice(tf.File) ||
144 !timeEqual(st.Atim, tf.File.Atime) ||
145 !timeEqual(st.Mtim, tf.File.Mtime) ||
146 !timeEqual(st.Ctim, tf.File.Ctime) {
147 t.Errorf("%s: stat mismatch, expected: %#v got: %#v", tf.Path, tf.File, st)
148 }
149
150 xattrs, err := readXattrs(name)
151 if err != nil {
152 t.Error(err)
153 } else if !xattrsEqual(xattrs, tf.File.Xattrs) {
154 t.Errorf("%s: xattr mismatch, expected: %#v got: %#v", tf.Path, tf.File.Xattrs, xattrs)
155 }
156
157 switch tf.File.Mode & format.TypeMask {
158 case S_IFREG:
159 if f, err := os.Open(name); err != nil {
160 t.Error(err)
161 } else {
162 same, err := streamEqual(f, tf.Reader())
163 if err != nil {
164 t.Error(err)
165 } else if !same {
166 t.Errorf("%s: data mismatch", tf.Path)
167 }
168 f.Close()
169 }
170 case S_IFLNK:
171 if link, err := os.Readlink(name); err != nil {
172 t.Error(err)
173 } else if link != tf.File.Linkname {
174 t.Errorf("%s: link mismatch, expected: %s got: %s", tf.Path, tf.File.Linkname, link)
175 }
176 }
177 } else {
178 lfi, err := os.Lstat(path.Join(mountPath, tf.Link))
179 if err != nil {
180 t.Error(err)
181 return
182 }
183
184 lst := lfi.Sys().(*syscall.Stat_t)
185 if lst.Ino != st.Ino {
186 t.Errorf("%s: hard link mismatch with %s, expected inode: %d got inode: %d", tf.Path, tf.Link, lst.Ino, st.Ino)
187 }
188 }
189 }
190
191 type capHeader struct {
192 version uint32
193 pid int
194 }
195
196 type capData struct {
197 effective uint32
198 permitted uint32
199 inheritable uint32
200 }
201
202 const CAP_SYS_ADMIN = 21
203
204 type caps struct {
205 hdr capHeader
206 data [2]capData
207 }
208
209 func getCaps() (caps, error) {
210 var c caps
211
212
213 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 {
214 return c, fmt.Errorf("SYS_CAPGET: %v", errno)
215 }
216
217
218 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 {
219 return c, fmt.Errorf("SYS_CAPGET: %v", errno)
220 }
221
222 return c, nil
223 }
224
225 func mountImage(t *testing.T, image string, mountPath string) bool {
226 t.Helper()
227 caps, err := getCaps()
228 if err != nil || caps.data[0].effective&(1<<uint(CAP_SYS_ADMIN)) == 0 {
229 t.Log("cannot mount to run verification tests without CAP_SYS_ADMIN")
230 return false
231 }
232
233 err = os.MkdirAll(mountPath, 0777)
234 if err != nil {
235 t.Fatal(err)
236 }
237
238 out, err := exec.Command("mount", "-o", "loop,ro", "-t", "ext4", image, mountPath).CombinedOutput()
239 t.Logf("%s", out)
240 if err != nil {
241 t.Fatal(err)
242 }
243 return true
244 }
245
246 func unmountImage(t *testing.T, mountPath string) {
247 t.Helper()
248 out, err := exec.Command("umount", mountPath).CombinedOutput()
249 t.Logf("%s", out)
250 if err != nil {
251 t.Log(err)
252 }
253 }
254
255 func fsck(t *testing.T, image string) {
256 t.Helper()
257 cmd := exec.Command("e2fsck", "-v", "-f", "-n", image)
258 out, err := cmd.CombinedOutput()
259 t.Logf("%s", out)
260 if err != nil {
261 t.Fatal(err)
262 }
263 }
264
View as plain text