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 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 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 #[cfg(target_os = "windows")]
63 {
64 file.sync_all()?;
65 }
66 };
67
68 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 #[cfg(not(unix))]
85 file.allocate(capacity as u64)?;
86 #[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}