ktstr 0.4.14

Test harness for Linux process schedulers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
#![cfg(test)]
#![allow(unused_imports)]

use super::testing::*;
use super::*;
use std::io::{Seek, Write};
use std::os::unix::fs::FileExt;
use std::sync::atomic::Ordering;
use tempfile::tempfile;
use vm_memory::Bytes;

// ----------------------------------------------------------------
// File backend + read_only
//
// The MMIO descriptor-chain path requires a real GuestMemoryMmap +
// a populated descriptor ring, which is too heavy for unit tests.
// The handlers (`handle_read`, `handle_write`, `handle_flush`) take
// `data_segments` slices and a status address; we drive them
// directly against a small synthetic GuestMemoryMmap to exercise
// the pread/pwrite + read_only paths.
// ----------------------------------------------------------------

#[test]
fn handle_read_pulls_bytes_from_backing_file() {
    // 1 sector = 512 bytes. Backing prefilled with 0xAB.
    let cap = 4096u64; // 8 sectors
    let f = make_backed_file_with_pattern(cap, 0xAB);
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
    let mem = make_guest_mem(8192);
    // Guest buffer at GPA 0x1000, 1 sector = 512 bytes,
    // device-writable.
    let data_addr = GuestAddress(0x1000);
    let status_addr = GuestAddress(0x1FFF); // 1 byte
    let segs = vec![ChainDescriptor {
        addr: data_addr,
        len: 512,
        is_write_only: true,
    }];
    let (status, used) = dev.handle_read(&mem, 0, &segs, status_addr);
    assert_eq!(status, VIRTIO_BLK_S_OK as u8);
    assert_eq!(used, 513); // 512 data + 1 status
    // Verify the guest buffer now contains the pattern.
    let mut readback = [0u8; 512];
    mem.read_slice(&mut readback, data_addr).unwrap();
    assert!(readback.iter().all(|&b| b == 0xAB));
    // Status byte set to OK.
    let mut s = [0u8; 1];
    mem.read_slice(&mut s, status_addr).unwrap();
    assert_eq!(s[0], VIRTIO_BLK_S_OK as u8);
}

#[test]
fn handle_write_persists_bytes_to_backing_file() {
    let cap = 4096u64;
    let f = make_backed_file_with_pattern(cap, 0x00);
    // Borrow the file's fd before passing into the device — we
    // use FileExt::read_at on a clone to verify post-write
    // contents.
    let f_for_verify = f.try_clone().unwrap();
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
    // Larger guest mem so data_addr + len + status_addr all fit
    // within the single region. 32 KB is generous; the previous
    // 16 KB region with status_addr=0x2FFF (12287) sat within
    // bounds but write_slice rejected the write (likely a
    // single-region GuestMemoryMmap quirk under address overlap).
    let mem = make_guest_mem(32768);
    let data_addr = GuestAddress(0x1000);
    let status_addr = GuestAddress(0x2000);
    // Stuff 0xCD pattern into guest memory at data_addr.
    let pattern = vec![0xCDu8; 512];
    mem.write_slice(&pattern, data_addr).unwrap();
    let segs = vec![ChainDescriptor {
        addr: data_addr,
        len: 512,
        is_write_only: false,
    }];
    let (status, used) = dev.handle_write(&mem, 1, &segs, status_addr); // sector 1
    assert_eq!(status, VIRTIO_BLK_S_OK as u8);
    assert_eq!(used, 1);
    // Verify backing file at offset 512 now contains 0xCD.
    let mut readback = [0u8; 512];
    f_for_verify.read_at(&mut readback, 512).unwrap();
    assert!(readback.iter().all(|&b| b == 0xCD));
}

#[test]
fn handle_read_rejects_out_of_range_sector() {
    let cap = 4096u64; // 8 sectors
    let f = make_backed_file_with_pattern(cap, 0x00);
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
    let mem = make_guest_mem(8192);
    let data_addr = GuestAddress(0x1000);
    let status_addr = GuestAddress(0x1FFF);
    let segs = vec![ChainDescriptor {
        addr: data_addr,
        len: 512,
        is_write_only: true,
    }];
    // sector 9 is past capacity (cap=4096 → 8 sectors → max idx 7).
    let (status, _) = dev.handle_read(&mem, 9, &segs, status_addr);
    assert_eq!(status, VIRTIO_BLK_S_IOERR as u8);
    let c = dev.counters();
    assert_eq!(c.io_errors.load(Ordering::Relaxed), 1);
    assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
}

#[test]
fn handle_write_rejects_out_of_range_sector() {
    let cap = 4096u64;
    let f = make_backed_file_with_pattern(cap, 0x00);
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
    // 16 KiB guest mem to hold both data buffer (sized 512) and
    // status byte addr without overlap. data at 0x1000, status
    // at 0x2000, both well within [0, 0x4000).
    let mem = make_guest_mem(16384);
    let data_addr = GuestAddress(0x1000);
    let status_addr = GuestAddress(0x2000);
    let segs = vec![ChainDescriptor {
        addr: data_addr,
        len: 512,
        is_write_only: false,
    }];
    let (status, _) = dev.handle_write(&mem, 9, &segs, status_addr);
    assert_eq!(status, VIRTIO_BLK_S_IOERR as u8);
}

#[test]
fn handle_flush_succeeds_on_writable_backing() {
    let cap = 4096u64;
    let f = make_backed_file_with_pattern(cap, 0x00);
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
    let mem = make_guest_mem(8192);
    let status_addr = GuestAddress(0x100);
    let (status, used) = dev.handle_flush(&mem, status_addr);
    assert_eq!(status, VIRTIO_BLK_S_OK as u8);
    assert_eq!(used, 1);
    let c = dev.counters();
    assert_eq!(c.flushes_completed.load(Ordering::Relaxed), 1);
}

#[test]
fn read_only_advertises_f_ro_feature_bit() {
    let cap = 4096u64;
    let f = make_backed_file_with_pattern(cap, 0x00);
    let dev = VirtioBlk::with_options(f, cap, DiskThrottle::default(), true);
    let feats = dev.device_features();
    assert_ne!(feats & (1u64 << VIRTIO_BLK_F_RO), 0);
}

#[test]
fn read_write_does_not_advertise_f_ro_feature_bit() {
    let cap = 4096u64;
    let f = make_backed_file_with_pattern(cap, 0x00);
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
    let feats = dev.device_features();
    assert_eq!(feats & (1u64 << VIRTIO_BLK_F_RO), 0);
}

#[test]
fn write_at_full_capacity_succeeds() {
    // The boundary case — a write whose end aligns exactly with
    // capacity. Should succeed (the spec wording is "if the
    // sector range overlaps a sector outside the capacity").
    let cap = 4096u64;
    let f = make_backed_file_with_pattern(cap, 0x00);
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
    let mem = make_guest_mem(16384);
    let data_addr = GuestAddress(0x2000);
    let status_addr = GuestAddress(0x2FFF);
    let pattern = vec![0xEEu8; 512];
    mem.write_slice(&pattern, data_addr).unwrap();
    let segs = vec![ChainDescriptor {
        addr: data_addr,
        len: 512,
        is_write_only: false,
    }];
    // sector 7 covers bytes 3584..4096 — exactly at capacity.
    let (status, _) = dev.handle_write(&mem, 7, &segs, status_addr);
    assert_eq!(status, VIRTIO_BLK_S_OK as u8);
}

#[test]
fn read_short_pads_with_zeros() {
    // Sparse-file semantics: reads past the written region must
    // return zeros, not stale data. Establish by truncating the
    // backing file shorter than requested capacity, then reading.
    let cap = 4096u64; // 8 sectors
    let mut f = tempfile().unwrap();
    f.set_len(512).unwrap(); // backing covers only 1 sector
    f.write_all(&[0xAA; 512]).unwrap();
    f.rewind().unwrap();
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
    let mem = make_guest_mem(8192);
    let data_addr = GuestAddress(0x1000);
    let status_addr = GuestAddress(0x1FFF);
    let segs = vec![ChainDescriptor {
        addr: data_addr,
        len: 512,
        is_write_only: true,
    }];
    // Sector 4 → offset 2048, well past the backing's 512 bytes.
    let (status, _) = dev.handle_read(&mem, 4, &segs, status_addr);
    assert_eq!(status, VIRTIO_BLK_S_OK as u8);
    let mut readback = [0u8; 512];
    mem.read_slice(&mut readback, data_addr).unwrap();
    assert!(
        readback.iter().all(|&b| b == 0),
        "out-of-data reads must zero-pad, not return stale memory"
    );
}

#[test]
fn read_only_flush_returns_ok() {
    // A read-only disk has no dirty data — a precautionary flush
    // from a guest mounting RO must return OK to avoid spurious
    // mount errors in the guest dmesg.
    let cap = 4096u64;
    let f = make_backed_file_with_pattern(cap, 0x00);
    let dev = VirtioBlk::with_options(f, cap, DiskThrottle::default(), true);
    // read_only flush behaviour is checked through the
    // process_requests dispatch table; here we just pin the
    // device's `read_only` flag is captured.
    assert!(dev.worker.read_only);
}

/// A read-only device must reject `VIRTIO_BLK_T_OUT` with
/// `VIRTIO_BLK_S_IOERR`, regardless of whether the guest
/// honoured the negotiated F_RO bit. The classification happens
/// in `classify_pre_throttle`; this test drives that helper
/// directly so the assertion follows the same code path
/// `process_requests` does.
#[test]
fn read_only_write_returns_ioerr() {
    let counters = VirtioBlkCounters::default();
    let result = VirtioBlk::classify_pre_throttle(VIRTIO_BLK_T_OUT, true, &counters);
    assert_eq!(result, Some((VIRTIO_BLK_S_IOERR as u8, 1)));
    // io_errors bumped — the rejection counts as an IO error
    // for failure-dump visibility.
    assert_eq!(counters.io_errors.load(Ordering::Relaxed), 1);
    assert_eq!(counters.writes_completed.load(Ordering::Relaxed), 0);
}

/// A read-only device's flush is a no-op that completes with
/// `VIRTIO_BLK_S_OK` AND increments `flushes_completed` for
/// monitor visibility. The semantic is "guest issued a real
/// flush, device serviced it (trivially, because nothing's
/// dirty)" — distinct from "rejected" which would surface as
/// IOERR.
#[test]
fn read_only_flush_returns_ok_and_increments_counter() {
    let counters = VirtioBlkCounters::default();
    let result = VirtioBlk::classify_pre_throttle(VIRTIO_BLK_T_FLUSH, true, &counters);
    assert_eq!(result, Some((VIRTIO_BLK_S_OK as u8, 1)));
    assert_eq!(counters.flushes_completed.load(Ordering::Relaxed), 1);
    assert_eq!(counters.io_errors.load(Ordering::Relaxed), 0);
}

/// A multi-segment read scatters successive backing-file bytes
/// into successive guest segments. Pin the per-segment file
/// offset advance: segment 0 receives bytes 0..512, segment 1
/// receives bytes 512..1024. A regression that reset the file
/// offset between segments (or wrote both segments to the same
/// file region) would either fill segment 1 with zeros or
/// duplicate segment 0's contents — this test catches both.
#[test]
fn scatter_gather_read_two_segments() {
    let cap = 4096u64;
    let mut f = tempfile().unwrap();
    f.set_len(cap).unwrap();
    f.rewind().unwrap();
    // First 512 bytes = 0x11, next 512 bytes = 0x22, rest 0.
    let mut backing_data = vec![0x11u8; 512];
    backing_data.extend(vec![0x22u8; 512]);
    f.write_all(&backing_data).unwrap();
    f.rewind().unwrap();
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());

    let mem = make_guest_mem(8192);
    let seg0_addr = GuestAddress(0x1000);
    let seg1_addr = GuestAddress(0x1400); // 0x1000 + 0x400 = 0x1400, no overlap
    let status_addr = GuestAddress(0x1FFF);
    let segs = vec![
        ChainDescriptor {
            addr: seg0_addr,
            len: 512,
            is_write_only: true,
        },
        ChainDescriptor {
            addr: seg1_addr,
            len: 512,
            is_write_only: true,
        },
    ];
    let (status, used) = dev.handle_read(&mem, 0, &segs, status_addr);
    assert_eq!(status, VIRTIO_BLK_S_OK as u8);
    assert_eq!(used, 1024 + 1); // 2 × 512 data + 1 status

    // Segment 0 should contain 0x11 (file bytes 0..512).
    let mut buf0 = [0u8; 512];
    mem.read_slice(&mut buf0, seg0_addr).unwrap();
    assert!(
        buf0.iter().all(|&b| b == 0x11),
        "segment 0 must receive file bytes 0..512 (0x11 pattern)",
    );
    // Segment 1 should contain 0x22 (file bytes 512..1024) —
    // proves the per-segment cursor advanced correctly.
    let mut buf1 = [0u8; 512];
    mem.read_slice(&mut buf1, seg1_addr).unwrap();
    assert!(
        buf1.iter().all(|&b| b == 0x22),
        "segment 1 must receive file bytes 512..1024 (0x22 pattern); \
             a regression that didn't advance the file cursor would \
             produce 0x11 here",
    );
}

/// An unknown request type (anything outside T_IN/T_OUT/T_FLUSH/
/// T_GET_ID) must be classified as `VIRTIO_BLK_S_UNSUPP` per
/// virtio-v1.2 §5.2.6.4. Pin the dispatch table's default
/// behaviour so a future patch that mis-handles a new request
/// type as IOERR (or, worse, OK) surfaces here. Counters are
/// untouched on UNSUPP because the request was never dispatched
/// to a backend.
#[test]
fn unknown_type_returns_unsupp() {
    let counters = VirtioBlkCounters::default();
    let result = VirtioBlk::classify_pre_throttle(0xBEEF, false, &counters);
    assert_eq!(result, Some((VIRTIO_BLK_S_UNSUPP as u8, 1)));
    // Unknown types don't bump io_errors — the device gracefully
    // declined a request it didn't recognise, not something it
    // tried and failed to service.
    assert_eq!(counters.io_errors.load(Ordering::Relaxed), 0);
    assert_eq!(counters.reads_completed.load(Ordering::Relaxed), 0);
    assert_eq!(counters.writes_completed.load(Ordering::Relaxed), 0);
    assert_eq!(counters.flushes_completed.load(Ordering::Relaxed), 0);
}

/// Multi-segment scatter read: pin that `handle_read_impl`
/// walks `data_segments` in order, advances `cur_offset` by
/// each segment's `len`, and writes each guest segment with
/// the correct slice of the backing file. This is the central
/// scatter-gather invariant — without per-segment offset
/// advancement, segments 1..N would either stamp on segment 0
/// or skip data.
#[test]
fn handle_read_multi_segment_scatter() {
    // 2-sector backing prefilled with a known pattern: bytes
    // 0..512 = 0xAA, bytes 512..1024 = 0xBB. Two guest data
    // segments each receive one sector. After the read,
    // segment 0 must hold 0xAA and segment 1 must hold 0xBB.
    let cap = 4096u64; // 8 sectors
    let mut f = tempfile().unwrap();
    f.set_len(cap).unwrap();
    f.write_all(&[0xAA; 512]).unwrap();
    f.write_all(&[0xBB; 512]).unwrap();
    f.rewind().unwrap();
    let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
    let mem = make_guest_mem(8192);
    // Two scatter segments: 512 bytes each, separated by a
    // gap so test failure on cur_offset arithmetic shows up
    // as cross-contamination.
    let seg0_addr = GuestAddress(0x1000);
    let seg1_addr = GuestAddress(0x1400); // 0x1000 + 0x400 (1 KB)
    let status_addr = GuestAddress(0x1FFF);
    let segs = vec![
        ChainDescriptor {
            addr: seg0_addr,
            len: 512,
            is_write_only: true,
        },
        ChainDescriptor {
            addr: seg1_addr,
            len: 512,
            is_write_only: true,
        },
    ];
    // Read starting at sector 0 — covers backing bytes 0..1024,
    // split across the two segments.
    let (status, used) = dev.handle_read(&mem, 0, &segs, status_addr);
    assert_eq!(status, VIRTIO_BLK_S_OK as u8);
    assert_eq!(used, 1024 + 1); // 2 * 512 data + 1 status

    let mut readback0 = [0u8; 512];
    mem.read_slice(&mut readback0, seg0_addr).unwrap();
    assert!(
        readback0.iter().all(|&b| b == 0xAA),
        "segment 0 must hold the FIRST sector's pattern (0xAA), \
             got cross-contamination: {:?}..{:?}",
        &readback0[..8],
        &readback0[504..],
    );

    let mut readback1 = [0u8; 512];
    mem.read_slice(&mut readback1, seg1_addr).unwrap();
    assert!(
        readback1.iter().all(|&b| b == 0xBB),
        "segment 1 must hold the SECOND sector's pattern (0xBB) — \
             cur_offset must have advanced by 512 between segments. \
             got: {:?}..{:?}",
        &readback1[..8],
        &readback1[504..],
    );

    let c = dev.counters();
    assert_eq!(c.reads_completed.load(Ordering::Relaxed), 1);
    assert_eq!(c.bytes_read.load(Ordering::Relaxed), 1024);
}