blob: 7f23f3eb5d5b649966acaca3db4d64fbae2c44d4 [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"
Lorenz Brun9956e722021-03-24 18:48:55 +010022 "math"
23 "os"
24 "runtime"
25 "syscall"
26 "testing"
27 "unsafe"
28
29 "github.com/stretchr/testify/assert"
30 "github.com/stretchr/testify/require"
31 "golang.org/x/sys/unix"
32)
33
Serge Bazanski216fe7b2021-05-21 18:36:16 +020034// Write a test file with a very specific pattern (increasing little-endian 16
35// bit unsigned integers) to detect offset correctness. File is always 128KiB
36// large (2^16 * 2 bytes).
Lorenz Brun9956e722021-03-24 18:48:55 +010037func makeTestFile() *os.File {
Lorenz Brun764a2de2021-11-22 16:26:36 +010038 f, err := os.CreateTemp("/tmp", "")
Lorenz Brun9956e722021-03-24 18:48:55 +010039 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)
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200103 // The filename of the temporary file is not available in this context, but
104 // we know that the file needs to be in /tmp, which should be a good-enough
105 // test.
Lorenz Brun9956e722021-03-24 18:48:55 +0100106 assert.Contains(t, backingFile, "/tmp/")
107}
108
109func TestCreate(t *testing.T) {
110 if os.Getenv("IN_KTEST") != "true" {
111 t.Skip("Not in ktest")
112 }
113 t.Parallel()
114 tests := []struct {
115 name string
116 config Config
117 validate func(t *testing.T, dev *Device)
118 }{
119 {"NoOpts", Config{}, func(t *testing.T, dev *Device) {
120 require.Equal(t, uint64(128*1024), getBlkdevSize(dev.dev))
121 require.Equal(t, uint16(0), getOffsetFromContent(dev))
122
123 _, err := dev.dev.WriteString("test")
124 assert.NoError(t, err)
125 }},
126 {"DirectIO", Config{Flags: FlagDirectIO}, func(t *testing.T, dev *Device) {
127 require.Equal(t, uint64(128*1024), getBlkdevSize(dev.dev))
128
129 _, err := dev.dev.WriteString("test")
130 assert.NoError(t, err)
131 }},
132 {"ReadOnly", Config{Flags: FlagReadOnly}, func(t *testing.T, dev *Device) {
133 _, err := dev.dev.WriteString("test")
134 assert.Error(t, err)
135 }},
136 {"Mapping", Config{BlockSize: 512, SizeLimit: 2048, Offset: 4096}, func(t *testing.T, dev *Device) {
137 assert.Equal(t, uint16(4096), getOffsetFromContent(dev))
138 assert.Equal(t, uint64(2048), getBlkdevSize(dev.dev))
139 }},
140 }
141 for _, test := range tests {
142 t.Run(test.name, func(t *testing.T) {
143 dev := setupCreate(t, test.config)
144 test.validate(t, dev)
145 assert.NoError(t, dev.Remove())
146 })
147 }
148}
149
150func TestOpenBadDevice(t *testing.T) {
151 if os.Getenv("IN_KTEST") != "true" {
152 t.Skip("Not in ktest")
153 }
154 dev, err := Open("/dev/null")
155 require.Error(t, err)
156 if dev != nil { // Prevent leaks in case this test fails
157 dev.Close()
158 }
159}
160
161func TestOpen(t *testing.T) {
162 if os.Getenv("IN_KTEST") != "true" {
163 t.Skip("Not in ktest")
164 }
165 f := makeTestFile()
166 defer os.Remove(f.Name())
167 defer f.Close()
168 dev, err := Create(f, Config{})
169 assert.NoError(t, err)
170 path, err := dev.DevPath()
171 assert.NoError(t, err)
172 assert.NoError(t, dev.Close())
173 reopenedDev, err := Open(path)
174 assert.NoError(t, err)
175 defer reopenedDev.Remove()
176 reopenedDevPath, err := reopenedDev.DevPath()
177 assert.NoError(t, err)
178 require.Equal(t, path, reopenedDevPath) // Still needs to be the same device
179}
180
181func TestResize(t *testing.T) {
182 if os.Getenv("IN_KTEST") != "true" {
183 t.Skip("Not in ktest")
184 }
Lorenz Brun764a2de2021-11-22 16:26:36 +0100185 f, err := os.CreateTemp("/tmp", "")
Lorenz Brun9956e722021-03-24 18:48:55 +0100186 assert.NoError(t, err)
187 empty1K := make([]byte, 1024)
188 for i := 0; i < 64; i++ {
189 _, err := f.Write(empty1K)
190 assert.NoError(t, err)
191 }
192 dev, err := Create(f, Config{})
193 assert.NoError(t, err)
194 require.Equal(t, uint64(64*1024), getBlkdevSize(dev.dev))
195 for i := 0; i < 32; i++ {
196 _, err := f.Write(empty1K)
197 assert.NoError(t, err)
198 }
199 assert.NoError(t, f.Sync())
200 assert.NoError(t, dev.RefreshSize())
201 require.Equal(t, uint64(96*1024), getBlkdevSize(dev.dev))
202}
203
204func TestStructSize(t *testing.T) {
205 if runtime.GOOS != "linux" && runtime.GOARCH != "amd64" {
206 t.Skip("Reference value not available")
207 }
208 require.Equal(t, uintptr(304), unsafe.Sizeof(loopConfig{}))
209}