| Tim Windelschmidt | 6d33a43 | 2025-02-04 14:34:25 +0100 | [diff] [blame] | 1 | // Copyright The Monogon Project Authors. |
| 2 | // SPDX-License-Identifier: Apache-2.0 |
| 3 | |
| Jan Schär | a6da171 | 2024-08-21 15:12:11 +0200 | [diff] [blame] | 4 | //go:build linux |
| 5 | |
| 6 | package blockdev |
| 7 | |
| 8 | import ( |
| Jan Schär | e0db72c | 2025-06-18 18:14:07 +0000 | [diff] [blame] | 9 | "io" |
| Jan Schär | a6da171 | 2024-08-21 15:12:11 +0200 | [diff] [blame] | 10 | "os" |
| 11 | "testing" |
| 12 | |
| 13 | "source.monogon.dev/osbase/loop" |
| 14 | ) |
| 15 | |
| 16 | const loopBlockSize = 1024 |
| 17 | const loopBlockCount = 8 |
| 18 | |
| 19 | func TestLoopDevice(t *testing.T) { |
| 20 | if os.Getenv("IN_KTEST") != "true" { |
| 21 | t.Skip("Not in ktest") |
| 22 | } |
| 23 | underlying, err := os.CreateTemp("/tmp", "") |
| 24 | if err != nil { |
| 25 | t.Fatalf("CreateTemp failed: %v", err) |
| 26 | } |
| 27 | defer os.Remove(underlying.Name()) |
| 28 | |
| 29 | _, err = underlying.Write(make([]byte, loopBlockSize*loopBlockCount)) |
| 30 | if err != nil { |
| 31 | t.Fatalf("Write failed: %v", err) |
| 32 | } |
| 33 | |
| 34 | loopDev, err := loop.Create(underlying, loop.Config{ |
| 35 | BlockSize: loopBlockSize, |
| 36 | }) |
| 37 | if err != nil { |
| 38 | t.Fatalf("loop.Create failed: %v", err) |
| 39 | } |
| 40 | defer loopDev.Remove() |
| 41 | |
| 42 | devPath, err := loopDev.DevPath() |
| 43 | if err != nil { |
| 44 | t.Fatalf("loopDev.DevPath failed: %v", err) |
| 45 | } |
| 46 | |
| 47 | loopDev.Close() |
| 48 | blk, err := Open(devPath) |
| 49 | if err != nil { |
| 50 | t.Fatalf("Failed to open loop device: %v", err) |
| 51 | } |
| 52 | defer blk.Close() |
| 53 | |
| 54 | ValidateBlockDev(t, blk, loopBlockCount, loopBlockSize, loopBlockSize) |
| 55 | } |
| 56 | |
| 57 | const fileBlockSize = 1024 |
| 58 | const fileBlockCount = 8 |
| 59 | |
| 60 | func TestFile(t *testing.T) { |
| 61 | if os.Getenv("IN_KTEST") != "true" { |
| 62 | t.Skip("Not in ktest") |
| 63 | } |
| 64 | |
| 65 | blk, err := CreateFile("/tmp/testfile", fileBlockSize, fileBlockCount) |
| 66 | if err != nil { |
| 67 | t.Fatalf("Failed to create file: %v", err) |
| 68 | } |
| 69 | defer os.Remove("/tmp/testfile") |
| Jan Schär | e0db72c | 2025-06-18 18:14:07 +0000 | [diff] [blame] | 70 | defer blk.Close() |
| Jan Schär | a6da171 | 2024-08-21 15:12:11 +0200 | [diff] [blame] | 71 | |
| 72 | ValidateBlockDev(t, blk, fileBlockCount, fileBlockSize, fileBlockSize) |
| Jan Schär | e0db72c | 2025-06-18 18:14:07 +0000 | [diff] [blame] | 73 | |
| 74 | // ReadFromAt |
| 75 | srcFile, err := os.Create("/tmp/copysrc") |
| 76 | if err != nil { |
| 77 | t.Fatalf("Failed to create source file: %v", err) |
| 78 | } |
| 79 | defer os.Remove("/tmp/copysrc") |
| 80 | defer srcFile.Close() |
| 81 | var size int64 = fileBlockSize * fileBlockCount |
| 82 | readFromAtTests := []struct { |
| 83 | name string |
| 84 | offset int64 |
| 85 | data string |
| 86 | limit int64 |
| 87 | ok bool |
| 88 | }{ |
| 89 | {"empty start", 0, "", -1, true}, |
| 90 | {"empty end", size, "", -1, true}, |
| 91 | {"normal", 3, "abcdef", -1, true}, |
| 92 | {"limited", 3, "abcdef", 4, true}, |
| 93 | {"large limit", 3, "abcdef", size, true}, |
| 94 | {"ends at the end", size - 4, "abcd", -1, true}, |
| 95 | {"ends past the end", size - 4, "abcde", -1, false}, |
| 96 | {"ends past the end with limit", size - 4, "abcde", 10, false}, |
| 97 | {"offset negative", -1, "abc", -1, false}, |
| 98 | {"starts at the end", size, "abc", -1, false}, |
| 99 | {"starts past the end", size + 4, "abc", -1, false}, |
| 100 | } |
| 101 | for _, tt := range readFromAtTests { |
| 102 | t.Run("readFromAt "+tt.name, func(t *testing.T) { |
| 103 | checkBlockDevOp(t, blk, func(content []byte) { |
| 104 | // Prepare source file |
| 105 | err = srcFile.Truncate(0) |
| 106 | if err != nil { |
| 107 | t.Fatalf("Failed to truncate source file: %v", err) |
| 108 | } |
| 109 | _, err = srcFile.WriteAt([]byte("123"+tt.data), 0) |
| 110 | if err != nil { |
| 111 | t.Fatalf("Failed to write source file: %v", err) |
| 112 | } |
| 113 | _, err = srcFile.Seek(3, io.SeekStart) |
| 114 | if err != nil { |
| 115 | t.Fatalf("Failed to seek source file: %v", err) |
| 116 | } |
| 117 | |
| 118 | // Do ReadFromAt |
| 119 | r := io.Reader(srcFile) |
| 120 | lr := &io.LimitedReader{R: srcFile, N: tt.limit} |
| 121 | if tt.limit != -1 { |
| 122 | r = lr |
| 123 | } |
| 124 | n, err := blk.ReadFromAt(r, tt.offset) |
| 125 | if (err == nil) != tt.ok { |
| 126 | t.Errorf("expected error %v, got %v", tt.ok, err) |
| 127 | } |
| 128 | expectedN := 0 |
| 129 | if tt.offset >= 0 && tt.offset < size { |
| 130 | c := content[tt.offset:] |
| 131 | if tt.limit != -1 && tt.limit < int64(len(c)) { |
| 132 | c = c[:tt.limit] |
| 133 | } |
| 134 | expectedN = copy(c, tt.data) |
| 135 | } |
| 136 | if n != int64(expectedN) { |
| 137 | t.Errorf("got n = %d, expected %d; err: %v", n, expectedN, err) |
| 138 | } |
| 139 | |
| 140 | // Check new offset |
| 141 | newOffset, err := srcFile.Seek(0, io.SeekCurrent) |
| 142 | if err != nil { |
| 143 | t.Fatalf("Failed to get source file position: %v", err) |
| 144 | } |
| 145 | newOffset -= 3 |
| 146 | minOffset := n |
| 147 | maxOffset := n |
| 148 | if !tt.ok { |
| 149 | maxOffset = int64(len(tt.data)) |
| 150 | if tt.limit != -1 { |
| 151 | maxOffset = min(maxOffset, tt.limit) |
| 152 | } |
| 153 | } |
| 154 | if minOffset > newOffset || newOffset > maxOffset { |
| 155 | t.Errorf("Got newOffset = %d, expected between %d and %d", newOffset, minOffset, maxOffset) |
| 156 | } |
| 157 | remaining := tt.limit - newOffset |
| 158 | if tt.limit != -1 && lr.N != remaining { |
| 159 | t.Errorf("Got lr.N = %d, expected %d", lr.N, remaining) |
| 160 | } |
| 161 | }) |
| 162 | }) |
| 163 | } |
| Jan Schär | a6da171 | 2024-08-21 15:12:11 +0200 | [diff] [blame] | 164 | } |