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

google / alioth / 17185259678

24 Aug 2025 06:24AM UTC coverage: 13.868% (-0.02%) from 13.887%
17185259678

Pull #277

github

web-flow
Merge 2d53e34b0 into 861f19073
Pull Request #277: feat: Unix domain socket based vsock device

62 of 101 new or added lines in 9 files covered. (61.39%)

72 existing lines in 5 files now uncovered.

972 of 7009 relevant lines covered (13.87%)

17.77 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::virtio::dev::{DevParam, Virtio, WakeEvent};
42
use crate::virtio::queue::{DescChain, QueueReg, Status as QStatus, VirtQueue};
43
#[cfg(target_os = "linux")]
44
use crate::virtio::worker::io_uring::{ActiveIoUring, BufferAction, IoUring, VirtioIoUring};
45
use crate::virtio::worker::mio::{ActiveMio, Mio, VirtioMio};
46
use crate::virtio::worker::{Waker, WorkerApi};
47
use crate::virtio::{DeviceId, FEATURE_BUILT_IN, IrqSender, Result, error};
48
use crate::{c_enum, impl_mmio_for_zerocopy};
49

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

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

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

83
pub const VIRTIO_BLK_ID_SIZE: usize = 20;
84

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

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

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

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

118
    blk_size: u32,
119

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

453
    fn complete_desc(&mut self, q_index: u16, chain: &mut DescChain, cqe: &Cqe) -> Result<u32> {
454
        let result = cqe.result();
455
        let status_code = if result >= 0 {
456
            Status::OK
457
        } else {
458
            let err = std::io::Error::from_raw_os_error(-result);
459
            log::error!("{}: queue-{q_index} io error: {err}", self.name,);
460
            Status::IOERR
461
        };
462
        match Block::handle_desc(self, chain)? {
463
            BlkRequest::Done { .. } => unreachable!(),
464
            BlkRequest::Flush { status } => {
465
                *status = status_code.into();
466
                Ok(1)
467
            }
468
            BlkRequest::In { data, status, .. } => {
469
                *status = status_code.into();
470
                Ok(data.len() as u32 + 1)
471
            }
472
            BlkRequest::Out { status, .. } => {
473
                *status = status_code.into();
474
                Ok(1)
475
            }
476
        }
477
    }
478
}
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

© 2026 Coveralls, Inc