diff --git a/Cargo.lock b/Cargo.lock index 272f63687b..13813dde0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,7 +445,9 @@ dependencies = [ name = "ided" version = "0.1.0" dependencies = [ + "block-io-wrapper", "log", + "partitionlib", "pcid", "redox-daemon", "redox-log", diff --git a/ided/Cargo.toml b/ided/Cargo.toml index d62d829374..27052da67e 100644 --- a/ided/Cargo.toml +++ b/ided/Cargo.toml @@ -4,7 +4,9 @@ version = "0.1.0" edition = "2018" [dependencies] +block-io-wrapper = { path = "../block-io-wrapper" } log = "0.4" +partitionlib = { git = "https://gitlab.redox-os.org/redox-os/partitionlib.git" } pcid = { path = "../pcid" } redox-daemon = "0.1" redox-log = "0.1" diff --git a/ided/src/ata.rs b/ided/src/ata.rs new file mode 100644 index 0000000000..6cd72310d5 --- /dev/null +++ b/ided/src/ata.rs @@ -0,0 +1,16 @@ +#[repr(u8)] +pub enum AtaCommand { + ReadPio = 0x20, + ReadPioExt = 0x24, + ReadDma = 0xC8, + ReadDmaExt = 0x25, + WritePio = 0x30, + WritePioExt = 0x34, + WriteDma = 0xCA, + WriteDmaExt = 0x35, + CacheFlush = 0xE7, + CacheFlushExt = 0xEA, + Packet = 0xA0, + IdentifyPacket = 0xA1, + Identify = 0xEC, +} diff --git a/ided/src/ide.rs b/ided/src/ide.rs new file mode 100644 index 0000000000..73fdbeff37 --- /dev/null +++ b/ided/src/ide.rs @@ -0,0 +1,224 @@ +use std::{ + sync::{Arc, Mutex}, + thread, +}; +use syscall::{ + error::{Error, Result, EIO}, + io::{Io, Pio, ReadOnly, WriteOnly}, +}; + +use crate::ata::AtaCommand; + +pub struct Channel { + pub data8: Pio, + pub data32: Pio, + pub error: ReadOnly>, + pub features: WriteOnly>, + pub sector_count: Pio, + pub lba_0: Pio, + pub lba_1: Pio, + pub lba_2: Pio, + pub device_select: Pio, + pub status: ReadOnly>, + pub command: WriteOnly>, + pub alt_status: ReadOnly>, + pub control: WriteOnly>, +} + +impl Channel { + pub fn new(base: u16, control_base: u16) -> Self { + Self { + data8: Pio::new(base + 0), + data32: Pio::new(base + 0), + error: ReadOnly::new(Pio::new(base + 1)), + features: WriteOnly::new(Pio::new(base + 1)), + sector_count: Pio::new(base + 2), + lba_0: Pio::new(base + 3), + lba_1: Pio::new(base + 4), + lba_2: Pio::new(base + 5), + device_select: Pio::new(base + 6), + status: ReadOnly::new(Pio::new(base + 7)), + command: WriteOnly::new(Pio::new(base + 7)), + alt_status: ReadOnly::new(Pio::new(control_base + 2)), + control: WriteOnly::new(Pio::new(control_base + 2)), + } + } + + pub fn primary_compat() -> Self { + Self::new(0x1F0, 0x3F6) + } + + pub fn secondary_compat() -> Self { + Self::new(0x170, 0x376) + } + + fn polling(&mut self, check: bool) -> Result<()> { + /* + #define ATA_SR_BSY 0x80 // Busy + #define ATA_SR_DRDY 0x40 // Drive ready + #define ATA_SR_DF 0x20 // Drive write fault + #define ATA_SR_DSC 0x10 // Drive seek complete + #define ATA_SR_DRQ 0x08 // Data request ready + #define ATA_SR_CORR 0x04 // Corrected data + #define ATA_SR_IDX 0x02 // Index + #define ATA_SR_ERR 0x01 // Error + */ + + for _ in 0..4 { + // Doing this 4 times creates a 400ns delay + self.alt_status.read(); + } + + while self.status.readf(0x80) { + thread::yield_now(); + } + + if check { + let status = self.status.read(); + + if status & 0x01 != 0 { + log::error!("IDE error"); + return Err(Error::new(EIO)); + } + + if status & 0x20 != 0 { + log::error!("IDE device write fault"); + return Err(Error::new(EIO)); + } + + if status & 0x08 == 0 { + log::error!("IDE data not ready"); + return Err(Error::new(EIO)); + } + } + + Ok(()) + } +} + +pub trait Disk { + fn id(&self) -> usize; + fn size(&mut self) -> u64; + fn read(&mut self, block: u64, buffer: &mut [u8]) -> Result>; + fn write(&mut self, block: u64, buffer: &[u8]) -> Result>; + fn block_length(&mut self) -> Result; +} + +pub struct AtaDisk { + pub chan: Arc>, + pub chan_i: usize, + pub dev: u8, + pub size: u64, +} + +impl Disk for AtaDisk { + fn id(&self) -> usize { + self.chan_i << 1 | self.dev as usize + } + + fn size(&mut self) -> u64 { + self.size + } + + fn read(&mut self, block: u64, buffer: &mut [u8]) -> Result> { + //TODO: support other LBA modes + assert!(block < 0x1_0000_0000_0000); + + let sectors = buffer.len() / 512; + assert!(sectors < 0x1_0000); + + let mut chan = self.chan.lock().unwrap(); + + // Select drive + chan.device_select.write(0xE0 | (self.dev << 4)); + + // Set high sector count and LBA + //TODO: only if LBA mode is 48-bit + chan.control.writef(0x80, true); + chan.sector_count.write((sectors >> 8) as u8); + chan.lba_0.write((block >> 24) as u8); + chan.lba_1.write((block >> 32) as u8); + chan.lba_2.write((block >> 40) as u8); + chan.control.writef(0x80, false); + + // Set low sector count and LBA + chan.sector_count.write(sectors as u8); + chan.lba_0.write(block as u8); + chan.lba_1.write((block >> 8) as u8); + chan.lba_2.write((block >> 16) as u8); + + // Send command + //TODO: use DMA + chan.command.write(AtaCommand::ReadPioExt as u8); + + // Read data + for sector in 0..sectors { + chan.polling(true)?; + + for i in 0..128 { + let data = chan.data32.read(); + buffer[sector * 512 + i * 4 + 0] = (data >> 0) as u8; + buffer[sector * 512 + i * 4 + 1] = (data >> 8) as u8; + buffer[sector * 512 + i * 4 + 2] = (data >> 16) as u8; + buffer[sector * 512 + i * 4 + 3] = (data >> 24) as u8; + } + } + + Ok(Some(sectors * 512)) + } + + fn write(&mut self, block: u64, buffer: &[u8]) -> Result> { + //TODO: support other LBA modes + assert!(block < 0x1_0000_0000_0000); + + let sectors = buffer.len() / 512; + assert!(sectors < 0x1_0000); + + let mut chan = self.chan.lock().unwrap(); + + // Select drive + chan.device_select.write(0xE0 | (self.dev << 4)); + + // Set high sector count and LBA + //TODO: only if LBA mode is 48-bit + chan.control.writef(0x80, true); + chan.sector_count.write((sectors >> 8) as u8); + chan.lba_0.write((block >> 24) as u8); + chan.lba_1.write((block >> 32) as u8); + chan.lba_2.write((block >> 40) as u8); + chan.control.writef(0x80, false); + + // Set low sector count and LBA + chan.sector_count.write(sectors as u8); + chan.lba_0.write(block as u8); + chan.lba_1.write((block >> 8) as u8); + chan.lba_2.write((block >> 16) as u8); + + // Send command + //TODO: use DMA + chan.command.write(AtaCommand::WritePioExt as u8); + + // Write data + for sector in 0..sectors { + chan.polling(false)?; + + for i in 0..128 { + chan.data32.write( + ((buffer[sector * 512 + i * 4 + 0] as u32) << 0) | + ((buffer[sector * 512 + i * 4 + 1] as u32) << 8) | + ((buffer[sector * 512 + i * 4 + 2] as u32) << 16) | + ((buffer[sector * 512 + i * 4 + 3] as u32) << 24) + ); + } + } + + chan.command.write(AtaCommand::CacheFlushExt as u8); + chan.polling(false)?; + + Ok(Some(sectors * 512)) + } + + fn block_length(&mut self) -> Result { + Ok(512) + } +} diff --git a/ided/src/main.rs b/ided/src/main.rs index 6391ce4f6b..315beb5cad 100644 --- a/ided/src/main.rs +++ b/ided/src/main.rs @@ -1,9 +1,33 @@ -use pcid_interface::PcidServerHandle; - use log::{error, info}; +use pcid_interface::PcidServerHandle; use redox_log::{OutputBuilder, RedoxLogger}; +use std::{ + fs::File, + io::{ErrorKind, Read, Write}, + os::unix::io::{FromRawFd, RawFd}, + sync::{Arc, Mutex}, + thread::sleep, + time::Duration, +}; +use syscall::{ + data::{Event, Packet}, + error::{Error, ENODEV}, + flag::{EVENT_READ}, + io::Io, + scheme::SchemeBlockMut, +}; -fn setup_logging() -> Option<&'static RedoxLogger> { +use crate::{ + ata::AtaCommand, + ide::{AtaDisk, Channel, Disk}, + scheme::DiskScheme, +}; + +pub mod ata; +pub mod ide; +pub mod scheme; + +fn setup_logging(name: &str) -> Option<&'static RedoxLogger> { let mut logger = RedoxLogger::new() .with_output( OutputBuilder::stderr() @@ -14,25 +38,25 @@ fn setup_logging() -> Option<&'static RedoxLogger> { ); #[cfg(target_os = "redox")] - match OutputBuilder::in_redox_logging_scheme("disk", "pcie", "ide.log") { + match OutputBuilder::in_redox_logging_scheme("disk", "pcie", &format!("{}.log", name)) { Ok(b) => logger = logger.with_output( // TODO: Add a configuration file for this b.with_filter(log::LevelFilter::Info) .flush_on_newline(true) .build() ), - Err(error) => eprintln!("ided: failed to create ide.log: {}", error), + Err(error) => eprintln!("ided: failed to create log: {}", error), } #[cfg(target_os = "redox")] - match OutputBuilder::in_redox_logging_scheme("disk", "pcie", "ide.ansi.log") { + match OutputBuilder::in_redox_logging_scheme("disk", "pcie", &format!("{}.ansi.log", name)) { Ok(b) => logger = logger.with_output( b.with_filter(log::LevelFilter::Info) .with_ansi_escape_codes() .flush_on_newline(true) .build() ), - Err(error) => eprintln!("ided: failed to create ide.ansi.log: {}", error), + Err(error) => eprintln!("ided: failed to create ansi log: {}", error), } match logger.enable() { @@ -52,12 +76,293 @@ fn main() { } fn daemon(daemon: redox_daemon::Daemon) -> ! { - let _logger_ref = setup_logging(); - let mut pcid_handle = PcidServerHandle::connect_default().expect("ided: failed to setup channel to pcid"); - println!("IDE {:#x?}", pcid_handle.fetch_header()); + let pci_config = pcid_handle.fetch_config().expect("ided: failed to fetch config"); + let mut name = pci_config.func.name(); + name.push_str("_ide"); + + let _logger_ref = setup_logging(&name); + + info!("IDE {:?}", pci_config); + + let pci_header = pcid_handle.fetch_header().expect("ided: failed to fetch PCI header"); + let (primary, primary_irq) = if pci_header.interface() & 1 != 0 { + panic!("TODO: IDE primary channel is PCI native"); + } else { + (Channel::primary_compat(), 14) + }; + let (secondary, secondary_irq) = if pci_header.interface() & 1 != 0 { + panic!("TODO: IDE secondary channel is PCI native"); + } else { + (Channel::secondary_compat(), 15) + }; + + unsafe { syscall::iopl(3).expect("ided: failed to get I/O privilege") }; + + //TODO: move this to ide.rs? + let chans = vec![ + Arc::new(Mutex::new(primary)), + Arc::new(Mutex::new(secondary)), + ]; + let mut disks: Vec> = Vec::new(); + for (chan_i, chan_lock) in chans.iter().enumerate() { + let mut chan = chan_lock.lock().unwrap(); + + println!(" - channel {}", chan_i); + + // Disable IRQs + chan.control.write(2); + + for dev in 0..=1 { + println!(" - device {}", dev); + + // Select device + chan.device_select.write(0xA0 | (dev << 4)); + sleep(Duration::from_millis(1)); + + // ATA identify command + chan.command.write(AtaCommand::Identify as u8); + sleep(Duration::from_millis(1)); + + // Check if device exists + if chan.status.read() == 0 { + println!(" not found"); + continue; + } + + // Poll for status + let error = loop { + let status = chan.status.read(); + if status & 1 != 0 { + // Error + break true; + } + if status & 0x80 == 0 && status & 0x08 != 0 { + // Not busy and data ready + break false; + } + }; + + //TODO: probe ATAPI + if error { + println!(" error"); + continue; + } + + // Read and print identity + { + let mut dest = [0u16; 256]; + for chunk in dest.chunks_mut(2) { + let data = chan.data32.read(); + chunk[0] = data as u16; + chunk[1] = (data >> 16) as u16; + } + + let mut serial = String::new(); + for word in 10..20 { + let d = dest[word]; + let a = ((d >> 8) as u8) as char; + if a != '\0' { + serial.push(a); + } + let b = (d as u8) as char; + if b != '\0' { + serial.push(b); + } + } + + let mut firmware = String::new(); + for word in 23..27 { + let d = dest[word]; + let a = ((d >> 8) as u8) as char; + if a != '\0' { + firmware.push(a); + } + let b = (d as u8) as char; + if b != '\0' { + firmware.push(b); + } + } + + let mut model = String::new(); + for word in 27..47 { + let d = dest[word]; + let a = ((d >> 8) as u8) as char; + if a != '\0' { + model.push(a); + } + let b = (d as u8) as char; + if b != '\0' { + model.push(b); + } + } + + let mut sectors = (dest[100] as u64) | + ((dest[101] as u64) << 16) | + ((dest[102] as u64) << 32) | + ((dest[103] as u64) << 48); + + let lba_bits = if sectors == 0 { + sectors = (dest[60] as u64) | ((dest[61] as u64) << 16); + 28 + } else { + 48 + }; + + println!(" Serial: {}", serial.trim()); + println!(" Firmware: {}", firmware.trim()); + println!(" Model: {}", model.trim()); + println!(" {}-bit LBA", lba_bits); + println!(" Size: {} MB", sectors / 2048); + + disks.push(Box::new(AtaDisk { + chan: chan_lock.clone(), + chan_i, + dev, + size: sectors * 512, + })); + } + } + } + + let scheme_name = format!("disk/{}", name); + let socket_fd = syscall::open( + &format!(":{}", scheme_name), + syscall::O_RDWR | syscall::O_CREAT | syscall::O_NONBLOCK + ).expect("ided: failed to create disk scheme"); + let mut socket = unsafe { File::from_raw_fd(socket_fd as RawFd) }; + + let primary_irq_fd = syscall::open( + &format!("irq:{}", primary_irq), + syscall::O_RDWR | syscall::O_NONBLOCK + ).expect("ided: failed to open irq file"); + let mut primary_irq_file = unsafe { File::from_raw_fd(primary_irq_fd as RawFd) }; + + let secondary_irq_fd = syscall::open( + &format!("irq:{}", secondary_irq), + syscall::O_RDWR | syscall::O_NONBLOCK + ).expect("ided: failed to open irq file"); + let mut secondary_irq_file = unsafe { File::from_raw_fd(secondary_irq_fd as RawFd) }; + + let mut event_file = File::open("event:").expect("ided: failed to open event file"); + + syscall::setrens(0, 0).expect("ided: failed to enter null namespace"); + + daemon.ready().expect("ided: failed to notify parent"); + + event_file.write(&Event { + id: socket_fd, + flags: EVENT_READ, + data: 0 + }).expect("ided: failed to event disk scheme"); + + event_file.write(&Event { + id: primary_irq_fd, + flags: EVENT_READ, + data: 0 + }).expect("ided: failed to event irq scheme"); + + event_file.write(&Event { + id: secondary_irq_fd, + flags: EVENT_READ, + data: 0 + }).expect("ided: failed to event irq scheme"); + + let mut scheme = DiskScheme::new(scheme_name, chans, disks); + + let mut mounted = true; + let mut todo = Vec::new(); + while mounted { + let mut event = Event::default(); + if event_file.read(&mut event).expect("ided: failed to read event file") == 0 { + break; + } + if event.id == socket_fd { + loop { + let mut packet = Packet::default(); + match socket.read(&mut packet) { + Ok(0) => { + mounted = false; + break; + }, + Ok(_) => (), + Err(err) => if err.kind() == ErrorKind::WouldBlock { + break; + } else { + panic!("ided: failed to read disk scheme: {}", err); + } + } + + if let Some(a) = scheme.handle(&packet) { + packet.a = a; + socket.write(&mut packet).expect("ided: failed to write disk scheme"); + } else { + todo.push(packet); + } + } + } else if event.id == primary_irq_fd { + let mut irq = [0; 8]; + if primary_irq_file.read(&mut irq).expect("ided: failed to read irq file") >= irq.len() { + if scheme.irq(0) { + primary_irq_file.write(&irq).expect("ided: failed to write irq file"); + + // Handle todos in order to finish previous packets if possible + let mut i = 0; + while i < todo.len() { + if let Some(a) = scheme.handle(&todo[i]) { + let mut packet = todo.remove(i); + packet.a = a; + socket.write(&mut packet).expect("ided: failed to write disk scheme"); + } else { + i += 1; + } + } + } + } + } else if event.id == secondary_irq_fd { + let mut irq = [0; 8]; + if secondary_irq_file.read(&mut irq).expect("ided: failed to read irq file") >= irq.len() { + if scheme.irq(1) { + secondary_irq_file.write(&irq).expect("ided: failed to write irq file"); + + // Handle todos in order to finish previous packets if possible + let mut i = 0; + while i < todo.len() { + if let Some(a) = scheme.handle(&todo[i]) { + let mut packet = todo.remove(i); + packet.a = a; + socket.write(&mut packet).expect("ided: failed to write disk scheme"); + } else { + i += 1; + } + } + } + } + } else { + error!("Unknown event {}", event.id); + } + + // Handle todos to start new packets if possible + let mut i = 0; + while i < todo.len() { + if let Some(a) = scheme.handle(&todo[i]) { + let mut packet = todo.remove(i); + packet.a = a; + socket.write(&packet).expect("ided: failed to write disk scheme"); + } else { + i += 1; + } + } + + if ! mounted { + for mut packet in todo.drain(..) { + packet.a = Error::mux(Err(Error::new(ENODEV))); + socket.write(&packet).expect("ided: failed to write disk scheme"); + } + } + } std::process::exit(0); } diff --git a/ided/src/scheme.rs b/ided/src/scheme.rs new file mode 100644 index 0000000000..e88fb46e0c --- /dev/null +++ b/ided/src/scheme.rs @@ -0,0 +1,426 @@ +use std::collections::BTreeMap; +use std::{cmp, str}; +use std::convert::{TryFrom}; +use std::fmt::Write; +use std::io::prelude::*; +use std::io::SeekFrom; +use std::io; +use std::sync::{Arc, Mutex}; + +use syscall::{ + Error, EACCES, EBADF, EINVAL, EISDIR, ENOENT, EOVERFLOW, Result, + Io, SchemeBlockMut, Stat, MODE_DIR, MODE_FILE, O_DIRECTORY, + O_STAT, SEEK_CUR, SEEK_END, SEEK_SET}; + +use crate::ide::{Channel, Disk}; + +use partitionlib::{LogicalBlockSize, PartitionTable}; + +#[derive(Clone)] +enum Handle { + List(Vec, usize), // Dir contents buffer, position + Disk(usize, usize), // Disk index, position + Partition(usize, u32, usize), // Disk index, partition index, position +} + +pub struct DiskWrapper { + disk: Box, + pt: Option, +} + +impl DiskWrapper { + fn pt(disk: &mut dyn Disk) -> Option { + let bs = match disk.block_length() { + Ok(512) => LogicalBlockSize::Lb512, + Ok(4096) => LogicalBlockSize::Lb4096, + _ => return None, + }; + struct Device<'a, 'b> { disk: &'a mut dyn Disk, offset: u64, block_bytes: &'b mut [u8] } + + impl<'a, 'b> Seek for Device<'a, 'b> { + fn seek(&mut self, from: SeekFrom) -> io::Result { + let size = i64::try_from(self.disk.size()).or(Err(io::Error::new(io::ErrorKind::Other, "Disk larger than 2^63 - 1 bytes")))?; + + self.offset = match from { + SeekFrom::Start(new_pos) => cmp::min(self.disk.size(), new_pos), + SeekFrom::Current(new_pos) => cmp::max(0, cmp::min(size, self.offset as i64 + new_pos)) as u64, + SeekFrom::End(new_pos) => cmp::max(0, cmp::min(size + new_pos, size)) as u64, + }; + + Ok(self.offset) + } + } + // TODO: Perhaps this impl should be used in the rest of the scheme. + impl<'a, 'b> Read for Device<'a, 'b> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let blksize = self.disk.block_length().map_err(|err| io::Error::from_raw_os_error(err.errno))?; + let size_in_blocks = self.disk.size() / u64::from(blksize); + + let disk = &mut self.disk; + + let read_block = |block: u64, block_bytes: &mut [u8]| { + if block >= size_in_blocks { + return Err(io::Error::from_raw_os_error(syscall::EOVERFLOW)); + } + loop { + match disk.read(block, block_bytes) { + Ok(Some(bytes)) => { + assert_eq!(bytes, block_bytes.len()); + assert_eq!(bytes, blksize as usize); + return Ok(()); + } + Ok(None) => { std::thread::yield_now(); continue } + Err(err) => return Err(io::Error::from_raw_os_error(err.errno)), + } + } + }; + let bytes_read = block_io_wrapper::read(self.offset, blksize, buf, self.block_bytes, read_block)?; + + self.offset += bytes_read as u64; + Ok(bytes_read) + } + } + + let mut block_bytes = [0u8; 4096]; + + partitionlib::get_partitions(&mut Device { disk, offset: 0, block_bytes: &mut block_bytes[..bs.into()] }, bs).ok().flatten() + } + fn new(mut disk: Box) -> Self { + Self { + pt: Self::pt(&mut *disk), + disk, + } + } +} + +impl std::ops::Deref for DiskWrapper { + type Target = dyn Disk; + + fn deref(&self) -> &Self::Target { + &*self.disk + } +} +impl std::ops::DerefMut for DiskWrapper { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.disk + } +} + +pub struct DiskScheme { + scheme_name: String, + chans: Box<[Arc>]>, + disks: Box<[DiskWrapper]>, + handles: BTreeMap, + next_id: usize +} + +impl DiskScheme { + pub fn new(scheme_name: String, chans: Vec>>, disks: Vec>) -> DiskScheme { + DiskScheme { + scheme_name: scheme_name, + chans: chans.into_boxed_slice(), + disks: disks.into_iter().map(DiskWrapper::new).collect::>().into_boxed_slice(), + handles: BTreeMap::new(), + next_id: 0 + } + } +} + +impl DiskScheme { + pub fn irq(&mut self, chan_i: usize) -> bool { + let mut chan = self.chans[chan_i].lock().unwrap(); + //TODO: check chan for irq + true + } +} + +impl SchemeBlockMut for DiskScheme { + fn open(&mut self, path: &str, flags: usize, uid: u32, _gid: u32) -> Result> { + if uid == 0 { + let path_str = path.trim_matches('/'); + if path_str.is_empty() { + if flags & O_DIRECTORY == O_DIRECTORY || flags & O_STAT == O_STAT { + let mut list = String::new(); + + for (disk_index, disk) in self.disks.iter().enumerate() { + write!(list, "{}\n", disk_index).unwrap(); + + if disk.pt.is_none() { + continue + } + for part_index in 0..disk.pt.as_ref().unwrap().partitions.len() { + write!(list, "{}p{}\n", disk_index, part_index).unwrap(); + } + } + + let id = self.next_id; + self.next_id += 1; + self.handles.insert(id, Handle::List(list.into_bytes(), 0)); + Ok(Some(id)) + } else { + Err(Error::new(EISDIR)) + } + } else if let Some(p_pos) = path_str.chars().position(|c| c == 'p') { + let disk_id_str = &path_str[..p_pos]; + if p_pos + 1 >= path_str.len() { + return Err(Error::new(ENOENT)); + } + let part_id_str = &path_str[p_pos + 1..]; + let i = disk_id_str.parse::().or(Err(Error::new(ENOENT)))?; + let p = part_id_str.parse::().or(Err(Error::new(ENOENT)))?; + + if let Some(disk) = self.disks.get(i) { + if disk.pt.is_none() || disk.pt.as_ref().unwrap().partitions.get(p as usize).is_none() { + return Err(Error::new(ENOENT)); + } + let id = self.next_id; + self.next_id += 1; + + self.handles.insert(id, Handle::Partition(i, p, 0)); + + Ok(Some(id)) + } else { + Err(Error::new(ENOENT)) + } + } else { + let i = path_str.parse::().or(Err(Error::new(ENOENT)))?; + + if self.disks.get(i).is_some() { + let id = self.next_id; + self.next_id += 1; + self.handles.insert(id, Handle::Disk(i, 0)); + Ok(Some(id)) + } else { + Err(Error::new(ENOENT)) + } + } + } else { + Err(Error::new(EACCES)) + } + } + + fn dup(&mut self, id: usize, buf: &[u8]) -> Result> { + if ! buf.is_empty() { + return Err(Error::new(EINVAL)); + } + + let new_handle = { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + handle.clone() + }; + + let new_id = self.next_id; + self.next_id += 1; + self.handles.insert(new_id, new_handle); + Ok(Some(new_id)) + } + + fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result> { + match *self.handles.get(&id).ok_or(Error::new(EBADF))? { + Handle::List(ref data, _) => { + stat.st_mode = MODE_DIR; + stat.st_size = data.len() as u64; + Ok(Some(0)) + }, + Handle::Disk(number, _) => { + let disk = self.disks.get_mut(number).ok_or(Error::new(EBADF))?; + stat.st_mode = MODE_FILE; + stat.st_size = disk.size(); + stat.st_blksize = disk.block_length()?; + Ok(Some(0)) + } + Handle::Partition(disk_id, part_num, _) => { + let disk = self.disks.get_mut(disk_id).ok_or(Error::new(EBADF))?; + let size = { + let pt = disk.pt.as_ref().ok_or(Error::new(EBADF))?; + let partition = pt.partitions.get(part_num as usize).ok_or(Error::new(EBADF))?; + partition.size + }; + + stat.st_mode = MODE_FILE; // TODO: Block device? + stat.st_size = size * u64::from(disk.block_length()?); + stat.st_blksize = disk.block_length()?; + stat.st_blocks = size; + Ok(Some(0)) + } + } + } + + fn fpath(&mut self, id: usize, buf: &mut [u8]) -> Result> { + let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?; + + let mut i = 0; + + let scheme_name = self.scheme_name.as_bytes(); + let mut j = 0; + while i < buf.len() && j < scheme_name.len() { + buf[i] = scheme_name[j]; + i += 1; + j += 1; + } + + if i < buf.len() { + buf[i] = b':'; + i += 1; + } + + match *handle { + Handle::List(_, _) => (), + Handle::Disk(number, _) => { + let number_str = format!("{}", number); + let number_bytes = number_str.as_bytes(); + j = 0; + while i < buf.len() && j < number_bytes.len() { + buf[i] = number_bytes[j]; + i += 1; + j += 1; + } + } + Handle::Partition(disk_num, part_num, _) => { + let path = format!("{}p{}", disk_num, part_num); + let path_bytes = path.as_bytes(); + j = 0; + while i < buf.len() && j < path_bytes.len() { + buf[i] = path_bytes[j]; + i += 1; + j += 1; + } + } + } + + Ok(Some(i)) + } + + fn read(&mut self, id: usize, buf: &mut [u8]) -> Result> { + match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? { + Handle::List(ref handle, ref mut size) => { + let count = (&handle[*size..]).read(buf).unwrap(); + *size += count; + Ok(Some(count)) + }, + Handle::Disk(number, ref mut size) => { + let disk = self.disks.get_mut(number).ok_or(Error::new(EBADF))?; + let blk_len = disk.block_length()?; + if let Some(count) = disk.read((*size as u64)/(blk_len as u64), buf)? { + *size += count; + Ok(Some(count)) + } else { + Ok(None) + } + } + Handle::Partition(disk_num, part_num, ref mut position) => { + let disk = self.disks.get_mut(disk_num).ok_or(Error::new(EBADF))?; + let blksize = disk.block_length()?; + + // validate that we're actually reading within the bounds of the partition + let rel_block = *position as u64 / blksize as u64; + + let abs_block = { + let pt = disk.pt.as_ref().ok_or(Error::new(EBADF))?; + let partition = pt.partitions.get(part_num as usize).ok_or(Error::new(EBADF))?; + + let abs_block = partition.start_lba + rel_block; + if rel_block >= partition.size { + return Err(Error::new(EOVERFLOW)); + } + abs_block + }; + + if let Some(count) = disk.read(abs_block, buf)? { + Ok(Some(count)) + } else { + Ok(None) + } + } + } + } + + fn write(&mut self, id: usize, buf: &[u8]) -> Result> { + match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? { + Handle::List(_, _) => { + Err(Error::new(EBADF)) + }, + Handle::Disk(number, ref mut size) => { + let disk = self.disks.get_mut(number).ok_or(Error::new(EBADF))?; + let blk_len = disk.block_length()?; + if let Some(count) = disk.write((*size as u64)/(blk_len as u64), buf)? { + *size += count; + Ok(Some(count)) + } else { + Ok(None) + } + } + Handle::Partition(disk_num, part_num, ref mut position) => { + let disk = self.disks.get_mut(disk_num).ok_or(Error::new(EBADF))?; + let blksize = disk.block_length()?; + + // validate that we're actually reading within the bounds of the partition + let rel_block = *position as u64 / blksize as u64; + + let abs_block = { + let pt = disk.pt.as_ref().ok_or(Error::new(EBADF))?; + let partition = pt.partitions.get(part_num as usize).ok_or(Error::new(EBADF))?; + + let abs_block = partition.start_lba + rel_block; + if rel_block >= partition.size { + return Err(Error::new(EOVERFLOW)); + } + abs_block + }; + + if let Some(count) = disk.write(abs_block, buf)? { + Ok(Some(count)) + } else { + Ok(None) + } + } + } + } + + fn seek(&mut self, id: usize, pos: isize, whence: usize) -> Result> { + let pos = pos as usize; + + match *self.handles.get_mut(&id).ok_or(Error::new(EBADF))? { + Handle::List(ref mut handle, ref mut size) => { + let len = handle.len() as usize; + *size = match whence { + SEEK_SET => cmp::min(len, pos), + SEEK_CUR => cmp::max(0, cmp::min(len as isize, *size as isize + pos as isize)) as usize, + SEEK_END => cmp::max(0, cmp::min(len as isize, len as isize + pos as isize)) as usize, + _ => return Err(Error::new(EINVAL)) + }; + + Ok(Some(*size as isize)) + }, + Handle::Disk(number, ref mut size) => { + let disk = self.disks.get_mut(number).ok_or(Error::new(EBADF))?; + let len = disk.size() as usize; + *size = match whence { + SEEK_SET => cmp::min(len, pos), + SEEK_CUR => cmp::max(0, cmp::min(len as isize, *size as isize + pos as isize)) as usize, + SEEK_END => cmp::max(0, cmp::min(len as isize, len as isize + pos as isize)) as usize, + _ => return Err(Error::new(EINVAL)) + }; + + Ok(Some(*size as isize)) + } + Handle::Partition(disk_num, part_num, ref mut position) => { + let disk = self.disks.get_mut(disk_num).ok_or(Error::new(EBADF))?; + let block_count = disk.pt.as_ref().ok_or(Error::new(EBADF))?.partitions.get(part_num as usize).ok_or(Error::new(EBADF))?.size; + let len = u64::from(disk.block_length()?) * block_count; + + *position = match whence { + SEEK_SET => cmp::min(len as usize, pos) as usize, // Why isn't pos u64? + SEEK_CUR => cmp::max(0, cmp::min(len as isize, *position as isize + pos as isize)) as usize, + SEEK_END => cmp::max(0, cmp::min(len as isize, len as isize + pos as isize)) as usize, + _ => return Err(Error::new(EINVAL)), + }; + Ok(Some(*position as isize)) + } + } + } + + fn close(&mut self, id: usize) -> Result> { + self.handles.remove(&id).ok_or(Error::new(EBADF)).and(Ok(Some(0))) + } +}