• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

google / alioth / 18638570188

20 Oct 2025 12:38AM UTC coverage: 20.202% (-0.01%) from 20.213%
18638570188

Pull #308

github

web-flow
Merge 73a1640e9 into 416357998
Pull Request #308: Add tests for PciSegment

0 of 59 new or added lines in 5 files covered. (0.0%)

1163 existing lines in 25 files now uncovered.

1578 of 7811 relevant lines covered (20.2%)

19.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/alioth/src/virtio/dev/blk.rs
1
// Copyright 2024 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
use std::fs::{File, OpenOptions};
16
use std::io::{IoSlice, IoSliceMut, Read, Write};
17
#[cfg(target_os = "linux")]
18
use std::os::fd::AsRawFd;
19
use std::os::unix::fs::FileExt;
20
use std::path::PathBuf;
21
use std::sync::Arc;
22
use std::sync::mpsc::Receiver;
23
use std::thread::JoinHandle;
24

25
use bitflags::bitflags;
26
#[cfg(target_os = "linux")]
27
use io_uring::cqueue::Entry as Cqe;
28
#[cfg(target_os = "linux")]
29
use io_uring::opcode;
30
#[cfg(target_os = "linux")]
31
use io_uring::types::Fd;
32
use mio::Registry;
33
use mio::event::Event;
34
use serde::Deserialize;
35
use serde_aco::Help;
36
use snafu::ResultExt;
37
use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes};
38

39
use crate::hv::IoeventFd;
40
use crate::mem::mapped::RamBus;
41
use crate::sync::notifier::Notifier;
42
use crate::virtio::dev::{DevParam, Virtio, WakeEvent};
43
use crate::virtio::queue::{DescChain, QueueReg, Status as QStatus, VirtQueue};
44
use crate::virtio::worker::WorkerApi;
45
#[cfg(target_os = "linux")]
46
use crate::virtio::worker::io_uring::{ActiveIoUring, BufferAction, IoUring, VirtioIoUring};
47
use crate::virtio::worker::mio::{ActiveMio, Mio, VirtioMio};
48
use crate::virtio::{DeviceId, FEATURE_BUILT_IN, IrqSender, Result, error};
49
use crate::{c_enum, impl_mmio_for_zerocopy};
50

51
c_enum! {
52
    #[derive(FromBytes)]
53
    pub struct RequestType(u32);
54
    {
55
        IN = 0;
56
        OUT = 1;
57
        FLUSH = 4;
58
        GET_ID = 8;
59
        GET_LIFETIME = 10;
60
        DISCARD = 11;
61
        WRITE_ZEROES = 13;
62
        SECURE_ERASE = 14;
63
    }
64
}
65

66
c_enum! {
67
    #[derive(FromBytes)]
68
    pub struct Status(u8);
69
    {
70
        OK = 0;
71
        IOERR = 1;
72
        UNSUPP = 2;
73
    }
74
}
75

76
#[repr(C)]
77
#[derive(Debug, FromBytes)]
78
pub struct Request {
79
    type_: RequestType,
80
    reserved: u32,
81
    sector: u64,
82
}
83

84
pub const VIRTIO_BLK_ID_SIZE: usize = 20;
85

86
const SECTOR_SIZE: usize = 1 << 9;
87

88
bitflags! {
89
    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
90
    pub struct BlockFeature: u128 {
91
        const SIZE_MAX = 1 << 1;
92
        const SEG_MAX = 1 << 2;
93
        const GEOMETRY = 1 << 4;
94
        const RO = 1 << 5;
95
        const BLK_SIZE = 1 << 6;
96
        const FLUSH = 1 << 9;
97
        const TOPOLOGY = 1 << 10;
98
        const CONFIG_WCE = 1 << 11;
99
        const MQ = 1 << 12;
100
        const DISCARD = 1 << 13;
101
        const WRITE_ZEROS = 1 << 14;
102
        const LIFETIME = 1 << 15;
103
        const SECURE_ERASE = 1 << 16;
104
    }
105
}
106

107
#[derive(Debug, Default, FromZeros, Immutable, IntoBytes)]
108
#[repr(C)]
109
pub struct BlockConfig {
110
    capacity: u64,
111
    size_max: u32,
112
    seg_max: u32,
113

114
    // geometry
115
    cylinders: u16,
116
    heads: u8,
117
    sectors: u8,
118

119
    blk_size: u32,
120

121
    // topology
122
    physical_block_exp: u8,
123
    alignment_offset: u8,
124
    min_io_size: u16,
125
    opt_io_size: u32,
126

127
    writeback: u8,
128
    unused0: u8,
129
    num_queues: u16,
130
    max_discard_sectors: u32,
131
    max_discard_seg: u32,
132
    discard_sector_alignment: u32,
133
    max_write_zeroes_sectors: u32,
134
    max_write_zeroes_seg: u32,
135
    write_zeroes_may_unmap: u8,
136
    _unused1: [u8; 3],
137
    max_secure_erase_sectors: u32,
138
    max_secure_erase_seg: u32,
139
    secure_erase_sector_alignment: u32,
140
}
141
impl_mmio_for_zerocopy!(BlockConfig);
142

143
#[derive(Debug, Clone, Deserialize, Help, Default)]
144
pub struct BlkFileParam {
145
    /// Path to a raw-formatted disk image.
146
    pub path: PathBuf,
147
    /// Set the device as readonly. [default: false]
148
    #[serde(default)]
149
    pub readonly: bool,
150
    /// System API for asynchronous IO.
151
    #[serde(default)]
152
    pub api: WorkerApi,
153
}
154

155
impl DevParam for BlkFileParam {
156
    type Device = Block;
157

158
    fn build(self, name: impl Into<Arc<str>>) -> Result<Block> {
×
159
        Block::new(self, name)
×
160
    }
161
}
162

163
enum BlkRequest<'d, 'm> {
164
    Done {
165
        written: u32,
166
    },
167
    In {
168
        data: &'d mut IoSliceMut<'m>,
169
        offset: u64,
170
        status: &'d mut u8,
171
    },
172
    Out {
173
        data: &'d IoSlice<'m>,
174
        offset: u64,
175
        status: &'d mut u8,
176
    },
177
    Flush {
178
        status: &'d mut u8,
179
    },
180
}
181

182
#[derive(Debug)]
183
pub struct Block {
184
    name: Arc<str>,
185
    config: Arc<BlockConfig>,
186
    disk: File,
187
    feature: BlockFeature,
188
    api: WorkerApi,
189
}
190

191
impl Block {
192
    pub fn new(param: BlkFileParam, name: impl Into<Arc<str>>) -> Result<Self> {
×
193
        let access_disk = error::AccessFile {
194
            path: param.path.as_path(),
×
195
        };
196
        let disk = OpenOptions::new()
×
197
            .read(true)
198
            .write(!param.readonly)
×
199
            .open(&param.path)
×
200
            .context(access_disk)?;
×
201
        let len = disk.metadata().context(access_disk)?.len();
×
202
        let config = BlockConfig {
203
            capacity: len / SECTOR_SIZE as u64,
×
204
            num_queues: 1,
205
            ..Default::default()
206
        };
207
        let config = Arc::new(config);
×
208
        let mut feature = BlockFeature::FLUSH;
×
209
        if param.readonly {
×
210
            feature |= BlockFeature::RO;
×
211
        }
212
        Ok(Block {
×
213
            name: name.into(),
×
214
            disk,
×
215
            config,
×
216
            feature,
×
217
            api: param.api,
×
218
        })
219
    }
220

221
    fn handle_desc<'d, 'm>(&self, desc: &'d mut DescChain<'m>) -> Result<BlkRequest<'d, 'm>> {
×
222
        let [hdr, data_out @ ..] = &desc.readable[..] else {
×
223
            return error::InvalidBuffer.fail();
×
224
        };
225
        let Ok(request) = Request::read_from_bytes(hdr) else {
×
226
            return error::InvalidBuffer.fail();
×
227
        };
228
        let [data_in @ .., status_buf] = &mut desc.writable[..] else {
×
229
            return error::InvalidBuffer.fail();
×
230
        };
231
        let [status] = &mut status_buf[..] else {
×
232
            return error::InvalidBuffer.fail();
×
233
        };
234
        let offset = request.sector * SECTOR_SIZE as u64;
×
235
        match request.type_ {
×
236
            RequestType::IN => {
×
237
                let [data] = data_in else {
×
238
                    return error::InvalidBuffer.fail();
×
239
                };
240
                Ok(BlkRequest::In {
×
241
                    data,
×
242
                    offset,
×
243
                    status,
×
244
                })
245
            }
246
            RequestType::OUT => {
×
247
                if self.feature.contains(BlockFeature::RO) {
×
248
                    log::error!("{}: attempt to write to a read-only device", self.name);
×
249
                    *status = Status::IOERR.into();
×
250
                    return Ok(BlkRequest::Done { written: 1 });
×
251
                }
252
                let [data] = data_out else {
×
253
                    return error::InvalidBuffer.fail();
×
254
                };
255
                Ok(BlkRequest::Out {
×
256
                    data,
×
257
                    offset,
×
258
                    status,
×
259
                })
260
            }
261
            RequestType::FLUSH => Ok(BlkRequest::Flush { status }),
×
262
            RequestType::GET_ID => {
×
263
                let mut name_bytes = self.name.as_bytes();
×
264
                let count = name_bytes.read_vectored(data_in)? as u32;
×
265
                *status = Status::OK.into();
×
266
                Ok(BlkRequest::Done { written: 1 + count })
×
267
            }
268
            unknown => {
×
269
                log::error!("{}: unimplemented op: {unknown:#x?}", self.name);
×
270
                *status = Status::UNSUPP.into();
×
271
                Ok(BlkRequest::Done { written: 1 })
×
272
            }
273
        }
274
    }
275
}
276

277
impl Virtio for Block {
278
    type Config = BlockConfig;
279
    type Feature = BlockFeature;
280

281
    fn id(&self) -> DeviceId {
×
282
        DeviceId::Block
283
    }
284

285
    fn name(&self) -> &str {
×
286
        &self.name
×
287
    }
288

289
    fn num_queues(&self) -> u16 {
×
290
        self.config.num_queues
×
291
    }
292

293
    fn config(&self) -> Arc<BlockConfig> {
×
294
        self.config.clone()
×
295
    }
296

297
    fn feature(&self) -> u128 {
×
298
        self.feature.bits() | FEATURE_BUILT_IN
×
299
    }
300

301
    fn spawn_worker<S, E>(
×
302
        self,
303
        event_rx: Receiver<WakeEvent<S, E>>,
304
        memory: Arc<RamBus>,
305
        queue_regs: Arc<[QueueReg]>,
306
    ) -> Result<(JoinHandle<()>, Arc<Notifier>)>
307
    where
308
        S: IrqSender,
309
        E: IoeventFd,
310
    {
311
        match self.api {
×
312
            #[cfg(target_os = "linux")]
313
            WorkerApi::IoUring => IoUring::spawn_worker(self, event_rx, memory, queue_regs),
×
314
            WorkerApi::Mio => Mio::spawn_worker(self, event_rx, memory, queue_regs),
×
315
        }
316
    }
317
}
318

319
impl VirtioMio for Block {
320
    fn reset(&mut self, _registry: &Registry) {}
×
321

322
    fn activate<'m, Q, S, E>(
×
323
        &mut self,
324
        _feature: u128,
325
        _active_mio: &mut ActiveMio<'_, '_, 'm, Q, S, E>,
326
    ) -> Result<()>
327
    where
328
        Q: VirtQueue<'m>,
329
        S: IrqSender,
330
        E: IoeventFd,
331
    {
332
        Ok(())
×
333
    }
334

335
    fn handle_event<'a, 'm, Q, S, E>(
×
336
        &mut self,
337
        _event: &Event,
338
        _active_mio: &mut ActiveMio<'_, '_, 'm, Q, S, E>,
339
    ) -> Result<()>
340
    where
341
        Q: VirtQueue<'m>,
342
        S: IrqSender,
343
        E: IoeventFd,
344
    {
345
        Ok(())
×
346
    }
347

348
    fn handle_queue<'m, Q, S, E>(
×
349
        &mut self,
350
        index: u16,
351
        active_mio: &mut ActiveMio<'_, '_, 'm, Q, S, E>,
352
    ) -> Result<()>
353
    where
354
        Q: VirtQueue<'m>,
355
        S: IrqSender,
356
        E: IoeventFd,
357
    {
358
        let Some(Some(queue)) = active_mio.queues.get_mut(index as usize) else {
×
359
            log::error!("{}: invalid queue index {index}", self.name);
×
360
            return Ok(());
×
361
        };
362
        let mut disk = &self.disk;
×
363
        queue.handle_desc(index, active_mio.irq_sender, |chain| {
×
364
            let written_len = match Block::handle_desc(self, chain) {
×
365
                Err(e) => {
×
366
                    log::error!("{}: handle descriptor: {e}", self.name);
×
367
                    0
×
368
                }
369
                Ok(BlkRequest::Done { written }) => written,
×
370
                Ok(BlkRequest::In {
×
371
                    data,
×
372
                    offset,
×
373
                    status,
×
374
                }) => match disk.read_exact_at(data, offset) {
×
375
                    Ok(_) => {
×
376
                        *status = Status::OK.into();
×
377
                        data.len() as u32 + 1
×
378
                    }
379
                    Err(e) => {
×
380
                        log::error!("{}: read: {e}", self.name);
×
381
                        *status = Status::IOERR.into();
×
382
                        1
×
383
                    }
384
                },
385
                Ok(BlkRequest::Out {
×
386
                    data,
×
387
                    offset,
×
388
                    status,
×
389
                }) => {
×
390
                    match disk.write_all_at(data, offset) {
×
391
                        Ok(_) => *status = Status::OK.into(),
×
392
                        Err(e) => {
×
393
                            log::error!("{}: write: {e}", self.name);
×
394
                            *status = Status::IOERR.into();
×
395
                        }
396
                    }
397
                    1
×
398
                }
399
                Ok(BlkRequest::Flush { status }) => {
×
400
                    match disk.flush() {
×
401
                        Ok(_) => *status = Status::OK.into(),
×
402
                        Err(e) => {
×
403
                            log::error!("{}: flush: {e}", self.name);
×
404
                            *status = Status::IOERR.into();
×
405
                        }
406
                    }
407
                    1
×
408
                }
409
            };
410
            Ok(QStatus::Done { len: written_len })
×
411
        })
412
    }
413
}
414

415
#[cfg(target_os = "linux")]
416
impl VirtioIoUring for Block {
UNCOV
417
    fn activate<'m, Q, S, E>(
418
        &mut self,
419
        _feature: u128,
420
        _ring: &mut ActiveIoUring<'_, '_, 'm, Q, S, E>,
421
    ) -> Result<()>
422
    where
423
        S: IrqSender,
424
        Q: VirtQueue<'m>,
425
        E: IoeventFd,
426
    {
427
        Ok(())
×
428
    }
429

UNCOV
430
    fn handle_desc(&mut self, _q_index: u16, chain: &mut DescChain) -> Result<BufferAction> {
UNCOV
431
        let fd = Fd(self.disk.as_raw_fd());
UNCOV
432
        let action = match Block::handle_desc(self, chain)? {
UNCOV
433
            BlkRequest::Done { written } => BufferAction::Written(written),
UNCOV
434
            BlkRequest::In { data, offset, .. } => {
UNCOV
435
                let read = opcode::Read::new(fd, data.as_mut_ptr(), data.len() as u32)
UNCOV
436
                    .offset(offset)
437
                    .build();
UNCOV
438
                BufferAction::Sqe(read)
439
            }
UNCOV
440
            BlkRequest::Out { data, offset, .. } => {
UNCOV
441
                let write = opcode::Write::new(fd, data.as_ptr(), data.len() as u32)
UNCOV
442
                    .offset(offset)
443
                    .build();
UNCOV
444
                BufferAction::Sqe(write)
445
            }
446
            BlkRequest::Flush { .. } => {
UNCOV
447
                let flush = opcode::Fsync::new(fd).build();
UNCOV
448
                BufferAction::Sqe(flush)
449
            }
450
        };
UNCOV
451
        Ok(action)
452
    }
453

UNCOV
454
    fn complete_desc(&mut self, q_index: u16, chain: &mut DescChain, cqe: &Cqe) -> Result<u32> {
UNCOV
455
        let result = cqe.result();
UNCOV
456
        let status_code = if result >= 0 {
UNCOV
457
            Status::OK
458
        } else {
UNCOV
459
            let err = std::io::Error::from_raw_os_error(-result);
UNCOV
460
            log::error!("{}: queue-{q_index} io error: {err}", self.name,);
UNCOV
461
            Status::IOERR
462
        };
UNCOV
463
        match Block::handle_desc(self, chain)? {
464
            BlkRequest::Done { .. } => unreachable!(),
UNCOV
465
            BlkRequest::Flush { status } => {
UNCOV
466
                *status = status_code.into();
UNCOV
467
                Ok(1)
468
            }
UNCOV
469
            BlkRequest::In { data, status, .. } => {
UNCOV
470
                *status = status_code.into();
UNCOV
471
                Ok(data.len() as u32 + 1)
472
            }
UNCOV
473
            BlkRequest::Out { status, .. } => {
UNCOV
474
                *status = status_code.into();
UNCOV
475
                Ok(1)
476
            }
477
        }
478
    }
479
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc