ext2: free deleted inodes/blocks
This commit is contained in:
parent
17e2fba8b7
commit
11f731bf0f
@ -1,6 +1,7 @@
|
||||
use libk::error::Error;
|
||||
use libk::{error::Error, time::real_time};
|
||||
use yggdrasil_abi::io::FileType;
|
||||
|
||||
use crate::{BlockGroupDescriptor, Ext2Fs, ExtendedSuperblock};
|
||||
use crate::{BlockGroupDescriptor, Ext2Fs, ExtendedSuperblock, Inode};
|
||||
|
||||
impl Ext2Fs {
|
||||
async fn allocate<
|
||||
@ -80,6 +81,57 @@ impl Ext2Fs {
|
||||
Ok(ino)
|
||||
}
|
||||
|
||||
pub(crate) async fn free_inode(&self, ino: u32, inode: &mut Inode) -> Result<(), Error> {
|
||||
if ino == 0 {
|
||||
log::warn!("Tried to free a NULL inode");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let is_directory = inode
|
||||
.mode
|
||||
.node_type()
|
||||
.map(|m| m == FileType::Directory)
|
||||
.unwrap_or(false);
|
||||
|
||||
// Free inode blocks
|
||||
inode.resize(self, 0).await?;
|
||||
|
||||
inode.dtime = real_time().seconds as u32;
|
||||
|
||||
self.write_inode(ino, &inode).await?;
|
||||
|
||||
let inode_index = ino - 1;
|
||||
let group_index = inode_index / self.block_group_inode_count;
|
||||
|
||||
let bitmap = self
|
||||
.with_bgdt_entry_mut(group_index, |descriptor| {
|
||||
if is_directory {
|
||||
descriptor.directories = descriptor.directories.saturating_sub(1);
|
||||
}
|
||||
descriptor.unallocated_inodes += 1;
|
||||
Ok(descriptor.inode_usage_bitmap)
|
||||
})
|
||||
.await?;
|
||||
self.with_block_mut(bitmap, size_of::<u32>(), |bitmap| {
|
||||
let index = (inode_index / 8) as usize;
|
||||
let bit = 1u8 << (inode_index % 8);
|
||||
|
||||
if bitmap[index] & bit == 0 {
|
||||
log::warn!("Trying to free an unallocated inode #{ino}");
|
||||
}
|
||||
bitmap[index] &= !bit;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
self.with_superblock_mut(|sb| {
|
||||
sb.total_unallocated_inodes += 1;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn allocate_block(&self) -> Result<u32, Error> {
|
||||
let no = self
|
||||
.allocate(
|
||||
@ -101,4 +153,38 @@ impl Ext2Fs {
|
||||
|
||||
Ok(no)
|
||||
}
|
||||
|
||||
pub(crate) async fn free_block(&self, block_index: u32) -> Result<(), Error> {
|
||||
if block_index == 0 {
|
||||
log::warn!("Tried to free a NULL block");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let group_index = block_index / self.block_group_block_count;
|
||||
let bitmap = self
|
||||
.with_bgdt_entry_mut(group_index, |descriptor| {
|
||||
descriptor.unallocated_blocks += 1;
|
||||
Ok(descriptor.block_usage_bitmap)
|
||||
})
|
||||
.await?;
|
||||
self.with_block_mut(bitmap, size_of::<u32>(), |bitmap| {
|
||||
let index = (block_index / 8) as usize;
|
||||
let bit = 1u8 << (block_index % 8);
|
||||
|
||||
if bitmap[index] & bit == 0 {
|
||||
log::warn!("Freeing a block #{block_index}, but bitmap says it's not allocated");
|
||||
}
|
||||
bitmap[index] &= !bit;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
self.with_superblock_mut(|sb| {
|
||||
sb.total_unallocated_blocks += 1;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -215,7 +215,16 @@ impl DirectoryNode {
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(_) => {
|
||||
// Remove "." link
|
||||
self.inode
|
||||
.map_mut(|inode| {
|
||||
inode.hard_links -= 1;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => {
|
||||
self.locked.store(false, Ordering::Release);
|
||||
Err(error)
|
||||
@ -228,16 +237,28 @@ impl DirectoryNode {
|
||||
Arc::as_ptr(&child.filesystem().unwrap()).addr(),
|
||||
Arc::as_ptr(&self.fs).addr()
|
||||
);
|
||||
let child_ino = match child.data_as_any() {
|
||||
let (child_ino, is_directory) = match child.data_as_any() {
|
||||
data if let Some(dir) = data.downcast_ref::<DirectoryNode>() => {
|
||||
// Check that the directory is not empty
|
||||
dir.prepare_for_removal().await?;
|
||||
dir.inode.ino()
|
||||
(dir.inode.ino(), true)
|
||||
}
|
||||
data if let Some(file) = data.downcast_ref::<RegularNode>() => {
|
||||
(file.inode.ino(), false)
|
||||
}
|
||||
data if let Some(file) = data.downcast_ref::<RegularNode>() => file.inode.ino(),
|
||||
_ => return Err(Error::InvalidOperation),
|
||||
};
|
||||
|
||||
if is_directory {
|
||||
// child's .. points to this node
|
||||
self.inode
|
||||
.map_mut(|inode| {
|
||||
inode.hard_links = inode.hard_links.saturating_sub(1);
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
let ino = self
|
||||
.inode
|
||||
.map_blocks_mut(|_inode, _index, block| {
|
||||
@ -257,7 +278,7 @@ impl DirectoryNode {
|
||||
child
|
||||
.map_mut(|inode| {
|
||||
inode.hard_links = inode.hard_links.saturating_sub(1);
|
||||
inode.dtime = real_time().seconds as u32;
|
||||
log::info!("#{ino} hard links = {}", inode.hard_links);
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
@ -142,6 +142,9 @@ impl<'a> DirentIter<'a> {
|
||||
if entry_count >= entries.len() {
|
||||
break;
|
||||
}
|
||||
if record.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(name) = record
|
||||
.name()
|
||||
|
@ -48,8 +48,12 @@ impl InodeAccess {
|
||||
) -> Result<T, Error> {
|
||||
let inode = self.inode_cache.get(self.ino).await?;
|
||||
let lock = inode.read();
|
||||
if lock.is_deleted() {
|
||||
Err(Error::DoesNotExist)
|
||||
} else {
|
||||
mapper(&lock.inode)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn map_mut<T, F: FnOnce(&mut Inode) -> Result<T, Error>>(
|
||||
&self,
|
||||
@ -57,7 +61,11 @@ impl InodeAccess {
|
||||
) -> Result<T, Error> {
|
||||
let inode = self.inode_cache.get(self.ino).await?;
|
||||
let mut lock = inode.write();
|
||||
let result = mapper(&mut lock.inode);
|
||||
let result = if lock.is_deleted() {
|
||||
Err(Error::DoesNotExist)
|
||||
} else {
|
||||
mapper(&mut lock.inode)
|
||||
};
|
||||
self.inode_cache.put(self.ino, &mut lock).await?;
|
||||
result
|
||||
}
|
||||
@ -68,8 +76,12 @@ impl InodeAccess {
|
||||
) -> Result<T, Error> {
|
||||
let inode = self.inode_cache.get(self.ino).await?;
|
||||
let lock = inode.read();
|
||||
if lock.is_deleted() {
|
||||
Err(Error::DoesNotExist)
|
||||
} else {
|
||||
mapper(&lock.inode).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn amap_mut<T, F: AsyncFnOnce(&mut Inode) -> Result<T, Error>>(
|
||||
&self,
|
||||
@ -77,7 +89,11 @@ impl InodeAccess {
|
||||
) -> Result<T, Error> {
|
||||
let inode = self.inode_cache.get(self.ino).await?;
|
||||
let mut lock = inode.write();
|
||||
let result = mapper(&mut lock.inode).await;
|
||||
let result = if lock.is_deleted() {
|
||||
Err(Error::DoesNotExist)
|
||||
} else {
|
||||
mapper(&mut lock.inode).await
|
||||
};
|
||||
self.inode_cache.put(self.ino, &mut lock).await?;
|
||||
result
|
||||
}
|
||||
@ -257,11 +273,11 @@ impl InodeCache {
|
||||
ino: u32,
|
||||
inode: Arc<IrqSafeRwLock<InodeHolder>>,
|
||||
) -> Result<(), Error> {
|
||||
let inode = inode.read();
|
||||
let mut inode = inode.write();
|
||||
if inode.dirty {
|
||||
assert!(!self.synchronous);
|
||||
log::debug!("Flush dirty inode {ino}");
|
||||
self.fs.write_inode(ino, &inode.inode).await?;
|
||||
self.fs.write_or_free_inode(ino, &mut inode.inode).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -314,7 +330,7 @@ impl InodeCache {
|
||||
async fn put(&self, ino: u32, holder: &mut InodeHolder) -> Result<(), Error> {
|
||||
if self.synchronous {
|
||||
// Immediately write-back
|
||||
self.fs.write_inode(ino, &holder.inode).await
|
||||
self.fs.write_or_free_inode(ino, &mut holder.inode).await
|
||||
} else {
|
||||
// Mark dirty
|
||||
holder.dirty = true;
|
||||
|
@ -12,18 +12,38 @@ pub mod cache;
|
||||
pub use cache::{InodeAccess, InodeCache, InodeHolder};
|
||||
|
||||
impl Inode {
|
||||
#[inline(always)]
|
||||
fn l0_capacity(blocks: u64) -> u64 {
|
||||
blocks.min(DIRECT_BLOCK_COUNT as u64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn l1_capacity(fs: &Ext2Fs, blocks: u64) -> u64 {
|
||||
blocks
|
||||
.saturating_sub(DIRECT_BLOCK_COUNT as u64)
|
||||
.min(fs.pointers_per_block as u64)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn l2_capacity(fs: &Ext2Fs, blocks: u64) -> (u64, u64) {
|
||||
let l2_l1 = blocks
|
||||
.saturating_sub((DIRECT_BLOCK_COUNT + fs.pointers_per_block) as u64)
|
||||
.min((fs.pointers_per_block * fs.pointers_per_block) as u64);
|
||||
let l2_l0 = l2_l1.div_ceil(fs.pointers_per_block as u64);
|
||||
(l2_l0, l2_l1)
|
||||
}
|
||||
|
||||
async fn grow_direct(
|
||||
&mut self,
|
||||
fs: &Ext2Fs,
|
||||
old_capacity: u64,
|
||||
new_capacity: u64,
|
||||
) -> Result<(), Error> {
|
||||
let old_l0_capacity = old_capacity.min(DIRECT_BLOCK_COUNT as u64);
|
||||
let new_l0_capacity = new_capacity.min(DIRECT_BLOCK_COUNT as u64);
|
||||
|
||||
debug_assert!(old_l0_capacity <= new_l0_capacity);
|
||||
let old_l0_capacity = Self::l0_capacity(old_capacity);
|
||||
let new_l0_capacity = Self::l0_capacity(new_capacity);
|
||||
|
||||
log::debug!("Grow L0: {old_l0_capacity} -> {new_l0_capacity}");
|
||||
debug_assert!(old_l0_capacity <= new_l0_capacity);
|
||||
|
||||
for i in old_l0_capacity..new_l0_capacity {
|
||||
let i = i as usize;
|
||||
@ -34,21 +54,37 @@ impl Inode {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn shrink_direct(
|
||||
&mut self,
|
||||
fs: &Ext2Fs,
|
||||
old_capacity: u64,
|
||||
new_capacity: u64,
|
||||
) -> Result<(), Error> {
|
||||
let old_l0_capacity = Self::l0_capacity(old_capacity);
|
||||
let new_l0_capacity = Self::l0_capacity(new_capacity);
|
||||
|
||||
log::debug!("Shrink L0: {old_l0_capacity} -> {new_l0_capacity}");
|
||||
debug_assert!(old_l0_capacity >= new_l0_capacity);
|
||||
|
||||
for i in new_l0_capacity..old_l0_capacity {
|
||||
let i = i as usize;
|
||||
fs.free_block(self.blocks.direct_blocks[i]).await?;
|
||||
self.blocks.direct_blocks[i] = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn grow_l1(
|
||||
&mut self,
|
||||
fs: &Ext2Fs,
|
||||
old_capacity: u64,
|
||||
new_capacity: u64,
|
||||
) -> Result<(), Error> {
|
||||
let old_l1_capacity = old_capacity
|
||||
.saturating_sub(DIRECT_BLOCK_COUNT as u64)
|
||||
.min(fs.pointers_per_block as u64);
|
||||
let new_l1_capacity = new_capacity
|
||||
.saturating_sub(DIRECT_BLOCK_COUNT as u64)
|
||||
.min(fs.pointers_per_block as u64);
|
||||
let old_l1_capacity = Self::l1_capacity(fs, old_capacity);
|
||||
let new_l1_capacity = Self::l1_capacity(fs, new_capacity);
|
||||
|
||||
log::debug!("Grow L1: {old_l1_capacity} -> {new_l1_capacity}");
|
||||
|
||||
debug_assert!(old_l1_capacity <= new_l1_capacity);
|
||||
|
||||
if old_l1_capacity == 0 && new_l1_capacity != 0 {
|
||||
@ -69,27 +105,48 @@ impl Inode {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn shrink_l1(
|
||||
&mut self,
|
||||
fs: &Ext2Fs,
|
||||
old_capacity: u64,
|
||||
new_capacity: u64,
|
||||
) -> Result<(), Error> {
|
||||
let old_l1_capacity = Self::l1_capacity(fs, old_capacity);
|
||||
let new_l1_capacity = Self::l1_capacity(fs, new_capacity);
|
||||
|
||||
log::debug!("Shrink L1: {old_l1_capacity} -> {new_l1_capacity}");
|
||||
debug_assert!(old_l1_capacity >= new_l1_capacity);
|
||||
|
||||
for i in new_l1_capacity..old_l1_capacity {
|
||||
debug_assert_ne!(self.blocks.indirect_block_l1, 0);
|
||||
let i = i as usize;
|
||||
let index = fs.remove_index(self.blocks.indirect_block_l1, i).await?;
|
||||
fs.free_block(index).await?;
|
||||
}
|
||||
|
||||
if old_l1_capacity != 0 && new_l1_capacity == 0 {
|
||||
// Free the L1 indirect block
|
||||
fs.free_block(self.blocks.indirect_block_l1).await?;
|
||||
self.blocks.indirect_block_l1 = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn grow_l2(
|
||||
&mut self,
|
||||
fs: &Ext2Fs,
|
||||
old_capacity: u64,
|
||||
new_capacity: u64,
|
||||
) -> Result<(), Error> {
|
||||
let old_l2_capacity = (old_capacity as usize)
|
||||
.saturating_sub(DIRECT_BLOCK_COUNT + fs.pointers_per_block)
|
||||
.min(fs.pointers_per_block * fs.pointers_per_block);
|
||||
let new_l2_capacity = (new_capacity as usize)
|
||||
.saturating_sub(DIRECT_BLOCK_COUNT + fs.pointers_per_block)
|
||||
.min(fs.pointers_per_block * fs.pointers_per_block);
|
||||
let (old_l2_l0, old_l2_l1) = Self::l2_capacity(fs, old_capacity);
|
||||
let (new_l2_l0, new_l2_l1) = Self::l2_capacity(fs, new_capacity);
|
||||
|
||||
let old_l2_l0 = old_l2_capacity.div_ceil(fs.pointers_per_block);
|
||||
let new_l2_l0 = new_l2_capacity.div_ceil(fs.pointers_per_block);
|
||||
log::debug!("Grow L2: {old_l2_l1} ({old_l2_l0} L2-1) -> {new_l2_l1} ({new_l2_l0} L2-1)");
|
||||
debug_assert!(old_l2_l1 <= new_l2_l1);
|
||||
debug_assert!(old_l2_l0 <= new_l2_l0);
|
||||
|
||||
log::debug!(
|
||||
"Grow L2: {old_l2_capacity} ({old_l2_l0} L2-1) -> {new_l2_capacity} ({new_l2_l0} L2-1)"
|
||||
);
|
||||
|
||||
if old_l2_capacity == 0 && new_l2_capacity != 0 {
|
||||
if old_l2_l1 == 0 && new_l2_l1 != 0 {
|
||||
// Allocate L2 indirect block
|
||||
let block = fs.allocate_block().await?;
|
||||
self.blocks.indirect_block_l2 = block;
|
||||
@ -98,14 +155,16 @@ impl Inode {
|
||||
// Grow L2 direct-indirect block
|
||||
for i in old_l2_l0..new_l2_l0 {
|
||||
debug_assert_ne!(self.blocks.indirect_block_l2, 0);
|
||||
let i = i as usize;
|
||||
let block = fs.allocate_block().await?;
|
||||
fs.write_index(self.blocks.indirect_block_l2, i, block)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Grow L2 indirect-indirect blocks
|
||||
for i in old_l2_capacity..new_l2_capacity {
|
||||
for i in old_l2_l1..new_l2_l1 {
|
||||
debug_assert_ne!(self.blocks.indirect_block_l2, 0);
|
||||
let i = i as usize;
|
||||
let l1i = i / fs.pointers_per_block;
|
||||
let l0i = i % fs.pointers_per_block;
|
||||
let indirect = fs.read_index(self.blocks.indirect_block_l2, l1i).await?;
|
||||
@ -117,6 +176,48 @@ impl Inode {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn shrink_l2(
|
||||
&mut self,
|
||||
fs: &Ext2Fs,
|
||||
old_capacity: u64,
|
||||
new_capacity: u64,
|
||||
) -> Result<(), Error> {
|
||||
let (old_l2_l0, old_l2_l1) = Self::l2_capacity(fs, old_capacity);
|
||||
let (new_l2_l0, new_l2_l1) = Self::l2_capacity(fs, new_capacity);
|
||||
|
||||
log::debug!("Grow L2: {old_l2_l1} ({old_l2_l0} L2-1) -> {new_l2_l1} ({new_l2_l0} L2-1)");
|
||||
debug_assert!(old_l2_l1 >= new_l2_l1);
|
||||
debug_assert!(old_l2_l0 >= new_l2_l0);
|
||||
|
||||
// Shrink L2 indirect-indirect blocks
|
||||
for i in new_l2_l1..old_l2_l1 {
|
||||
debug_assert_ne!(self.blocks.indirect_block_l2, 0);
|
||||
let i = i as usize;
|
||||
let l1i = i / fs.pointers_per_block;
|
||||
let l0i = i % fs.pointers_per_block;
|
||||
let indirect = fs.read_index(self.blocks.indirect_block_l2, l1i).await?;
|
||||
debug_assert_ne!(indirect, 0);
|
||||
let block = fs.remove_index(indirect, l0i).await?;
|
||||
fs.free_block(block).await?;
|
||||
}
|
||||
|
||||
// Shrink L2 direct-indirect block
|
||||
for i in new_l2_l0..old_l2_l0 {
|
||||
debug_assert_ne!(self.blocks.indirect_block_l2, 0);
|
||||
let i = i as usize;
|
||||
let block = fs.remove_index(self.blocks.indirect_block_l2, i).await?;
|
||||
fs.free_block(block).await?;
|
||||
}
|
||||
|
||||
if new_l2_l1 == 0 && old_l2_l1 != 0 {
|
||||
// Free L2 indirect block
|
||||
fs.free_block(self.blocks.indirect_block_l2).await?;
|
||||
self.blocks.indirect_block_l2 = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_size(&mut self, fs: &Ext2Fs, size: u64) {
|
||||
let block_count = size.div_ceil(fs.block_size as u64);
|
||||
|
||||
@ -164,7 +265,12 @@ impl Inode {
|
||||
self.grow_l2(fs, old_blocks, new_blocks).await?;
|
||||
}
|
||||
// Shrink
|
||||
Ordering::Greater => todo!(),
|
||||
Ordering::Greater => {
|
||||
log::debug!("Shrink inode: {old_blocks} -> {new_blocks} blocks");
|
||||
self.shrink_l2(fs, old_blocks, new_blocks).await?;
|
||||
self.shrink_l1(fs, old_blocks, new_blocks).await?;
|
||||
self.shrink_direct(fs, old_blocks, new_blocks).await?;
|
||||
}
|
||||
// No change
|
||||
Ordering::Equal => (),
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use core::mem;
|
||||
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use bytemuck::Zeroable;
|
||||
@ -14,6 +16,7 @@ use inode::{InodeAccess, InodeCache};
|
||||
use libk::{
|
||||
device::block::{cache::DeviceMapper, BlockDevice},
|
||||
error::Error,
|
||||
time::real_time,
|
||||
vfs::{Filesystem, FilesystemMountOption, NodeRef},
|
||||
};
|
||||
use libk_util::OneTimeInit;
|
||||
@ -343,6 +346,14 @@ impl Ext2Fs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn write_or_free_inode(&self, ino: u32, inode: &mut Inode) -> Result<(), Error> {
|
||||
if inode.is_deleted() {
|
||||
self.free_inode(ino, inode).await
|
||||
} else {
|
||||
self.write_inode(ino, inode).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_inode_data(
|
||||
&self,
|
||||
inode: &Inode,
|
||||
@ -426,6 +437,17 @@ impl Ext2Fs {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn remove_index(&self, block_index: u32, index: usize) -> Result<u32, Error> {
|
||||
self.with_block_mut(block_index, size_of::<u32>(), |block| {
|
||||
let indirect: &mut [u32] = unsafe {
|
||||
core::slice::from_raw_parts_mut(block.as_mut_ptr().cast(), self.pointers_per_block)
|
||||
};
|
||||
|
||||
Ok(mem::replace(&mut indirect[index], 0))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn inode_block_index(&self, inode: &Inode, index: u32) -> Result<u32, Error> {
|
||||
let mut index = index as usize;
|
||||
// L0
|
||||
|
@ -107,15 +107,12 @@ impl Node {
|
||||
let directory = self.as_directory()?;
|
||||
let node = directory.imp.create_node(self, info.ty)?;
|
||||
|
||||
// Fill out the node info
|
||||
node.set_access(
|
||||
Some(info.uid),
|
||||
Some(info.gid),
|
||||
Some(info.mode),
|
||||
check.clone(),
|
||||
)?;
|
||||
self.create_node(node.clone(), info.name, check.clone())?;
|
||||
|
||||
self.create_node(node, info.name, check)
|
||||
// Fill out the node info
|
||||
node.set_access(Some(info.uid), Some(info.gid), Some(info.mode), check)?;
|
||||
|
||||
Ok(node)
|
||||
}
|
||||
|
||||
/// Attaches a pre-created node to its parent
|
||||
|
Loading…
x
Reference in New Issue
Block a user