blob: 1ddb34f337ee27a5a8f119b90d92b1d1228dcbb7 [file] [log] [blame]
Lorenz Brun9956e722021-03-24 18:48:55 +01001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package loop
18
19import (
20 "encoding/binary"
21 "io"
22 "io/ioutil"
23 "math"
24 "os"
25 "runtime"
26 "syscall"
27 "testing"
28 "unsafe"
29
30 "github.com/stretchr/testify/assert"
31 "github.com/stretchr/testify/require"
32 "golang.org/x/sys/unix"
33)
34
35// Write a test file with a very specific pattern (increasing little-endian 16 bit unsigned integers) to detect offset
36// correctness. File is always 128KiB large (2^16 * 2 bytes).
37func makeTestFile() *os.File {
38 f, err := ioutil.TempFile("/tmp", "")
39 if err != nil {
40 panic(err)
41 }
42 for i := 0; i <= math.MaxUint16; i++ {
43 if err := binary.Write(f, binary.LittleEndian, uint16(i)); err != nil {
44 panic(err)
45 }
46 }
47 if _, err := f.Seek(0, io.SeekStart); err != nil {
48 panic(err)
49 }
50 return f
51}
52
53func getBlkdevSize(f *os.File) (size uint64) {
54 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 {
55 panic(err)
56 }
57 return
58}
59
60func getOffsetFromContent(dev *Device) (firstIndex uint16) {
61 if err := binary.Read(dev.dev, binary.LittleEndian, &firstIndex); err != nil {
62 panic(err)
63 }
64 firstIndex *= 2 // 2 bytes per index
65 return
66}
67
68func setupCreate(t *testing.T, config Config) *Device {
69 f := makeTestFile()
70 dev, err := Create(f, config)
71 defer f.Close()
72 assert.NoError(t, err)
73 t.Cleanup(func() {
74 if dev != nil {
75 dev.Remove()
76 }
77 os.Remove(f.Name())
78 })
79 if dev == nil {
80 t.FailNow()
81 }
82 return dev
83}
84
85func TestDeviceAccessors(t *testing.T) {
86 if os.Getenv("IN_KTEST") != "true" {
87 t.Skip("Not in ktest")
88 }
89 dev := setupCreate(t, Config{})
90
91 devPath, err := dev.DevPath()
92 assert.NoError(t, err)
93 require.Equal(t, "/dev/loop0", devPath)
94
95 var stat unix.Stat_t
96 assert.NoError(t, unix.Stat("/dev/loop0", &stat))
97 devNum, err := dev.Dev()
98 assert.NoError(t, err)
99 require.Equal(t, stat.Rdev, devNum)
100
101 backingFile, err := dev.BackingFilePath()
102 assert.NoError(t, err)
103 // The filename of the temporary file is not available in this context, but we know that the file
104 // needs to be in /tmp, which should be a good-enough test.
105 assert.Contains(t, backingFile, "/tmp/")
106}
107
108func TestCreate(t *testing.T) {
109 if os.Getenv("IN_KTEST") != "true" {
110 t.Skip("Not in ktest")
111 }
112 t.Parallel()
113 tests := []struct {
114 name string
115 config Config
116 validate func(t *testing.T, dev *Device)
117 }{
118 {"NoOpts", Config{}, func(t *testing.T, dev *Device) {
119 require.Equal(t, uint64(128*1024), getBlkdevSize(dev.dev))
120 require.Equal(t, uint16(0), getOffsetFromContent(dev))
121
122 _, err := dev.dev.WriteString("test")
123 assert.NoError(t, err)
124 }},
125 {"DirectIO", Config{Flags: FlagDirectIO}, func(t *testing.T, dev *Device) {
126 require.Equal(t, uint64(128*1024), getBlkdevSize(dev.dev))
127
128 _, err := dev.dev.WriteString("test")
129 assert.NoError(t, err)
130 }},
131 {"ReadOnly", Config{Flags: FlagReadOnly}, func(t *testing.T, dev *Device) {
132 _, err := dev.dev.WriteString("test")
133 assert.Error(t, err)
134 }},
135 {"Mapping", Config{BlockSize: 512, SizeLimit: 2048, Offset: 4096}, func(t *testing.T, dev *Device) {
136 assert.Equal(t, uint16(4096), getOffsetFromContent(dev))
137 assert.Equal(t, uint64(2048), getBlkdevSize(dev.dev))
138 }},
139 }
140 for _, test := range tests {
141 t.Run(test.name, func(t *testing.T) {
142 dev := setupCreate(t, test.config)
143 test.validate(t, dev)
144 assert.NoError(t, dev.Remove())
145 })
146 }
147}
148
149func TestOpenBadDevice(t *testing.T) {
150 if os.Getenv("IN_KTEST") != "true" {
151 t.Skip("Not in ktest")
152 }
153 dev, err := Open("/dev/null")
154 require.Error(t, err)
155 if dev != nil { // Prevent leaks in case this test fails
156 dev.Close()
157 }
158}
159
160func TestOpen(t *testing.T) {
161 if os.Getenv("IN_KTEST") != "true" {
162 t.Skip("Not in ktest")
163 }
164 f := makeTestFile()
165 defer os.Remove(f.Name())
166 defer f.Close()
167 dev, err := Create(f, Config{})
168 assert.NoError(t, err)
169 path, err := dev.DevPath()
170 assert.NoError(t, err)
171 assert.NoError(t, dev.Close())
172 reopenedDev, err := Open(path)
173 assert.NoError(t, err)
174 defer reopenedDev.Remove()
175 reopenedDevPath, err := reopenedDev.DevPath()
176 assert.NoError(t, err)
177 require.Equal(t, path, reopenedDevPath) // Still needs to be the same device
178}
179
180func TestResize(t *testing.T) {
181 if os.Getenv("IN_KTEST") != "true" {
182 t.Skip("Not in ktest")
183 }
184 f, err := ioutil.TempFile("/tmp", "")
185 assert.NoError(t, err)
186 empty1K := make([]byte, 1024)
187 for i := 0; i < 64; i++ {
188 _, err := f.Write(empty1K)
189 assert.NoError(t, err)
190 }
191 dev, err := Create(f, Config{})
192 assert.NoError(t, err)
193 require.Equal(t, uint64(64*1024), getBlkdevSize(dev.dev))
194 for i := 0; i < 32; i++ {
195 _, err := f.Write(empty1K)
196 assert.NoError(t, err)
197 }
198 assert.NoError(t, f.Sync())
199 assert.NoError(t, dev.RefreshSize())
200 require.Equal(t, uint64(96*1024), getBlkdevSize(dev.dev))
201}
202
203func TestStructSize(t *testing.T) {
204 if runtime.GOOS != "linux" && runtime.GOARCH != "amd64" {
205 t.Skip("Reference value not available")
206 }
207 require.Equal(t, uintptr(304), unsafe.Sizeof(loopConfig{}))
208}