blob: 16ead648161163981f06f0f36861f47092a06c8d [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
Serge Bazanski216fe7b2021-05-21 18:36:16 +020035// Write a test file with a very specific pattern (increasing little-endian 16
36// bit unsigned integers) to detect offset correctness. File is always 128KiB
37// large (2^16 * 2 bytes).
Lorenz Brun9956e722021-03-24 18:48:55 +010038func makeTestFile() *os.File {
39 f, err := ioutil.TempFile("/tmp", "")
40 if err != nil {
41 panic(err)
42 }
43 for i := 0; i <= math.MaxUint16; i++ {
44 if err := binary.Write(f, binary.LittleEndian, uint16(i)); err != nil {
45 panic(err)
46 }
47 }
48 if _, err := f.Seek(0, io.SeekStart); err != nil {
49 panic(err)
50 }
51 return f
52}
53
54func getBlkdevSize(f *os.File) (size uint64) {
55 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 {
56 panic(err)
57 }
58 return
59}
60
61func getOffsetFromContent(dev *Device) (firstIndex uint16) {
62 if err := binary.Read(dev.dev, binary.LittleEndian, &firstIndex); err != nil {
63 panic(err)
64 }
65 firstIndex *= 2 // 2 bytes per index
66 return
67}
68
69func setupCreate(t *testing.T, config Config) *Device {
70 f := makeTestFile()
71 dev, err := Create(f, config)
72 defer f.Close()
73 assert.NoError(t, err)
74 t.Cleanup(func() {
75 if dev != nil {
76 dev.Remove()
77 }
78 os.Remove(f.Name())
79 })
80 if dev == nil {
81 t.FailNow()
82 }
83 return dev
84}
85
86func TestDeviceAccessors(t *testing.T) {
87 if os.Getenv("IN_KTEST") != "true" {
88 t.Skip("Not in ktest")
89 }
90 dev := setupCreate(t, Config{})
91
92 devPath, err := dev.DevPath()
93 assert.NoError(t, err)
94 require.Equal(t, "/dev/loop0", devPath)
95
96 var stat unix.Stat_t
97 assert.NoError(t, unix.Stat("/dev/loop0", &stat))
98 devNum, err := dev.Dev()
99 assert.NoError(t, err)
100 require.Equal(t, stat.Rdev, devNum)
101
102 backingFile, err := dev.BackingFilePath()
103 assert.NoError(t, err)
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200104 // The filename of the temporary file is not available in this context, but
105 // we know that the file needs to be in /tmp, which should be a good-enough
106 // test.
Lorenz Brun9956e722021-03-24 18:48:55 +0100107 assert.Contains(t, backingFile, "/tmp/")
108}
109
110func TestCreate(t *testing.T) {
111 if os.Getenv("IN_KTEST") != "true" {
112 t.Skip("Not in ktest")
113 }
114 t.Parallel()
115 tests := []struct {
116 name string
117 config Config
118 validate func(t *testing.T, dev *Device)
119 }{
120 {"NoOpts", Config{}, func(t *testing.T, dev *Device) {
121 require.Equal(t, uint64(128*1024), getBlkdevSize(dev.dev))
122 require.Equal(t, uint16(0), getOffsetFromContent(dev))
123
124 _, err := dev.dev.WriteString("test")
125 assert.NoError(t, err)
126 }},
127 {"DirectIO", Config{Flags: FlagDirectIO}, func(t *testing.T, dev *Device) {
128 require.Equal(t, uint64(128*1024), getBlkdevSize(dev.dev))
129
130 _, err := dev.dev.WriteString("test")
131 assert.NoError(t, err)
132 }},
133 {"ReadOnly", Config{Flags: FlagReadOnly}, func(t *testing.T, dev *Device) {
134 _, err := dev.dev.WriteString("test")
135 assert.Error(t, err)
136 }},
137 {"Mapping", Config{BlockSize: 512, SizeLimit: 2048, Offset: 4096}, func(t *testing.T, dev *Device) {
138 assert.Equal(t, uint16(4096), getOffsetFromContent(dev))
139 assert.Equal(t, uint64(2048), getBlkdevSize(dev.dev))
140 }},
141 }
142 for _, test := range tests {
143 t.Run(test.name, func(t *testing.T) {
144 dev := setupCreate(t, test.config)
145 test.validate(t, dev)
146 assert.NoError(t, dev.Remove())
147 })
148 }
149}
150
151func TestOpenBadDevice(t *testing.T) {
152 if os.Getenv("IN_KTEST") != "true" {
153 t.Skip("Not in ktest")
154 }
155 dev, err := Open("/dev/null")
156 require.Error(t, err)
157 if dev != nil { // Prevent leaks in case this test fails
158 dev.Close()
159 }
160}
161
162func TestOpen(t *testing.T) {
163 if os.Getenv("IN_KTEST") != "true" {
164 t.Skip("Not in ktest")
165 }
166 f := makeTestFile()
167 defer os.Remove(f.Name())
168 defer f.Close()
169 dev, err := Create(f, Config{})
170 assert.NoError(t, err)
171 path, err := dev.DevPath()
172 assert.NoError(t, err)
173 assert.NoError(t, dev.Close())
174 reopenedDev, err := Open(path)
175 assert.NoError(t, err)
176 defer reopenedDev.Remove()
177 reopenedDevPath, err := reopenedDev.DevPath()
178 assert.NoError(t, err)
179 require.Equal(t, path, reopenedDevPath) // Still needs to be the same device
180}
181
182func TestResize(t *testing.T) {
183 if os.Getenv("IN_KTEST") != "true" {
184 t.Skip("Not in ktest")
185 }
186 f, err := ioutil.TempFile("/tmp", "")
187 assert.NoError(t, err)
188 empty1K := make([]byte, 1024)
189 for i := 0; i < 64; i++ {
190 _, err := f.Write(empty1K)
191 assert.NoError(t, err)
192 }
193 dev, err := Create(f, Config{})
194 assert.NoError(t, err)
195 require.Equal(t, uint64(64*1024), getBlkdevSize(dev.dev))
196 for i := 0; i < 32; i++ {
197 _, err := f.Write(empty1K)
198 assert.NoError(t, err)
199 }
200 assert.NoError(t, f.Sync())
201 assert.NoError(t, dev.RefreshSize())
202 require.Equal(t, uint64(96*1024), getBlkdevSize(dev.dev))
203}
204
205func TestStructSize(t *testing.T) {
206 if runtime.GOOS != "linux" && runtime.GOARCH != "amd64" {
207 t.Skip("Reference value not available")
208 }
209 require.Equal(t, uintptr(304), unsafe.Sizeof(loopConfig{}))
210}