blob: 8624eb41873166a7912ec7ce789e15ce7897437d [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
Lorenz Brun9956e722021-03-24 18:48:55 +01002// SPDX-License-Identifier: Apache-2.0
Lorenz Brun9956e722021-03-24 18:48:55 +01003
4package loop
5
6import (
7 "encoding/binary"
8 "io"
Lorenz Brun9956e722021-03-24 18:48:55 +01009 "math"
10 "os"
Lorenz Brun9956e722021-03-24 18:48:55 +010011 "syscall"
12 "testing"
13 "unsafe"
14
15 "github.com/stretchr/testify/assert"
16 "github.com/stretchr/testify/require"
17 "golang.org/x/sys/unix"
18)
19
Serge Bazanski216fe7b2021-05-21 18:36:16 +020020// Write a test file with a very specific pattern (increasing little-endian 16
21// bit unsigned integers) to detect offset correctness. File is always 128KiB
22// large (2^16 * 2 bytes).
Lorenz Brun9956e722021-03-24 18:48:55 +010023func makeTestFile() *os.File {
Lorenz Brun764a2de2021-11-22 16:26:36 +010024 f, err := os.CreateTemp("/tmp", "")
Lorenz Brun9956e722021-03-24 18:48:55 +010025 if err != nil {
26 panic(err)
27 }
28 for i := 0; i <= math.MaxUint16; i++ {
29 if err := binary.Write(f, binary.LittleEndian, uint16(i)); err != nil {
30 panic(err)
31 }
32 }
33 if _, err := f.Seek(0, io.SeekStart); err != nil {
34 panic(err)
35 }
36 return f
37}
38
39func getBlkdevSize(f *os.File) (size uint64) {
40 if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, f.Fd(), unix.BLKGETSIZE64, uintptr(unsafe.Pointer(&size))); err != 0 {
41 panic(err)
42 }
43 return
44}
45
46func getOffsetFromContent(dev *Device) (firstIndex uint16) {
47 if err := binary.Read(dev.dev, binary.LittleEndian, &firstIndex); err != nil {
48 panic(err)
49 }
50 firstIndex *= 2 // 2 bytes per index
51 return
52}
53
54func setupCreate(t *testing.T, config Config) *Device {
55 f := makeTestFile()
56 dev, err := Create(f, config)
57 defer f.Close()
58 assert.NoError(t, err)
59 t.Cleanup(func() {
60 if dev != nil {
61 dev.Remove()
62 }
63 os.Remove(f.Name())
64 })
65 if dev == nil {
66 t.FailNow()
67 }
68 return dev
69}
70
71func TestDeviceAccessors(t *testing.T) {
72 if os.Getenv("IN_KTEST") != "true" {
73 t.Skip("Not in ktest")
74 }
75 dev := setupCreate(t, Config{})
76
77 devPath, err := dev.DevPath()
78 assert.NoError(t, err)
79 require.Equal(t, "/dev/loop0", devPath)
80
81 var stat unix.Stat_t
82 assert.NoError(t, unix.Stat("/dev/loop0", &stat))
83 devNum, err := dev.Dev()
84 assert.NoError(t, err)
85 require.Equal(t, stat.Rdev, devNum)
86
87 backingFile, err := dev.BackingFilePath()
88 assert.NoError(t, err)
Serge Bazanski216fe7b2021-05-21 18:36:16 +020089 // The filename of the temporary file is not available in this context, but
90 // we know that the file needs to be in /tmp, which should be a good-enough
91 // test.
Lorenz Brun9956e722021-03-24 18:48:55 +010092 assert.Contains(t, backingFile, "/tmp/")
93}
94
95func TestCreate(t *testing.T) {
96 if os.Getenv("IN_KTEST") != "true" {
97 t.Skip("Not in ktest")
98 }
99 t.Parallel()
100 tests := []struct {
101 name string
102 config Config
103 validate func(t *testing.T, dev *Device)
104 }{
105 {"NoOpts", Config{}, func(t *testing.T, dev *Device) {
106 require.Equal(t, uint64(128*1024), getBlkdevSize(dev.dev))
107 require.Equal(t, uint16(0), getOffsetFromContent(dev))
108
109 _, err := dev.dev.WriteString("test")
110 assert.NoError(t, err)
111 }},
112 {"DirectIO", Config{Flags: FlagDirectIO}, func(t *testing.T, dev *Device) {
113 require.Equal(t, uint64(128*1024), getBlkdevSize(dev.dev))
114
115 _, err := dev.dev.WriteString("test")
116 assert.NoError(t, err)
117 }},
118 {"ReadOnly", Config{Flags: FlagReadOnly}, func(t *testing.T, dev *Device) {
119 _, err := dev.dev.WriteString("test")
120 assert.Error(t, err)
121 }},
122 {"Mapping", Config{BlockSize: 512, SizeLimit: 2048, Offset: 4096}, func(t *testing.T, dev *Device) {
123 assert.Equal(t, uint16(4096), getOffsetFromContent(dev))
124 assert.Equal(t, uint64(2048), getBlkdevSize(dev.dev))
125 }},
126 }
127 for _, test := range tests {
128 t.Run(test.name, func(t *testing.T) {
129 dev := setupCreate(t, test.config)
130 test.validate(t, dev)
131 assert.NoError(t, dev.Remove())
132 })
133 }
134}
135
136func TestOpenBadDevice(t *testing.T) {
137 if os.Getenv("IN_KTEST") != "true" {
138 t.Skip("Not in ktest")
139 }
140 dev, err := Open("/dev/null")
141 require.Error(t, err)
142 if dev != nil { // Prevent leaks in case this test fails
143 dev.Close()
144 }
145}
146
147func TestOpen(t *testing.T) {
148 if os.Getenv("IN_KTEST") != "true" {
149 t.Skip("Not in ktest")
150 }
151 f := makeTestFile()
152 defer os.Remove(f.Name())
153 defer f.Close()
154 dev, err := Create(f, Config{})
155 assert.NoError(t, err)
156 path, err := dev.DevPath()
157 assert.NoError(t, err)
158 assert.NoError(t, dev.Close())
159 reopenedDev, err := Open(path)
160 assert.NoError(t, err)
161 defer reopenedDev.Remove()
162 reopenedDevPath, err := reopenedDev.DevPath()
163 assert.NoError(t, err)
164 require.Equal(t, path, reopenedDevPath) // Still needs to be the same device
165}
166
167func TestResize(t *testing.T) {
168 if os.Getenv("IN_KTEST") != "true" {
169 t.Skip("Not in ktest")
170 }
Lorenz Brun764a2de2021-11-22 16:26:36 +0100171 f, err := os.CreateTemp("/tmp", "")
Lorenz Brun9956e722021-03-24 18:48:55 +0100172 assert.NoError(t, err)
173 empty1K := make([]byte, 1024)
174 for i := 0; i < 64; i++ {
175 _, err := f.Write(empty1K)
176 assert.NoError(t, err)
177 }
178 dev, err := Create(f, Config{})
179 assert.NoError(t, err)
180 require.Equal(t, uint64(64*1024), getBlkdevSize(dev.dev))
181 for i := 0; i < 32; i++ {
182 _, err := f.Write(empty1K)
183 assert.NoError(t, err)
184 }
185 assert.NoError(t, f.Sync())
186 assert.NoError(t, dev.RefreshSize())
187 require.Equal(t, uint64(96*1024), getBlkdevSize(dev.dev))
188}