use crate::dir::fetch::lfs_dir_getgstate;
use crate::dir::LfsCommit;
use crate::dir::LfsMdir;
use crate::fs::stat::lfs_fs_size_;
use crate::fs::Lfs;
use crate::types::{lfs_block_t, lfs_off_t, lfs_size_t, lfs_tag_t};
pub fn lfs_dir_commitprog(
lfs: *mut crate::fs::Lfs,
commit: *mut LfsCommit,
buffer: *const core::ffi::c_void,
size: lfs_size_t,
) -> i32 {
use crate::bd::bd::lfs_bd_prog;
use crate::crc::lfs_crc;
unsafe {
let lfs_ref = &mut *lfs;
let commit_ref = &mut *commit;
let buf = buffer as *const u8;
let err = lfs_bd_prog(
lfs_ref,
&mut lfs_ref.pcache,
&mut lfs_ref.rcache,
false,
commit_ref.block,
commit_ref.off,
buf,
size,
);
if err != 0 {
return crate::lfs_pass_err!(err);
}
commit_ref.crc = lfs_crc(commit_ref.crc, buf, size as usize);
commit_ref.off += size;
0
}
}
pub fn lfs_dir_commitattr(
lfs: *mut crate::fs::Lfs,
commit: *mut LfsCommit,
tag: lfs_tag_t,
buffer: *const core::ffi::c_void,
) -> i32 {
use crate::bd::bd::lfs_bd_read;
use crate::error::LFS_ERR_NOSPC;
use crate::tag::{lfs_tag_dsize, lfs_tag_isvalid};
use crate::util::lfs_tobe32;
unsafe {
let commit_ref = &mut *commit;
let dsize = lfs_tag_dsize(tag);
if commit_ref.off + dsize > commit_ref.end {
crate::lfs_trace!(
"lfs_dir_commitattr NOSPC: off+dsize>end off={} dsize={} end={} block={}",
commit_ref.off,
dsize,
commit_ref.end,
commit_ref.block
);
return crate::lfs_err!(LFS_ERR_NOSPC);
}
let ntag = lfs_tobe32((tag & 0x7fff_ffff) ^ commit_ref.ptag);
let mut err = lfs_dir_commitprog(lfs, commit, &ntag as *const _ as *const _, 4);
if err != 0 {
return crate::lfs_pass_err!(err);
}
if u32::from(crate::tag::lfs_tag_type1(tag))
== crate::lfs_type::lfs_type::LFS_TYPE_SUPERBLOCK
{
crate::lfs_trace!(
"commitattr SUPERBLOCK: dsize={} buffer={:p} commit.block={} commit.off={}",
dsize,
buffer,
commit_ref.block,
commit_ref.off
);
if !buffer.is_null() && dsize >= 8 {
crate::lfs_trace!(
"commitattr SUPERBLOCK data (first 8): {:?}",
core::slice::from_raw_parts(buffer as *const u8, 8)
);
}
}
if lfs_tag_isvalid(tag) {
err = lfs_dir_commitprog(lfs, commit, buffer, dsize.saturating_sub(4));
if err != 0 {
return crate::lfs_pass_err!(err);
}
} else {
let disk = buffer as *const crate::tag::lfs_diskoff;
let disk_ref = &*disk;
let data_size = dsize.saturating_sub(4);
for i in 0..data_size {
let mut dat: u8 = 0;
err = lfs_bd_read(
lfs,
core::ptr::null(),
&mut (*lfs).rcache,
data_size - i,
disk_ref.block,
disk_ref.off + i,
&mut dat as *mut u8,
1,
);
if err != 0 {
return crate::lfs_pass_err!(err);
}
err = lfs_dir_commitprog(lfs, commit, &dat as *const _ as *const _, 1);
if err != 0 {
return crate::lfs_pass_err!(err);
}
}
}
commit_ref.ptag = tag & 0x7fff_ffff;
0
}
}
pub fn lfs_dir_commitcrc(lfs: *mut crate::fs::Lfs, commit: *mut LfsCommit) -> i32 {
use crate::bd::bd::{lfs_bd_crc, lfs_bd_prog, lfs_bd_sync};
use crate::crc::lfs_crc;
use crate::error::LFS_ERR_CORRUPT;
use crate::tag::lfs_mktag;
use crate::util::{lfs_alignup, lfs_min, lfs_tobe32, lfs_tole32};
unsafe {
let lfs_ref = &*lfs;
let cfg = lfs_ref.cfg.as_ref().unwrap();
let block_size = cfg.block_size;
let prog_size = cfg.prog_size;
let end = lfs_alignup(lfs_min((*commit).off + 20, block_size), prog_size);
let mut off1: lfs_off_t = 0;
let mut crc1: u32 = 0;
while (*commit).off < end {
let noff = lfs_min(end - ((*commit).off + 4), 0x3fe) + ((*commit).off + 4);
let noff = if noff < end {
lfs_min(noff, end - 20)
} else {
noff
};
let mut eperturb: u8 = 0xff;
if noff >= end && noff <= block_size - prog_size {
let err = crate::bd::bd::lfs_bd_read(
lfs,
core::ptr::null_mut(),
&mut (*lfs).rcache,
prog_size,
(*commit).block,
noff,
&mut eperturb,
1,
);
if err != 0 && err != crate::error::LFS_ERR_CORRUPT {
return crate::lfs_pass_err!(err);
}
}
let ntag = lfs_mktag(
crate::lfs_type::lfs_type::LFS_TYPE_CCRC + (u32::from(!eperturb) >> 7),
0x3ff,
noff - ((*commit).off + 4),
);
let xor_tag = lfs_tobe32(ntag ^ (*commit).ptag);
(*commit).crc = lfs_crc((*commit).crc, &xor_tag as *const _ as *const u8, 4);
let crc_le = lfs_tole32((*commit).crc);
let mut ccrc: [u8; 8] = [0; 8];
core::ptr::copy_nonoverlapping(&xor_tag as *const _ as *const u8, ccrc.as_mut_ptr(), 4);
core::ptr::copy_nonoverlapping(
&crc_le as *const _ as *const u8,
ccrc.as_mut_ptr().add(4),
4,
);
let err = lfs_bd_prog(
lfs,
&mut (*lfs).pcache,
&mut (*lfs).rcache,
false,
(*commit).block,
(*commit).off,
ccrc.as_ptr(),
8,
);
if err != 0 {
return crate::lfs_pass_err!(err);
}
if off1 == 0 {
off1 = (*commit).off + 4;
crc1 = (*commit).crc;
}
(*commit).off = noff;
(*commit).ptag = ntag ^ ((0x80 & !eperturb) as u32) << 24;
(*commit).crc = 0xffff_ffff;
if noff >= end || noff >= (*lfs).pcache.off + cfg.cache_size {
let err = lfs_bd_sync(lfs, &mut (*lfs).pcache, &mut (*lfs).rcache, false);
if err != 0 {
return crate::lfs_pass_err!(err);
}
}
}
let mut crc: u32 = 0xffff_ffff;
let err = lfs_bd_crc(
lfs,
core::ptr::null(),
&mut (*lfs).rcache,
off1 + 4,
(*commit).block,
(*commit).begin,
off1 - (*commit).begin,
&mut crc,
);
if err != 0 {
return crate::lfs_pass_err!(err);
}
if crc != crc1 {
return crate::lfs_err!(LFS_ERR_CORRUPT);
}
let err = lfs_bd_crc(
lfs,
core::ptr::null(),
&mut (*lfs).rcache,
4,
(*commit).block,
off1,
4,
&mut crc,
);
if err != 0 {
return crate::lfs_pass_err!(err);
}
if crc != 0 {
return crate::lfs_err!(LFS_ERR_CORRUPT);
}
0
}
}
pub unsafe fn lfs_dir_alloc(lfs: *mut crate::fs::Lfs, dir: *mut LfsMdir) -> i32 {
use crate::bd::bd::lfs_bd_read;
use crate::block_alloc::alloc::lfs_alloc;
use crate::types::LFS_BLOCK_NULL;
use crate::util::{lfs_alignup, lfs_fromle32};
unsafe {
let lfs_ref = &*lfs;
let dir_ref = &mut *dir;
for i in 0..2 {
let out_block = &mut dir_ref.pair[(i + 1) % 2];
let err = lfs_alloc(lfs, out_block);
if err != 0 {
return crate::lfs_pass_err!(err);
}
}
dir_ref.rev = 0;
let mut rev_buf: u32 = 0;
let err = lfs_bd_read(
lfs,
core::ptr::null(),
&mut (*lfs).rcache,
core::mem::size_of::<u32>() as u32,
dir_ref.pair[0],
0,
&mut rev_buf as *mut u32 as *mut u8,
core::mem::size_of::<u32>() as u32,
);
dir_ref.rev = lfs_fromle32(rev_buf);
if err != 0 && err != crate::error::LFS_ERR_CORRUPT {
return crate::lfs_pass_err!(err);
}
if lfs_ref.cfg.as_ref().is_some_and(|c| c.block_cycles > 0) {
let modulus = (lfs_ref.cfg.as_ref().unwrap().block_cycles as u32 + 1) | 1;
dir_ref.rev = lfs_alignup(dir_ref.rev, modulus);
}
dir_ref.off = core::mem::size_of::<u32>() as u32;
dir_ref.etag = 0xffff_ffff;
dir_ref.count = 0;
dir_ref.tail = [LFS_BLOCK_NULL, LFS_BLOCK_NULL];
dir_ref.erased = false;
dir_ref.split = false;
0
}
}
pub fn lfs_dir_drop(lfs: *mut crate::fs::Lfs, dir: *mut LfsMdir, tail: *const LfsMdir) -> i32 {
use crate::lfs_type::lfs_type::LFS_TYPE_TAIL;
use crate::tag::lfs_mktag;
use crate::util::lfs_pair_tole32;
unsafe {
let err = lfs_dir_getgstate(lfs, tail, &mut (*lfs).gdelta);
if err != 0 {
return crate::lfs_pass_err!(err);
}
let tail_ref = &*tail;
let mut tail_pair = tail_ref.tail;
lfs_pair_tole32(&mut tail_pair);
let attrs = [crate::tag::lfs_mattr {
tag: lfs_mktag(LFS_TYPE_TAIL + if tail_ref.split { 1 } else { 0 }, 0x3ff, 8),
buffer: tail_pair.as_ptr() as *const core::ffi::c_void,
}];
lfs_dir_commit(lfs, dir, attrs.as_ptr() as *const _, 1)
}
}
pub fn lfs_dir_split(
lfs: *mut Lfs,
dir: *mut LfsMdir,
attrs: *const core::ffi::c_void,
attrcount: i32,
source: *const LfsMdir,
split: u16,
end: u16,
) -> i32 {
use crate::util::lfs_pair_cmp;
unsafe {
let mut tail = LfsMdir {
pair: [0, 0],
rev: 0,
off: 0,
etag: 0,
count: 0,
erased: false,
split: false,
tail: [0, 0],
};
let err = lfs_dir_alloc(lfs, &mut tail);
if err != 0 {
return crate::lfs_pass_err!(err);
}
let dir_ref = &mut *dir;
tail.split = dir_ref.split;
tail.tail[0] = dir_ref.tail[0];
tail.tail[1] = dir_ref.tail[1];
let res = lfs_dir_compact(lfs, &mut tail, attrs, attrcount, source, split, end);
if res < 0 {
return res;
}
dir_ref.tail[0] = tail.pair[0];
dir_ref.tail[1] = tail.pair[1];
dir_ref.split = true;
crate::lfs_trace!(
"dir_split: split={} end={} new_tail=[{},{}] dir.pair=[{},{}] dir.tail=[{},{}]",
split,
end,
tail.pair[0],
tail.pair[1],
dir_ref.pair[0],
dir_ref.pair[1],
dir_ref.tail[0],
dir_ref.tail[1]
);
let root = &(*lfs).root;
if lfs_pair_cmp(&dir_ref.pair, root) == 0 && split == 0 {
(*lfs).root[0] = tail.pair[0];
(*lfs).root[1] = tail.pair[1];
}
0
}
}
pub fn lfs_dir_commit_size(
p: *mut core::ffi::c_void,
tag: lfs_tag_t,
_buffer: *const core::ffi::c_void,
) -> i32 {
use crate::tag::lfs_tag_dsize;
use crate::types::lfs_size_t;
unsafe {
let size = p as *mut lfs_size_t;
*size += lfs_tag_dsize(tag);
}
0
}
pub fn lfs_dir_commit_commit(
p: *mut core::ffi::c_void,
tag: lfs_tag_t,
buffer: *const core::ffi::c_void,
) -> i32 {
if p.is_null() {
return crate::error::LFS_ERR_INVAL;
}
unsafe {
let commit_commit = &*(p as *const (*mut Lfs, *mut LfsCommit));
let (lfs, commit) = *commit_commit;
lfs_dir_commitattr(lfs, commit, tag, buffer)
}
}
pub fn lfs_dir_needsrelocation(lfs: *const Lfs, dir: *const LfsMdir) -> bool {
unsafe {
let cfg = (*lfs).cfg.as_ref();
match cfg {
None => false,
Some(c) if c.block_cycles <= 0 => false, Some(c) => {
let modulus = ((c.block_cycles as u32).wrapping_add(1)) | 1;
let dir_ref = &*dir;
(dir_ref.rev.wrapping_add(1)).is_multiple_of(modulus)
}
}
}
}
pub fn lfs_dir_compact(
lfs: *mut Lfs,
dir: *mut LfsMdir,
attrs: *const core::ffi::c_void,
attrcount: i32,
source: *const LfsMdir,
begin: u16,
end: u16,
) -> i32 {
use crate::bd::bd::{lfs_bd_erase, lfs_cache_drop};
use crate::block_alloc::alloc::{lfs_alloc, lfs_alloc_lookahead};
use crate::dir::traverse::lfs_dir_traverse;
use crate::error::{LFS_ERR_CORRUPT, LFS_ERR_NOSPC};
use crate::lfs_gstate::{lfs_gstate_iszero, lfs_gstate_tole32, lfs_gstate_xor};
use crate::tag::lfs_mktag;
use crate::types::LFS_BLOCK_NULL;
use crate::util::{
lfs_fromle32, lfs_pair_cmp, lfs_pair_fromle32, lfs_pair_isnull, lfs_pair_swap,
lfs_pair_tole32, lfs_tole32,
};
unsafe {
let mut relocated = false;
let mut relocate_count: u32 = 0;
let mut tired = lfs_dir_needsrelocation(lfs, dir);
let dir_ref = &mut *dir;
let superblock_pair = [0u32, 1u32];
dir_ref.rev = dir_ref.rev.wrapping_add(1);
if tired && lfs_pair_cmp(&dir_ref.pair, &superblock_pair) != 0 {
relocated = true;
lfs_cache_drop(lfs, &mut (*lfs).pcache as *mut _);
let err = lfs_alloc(lfs, &mut dir_ref.pair[1]);
if err != 0 && (err != LFS_ERR_NOSPC || !tired) {
crate::lfs_trace!(
"lfs_dir_compact: tired pre-alloc failed err={} pair={:?}",
err,
dir_ref.pair
);
return crate::lfs_pass_err!(err);
}
tired = false;
}
#[cfg(feature = "loop_limits")]
const MAX_COMPACT_ITER: u32 = 1024;
#[cfg(feature = "loop_limits")]
let mut compact_iter: u32 = 0;
loop {
#[cfg(feature = "loop_limits")]
{
if compact_iter >= MAX_COMPACT_ITER {
panic!(
"loop_limits: MAX_COMPACT_ITER ({}) exceeded in lfs_dir_compact",
MAX_COMPACT_ITER
);
}
compact_iter += 1;
}
let metadata_max = (*lfs).cfg.as_ref().map_or(0, |c| c.metadata_max);
let block_size = (*lfs).cfg.as_ref().unwrap().block_size;
let end_off = if metadata_max != 0 {
metadata_max
} else {
block_size
} - 8;
let mut commit = LfsCommit {
block: dir_ref.pair[1],
off: 0,
ptag: 0xffff_ffff,
crc: 0xffff_ffff,
begin: 0,
end: end_off,
};
let err = lfs_bd_erase(lfs, dir_ref.pair[1]);
if err != 0 {
if err == LFS_ERR_CORRUPT {
relocated = true;
relocate_count += 1;
crate::lfs_trace!(
"lfs_dir_compact relocate #{}: bd_erase CORRUPT pair={:?}",
relocate_count,
dir_ref.pair
);
lfs_alloc_lookahead(lfs, dir_ref.pair[1]);
lfs_cache_drop(lfs, &mut (*lfs).pcache as *mut _);
if lfs_pair_cmp(&dir_ref.pair, &superblock_pair) == 0 {
crate::lfs_trace!("lfs_dir_compact NOSPC: root+CORRUPT bd_erase");
return crate::lfs_err!(LFS_ERR_NOSPC);
}
let err2 = lfs_alloc(lfs, &mut dir_ref.pair[1]);
if err2 != 0 && (err2 != LFS_ERR_NOSPC || !tired) {
crate::lfs_trace!(
"lfs_dir_compact NOSPC: alloc failed after bd_erase err={}",
err2
);
return err2;
}
tired = false;
continue;
}
return crate::lfs_pass_err!(err);
}
let mut rev = lfs_tole32(dir_ref.rev);
let mut err = lfs_dir_commitprog(lfs, &mut commit, &rev as *const _ as *const _, 4);
dir_ref.rev = lfs_fromle32(rev);
if err != 0 {
if err == LFS_ERR_CORRUPT {
relocated = true;
relocate_count += 1;
crate::lfs_trace!(
"lfs_dir_compact relocate #{}: commitprog CORRUPT pair={:?}",
relocate_count,
dir_ref.pair
);
lfs_alloc_lookahead(lfs, dir_ref.pair[1]);
lfs_cache_drop(lfs, &mut (*lfs).pcache as *mut _);
if lfs_pair_cmp(&dir_ref.pair, &superblock_pair) == 0 {
crate::lfs_trace!("lfs_dir_compact NOSPC: root+CORRUPT commitprog");
return crate::lfs_err!(LFS_ERR_NOSPC);
}
let err2 = lfs_alloc(lfs, &mut dir_ref.pair[1]);
if err2 != 0 && (err2 != LFS_ERR_NOSPC || !tired) {
crate::lfs_trace!(
"lfs_dir_compact NOSPC: alloc failed after commitprog err={}",
err2
);
return err2;
}
tired = false;
continue;
}
return crate::lfs_pass_err!(err);
}
let mut commit_commit: (*mut Lfs, *mut LfsCommit) = (lfs, &mut commit as *mut _);
err = lfs_dir_traverse(
lfs,
source,
0,
0xffff_ffff,
attrs,
attrcount,
lfs_mktag(0x400, 0x3ff, 0),
lfs_mktag(crate::lfs_type::lfs_type::LFS_TYPE_NAME, 0, 0),
begin,
end,
-(begin as i16),
Some(lfs_dir_commit_commit_raw),
&mut commit_commit as *mut _ as *mut core::ffi::c_void,
);
if err != 0 {
if err == LFS_ERR_CORRUPT {
relocated = true;
relocate_count += 1;
crate::lfs_trace!(
"lfs_dir_compact relocate #{}: traverse CORRUPT pair={:?}",
relocate_count,
dir_ref.pair
);
lfs_alloc_lookahead(lfs, dir_ref.pair[1]);
lfs_cache_drop(lfs, &mut (*lfs).pcache as *mut _);
if lfs_pair_cmp(&dir_ref.pair, &superblock_pair) == 0 {
crate::lfs_trace!("lfs_dir_compact NOSPC: root+err traverse");
return crate::lfs_err!(LFS_ERR_NOSPC);
}
let err2 = lfs_alloc(lfs, &mut dir_ref.pair[1]);
if err2 != 0 && (err2 != LFS_ERR_NOSPC || !tired) {
crate::lfs_trace!(
"lfs_dir_compact NOSPC: alloc failed after traverse err={}",
err2
);
return err2;
}
tired = false;
continue;
}
crate::lfs_trace!("lfs_dir_compact: traverse returned err={}", err);
return crate::lfs_pass_err!(err);
}
if !lfs_pair_isnull(&dir_ref.tail) {
lfs_pair_tole32(&mut dir_ref.tail);
err = lfs_dir_commitattr(
lfs,
&mut commit as *mut _,
lfs_mktag(
crate::lfs_type::lfs_type::LFS_TYPE_TAIL
+ if dir_ref.split { 1 } else { 0 },
0x3ff,
8,
),
&dir_ref.tail as *const _ as *const _,
);
lfs_pair_fromle32(&mut dir_ref.tail);
if err != 0 {
if err == LFS_ERR_CORRUPT {
relocated = true;
relocate_count += 1;
crate::lfs_trace!(
"lfs_dir_compact relocate #{}: tail CORRUPT pair={:?}",
relocate_count,
dir_ref.pair
);
lfs_alloc_lookahead(lfs, dir_ref.pair[1]);
lfs_cache_drop(lfs, &mut (*lfs).pcache as *mut _);
if lfs_pair_cmp(&dir_ref.pair, &superblock_pair) == 0 {
crate::lfs_trace!("lfs_dir_compact NOSPC: root+CORRUPT tail");
return crate::lfs_err!(LFS_ERR_NOSPC);
}
let err2 = lfs_alloc(lfs, &mut dir_ref.pair[1]);
if err2 != 0 && (err2 != LFS_ERR_NOSPC || !tired) {
crate::lfs_trace!(
"lfs_dir_compact NOSPC: alloc failed after tail err={}",
err2
);
return err2;
}
tired = false;
continue;
}
return crate::lfs_pass_err!(err);
}
}
let mut delta = crate::lfs_gstate::LfsGstate {
tag: 0,
pair: [0, 0],
};
if !relocated {
lfs_gstate_xor(&mut delta, &(*lfs).gdisk);
lfs_gstate_xor(&mut delta, &(*lfs).gstate);
}
lfs_gstate_xor(&mut delta, &(*lfs).gdelta);
delta.tag &= !lfs_mktag(0, 0, 0x3ff);
err = lfs_dir_getgstate(lfs, dir, &mut delta);
if err != 0 {
return crate::lfs_pass_err!(err);
}
if !lfs_gstate_iszero(&delta) {
lfs_gstate_tole32(&mut delta);
err = lfs_dir_commitattr(
lfs,
&mut commit as *mut _,
lfs_mktag(
crate::lfs_type::lfs_type::LFS_TYPE_MOVESTATE,
0x3ff,
core::mem::size_of::<crate::lfs_gstate::LfsGstate>() as u32,
),
&delta as *const _ as *const _,
);
if err != 0 {
if err == LFS_ERR_CORRUPT {
relocated = true;
relocate_count += 1;
crate::lfs_trace!(
"lfs_dir_compact relocate #{}: movestate CORRUPT pair={:?}",
relocate_count,
dir_ref.pair
);
lfs_alloc_lookahead(lfs, dir_ref.pair[1]);
lfs_cache_drop(lfs, &mut (*lfs).pcache as *mut _);
if lfs_pair_cmp(&dir_ref.pair, &superblock_pair) == 0 {
crate::lfs_trace!("lfs_dir_compact NOSPC: root+CORRUPT movestate");
return crate::lfs_err!(LFS_ERR_NOSPC);
}
let err2 = lfs_alloc(lfs, &mut dir_ref.pair[1]);
if err2 != 0 && (err2 != LFS_ERR_NOSPC || !tired) {
crate::lfs_trace!(
"lfs_dir_compact NOSPC: alloc failed after movestate err={}",
err2
);
return err2;
}
tired = false;
continue;
}
return crate::lfs_pass_err!(err);
}
}
err = lfs_dir_commitcrc(lfs, &mut commit);
if err != 0 {
if err == LFS_ERR_CORRUPT {
relocated = true;
relocate_count += 1;
crate::lfs_trace!(
"lfs_dir_compact relocate #{}: commitcrc CORRUPT pair={:?}",
relocate_count,
dir_ref.pair
);
lfs_alloc_lookahead(lfs, dir_ref.pair[1]);
lfs_cache_drop(lfs, &mut (*lfs).pcache as *mut _);
if lfs_pair_cmp(&dir_ref.pair, &superblock_pair) == 0 {
crate::lfs_trace!("lfs_dir_compact NOSPC: root+CORRUPT commitcrc");
return crate::lfs_err!(LFS_ERR_NOSPC);
}
let err2 = lfs_alloc(lfs, &mut dir_ref.pair[1]);
if err2 != 0 && (err2 != LFS_ERR_NOSPC || !tired) {
crate::lfs_trace!(
"lfs_dir_compact NOSPC: alloc failed after commitcrc err={}",
err2
);
return err2;
}
tired = false;
continue;
}
return crate::lfs_pass_err!(err);
}
crate::lfs_assert!(commit
.off
.is_multiple_of((*lfs).cfg.as_ref().unwrap().prog_size));
lfs_pair_swap(&mut dir_ref.pair);
dir_ref.count = end - begin;
dir_ref.off = commit.off;
dir_ref.etag = commit.ptag;
(*lfs).gdelta = crate::lfs_gstate::LfsGstate {
tag: 0,
pair: [0, 0],
};
if !relocated {
(*lfs).gdisk = (*lfs).gstate;
}
break;
}
if relocated {
crate::error::LFS_OK_RELOCATED
} else {
0
}
}
}
pub fn lfs_dir_splittingcompact(
lfs: *mut Lfs,
dir: *mut LfsMdir,
attrs: *const core::ffi::c_void,
attrcount: i32,
source: *const LfsMdir,
begin: u16,
end: u16,
) -> i32 {
use crate::dir::traverse::lfs_dir_traverse;
use crate::tag::lfs_mktag;
use crate::types::lfs_size_t;
use crate::util::{lfs_alignup, lfs_min, lfs_pair_cmp};
unsafe {
let mut split = begin;
let mut end_val = end;
loop {
while end_val - split > 1 {
let mut size: lfs_size_t = 0;
let mut size_ptr = size;
let err = lfs_dir_traverse(
lfs,
source,
0,
0xffff_ffff,
attrs,
attrcount,
lfs_mktag(0x400, 0x3ff, 0),
lfs_mktag(crate::lfs_type::lfs_type::LFS_TYPE_NAME, 0, 0),
split,
end_val,
-(split as i16),
Some(lfs_dir_commit_size_raw),
&mut size_ptr as *mut _ as *mut core::ffi::c_void,
);
if err != 0 {
return crate::lfs_pass_err!(err);
}
size = size_ptr;
let metadata_max = (*lfs).cfg.as_ref().map_or(0, |c| c.metadata_max);
let block_size = (*lfs).cfg.as_ref().unwrap().block_size;
let prog_size = (*lfs).cfg.as_ref().unwrap().prog_size;
let effective_max = if metadata_max != 0 {
metadata_max
} else {
block_size
};
let max_space = effective_max - 40;
let half_block = lfs_alignup(effective_max / 2, prog_size);
crate::lfs_trace!(
"splittingcompact: split={} end_val={} size={} max_space={} half_block={} break={}",
split,
end_val,
size,
max_space,
half_block,
end_val - split < 0xff && size <= lfs_min(max_space, half_block)
);
if end_val - split < 0xff && size <= lfs_min(max_space, half_block) {
break;
}
split = split + ((end_val - split) / 2);
}
if split == begin {
crate::lfs_trace!("splittingcompact: no split needed split==begin");
break;
}
if end_val <= split {
crate::lfs_trace!(
"splittingcompact: skip empty range split={} end_val={}",
split,
end_val
);
break;
}
crate::lfs_trace!(
"splittingcompact: calling dir_split split={} end_val={}",
split,
end_val
);
let err = lfs_dir_split(lfs, dir, attrs, attrcount, source, split, end_val);
if err != 0 && err != crate::error::LFS_ERR_NOSPC {
return crate::lfs_pass_err!(err);
}
if err != 0 {
break;
} else {
end_val = split;
}
}
let dir_ref = &*dir;
let superblock_pair = [0u32, 1u32];
if lfs_dir_needsrelocation(lfs, dir) && lfs_pair_cmp(&dir_ref.pair, &superblock_pair) == 0 {
let size = lfs_fs_size_(lfs);
if size < 0 {
return size;
}
if (*lfs).block_count as i64 - size as i64 > ((*lfs).block_count as i64) / 8 {
let err = lfs_dir_split(lfs, dir, attrs, attrcount, source, begin, end_val);
if err != 0 && err != crate::error::LFS_ERR_NOSPC {
return crate::lfs_pass_err!(err);
}
if err == 0 {
end_val = 1;
}
}
}
lfs_dir_compact(lfs, dir, attrs, attrcount, source, begin, end_val)
}
}
unsafe extern "C" fn lfs_dir_commit_size_raw(
p: *mut core::ffi::c_void,
tag: lfs_tag_t,
buffer: *const core::ffi::c_void,
) -> i32 {
lfs_dir_commit_size(p, tag, buffer)
}
pub fn lfs_dir_relocatingcommit(
lfs: *mut Lfs,
dir: *mut LfsMdir,
pair: *const [lfs_block_t; 2],
attrs: *const core::ffi::c_void,
attrcount: i32,
pdir: *mut LfsMdir,
) -> i32 {
use crate::bd::bd::lfs_cache_drop;
use crate::dir::traverse::lfs_dir_traverse;
use crate::error::{LFS_ERR_CORRUPT, LFS_ERR_NOSPC};
use crate::lfs_gstate::{lfs_gstate_iszero, lfs_gstate_tole32, lfs_gstate_xor};
use crate::lfs_type::lfs_type::{LFS_TYPE_CREATE, LFS_TYPE_DELETE, LFS_TYPE_TAIL};
use crate::tag::{lfs_mktag, lfs_tag_type1, lfs_tag_type3};
use crate::types::LFS_BLOCK_NULL;
use crate::util::{lfs_pair_cmp, lfs_pair_fromle32, lfs_pair_tole32};
unsafe {
let mut state = 0i32;
let dir_ref = &mut *dir;
let pair_ref = &*pair;
let attrs_slice = if attrcount > 0 && !attrs.is_null() {
core::slice::from_raw_parts(attrs as *const crate::tag::lfs_mattr, attrcount as usize)
} else {
&[]
};
let mut hasdelete = false;
for attr in attrs_slice.iter() {
let tag = attr.tag;
if u32::from(lfs_tag_type3(tag)) == LFS_TYPE_CREATE {
dir_ref.count = dir_ref.count.wrapping_add(1);
} else if u32::from(lfs_tag_type3(tag)) == LFS_TYPE_DELETE {
crate::lfs_assert!(dir_ref.count > 0);
dir_ref.count -= 1;
hasdelete = true;
} else if u32::from(lfs_tag_type1(tag)) == LFS_TYPE_TAIL {
let buf = attr.buffer as *const [lfs_block_t; 2];
if !buf.is_null() {
dir_ref.tail[0] = (*buf)[0];
dir_ref.tail[1] = (*buf)[1];
}
dir_ref.split = (crate::tag::lfs_tag_chunk(tag) & 1) != 0;
lfs_pair_fromle32(&mut dir_ref.tail);
}
}
if hasdelete && dir_ref.count == 0 {
crate::lfs_assert!(!pdir.is_null());
let err = crate::fs::parent::lfs_fs_pred(lfs, &dir_ref.pair, pdir);
if err != 0 && err != crate::error::LFS_ERR_NOENT {
return crate::lfs_pass_err!(err);
}
if err != crate::error::LFS_ERR_NOENT && (*pdir).split {
state = crate::error::LFS_OK_DROPPED;
}
}
if state == crate::error::LFS_OK_DROPPED {
return relocatingcommit_fixmlist(lfs, dir, pair, attrs, attrcount, state);
}
let mut do_compact = true;
if dir_ref.erased && dir_ref.count < 0xff {
let metadata_max = (*lfs).cfg.as_ref().map_or(0, |c| c.metadata_max);
let block_size = (*lfs).cfg.as_ref().unwrap().block_size;
let end = if metadata_max != 0 {
metadata_max
} else {
block_size
} - 8;
let mut commit = LfsCommit {
block: dir_ref.pair[0],
off: dir_ref.off,
ptag: dir_ref.etag,
crc: 0xffff_ffff,
begin: dir_ref.off,
end,
};
lfs_pair_tole32(&mut dir_ref.tail);
let mut commit_commit: (*mut Lfs, *mut LfsCommit) = (lfs, &mut commit as *mut _);
let err = lfs_dir_traverse(
lfs,
dir,
dir_ref.off,
dir_ref.etag,
attrs,
attrcount,
0,
0,
0,
0,
0,
Some(lfs_dir_commit_commit_raw),
&mut commit_commit as *mut _ as *mut core::ffi::c_void,
);
lfs_pair_fromle32(&mut dir_ref.tail);
if err == 0 {
do_compact = false;
let mut delta = crate::lfs_gstate::LfsGstate {
tag: 0,
pair: [0, 0],
};
lfs_gstate_xor(&mut delta, &(*lfs).gstate);
lfs_gstate_xor(&mut delta, &(*lfs).gdisk);
lfs_gstate_xor(&mut delta, &(*lfs).gdelta);
delta.tag &= !lfs_mktag(0, 0, 0x3ff);
if !lfs_gstate_iszero(&delta) {
let err2 = lfs_dir_getgstate(lfs, dir, &mut delta);
if err2 != 0 {
return err2;
}
lfs_gstate_tole32(&mut delta);
let movestate_tag = lfs_mktag(
crate::lfs_type::lfs_type::LFS_TYPE_MOVESTATE,
0x3ff,
core::mem::size_of::<crate::lfs_gstate::LfsGstate>() as u32,
);
let err2 = lfs_dir_commitattr(
lfs,
&mut commit as *mut _,
movestate_tag,
&delta as *const _ as *const _,
);
if err2 != 0 {
if err2 == LFS_ERR_NOSPC || err2 == LFS_ERR_CORRUPT {
do_compact = true;
} else {
return err2;
}
}
}
if !do_compact {
let err2 = lfs_dir_commitcrc(lfs, &mut commit);
if err2 != 0 {
if err2 == LFS_ERR_NOSPC || err2 == LFS_ERR_CORRUPT {
do_compact = true;
} else {
return err2;
}
} else {
dir_ref.off = commit.off;
dir_ref.etag = commit.ptag;
(*lfs).gdisk = (*lfs).gstate;
(*lfs).gdelta = crate::lfs_gstate::LfsGstate {
tag: 0,
pair: [0, 0],
};
}
}
} else if err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT {
do_compact = true;
} else {
return crate::lfs_pass_err!(err);
}
}
if do_compact {
lfs_cache_drop(lfs, &mut (*lfs).pcache as *mut _);
state = lfs_dir_splittingcompact(lfs, dir, attrs, attrcount, dir, 0, (*dir).count);
if state < 0 {
return state;
}
}
relocatingcommit_fixmlist(lfs, dir, pair, attrs, attrcount, state)
}
}
#[inline(never)]
fn relocatingcommit_fixmlist(
lfs: *mut Lfs,
dir: *mut LfsMdir,
pair: *const [lfs_block_t; 2],
attrs: *const core::ffi::c_void,
attrcount: i32,
state: i32,
) -> i32 {
use crate::dir::fetch::lfs_dir_fetch;
use crate::lfs_type::lfs_type::{LFS_TYPE_CREATE, LFS_TYPE_DELETE};
use crate::tag::{lfs_tag_id, lfs_tag_type3};
use crate::types::LFS_BLOCK_NULL;
use crate::util::lfs_pair_cmp;
unsafe {
let oldpair = [(*pair)[0], (*pair)[1]];
let mut d = (*lfs).mlist;
#[cfg(feature = "loop_limits")]
const MAX_MLIST_COMMIT_ITER: u32 = 128;
#[cfg(feature = "loop_limits")]
let mut mlist_iter: u32 = 0;
while !d.is_null() {
#[cfg(feature = "loop_limits")]
{
if mlist_iter >= MAX_MLIST_COMMIT_ITER {
panic!(
"loop_limits: MAX_MLIST_COMMIT_ITER ({}) exceeded",
MAX_MLIST_COMMIT_ITER
);
}
mlist_iter += 1;
}
let d_ref = &mut *d;
if lfs_pair_cmp(&d_ref.m.pair, &oldpair) == 0 {
d_ref.m = *dir;
if !core::ptr::eq(&d_ref.m.pair as *const _, pair as *const _) {
let attrs_slice = if attrcount > 0 && !attrs.is_null() {
core::slice::from_raw_parts(
attrs as *const crate::tag::lfs_mattr,
attrcount as usize,
)
} else {
&[]
};
for attr in attrs_slice.iter() {
let tag = attr.tag;
if u32::from(lfs_tag_type3(tag)) == LFS_TYPE_DELETE
&& d_ref.id == lfs_tag_id(tag)
&& d_ref.type_ != crate::lfs_type::lfs_type::LFS_TYPE_DIR as u8
{
d_ref.m.pair = [LFS_BLOCK_NULL, LFS_BLOCK_NULL];
} else if u32::from(lfs_tag_type3(tag)) == LFS_TYPE_DELETE
&& d_ref.id > lfs_tag_id(tag)
{
d_ref.id -= 1;
} else if u32::from(lfs_tag_type3(tag)) == LFS_TYPE_CREATE
&& d_ref.id >= lfs_tag_id(tag)
{
d_ref.id = d_ref.id.wrapping_add(1);
}
}
}
#[cfg(feature = "loop_limits")]
const MAX_COMMIT_DIR_ADVANCE: u32 = 2048;
#[cfg(feature = "loop_limits")]
let mut advance_iter: u32 = 0;
while d_ref.id >= d_ref.m.count && d_ref.m.split {
#[cfg(feature = "loop_limits")]
{
if advance_iter >= MAX_COMMIT_DIR_ADVANCE {
panic!(
"loop_limits: MAX_COMMIT_DIR_ADVANCE ({}) exceeded",
MAX_COMMIT_DIR_ADVANCE
);
}
advance_iter += 1;
}
if lfs_pair_cmp(&d_ref.m.tail, &(*lfs).root) != 0 {
d_ref.id -= d_ref.m.count;
}
let err = lfs_dir_fetch(lfs, &mut d_ref.m, &d_ref.m.tail);
if err != 0 {
return crate::lfs_pass_err!(err);
}
}
}
d = d_ref.next;
}
state
}
}
unsafe extern "C" fn lfs_dir_commit_commit_raw(
p: *mut core::ffi::c_void,
tag: lfs_tag_t,
buffer: *const core::ffi::c_void,
) -> i32 {
crate::lfs_trace!(
"commit_commit_raw: tag=0x{:08x} type1={} buffer={:p}",
tag,
crate::tag::lfs_tag_type1(tag),
buffer
);
if u32::from(crate::tag::lfs_tag_type1(tag)) == crate::lfs_type::lfs_type::LFS_TYPE_SUPERBLOCK {
let preview: [u8; 8] = if buffer.is_null() {
[0u8; 8]
} else {
unsafe { core::ptr::read(buffer as *const [u8; 8]) }
};
crate::lfs_trace!(
"commit_commit_raw SUPERBLOCK: buffer={:p} first 8 bytes: {:?}",
buffer,
preview
);
}
let commit_commit = &*(p as *const (*mut Lfs, *mut LfsCommit));
let (lfs, commit) = *commit_commit;
lfs_dir_commitattr(lfs, commit, tag, buffer)
}
pub fn lfs_dir_orphaningcommit(
lfs: *mut crate::fs::Lfs,
dir: *mut LfsMdir,
attrs: *const core::ffi::c_void,
attrcount: i32,
) -> i32 {
use crate::error::LFS_OK_ORPHANED;
use crate::util::{lfs_pair_cmp, lfs_pair_fromle32, lfs_pair_tole32};
unsafe {
let lpair = (*dir).pair;
let mut ldir = *dir;
let mut pdir = core::mem::zeroed();
let state =
lfs_dir_relocatingcommit(lfs, &mut ldir, &(*dir).pair, attrs, attrcount, &mut pdir);
if state < 0 {
return state;
}
if lfs_pair_cmp(&(*dir).pair, &lpair) == 0 {
*dir = ldir;
}
if state == crate::error::LFS_OK_DROPPED {
let err = lfs_dir_getgstate(lfs, dir, &mut (*lfs).gdelta);
if err != 0 {
return crate::lfs_pass_err!(err);
}
let plpair = [pdir.pair[0], pdir.pair[1]];
lfs_pair_tole32(&mut (*dir).tail);
let tail_attrs = [crate::tag::lfs_mattr {
tag: crate::tag::lfs_mktag(
crate::lfs_type::lfs_type::LFS_TYPE_TAIL + if (*dir).split { 1 } else { 0 },
0x3ff,
8,
),
buffer: (*dir).tail.as_ptr() as *const core::ffi::c_void,
}];
let tail_state = lfs_dir_relocatingcommit(
lfs,
&mut pdir,
&plpair,
tail_attrs.as_ptr() as *const _,
1,
core::ptr::null_mut(),
);
lfs_pair_fromle32(&mut (*dir).tail);
if tail_state < 0 {
return tail_state;
}
ldir = pdir;
}
let mut orphans = false;
let mut state = state;
let mut lpair = lpair;
#[cfg(feature = "loop_limits")]
const MAX_RELOCATE_ITER: u32 = 512;
#[cfg(feature = "loop_limits")]
let mut relocate_iter: u32 = 0;
while state == crate::error::LFS_OK_RELOCATED {
#[cfg(feature = "loop_limits")]
{
if relocate_iter >= MAX_RELOCATE_ITER {
panic!(
"loop_limits: MAX_RELOCATE_ITER ({}) exceeded",
MAX_RELOCATE_ITER
);
}
relocate_iter += 1;
}
state = 0;
if lfs_pair_cmp(&lpair, &(*lfs).root) == 0 {
(*lfs).root[0] = ldir.pair[0];
(*lfs).root[1] = ldir.pair[1];
}
{
let mut d = (*lfs).mlist;
while !d.is_null() {
if lfs_pair_cmp(&lpair, &(*d).m.pair) == 0 {
(*d).m.pair[0] = ldir.pair[0];
(*d).m.pair[1] = ldir.pair[1];
}
d = (*d).next;
}
}
let mut tag = crate::fs::parent::lfs_fs_parent(lfs, &lpair, &mut pdir);
if tag < 0 && tag != crate::error::LFS_ERR_NOENT {
return tag;
}
let hasparent = tag != crate::error::LFS_ERR_NOENT;
if hasparent {
let err = crate::fs::superblock::lfs_fs_preporphans(lfs, 1);
if err != 0 {
return crate::lfs_pass_err!(err);
}
let mut moveid: u16 = 0x3ff;
if crate::lfs_gstate::lfs_gstate_hasmovehere(&(*lfs).gstate, &pdir.pair) {
moveid = crate::tag::lfs_tag_id((*lfs).gstate.tag);
crate::fs::superblock::lfs_fs_prepmove(lfs, 0x3ff, core::ptr::null());
if moveid < crate::tag::lfs_tag_id(tag as u32) {
tag -= crate::tag::lfs_mktag(0, 1, 0) as i32;
}
}
let ppair = [pdir.pair[0], pdir.pair[1]];
lfs_pair_tole32(&mut ldir.pair);
let relocate_attrs = [
crate::tag::lfs_mattr {
tag: crate::tag::lfs_mktag_if(
moveid != 0x3ff,
crate::lfs_type::lfs_type::LFS_TYPE_DELETE,
moveid.into(),
0,
),
buffer: core::ptr::null(),
},
crate::tag::lfs_mattr {
tag: tag as lfs_tag_t,
buffer: ldir.pair.as_ptr() as *const core::ffi::c_void,
},
];
state = lfs_dir_relocatingcommit(
lfs,
&mut pdir,
&ppair,
relocate_attrs.as_ptr() as *const _,
2,
core::ptr::null_mut(),
);
lfs_pair_fromle32(&mut ldir.pair);
if state < 0 {
return state;
}
if state == crate::error::LFS_OK_RELOCATED {
lpair = ppair;
ldir = pdir;
orphans = true;
continue;
}
}
let err = crate::fs::parent::lfs_fs_pred(lfs, &lpair, &mut pdir);
if err != 0 && err != crate::error::LFS_ERR_NOENT {
return crate::lfs_pass_err!(err);
}
crate::lfs_assert!(!(hasparent && err == crate::error::LFS_ERR_NOENT));
if err != crate::error::LFS_ERR_NOENT {
if crate::lfs_gstate::lfs_gstate_hasorphans(&(*lfs).gstate) {
let deorphan_delta = if hasparent { -1 } else { 0 };
let err2 = crate::fs::superblock::lfs_fs_preporphans(lfs, deorphan_delta);
if err2 != 0 {
return err2;
}
}
let mut moveid: u16 = 0x3ff;
if crate::lfs_gstate::lfs_gstate_hasmovehere(&(*lfs).gstate, &pdir.pair) {
moveid = crate::tag::lfs_tag_id((*lfs).gstate.tag);
crate::fs::superblock::lfs_fs_prepmove(lfs, 0x3ff, core::ptr::null());
}
lpair[0] = pdir.pair[0];
lpair[1] = pdir.pair[1];
lfs_pair_tole32(&mut ldir.pair);
let tail_attrs = [
crate::tag::lfs_mattr {
tag: crate::tag::lfs_mktag_if(
moveid != 0x3ff,
crate::lfs_type::lfs_type::LFS_TYPE_DELETE,
moveid.into(),
0,
),
buffer: core::ptr::null(),
},
crate::tag::lfs_mattr {
tag: crate::tag::lfs_mktag(
crate::lfs_type::lfs_type::LFS_TYPE_TAIL
+ if pdir.split { 1 } else { 0 },
0x3ff,
8,
),
buffer: ldir.pair.as_ptr() as *const core::ffi::c_void,
},
];
state = lfs_dir_relocatingcommit(
lfs,
&mut pdir,
&lpair,
tail_attrs.as_ptr() as *const _,
2,
core::ptr::null_mut(),
);
lfs_pair_fromle32(&mut ldir.pair);
if state < 0 {
return state;
}
ldir = pdir;
}
}
if orphans {
LFS_OK_ORPHANED
} else {
0
}
}
}
pub fn lfs_dir_commit(
lfs: *mut crate::fs::Lfs,
dir: *mut LfsMdir,
attrs: *const core::ffi::c_void,
attrcount: i32,
) -> i32 {
use crate::error::LFS_OK_ORPHANED;
use crate::fs::superblock::lfs_fs_deorphan;
unsafe {
let orphans = lfs_dir_orphaningcommit(lfs, dir, attrs, attrcount);
if orphans < 0 {
return orphans;
}
if orphans == LFS_OK_ORPHANED {
let err = lfs_fs_deorphan(lfs, false);
if err != 0 {
return crate::lfs_pass_err!(err);
}
}
0
}
}