| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| #![deny(warnings)] |
| |
| use libc::{self, c_int}; |
| use std::fs::{self, File, OpenOptions}; |
| use std::io::{Error, ErrorKind, Read, Seek, SeekFrom, Write}; |
| use std::os::unix::io::AsRawFd; |
| use std::path::Path; |
| use structopt::StructOpt; |
| |
| const DEV_DIR: &str = "/dev"; |
| |
| // Ioctl requests are comprised of 32 bits: |
| // [31:30] Access mode |
| // [29:16] Size of the parameter structure |
| // [15:8] Request type |
| // [7:0] Request number |
| // For the ioctls we care about here there are no parameters, so we set only a type and a number. |
| // See Linux:include/uapi/asm-generic/ioctl.h for more details. |
| const TYPE_SHIFT: usize = 8; |
| macro_rules! define_ioctl { |
| ($name:ident, $typ:expr, $num:expr, $return_type:ty) => { |
| fn $name(file: &File) -> $return_type { |
| let request: c_int = ($typ << TYPE_SHIFT) | ($num & 0xff); |
| let mut r: $return_type = 0; |
| unsafe { |
| libc::ioctl(file.as_raw_fd(), request, &mut r); |
| } |
| r |
| } |
| }; |
| } |
| |
| // Block ioctl types and number are defined in Linux:include/uapi/linux/fs.h. |
| define_ioctl!(block_dev_size, 0x12, 96, u64); |
| define_ioctl!(block_dev_sector_size, 0x12, 104, u32); |
| |
| #[derive(StructOpt, Debug)] |
| struct Config { |
| block_size: u32, |
| block_count: u64, |
| #[structopt(subcommand)] |
| cmd: Command, |
| } |
| |
| #[derive(StructOpt, Debug)] |
| enum Command { |
| #[structopt(name = "check")] |
| Check, |
| #[structopt(name = "read")] |
| Read { offset: u64, expected: u8 }, |
| #[structopt(name = "write")] |
| Write { offset: u64, value: u8 }, |
| } |
| |
| fn find_block_device(block_size: u32, block_count: u64, write: bool) -> std::io::Result<File> { |
| let dir = Path::new(DEV_DIR); |
| if !dir.is_dir() { |
| return Err(Error::new( |
| ErrorKind::Other, |
| format!("{} is not a directory", DEV_DIR), |
| )); |
| } |
| for entry in fs::read_dir(dir)?.collect::<std::io::Result<Vec<_>>>()? { |
| if !entry |
| .file_name() |
| .to_str() |
| .map_or(false, |f| f.starts_with("vd")) |
| { |
| continue; |
| } |
| let file = OpenOptions::new() |
| .read(true) |
| .write(write) |
| .open(entry.path())?; |
| if block_size != block_dev_sector_size(&file) { |
| continue; |
| } |
| if block_count != block_dev_size(&file) { |
| continue; |
| } |
| return Ok(file); |
| } |
| Err(Error::new(ErrorKind::NotFound, "Block device not found")) |
| } |
| |
| fn read_block( |
| block_dev: &mut File, |
| block_size: u32, |
| offset: u64, |
| expected: u8, |
| ) -> std::io::Result<()> { |
| block_dev.seek(SeekFrom::Start(offset * block_size as u64))?; |
| let mut data: Vec<u8> = vec![0; block_size as usize]; |
| block_dev.read_exact(&mut data)?; |
| if !data.iter().all(|&b| b == expected) { |
| return Err(Error::new(ErrorKind::Other, "Incorrect data read")); |
| } |
| Ok(()) |
| } |
| |
| fn write_block( |
| block_dev: &mut File, |
| block_size: u32, |
| offset: u64, |
| value: u8, |
| ) -> std::io::Result<()> { |
| block_dev.seek(SeekFrom::Start(offset * block_size as u64))?; |
| let data: Vec<u8> = vec![value; block_size as usize]; |
| block_dev.write_all(&data)?; |
| block_dev.sync_all()?; |
| Ok(()) |
| } |
| |
| fn main() -> std::io::Result<()> { |
| let config = Config::from_args(); |
| let write = if let Command::Write { .. } = config.cmd { |
| true |
| } else { |
| false |
| }; |
| let mut block_dev = find_block_device(config.block_size, config.block_count, write)?; |
| let result = match config.cmd { |
| Command::Check => Ok(()), |
| Command::Read { offset, expected } => { |
| read_block(&mut block_dev, config.block_size, offset, expected) |
| } |
| Command::Write { offset, value } => { |
| write_block(&mut block_dev, config.block_size, offset, value) |
| } |
| }; |
| if result.is_ok() { |
| println!("PASS"); |
| } |
| result |
| } |