Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/virtio/block/mod.rs
5394 views
1
// Copyright 2021 The ChromiumOS Authors
2
// Use of this source code is governed by a BSD-style license that can be
3
// found in the LICENSE file.
4
5
#[cfg(windows)]
6
use std::num::NonZeroU32;
7
use std::path::PathBuf;
8
9
use cros_async::ExecutorKind;
10
use serde::Deserialize;
11
use serde::Deserializer;
12
use serde::Serialize;
13
use serde::Serializer;
14
15
use crate::PciAddress;
16
17
pub mod asynchronous;
18
pub(crate) mod sys;
19
20
pub use asynchronous::BlockAsync;
21
22
fn block_option_sparse_default() -> bool {
23
true
24
}
25
fn block_option_lock_default() -> bool {
26
true
27
}
28
fn block_option_block_size_default() -> u32 {
29
512
30
}
31
// TODO(b/237829580): Move to sys module once virtio block sys is refactored to
32
// match the style guide.
33
#[cfg(windows)]
34
fn block_option_io_concurrency_default() -> NonZeroU32 {
35
NonZeroU32::new(1).unwrap()
36
}
37
38
/// Maximum length of a `DiskOption` identifier.
39
///
40
/// This is based on the virtio-block ID length limit.
41
pub const DISK_ID_LEN: usize = 20;
42
43
pub fn serialize_disk_id<S: Serializer>(
44
id: &Option<[u8; DISK_ID_LEN]>,
45
serializer: S,
46
) -> Result<S::Ok, S::Error> {
47
match id {
48
None => serializer.serialize_none(),
49
Some(id) => {
50
// Find the first zero byte in the id.
51
let len = id.iter().position(|v| *v == 0).unwrap_or(DISK_ID_LEN);
52
serializer.serialize_some(
53
std::str::from_utf8(&id[0..len])
54
.map_err(|e| serde::ser::Error::custom(e.to_string()))?,
55
)
56
}
57
}
58
}
59
60
fn deserialize_disk_id<'de, D: Deserializer<'de>>(
61
deserializer: D,
62
) -> Result<Option<[u8; DISK_ID_LEN]>, D::Error> {
63
let id = Option::<String>::deserialize(deserializer)?;
64
65
match id {
66
None => Ok(None),
67
Some(id) => {
68
if id.len() > DISK_ID_LEN {
69
return Err(serde::de::Error::custom(format!(
70
"disk id must be {DISK_ID_LEN} or fewer characters"
71
)));
72
}
73
74
let mut ret = [0u8; DISK_ID_LEN];
75
// Slicing id to value's length will never panic
76
// because we checked that value will fit into id above.
77
ret[..id.len()].copy_from_slice(id.as_bytes());
78
Ok(Some(ret))
79
}
80
}
81
}
82
83
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, serde_keyvalue::FromKeyValues)]
84
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
85
pub struct DiskOption {
86
pub path: PathBuf,
87
#[serde(default, rename = "ro")]
88
pub read_only: bool,
89
#[serde(default)]
90
/// Whether this disk should be the root device. Can only be set once. Only useful for adding
91
/// specific command-line options.
92
pub root: bool,
93
#[serde(default = "block_option_sparse_default")]
94
pub sparse: bool,
95
// camel_case variant allowed for backward compatibility.
96
#[serde(default, alias = "o_direct")]
97
pub direct: bool,
98
/// Whether to lock the disk files. Uses flock on Unix and FILE_SHARE_* flags on Windows.
99
#[serde(default = "block_option_lock_default")]
100
pub lock: bool,
101
// camel_case variant allowed for backward compatibility.
102
#[serde(default = "block_option_block_size_default", alias = "block_size")]
103
pub block_size: u32,
104
#[serde(
105
default,
106
serialize_with = "serialize_disk_id",
107
deserialize_with = "deserialize_disk_id"
108
)]
109
pub id: Option<[u8; DISK_ID_LEN]>,
110
// Deprecated: Use async_executor=overlapped[concurrency=N]"
111
// camel_case variant allowed for backward compatibility.
112
#[cfg(windows)]
113
#[serde(
114
default = "block_option_io_concurrency_default",
115
alias = "io_concurrency"
116
)]
117
pub io_concurrency: NonZeroU32,
118
#[serde(default)]
119
/// Experimental option to run multiple worker threads in parallel. If false, only single
120
/// thread runs by default. Note this option is not effective for vhost-user blk device.
121
pub multiple_workers: bool,
122
#[serde(default, alias = "async_executor")]
123
/// The async executor kind to simulate the block device with. This option takes
124
/// precedence over the async executor kind specified by the subcommand's option.
125
/// If None, the default or the specified by the subcommand's option would be used.
126
pub async_executor: Option<ExecutorKind>,
127
#[serde(default)]
128
//Option to choose virtqueue type. If true, use the packed virtqueue. If false
129
//or by default, use split virtqueue
130
pub packed_queue: bool,
131
132
/// Specify the boot index for this device that the BIOS will use when attempting to boot from
133
/// bootable devices. For example, if bootindex=2, then the BIOS will attempt to boot from the
134
/// device right after booting from the device with bootindex=1 fails.
135
pub bootindex: Option<usize>,
136
137
/// Specify PCI address will be used to attach this device
138
pub pci_address: Option<PciAddress>,
139
}
140
141
impl Default for DiskOption {
142
fn default() -> Self {
143
Self {
144
path: PathBuf::new(),
145
read_only: false,
146
root: false,
147
sparse: block_option_sparse_default(),
148
direct: false,
149
lock: block_option_lock_default(),
150
block_size: block_option_block_size_default(),
151
id: None,
152
#[cfg(windows)]
153
io_concurrency: block_option_io_concurrency_default(),
154
multiple_workers: false,
155
async_executor: None,
156
packed_queue: false,
157
bootindex: None,
158
pci_address: None,
159
}
160
}
161
}
162
163
#[cfg(test)]
164
mod tests {
165
#[cfg(any(target_os = "android", target_os = "linux"))]
166
use cros_async::sys::linux::ExecutorKindSys;
167
#[cfg(windows)]
168
use cros_async::sys::windows::ExecutorKindSys;
169
use serde_keyvalue::*;
170
171
use super::*;
172
173
fn from_block_arg(options: &str) -> Result<DiskOption, ParseError> {
174
from_key_values(options)
175
}
176
177
#[test]
178
fn check_default_matches_from_key_values() {
179
let path = "/path/to/disk.img";
180
let disk = DiskOption {
181
path: PathBuf::from(path),
182
..DiskOption::default()
183
};
184
assert_eq!(disk, from_key_values(path).unwrap());
185
}
186
187
#[test]
188
fn params_from_key_values() {
189
// Path argument is mandatory.
190
let err = from_block_arg("").unwrap_err();
191
assert_eq!(
192
err,
193
ParseError {
194
kind: ErrorKind::SerdeError("missing field `path`".into()),
195
pos: 0,
196
}
197
);
198
199
// Path is the default argument.
200
let params = from_block_arg("/path/to/disk.img").unwrap();
201
assert_eq!(
202
params,
203
DiskOption {
204
path: "/path/to/disk.img".into(),
205
read_only: false,
206
root: false,
207
sparse: true,
208
direct: false,
209
lock: true,
210
block_size: 512,
211
id: None,
212
#[cfg(windows)]
213
io_concurrency: NonZeroU32::new(1).unwrap(),
214
multiple_workers: false,
215
async_executor: None,
216
packed_queue: false,
217
bootindex: None,
218
pci_address: None,
219
}
220
);
221
222
// bootindex
223
let params = from_block_arg("/path/to/disk.img,bootindex=5").unwrap();
224
assert_eq!(
225
params,
226
DiskOption {
227
path: "/path/to/disk.img".into(),
228
read_only: false,
229
root: false,
230
sparse: true,
231
direct: false,
232
lock: true,
233
block_size: 512,
234
id: None,
235
#[cfg(windows)]
236
io_concurrency: NonZeroU32::new(1).unwrap(),
237
multiple_workers: false,
238
async_executor: None,
239
packed_queue: false,
240
bootindex: Some(5),
241
pci_address: None,
242
}
243
);
244
245
// Explicitly-specified path.
246
let params = from_block_arg("path=/path/to/disk.img").unwrap();
247
assert_eq!(
248
params,
249
DiskOption {
250
path: "/path/to/disk.img".into(),
251
read_only: false,
252
root: false,
253
sparse: true,
254
direct: false,
255
lock: true,
256
block_size: 512,
257
id: None,
258
#[cfg(windows)]
259
io_concurrency: NonZeroU32::new(1).unwrap(),
260
multiple_workers: false,
261
async_executor: None,
262
packed_queue: false,
263
bootindex: None,
264
pci_address: None,
265
}
266
);
267
268
// read_only
269
let params = from_block_arg("/some/path.img,ro").unwrap();
270
assert_eq!(
271
params,
272
DiskOption {
273
path: "/some/path.img".into(),
274
read_only: true,
275
root: false,
276
sparse: true,
277
direct: false,
278
lock: true,
279
block_size: 512,
280
id: None,
281
#[cfg(windows)]
282
io_concurrency: NonZeroU32::new(1).unwrap(),
283
multiple_workers: false,
284
async_executor: None,
285
packed_queue: false,
286
bootindex: None,
287
pci_address: None,
288
}
289
);
290
291
// root
292
let params = from_block_arg("/some/path.img,root").unwrap();
293
assert_eq!(
294
params,
295
DiskOption {
296
path: "/some/path.img".into(),
297
read_only: false,
298
root: true,
299
sparse: true,
300
direct: false,
301
lock: true,
302
block_size: 512,
303
id: None,
304
#[cfg(windows)]
305
io_concurrency: NonZeroU32::new(1).unwrap(),
306
multiple_workers: false,
307
async_executor: None,
308
packed_queue: false,
309
bootindex: None,
310
pci_address: None,
311
}
312
);
313
314
// sparse
315
let params = from_block_arg("/some/path.img,sparse").unwrap();
316
assert_eq!(
317
params,
318
DiskOption {
319
path: "/some/path.img".into(),
320
read_only: false,
321
root: false,
322
sparse: true,
323
direct: false,
324
lock: true,
325
block_size: 512,
326
id: None,
327
#[cfg(windows)]
328
io_concurrency: NonZeroU32::new(1).unwrap(),
329
multiple_workers: false,
330
async_executor: None,
331
packed_queue: false,
332
bootindex: None,
333
pci_address: None,
334
}
335
);
336
let params = from_block_arg("/some/path.img,sparse=false").unwrap();
337
assert_eq!(
338
params,
339
DiskOption {
340
path: "/some/path.img".into(),
341
read_only: false,
342
root: false,
343
sparse: false,
344
direct: false,
345
lock: true,
346
block_size: 512,
347
id: None,
348
#[cfg(windows)]
349
io_concurrency: NonZeroU32::new(1).unwrap(),
350
multiple_workers: false,
351
async_executor: None,
352
packed_queue: false,
353
bootindex: None,
354
pci_address: None,
355
}
356
);
357
358
// direct
359
let params = from_block_arg("/some/path.img,direct").unwrap();
360
assert_eq!(
361
params,
362
DiskOption {
363
path: "/some/path.img".into(),
364
read_only: false,
365
root: false,
366
sparse: true,
367
direct: true,
368
lock: true,
369
block_size: 512,
370
id: None,
371
#[cfg(windows)]
372
io_concurrency: NonZeroU32::new(1).unwrap(),
373
multiple_workers: false,
374
async_executor: None,
375
packed_queue: false,
376
bootindex: None,
377
pci_address: None,
378
}
379
);
380
381
// o_direct (deprecated, kept for backward compatibility)
382
let params = from_block_arg("/some/path.img,o_direct").unwrap();
383
assert_eq!(
384
params,
385
DiskOption {
386
path: "/some/path.img".into(),
387
read_only: false,
388
root: false,
389
sparse: true,
390
direct: true,
391
lock: true,
392
block_size: 512,
393
id: None,
394
#[cfg(windows)]
395
io_concurrency: NonZeroU32::new(1).unwrap(),
396
multiple_workers: false,
397
async_executor: None,
398
packed_queue: false,
399
bootindex: None,
400
pci_address: None,
401
}
402
);
403
404
// block-size
405
let params = from_block_arg("/some/path.img,block-size=128").unwrap();
406
assert_eq!(
407
params,
408
DiskOption {
409
path: "/some/path.img".into(),
410
read_only: false,
411
root: false,
412
sparse: true,
413
direct: false,
414
lock: true,
415
block_size: 128,
416
id: None,
417
#[cfg(windows)]
418
io_concurrency: NonZeroU32::new(1).unwrap(),
419
multiple_workers: false,
420
async_executor: None,
421
packed_queue: false,
422
bootindex: None,
423
pci_address: None,
424
}
425
);
426
427
// block_size (deprecated, kept for backward compatibility)
428
let params = from_block_arg("/some/path.img,block_size=128").unwrap();
429
assert_eq!(
430
params,
431
DiskOption {
432
path: "/some/path.img".into(),
433
read_only: false,
434
root: false,
435
sparse: true,
436
direct: false,
437
lock: true,
438
block_size: 128,
439
id: None,
440
async_executor: None,
441
#[cfg(windows)]
442
io_concurrency: NonZeroU32::new(1).unwrap(),
443
multiple_workers: false,
444
packed_queue: false,
445
bootindex: None,
446
pci_address: None,
447
}
448
);
449
450
// io_concurrency
451
#[cfg(windows)]
452
{
453
let params = from_block_arg("/some/path.img,io_concurrency=4").unwrap();
454
assert_eq!(
455
params,
456
DiskOption {
457
path: "/some/path.img".into(),
458
read_only: false,
459
root: false,
460
sparse: true,
461
direct: false,
462
lock: true,
463
block_size: 512,
464
id: None,
465
io_concurrency: NonZeroU32::new(4).unwrap(),
466
multiple_workers: false,
467
async_executor: None,
468
packed_queue: false,
469
bootindex: None,
470
pci_address: None,
471
}
472
);
473
let params = from_block_arg("/some/path.img,async-executor=overlapped").unwrap();
474
assert_eq!(
475
params,
476
DiskOption {
477
path: "/some/path.img".into(),
478
read_only: false,
479
root: false,
480
sparse: true,
481
direct: false,
482
lock: true,
483
block_size: 512,
484
id: None,
485
io_concurrency: NonZeroU32::new(1).unwrap(),
486
multiple_workers: false,
487
async_executor: Some(ExecutorKindSys::Overlapped { concurrency: None }.into()),
488
packed_queue: false,
489
bootindex: None,
490
pci_address: None,
491
}
492
);
493
let params =
494
from_block_arg("/some/path.img,async-executor=\"overlapped,concurrency=4\"")
495
.unwrap();
496
assert_eq!(
497
params,
498
DiskOption {
499
path: "/some/path.img".into(),
500
read_only: false,
501
root: false,
502
sparse: true,
503
direct: false,
504
lock: true,
505
block_size: 512,
506
id: None,
507
io_concurrency: NonZeroU32::new(1).unwrap(),
508
multiple_workers: false,
509
async_executor: Some(
510
ExecutorKindSys::Overlapped {
511
concurrency: Some(4)
512
}
513
.into()
514
),
515
packed_queue: false,
516
bootindex: None,
517
pci_address: None,
518
}
519
);
520
}
521
522
// id
523
let params = from_block_arg("/some/path.img,id=DISK").unwrap();
524
assert_eq!(
525
params,
526
DiskOption {
527
path: "/some/path.img".into(),
528
read_only: false,
529
root: false,
530
sparse: true,
531
direct: false,
532
lock: true,
533
block_size: 512,
534
id: Some(*b"DISK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
535
#[cfg(windows)]
536
io_concurrency: NonZeroU32::new(1).unwrap(),
537
multiple_workers: false,
538
async_executor: None,
539
packed_queue: false,
540
bootindex: None,
541
pci_address: None,
542
}
543
);
544
let err = from_block_arg("/some/path.img,id=DISK_ID_IS_WAY_TOO_LONG").unwrap_err();
545
assert_eq!(
546
err,
547
ParseError {
548
kind: ErrorKind::SerdeError("disk id must be 20 or fewer characters".into()),
549
pos: 0,
550
}
551
);
552
553
// async-executor
554
#[cfg(windows)]
555
let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Handle.into(), "handle");
556
#[cfg(any(target_os = "android", target_os = "linux"))]
557
let (ex_kind, ex_kind_opt) = (ExecutorKindSys::Fd.into(), "epoll");
558
let params =
559
from_block_arg(&format!("/some/path.img,async-executor={ex_kind_opt}")).unwrap();
560
assert_eq!(
561
params,
562
DiskOption {
563
path: "/some/path.img".into(),
564
read_only: false,
565
root: false,
566
sparse: true,
567
direct: false,
568
lock: true,
569
block_size: 512,
570
id: None,
571
#[cfg(windows)]
572
io_concurrency: NonZeroU32::new(1).unwrap(),
573
multiple_workers: false,
574
async_executor: Some(ex_kind),
575
packed_queue: false,
576
bootindex: None,
577
pci_address: None,
578
}
579
);
580
581
// packed queue
582
let params = from_block_arg("/path/to/disk.img,packed-queue").unwrap();
583
assert_eq!(
584
params,
585
DiskOption {
586
path: "/path/to/disk.img".into(),
587
read_only: false,
588
root: false,
589
sparse: true,
590
direct: false,
591
lock: true,
592
block_size: 512,
593
id: None,
594
#[cfg(windows)]
595
io_concurrency: NonZeroU32::new(1).unwrap(),
596
multiple_workers: false,
597
async_executor: None,
598
packed_queue: true,
599
bootindex: None,
600
pci_address: None,
601
}
602
);
603
604
// pci-address
605
let params = from_block_arg("/path/to/disk.img,pci-address=00:01.1").unwrap();
606
assert_eq!(
607
params,
608
DiskOption {
609
path: "/path/to/disk.img".into(),
610
read_only: false,
611
root: false,
612
sparse: true,
613
direct: false,
614
lock: true,
615
block_size: 512,
616
id: None,
617
#[cfg(windows)]
618
io_concurrency: NonZeroU32::new(1).unwrap(),
619
multiple_workers: false,
620
async_executor: None,
621
packed_queue: false,
622
bootindex: None,
623
pci_address: Some(PciAddress {
624
bus: 0,
625
dev: 1,
626
func: 1,
627
}),
628
}
629
);
630
631
// lock=true
632
let params = from_block_arg("/path/to/disk.img,lock=true").unwrap();
633
assert_eq!(
634
params,
635
DiskOption {
636
path: "/path/to/disk.img".into(),
637
read_only: false,
638
root: false,
639
sparse: true,
640
direct: false,
641
lock: true,
642
block_size: 512,
643
id: None,
644
#[cfg(windows)]
645
io_concurrency: NonZeroU32::new(1).unwrap(),
646
multiple_workers: false,
647
async_executor: None,
648
packed_queue: false,
649
bootindex: None,
650
pci_address: None,
651
}
652
);
653
// lock=false
654
let params = from_block_arg("/path/to/disk.img,lock=false").unwrap();
655
assert_eq!(
656
params,
657
DiskOption {
658
path: "/path/to/disk.img".into(),
659
read_only: false,
660
root: false,
661
sparse: true,
662
direct: false,
663
lock: false,
664
block_size: 512,
665
id: None,
666
#[cfg(windows)]
667
io_concurrency: NonZeroU32::new(1).unwrap(),
668
multiple_workers: false,
669
async_executor: None,
670
packed_queue: false,
671
bootindex: None,
672
pci_address: None,
673
}
674
);
675
676
// All together
677
let params = from_block_arg(&format!(
678
"/some/path.img,block_size=256,ro,root,sparse=false,id=DISK_LABEL\
679
,direct,async-executor={ex_kind_opt},packed-queue=false,pci-address=00:01.1"
680
))
681
.unwrap();
682
assert_eq!(
683
params,
684
DiskOption {
685
path: "/some/path.img".into(),
686
read_only: true,
687
root: true,
688
sparse: false,
689
direct: true,
690
lock: true,
691
block_size: 256,
692
id: Some(*b"DISK_LABEL\0\0\0\0\0\0\0\0\0\0"),
693
#[cfg(windows)]
694
io_concurrency: NonZeroU32::new(1).unwrap(),
695
multiple_workers: false,
696
async_executor: Some(ex_kind),
697
packed_queue: false,
698
bootindex: None,
699
pci_address: Some(PciAddress {
700
bus: 0,
701
dev: 1,
702
func: 1,
703
}),
704
}
705
);
706
}
707
708
#[test]
709
fn diskoption_serialize_deserialize() {
710
// With id == None
711
let original = DiskOption {
712
path: "./rootfs".into(),
713
read_only: false,
714
root: false,
715
sparse: true,
716
direct: false,
717
lock: true,
718
block_size: 512,
719
id: None,
720
#[cfg(windows)]
721
io_concurrency: NonZeroU32::new(1).unwrap(),
722
multiple_workers: false,
723
async_executor: None,
724
packed_queue: false,
725
bootindex: None,
726
pci_address: None,
727
};
728
let json = serde_json::to_string(&original).unwrap();
729
let deserialized = serde_json::from_str(&json).unwrap();
730
assert_eq!(original, deserialized);
731
732
// With id == Some
733
let original = DiskOption {
734
path: "./rootfs".into(),
735
read_only: false,
736
root: false,
737
sparse: true,
738
direct: false,
739
lock: true,
740
block_size: 512,
741
id: Some(*b"BLK\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"),
742
#[cfg(windows)]
743
io_concurrency: NonZeroU32::new(1).unwrap(),
744
multiple_workers: false,
745
async_executor: Some(ExecutorKind::default()),
746
packed_queue: false,
747
bootindex: None,
748
pci_address: None,
749
};
750
let json = serde_json::to_string(&original).unwrap();
751
let deserialized = serde_json::from_str(&json).unwrap();
752
assert_eq!(original, deserialized);
753
754
// With id taking all the available space.
755
let original = DiskOption {
756
path: "./rootfs".into(),
757
read_only: false,
758
root: false,
759
sparse: true,
760
direct: false,
761
lock: true,
762
block_size: 512,
763
id: Some(*b"QWERTYUIOPASDFGHJKL:"),
764
#[cfg(windows)]
765
io_concurrency: NonZeroU32::new(1).unwrap(),
766
multiple_workers: false,
767
async_executor: Some(ExecutorKind::default()),
768
packed_queue: false,
769
bootindex: None,
770
pci_address: None,
771
};
772
let json = serde_json::to_string(&original).unwrap();
773
let deserialized = serde_json::from_str(&json).unwrap();
774
assert_eq!(original, deserialized);
775
}
776
}
777
778