use std::fs::{self, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
pub fn truncate_at(path: &Path, offset: u64) {
let f = OpenOptions::new()
.write(true)
.open(path)
.unwrap_or_else(|e| panic!("truncate_at: open {}: {e}", path.display()));
f.set_len(offset)
.unwrap_or_else(|e| panic!("truncate_at: set_len {} -> {offset}: {e}", path.display()));
}
pub fn flip_bit(path: &Path, byte_offset: u64, bit: u8) {
assert!(bit < 8, "flip_bit: bit {bit} is not in 0..8");
let mut f = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.unwrap_or_else(|e| panic!("flip_bit: open {}: {e}", path.display()));
let len = f.metadata().expect("flip_bit: metadata").len();
assert!(
byte_offset < len,
"flip_bit: offset {byte_offset} is past the end of {} (len {len})",
path.display(),
);
f.seek(SeekFrom::Start(byte_offset))
.expect("flip_bit: seek");
let mut b = [0u8; 1];
f.read_exact(&mut b).expect("flip_bit: read");
b[0] ^= 1 << bit;
f.seek(SeekFrom::Start(byte_offset))
.expect("flip_bit: seek");
f.write_all(&b).expect("flip_bit: write");
f.sync_all().expect("flip_bit: sync");
}
pub fn overwrite_range(path: &Path, offset: u64, bytes: &[u8]) {
let mut f = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.unwrap_or_else(|e| panic!("overwrite_range: open {}: {e}", path.display()));
f.seek(SeekFrom::Start(offset))
.expect("overwrite_range: seek");
f.write_all(bytes).expect("overwrite_range: write");
f.sync_all().expect("overwrite_range: sync");
}
pub fn garbage(seed: u64, len: usize) -> Vec<u8> {
let mut s = seed | 1;
(0..len)
.map(|_| {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
(s >> 24) as u8
})
.collect()
}
pub fn file_len(path: &Path) -> u64 {
fs::metadata(path).map(|m| m.len()).unwrap_or(0)
}
fn sorted_with_ext(dir: &Path, ext: &str) -> Vec<PathBuf> {
let mut out: Vec<PathBuf> = match fs::read_dir(dir) {
Ok(rd) => rd
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.extension().and_then(|s| s.to_str()) == Some(ext))
.collect(),
Err(_) => Vec::new(),
};
out.sort();
out
}
pub fn find_wals(db_dir: &Path) -> Vec<PathBuf> {
sorted_with_ext(&db_dir.join("wal"), "log")
}
pub fn newest_wal(db_dir: &Path) -> PathBuf {
find_wals(db_dir)
.pop()
.unwrap_or_else(|| panic!("no WAL under {}", db_dir.join("wal").display()))
}
pub fn find_manifest(db_dir: &Path) -> PathBuf {
db_dir.join("MANIFEST")
}
pub fn find_ssts(db_dir: &Path) -> Vec<PathBuf> {
sorted_with_ext(&db_dir.join("sst"), "sst")
}
pub fn first_sst(db_dir: &Path) -> PathBuf {
let mut all = find_ssts(db_dir);
if all.is_empty() {
panic!("no SSTable under {}", db_dir.join("sst").display());
}
all.remove(0)
}
pub fn copy_tree(from: &Path, to: &Path) {
fs::create_dir_all(to).expect("copy_tree: create_dir_all");
for entry in fs::read_dir(from).expect("copy_tree: read_dir") {
let entry = entry.expect("copy_tree: dir entry");
let src = entry.path();
let dst = to.join(entry.file_name());
if entry.file_type().expect("copy_tree: file_type").is_dir() {
copy_tree(&src, &dst);
} else {
fs::copy(&src, &dst).expect("copy_tree: copy");
}
}
}