blob: ca6a760c789a193566e4062bd76c017fdaf8d606 [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"
Lorenz Brun9956e722021-03-24 18:48:55 +010024 "syscall"
25 "testing"
26 "unsafe"
27
28 "github.com/stretchr/testify/assert"
29 "github.com/stretchr/testify/require"
30 "golang.org/x/sys/unix"
31)
32
Serge Bazanski216fe7b2021-05-21 18:36:16 +020033// Write a test file with a very specific pattern (increasing little-endian 16
34// bit unsigned integers) to detect offset correctness. File is always 128KiB
35// large (2^16 * 2 bytes).
Lorenz Brun9956e722021-03-24 18:48:55 +010036func makeTestFile() *os.File {
Lorenz Brun764a2de2021-11-22 16:26:36 +010037 f, err := os.CreateTemp("/tmp", "")
Lorenz Brun9956e722021-03-24 18:48:55 +010038 if err != nil {
39 panic(err)
40 }
41 for i := 0; i <= math.MaxUint16; i++ {
42 if err := binary.Write(f, binary.LittleEndian, uint16(i)); err != nil {
43 panic(err)
44 }
45 }
46 if _, err := f.Seek(0, io.SeekStart); err != nil {
47 panic(err)
48 }
49 return f
50}
51
52func getBlkdevSize(f *os.File) (size uint64) {
53 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 {
54 panic(err)
55 }
56 return
57}
58
59func getOffsetFromContent(dev *Device) (firstIndex uint16) {
60 if err := binary.Read(dev.dev, binary.LittleEndian, &firstIndex); err != nil {
61 panic(err)
62 }
63 firstIndex *= 2 // 2 bytes per index
64 return
65}
66
67func setupCreate(t *testing.T, config Config) *Device {
68 f := makeTestFile()
69 dev, err := Create(f, config)
70 defer f.Close()
71 assert.NoError(t, err)
72 t.Cleanup(func() {
73 if dev != nil {
74 dev.Remove()
75 }
76 os.Remove(f.Name())
77 })
78 if dev == nil {
79 t.FailNow()
80 }
81 return dev
82}
83
84func TestDeviceAccessors(t *testing.T) {
85 if os.Getenv("IN_KTEST") != "true" {
86 t.Skip("Not in ktest")
87 }
88 dev := setupCreate(t, Config{})
89
90 devPath, err := dev.DevPath()
91 assert.NoError(t, err)
92 require.Equal(t, "/dev/loop0", devPath)
93
94 var stat unix.Stat_t
95 assert.NoError(t, unix.Stat("/dev/loop0", &stat))
96 devNum, err := dev.Dev()
97 assert.NoError(t, err)
98 require.Equal(t, stat.Rdev, devNum)
99
100 backingFile, err := dev.BackingFilePath()
101 assert.NoError(t, err)
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200102 // The filename of the temporary file is not available in this context, but
103 // we know that the file needs to be in /tmp, which should be a good-enough
104 // test.
Lorenz Brun9956e722021-03-24 18:48:55 +0100105 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 }
Lorenz Brun764a2de2021-11-22 16:26:36 +0100184 f, err := os.CreateTemp("/tmp", "")
Lorenz Brun9956e722021-03-24 18:48:55 +0100185 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}