use crate::prelude::*;
use crate::return_errno_with_message;
use crate::utils::path_check;
use crate::ext4_defs::*;
use core::cmp::max;
impl Ext4 {
pub fn link(
&self,
parent: &mut Ext4InodeRef,
child: &mut Ext4InodeRef,
name: &str,
) -> Result<usize> {
self.dir_add_entry(parent, child, name)?;
self.write_back_inode_without_csum(parent);
if child.inode.is_dir() {
let new_child_ref = Ext4InodeRef {
inode_num: child.inode_num,
inode: child.inode,
};
self.dir_add_entry(child, &new_child_ref, ".")?;
self.dir_add_entry(child, parent, "..")?;
child.inode.set_links_count(2);
let link_cnt = parent.inode.links_count() + 1;
parent.inode.set_links_count(link_cnt);
return Ok(EOK);
}
let link_cnt = child.inode.links_count() + 1;
child.inode.set_links_count(link_cnt);
Ok(EOK)
}
pub fn create(&self, parent: u32, name: &str, inode_mode: u16) -> Result<Ext4InodeRef> {
let mut parent_inode_ref = self.get_inode_ref(parent);
let init_child_ref = self.create_inode(inode_mode)?;
self.write_back_inode_without_csum(&init_child_ref);
let mut child_inode_ref = self.get_inode_ref(init_child_ref.inode_num);
self.link(&mut parent_inode_ref, &mut child_inode_ref, name)?;
self.write_back_inode(&mut parent_inode_ref);
self.write_back_inode(&mut child_inode_ref);
Ok(child_inode_ref)
}
pub fn create_inode(&self, inode_mode: u16) -> Result<Ext4InodeRef> {
let inode_file_type = match InodeFileType::from_bits(inode_mode) {
Some(file_type) => file_type,
None => InodeFileType::S_IFREG,
};
let is_dir = inode_file_type == InodeFileType::S_IFDIR;
let inode_num = self.alloc_inode(is_dir)?;
let mut inode = Ext4Inode::default();
inode.set_mode(inode_mode | 0o777);
let inode_size = self.super_block.inode_size();
let extra_size = self.super_block.extra_size();
if inode_size > EXT4_GOOD_OLD_INODE_SIZE {
inode.set_i_extra_isize(extra_size);
}
inode.set_flags(EXT4_INODE_FLAG_EXTENTS as u32);
inode.extent_tree_init();
let inode_ref = Ext4InodeRef {
inode_num,
inode,
};
Ok(inode_ref)
}
pub fn create_with_attr(&self, parent: u32, name: &str, inode_mode: u16, uid:u16, gid: u16) -> Result<Ext4InodeRef> {
let mut parent_inode_ref = self.get_inode_ref(parent);
let mut init_child_ref = self.create_inode(inode_mode)?;
init_child_ref.inode.set_uid(uid);
init_child_ref.inode.set_gid(gid);
self.write_back_inode_without_csum(&init_child_ref);
let mut child_inode_ref = self.get_inode_ref(init_child_ref.inode_num);
self.link(&mut parent_inode_ref, &mut child_inode_ref, name)?;
self.write_back_inode(&mut parent_inode_ref);
self.write_back_inode(&mut child_inode_ref);
Ok(child_inode_ref)
}
pub fn read_at(&self, inode: u32, offset: usize, read_buf: &mut [u8]) -> Result<usize> {
let mut read_buf_len = read_buf.len();
if read_buf_len == 0 {
log::error!("[Read] Empty read buffer, returning 0");
return Ok(0);
}
let inode_ref = self.get_inode_ref(inode);
let file_size = inode_ref.inode.size();
let total_blocks = (file_size + BLOCK_SIZE as u64 - 1) / BLOCK_SIZE as u64;
if offset >= file_size as usize {
return Ok(0);
}
if offset + read_buf_len > file_size as usize {
read_buf_len = file_size as usize - offset;
log::trace!("[Read] Adjusted read size to {} bytes to not exceed file size (offset: {}, file_size: {})",
read_buf_len, offset, file_size);
}
let iblock_start = offset / BLOCK_SIZE;
let iblock_last = (offset + read_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE; let unaligned_start_offset = offset % BLOCK_SIZE;
let iblock_last = min(iblock_last, total_blocks as usize);
let mut cursor = 0;
let mut total_bytes_read = 0;
let mut iblock = iblock_start;
if unaligned_start_offset > 0 {
let adjust_read_size = min(BLOCK_SIZE - unaligned_start_offset, read_buf_len);
let pblock_idx = match self.get_pblock_idx(&inode_ref, iblock as u32) {
Ok(idx) => {
idx
},
Err(e) => {
return_errno_with_message!(Errno::EIO, "Failed to get physical block for logical block");
}
};
let data = self.block_device.read_offset(pblock_idx as usize * BLOCK_SIZE);
read_buf[cursor..cursor + adjust_read_size].copy_from_slice(
&data[unaligned_start_offset..unaligned_start_offset + adjust_read_size],
);
cursor += adjust_read_size;
total_bytes_read += adjust_read_size;
iblock += 1;
}
while total_bytes_read < read_buf_len && iblock < iblock_last {
let mut read_length = min(BLOCK_SIZE, read_buf_len - total_bytes_read);
if iblock as u64 >= total_blocks - 1 {
let remaining_bytes = file_size as usize - (iblock * BLOCK_SIZE);
let actual_read_length = min(read_length, remaining_bytes);
if actual_read_length < read_length {
read_length = actual_read_length;
}
}
let pblock_idx = match self.get_pblock_idx(&inode_ref, iblock as u32) {
Ok(idx) => {
idx
},
Err(e) => {
return_errno_with_message!(Errno::EIO, "Failed to get physical block for logical block");
}
};
let data = self.block_device.read_offset(pblock_idx as usize * BLOCK_SIZE);
read_buf[cursor..cursor + read_length].copy_from_slice(&data[..read_length]);
cursor += read_length;
total_bytes_read += read_length;
iblock += 1;
}
Ok(total_bytes_read)
}
pub fn write_at(&self, inode: u32, offset: usize, write_buf: &[u8]) -> Result<usize> {
let mut write_buf_len = write_buf.len();
if write_buf_len == 0 {
log::info!("[Write] Empty write buffer, returning 0");
return Ok(0);
}
let mut inode_ref = self.get_inode_ref(inode);
let file_size = inode_ref.inode.size();
log::trace!("[Write] Starting write - inode: {}, offset: {}, size: {}, current file size: {}",
inode, offset, write_buf_len, file_size);
let iblock_start = offset / BLOCK_SIZE;
let iblock_last = (offset + write_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE;
let total_blocks_needed = iblock_last - iblock_start;
let mut iblk_idx = iblock_start;
let ifile_blocks = (file_size + BLOCK_SIZE as u64 - 1) / BLOCK_SIZE as u64;
let unaligned = offset % BLOCK_SIZE;
if unaligned > 0 {
log::trace!("[Alignment] Unaligned start: {} bytes", unaligned);
}
let mut written = 0;
let mut total_blocks = 0;
let mut new_blocks = 0;
let mut start_bgid = 1;
let blocks_to_allocate = if iblk_idx >= ifile_blocks as usize {
total_blocks_needed
} else {
max(0, total_blocks_needed - (ifile_blocks as usize - iblk_idx))
};
if blocks_to_allocate > 0 {
log::trace!("[Pre-allocation] Allocating {} blocks", blocks_to_allocate);
let allocated_blocks = self.append_inode_pblk_batch(&mut inode_ref, &mut start_bgid, blocks_to_allocate)?;
if allocated_blocks.len() < blocks_to_allocate {
log::trace!("[Write] Could only allocate {} out of {} blocks", allocated_blocks.len(), blocks_to_allocate);
let max_write_size = allocated_blocks.len() * BLOCK_SIZE;
let adjusted_write_size = if unaligned > 0 {
if allocated_blocks.len() > 0 {
let first_block_available = BLOCK_SIZE - unaligned;
let remaining_blocks_available = (allocated_blocks.len() - 1) * BLOCK_SIZE;
first_block_available + remaining_blocks_available
} else {
0
}
} else {
max_write_size
};
if adjusted_write_size == 0 {
log::error!("[Write] No space available for write after block allocation");
return return_errno_with_message!(Errno::ENOSPC, "No blocks available for write");
}
write_buf_len = min(write_buf_len, adjusted_write_size);
log::trace!("[Write] Adjusted write size from {} to {} bytes", write_buf.len(), write_buf_len);
}
new_blocks += allocated_blocks.len();
}
let required_blocks = (write_buf_len + BLOCK_SIZE - 1) / BLOCK_SIZE;
let available_blocks = if iblk_idx >= ifile_blocks as usize {
new_blocks
} else {
(ifile_blocks as usize - iblk_idx) + new_blocks
};
if available_blocks < required_blocks {
log::error!("[Write] Not enough blocks available: required {}, available {}",
required_blocks, available_blocks);
return return_errno_with_message!(Errno::ENOSPC, "Not enough blocks available for write");
}
if unaligned > 0 && written < write_buf_len {
let len = min(write_buf_len, BLOCK_SIZE - unaligned);
log::trace!("[Unaligned Write] Writing {} bytes", len);
let pblock_idx = match self.get_pblock_idx(&inode_ref, iblk_idx as u32) {
Ok(idx) => idx,
Err(e) => {
log::error!("[Write] Failed to get physical block for logical block {}: {:?}", iblk_idx, e);
return Err(e);
}
};
total_blocks += 1;
let mut block = Block::load(&self.block_device, pblock_idx as usize * BLOCK_SIZE);
if unaligned > 0 || len < BLOCK_SIZE {
let existing_data = self.block_device.read_offset(pblock_idx as usize * BLOCK_SIZE);
block.data.copy_from_slice(&existing_data);
}
block.write_offset(unaligned, &write_buf[..len], len);
block.sync_blk_to_disk(&self.block_device);
let verify_block = Block::load(&self.block_device, pblock_idx as usize * BLOCK_SIZE);
if verify_block.data[unaligned..unaligned + len] != write_buf[..len] {
log::error!("[Write] Verification failed for unaligned write at block {}", pblock_idx);
return return_errno_with_message!(Errno::EIO, "Write verification failed");
}
drop(block);
drop(verify_block);
written += len;
iblk_idx += 1;
}
let mut aligned_blocks = 0;
log::info!("[Aligned Write] Starting aligned writes for {} blocks", (write_buf_len - written + BLOCK_SIZE - 1) / BLOCK_SIZE);
while written < write_buf_len {
aligned_blocks += 1;
let pblock_idx = match self.get_pblock_idx(&inode_ref, iblk_idx as u32) {
Ok(idx) => idx,
Err(e) => {
log::error!("[Write] Failed to get physical block for logical block {}: {:?}", iblk_idx, e);
return Err(e);
}
};
total_blocks += 1;
let block_offset = pblock_idx as usize * BLOCK_SIZE;
let mut block = Block::load(&self.block_device, block_offset);
let write_size = min(BLOCK_SIZE, write_buf_len - written);
if write_size < BLOCK_SIZE {
let existing_data = self.block_device.read_offset(block_offset);
block.data.copy_from_slice(&existing_data);
}
block.write_offset(0, &write_buf[written..written + write_size], write_size);
block.sync_blk_to_disk(&self.block_device);
let verify_block = Block::load(&self.block_device, block_offset);
if verify_block.data[..write_size] != write_buf[written..written + write_size] {
log::error!("[Write] Verification failed for aligned write at block {}", pblock_idx);
return return_errno_with_message!(Errno::EIO, "Write verification failed");
}
drop(block);
drop(verify_block);
written += write_size;
iblk_idx += 1;
if aligned_blocks % 1000 == 0 {
log::trace!("[Progress] Written {} blocks, {} bytes", aligned_blocks, written);
}
}
let new_size = offset + written;
if new_size > file_size as usize {
log::trace!("[Write] Updating file size from {} to {}", file_size, new_size);
if new_size > EXT4_MAX_FILE_SIZE as usize {
log::error!("[Write] New file size {} exceeds maximum allowed size", new_size);
return return_errno_with_message!(Errno::EFBIG, "File size too large");
}
inode_ref.inode.set_size(new_size as u64);
self.write_back_inode(&mut inode_ref);
let verify_inode = self.get_inode_ref(inode);
if verify_inode.inode.size() != new_size as u64 {
log::error!("[Write] File size update verification failed: expected {}, got {}",
new_size, verify_inode.inode.size());
return return_errno_with_message!(Errno::EIO, "File size update verification failed");
}
}
log::info!("=== Write Performance Summary ===");
log::info!("[Blocks] Total blocks: {}, New blocks: {}, Aligned blocks: {}",
total_blocks, new_blocks, aligned_blocks);
log::info!("[Bytes] Total written: {}", written);
log::info!("[File] Final size: {}", inode_ref.inode.size());
log::info!("=== End of Write Analysis ===");
Ok(written)
}
pub fn file_remove(&self, path: &str) -> Result<usize> {
let mut parent_inode_num = ROOT_INODE;
let mut nameoff = 0;
let child_inode = self.generic_open(path, &mut parent_inode_num, false, 0, &mut nameoff)?;
let mut child_inode_ref = self.get_inode_ref(child_inode);
let child_link_cnt = child_inode_ref.inode.links_count();
if child_link_cnt == 1 {
self.truncate_inode(&mut child_inode_ref, 0)?;
}
let mut is_goal = false;
let p = &path[nameoff as usize..];
let len = path_check(p, &mut is_goal);
let mut parent_inode_ref = self.get_inode_ref(parent_inode_num);
let r = self.unlink(
&mut parent_inode_ref,
&mut child_inode_ref,
&p[..len],
)?;
Ok(EOK)
}
pub fn truncate_inode(&self, inode_ref: &mut Ext4InodeRef, new_size: u64) -> Result<usize> {
let old_size = inode_ref.inode.size();
assert!(old_size > new_size);
if old_size == new_size {
return Ok(EOK);
}
let block_size = BLOCK_SIZE as u64;
let new_blocks_cnt = ((new_size + block_size - 1) / block_size) as u32;
let old_blocks_cnt = ((old_size + block_size - 1) / block_size) as u32;
let diff_blocks_cnt = old_blocks_cnt - new_blocks_cnt;
if diff_blocks_cnt > 0{
self.extent_remove_space(inode_ref, new_blocks_cnt, EXT_MAX_BLOCKS)?;
}
inode_ref.inode.set_size(new_size);
self.write_back_inode(inode_ref);
Ok(EOK)
}
}