bitcasky_common/
lib.rs

1use std::{
2    fs::{File, OpenOptions},
3    io::{Seek, SeekFrom},
4    path::{Path, PathBuf},
5    ptr,
6};
7
8use formatter::BitcaskyFormatter;
9use fs::FileType;
10#[cfg(not(unix))]
11use fs4::FileExt;
12use storage_id::StorageId;
13
14use crate::formatter::FILE_HEADER_SIZE;
15
16pub mod clock;
17pub mod formatter;
18pub mod fs;
19pub mod options;
20pub mod storage_id;
21pub mod tombstone;
22
23#[cfg(test)]
24#[macro_use]
25extern crate assert_matches;
26
27pub fn create_file<P: AsRef<Path>>(
28    base_dir: P,
29    file_type: FileType,
30    storage_id: Option<StorageId>,
31    formatter: &BitcaskyFormatter,
32    init_data_file_capacity: usize,
33) -> std::io::Result<File> {
34    // Round capacity down to the nearest 8-byte alignment, since the
35    // data storage would not be able to take advantage of the space.
36    let capacity = std::cmp::max(FILE_HEADER_SIZE, init_data_file_capacity) & !7;
37
38    let path = file_type.get_path(&base_dir, storage_id);
39    let file_name = path
40        .file_name()
41        .and_then(|file_name| file_name.to_str())
42        .expect("File name required");
43
44    let tmp_file_path = match path.parent() {
45        Some(parent) => parent.join(format!("tmp-{file_name}")),
46        None => PathBuf::from(format!("tmp-{file_name}")),
47    };
48
49    {
50        // Prepare properly formatted file in a temporary file, so in case of failure it won't be corrupted.
51        let mut file = OpenOptions::new()
52            .read(true)
53            .write(true)
54            .create(true)
55            .open(&tmp_file_path)?;
56
57        fs::truncate_file(&mut file, capacity)?;
58
59        formatter::initialize_new_file(&mut file, formatter.version())?;
60
61        // Manually sync each file in Windows since sync-ing cannot be done for the whole directory.
62        #[cfg(target_os = "windows")]
63        {
64            file.sync_all()?;
65        }
66    };
67
68    // File renames are atomic, so we can safely rename the temporary file to the final file.
69    std::fs::rename(&tmp_file_path, &path)?;
70
71    let mut file = OpenOptions::new()
72        .read(true)
73        .write(true)
74        .create(false)
75        .open(&path)?;
76    file.seek(SeekFrom::Start(FILE_HEADER_SIZE as u64))?;
77
78    Ok(file)
79}
80
81pub fn resize_file(file: &File, required_capacity: usize) -> std::io::Result<usize> {
82    let capacity = required_capacity & !7;
83    // fs4 provides some cross-platform bindings which help for Windows.
84    #[cfg(not(unix))]
85    file.allocate(capacity as u64)?;
86    // For all unix systems WAL can just use ftruncate directly
87    #[cfg(unix)]
88    {
89        rustix::fs::ftruncate(file, capacity as u64)?;
90    }
91    Ok(capacity)
92}
93
94pub fn copy_memory(src: &[u8], dst: &mut [u8]) {
95    let len_src = src.len();
96    assert!(dst.len() >= len_src);
97    unsafe {
98        ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), len_src);
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use std::io::{Read, Write};
105
106    use crate::formatter::get_formatter_from_file;
107
108    use super::*;
109
110    use bytes::{Buf, BufMut, Bytes, BytesMut};
111    use test_log::test;
112    use utilities::common::get_temporary_directory_path;
113
114    #[test]
115    fn test_create_file() {
116        let dir = get_temporary_directory_path();
117        let storage_id = 1;
118        let formatter = BitcaskyFormatter::default();
119        let mut file =
120            create_file(&dir, FileType::DataFile, Some(storage_id), &formatter, 100).unwrap();
121
122        let mut bs = BytesMut::with_capacity(4);
123        bs.put_u32(101);
124
125        file.write_all(&bs.freeze()).unwrap();
126        file.flush().unwrap();
127
128        let mut reopen_file = fs::open_file(dir, FileType::DataFile, Some(storage_id))
129            .unwrap()
130            .file;
131        assert_eq!(
132            formatter,
133            get_formatter_from_file(&mut reopen_file).unwrap()
134        );
135
136        reopen_file
137            .seek(SeekFrom::Start(FILE_HEADER_SIZE as u64))
138            .unwrap();
139        let mut buf = vec![0; 4];
140        reopen_file.read_exact(&mut buf).unwrap();
141        assert_eq!(101, Bytes::from(buf).get_u32());
142    }
143}