1mod interface;
48
49#[cfg(any(target_os = "linux", target_os = "macos"))]
50mod posix;
51
52#[cfg(target_os = "windows")]
53mod windows;
54
55use crate::{
56 ack::{AckTicket, Completion, SyncTrigger, TEpoch},
57 error::{BindModule, ErrCode, FrozenError, FrozenResult},
58};
59use interface::FileInterface;
60use std::sync::{Arc, Condvar, Mutex, RwLock, atomic};
61
62#[cfg(any(target_os = "linux", target_os = "macos"))]
63pub(in crate::file) type PlatformFile = posix::POSIXFile;
64
65#[cfg(target_os = "windows")]
66pub(in crate::file) type PlatformFile = windows::WINFile;
67
68pub(in crate::file) mod err {
70 use super::{ErrCode, FrozenError, FrozenResult};
71
72 const ERRDOMAIN: u8 = 0x08;
74
75 pub const HCF: ErrCode = ErrCode::new(0x02, "hault and catch fire");
77
78 pub const UNK: ErrCode = ErrCode::new(0x04, "unknown error");
80
81 pub const NSP: ErrCode = ErrCode::new(0x08, "not enough space available on the storage device");
83
84 pub const SYN: ErrCode = ErrCode::new(0x0A, "failed to sync/flush data to storage device");
86
87 pub const WRT: ErrCode = ErrCode::new(0x0C, "missing permissions for write");
89
90 pub const RED: ErrCode = ErrCode::new(0x0E, "missing permissions for read");
92
93 pub const INV: ErrCode = ErrCode::new(0x10, "invalid path to file");
95
96 pub const CPT: ErrCode = ErrCode::new(0x12, "file is either invalid or corrupted");
98
99 pub const GRW: ErrCode = ErrCode::new(0x14, "unable to zero-extend file");
101
102 pub const LEX: ErrCode =
104 ErrCode::new(0x18, "failed to obtain lock, as no more locks available");
105
106 pub const PRM: ErrCode = ErrCode::new(0x1A, "missing permissions for IO");
108
109 pub const LCK: ErrCode =
111 ErrCode::new(0x1C, "failed to obtain exclusive lock as file may already opened");
112
113 pub const EXS: ErrCode = ErrCode::new(0x1E, "file already exists");
115
116 #[inline]
117 pub(in crate::file) fn raw_error<R, E: std::fmt::Display>(
118 code: ErrCode,
119 error: E,
120 ) -> FrozenResult<R> {
121 let err = FrozenError::unbound_raw(ERRDOMAIN, code, error);
122 Err(err)
123 }
124
125 #[inline]
126 pub(in crate::file) fn default_error<R>(code: ErrCode) -> FrozenResult<R> {
127 let err = FrozenError::unbound(ERRDOMAIN, code, "");
128 Err(err)
129 }
130
131 #[inline]
132 pub(in crate::file) fn make_error(code: ErrCode) -> FrozenError {
133 FrozenError::unbound(ERRDOMAIN, code, "")
134 }
135
136 #[inline]
137 pub(in crate::file) fn make_raw_error<E: std::fmt::Display>(
138 code: ErrCode,
139 error: E,
140 ) -> FrozenError {
141 FrozenError::unbound_raw(ERRDOMAIN, code, error)
142 }
143}
144
145#[cfg(any(target_os = "linux", target_os = "macos"))]
147pub type FileId = libc::c_int;
148
149#[cfg(target_os = "windows")]
156pub type FileId = isize;
157
158#[derive(Debug, Clone)]
180pub struct FileCfg {
181 pub module_id: u8,
183
184 pub path: std::path::PathBuf,
189
190 pub buffer_size: usize,
199
200 pub initial_available_buffers: usize,
204
205 pub sync_interval: Option<std::time::Duration>,
211
212 pub sequential_access_hint: bool,
217}
218
219#[derive(Debug)]
220struct FileInner {
221 cfg: FileCfg,
222 file: RwLock<Option<PlatformFile>>,
223 current_length: atomic::AtomicUsize,
224 completion: Arc<Completion>,
225 sync_condvar: Condvar,
226 sync_mutex: Mutex<bool>,
227 shutdown: atomic::AtomicBool,
228}
229
230impl SyncTrigger for FileInner {
231 fn trigger_sync(&self) -> FrozenResult<()> {
232 if self.cfg.sync_interval.is_some() {
233 {
234 let mut guard = self.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
235 *guard = true;
236 }
237
238 self.sync_condvar.notify_one();
239 Ok(())
240 } else {
241 self.sync_internal()
242 }
243 }
244}
245
246impl FileInner {
247 fn sync_internal(&self) -> FrozenResult<()> {
248 let guard = self.file.write().unwrap_or_else(|e| e.into_inner());
249 let file = match guard.as_ref() {
250 Some(f) => f,
251 None => return Ok(()),
252 };
253
254 let target_epoch = self.completion.read_current_epoch();
255 let durable_epoch = self.completion.read_durable_epoch();
256
257 if target_epoch == durable_epoch {
258 return Ok(());
259 }
260
261 match file.sync() {
262 Ok(()) => {
263 self.completion.mark_epoch_as_durable(target_epoch);
264 self.completion.del_err();
265 self.completion.notify_all_listeners();
266
267 Ok(())
268 }
269 Err(e) => {
270 let e = e.with_module(self.cfg.module_id);
271 self.completion.set_err(e.clone());
272 self.completion.notify_all_listeners();
273
274 Err(e)
275 }
276 }
277 }
278}
279
280#[derive(Debug)]
282pub struct File {
283 cfg: FileCfg,
284 inner: Arc<FileInner>,
285 bg_thread: Option<std::thread::JoinHandle<()>>,
286}
287
288unsafe impl Send for File {}
289unsafe impl Sync for File {}
290
291impl File {
292 fn from_platform_file(
293 cfg: FileCfg,
294 file: PlatformFile,
295 current_length: usize,
296 ) -> FrozenResult<Self> {
297 let completion = Arc::new(Completion::default());
298 let sync_interval = cfg.sync_interval;
299
300 let inner = Arc::new(FileInner {
301 cfg: cfg.clone(),
302 file: RwLock::new(Some(file)),
303 current_length: atomic::AtomicUsize::new(current_length),
304 completion: completion.clone(),
305 sync_condvar: Condvar::new(),
306 sync_mutex: Mutex::new(false),
307 shutdown: atomic::AtomicBool::new(false),
308 });
309
310 let weak_inner: std::sync::Weak<dyn SyncTrigger> =
311 Arc::downgrade(&inner) as std::sync::Weak<dyn SyncTrigger>;
312 completion.set_sync_trigger(weak_inner);
313
314 let bg_thread = if let Some(interval) = sync_interval {
315 let worker_inner = Arc::clone(&inner);
316 let handle =
317 std::thread::Builder::new().name("frozen-sync-worker".into()).spawn(move || {
318 let mut guard =
319 worker_inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
320
321 while !worker_inner.shutdown.load(atomic::Ordering::Acquire) {
322 if *guard {
323 *guard = false;
324 drop(guard);
325
326 let _ = worker_inner.sync_internal();
327 guard =
328 worker_inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
329
330 continue;
331 }
332
333 let (new_guard, _) = worker_inner
334 .sync_condvar
335 .wait_timeout(guard, interval)
336 .unwrap_or_else(|e| e.into_inner());
337
338 guard = new_guard;
339 if worker_inner.shutdown.load(atomic::Ordering::Acquire) {
340 break;
341 }
342
343 *guard = false;
344 drop(guard);
345
346 let _ = worker_inner.sync_internal();
347 guard = worker_inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
348 }
349 });
350
351 let handle = match handle {
352 Ok(h) => h,
353 Err(spawn_err) => {
354 let mut file_guard = inner.file.write().unwrap_or_else(|e| e.into_inner());
355 if let Some(f) = file_guard.take() {
356 let mut err = err::make_raw_error(err::HCF, spawn_err);
357 if let Err(close_err) = f.close() {
358 err.add_suppressed(close_err);
359 }
360
361 return Err(err.with_module(cfg.module_id));
362 }
363
364 return err::raw_error(err::HCF, spawn_err).with_module(cfg.module_id);
365 }
366 };
367
368 Some(handle)
369 } else {
370 None
371 };
372
373 Ok(Self { cfg, inner, bg_thread })
374 }
375
376 pub fn new(cfg: FileCfg) -> FrozenResult<Self> {
410 let mid = cfg.module_id;
411 if cfg.buffer_size == 0 || cfg.initial_available_buffers == 0 {
412 return err::default_error(err::INV).with_module(mid);
413 }
414
415 let init_len = match cfg.buffer_size.checked_mul(cfg.initial_available_buffers) {
416 Some(len) => len,
417 None => return err::default_error(err::INV).with_module(mid),
418 };
419
420 let file = if cfg.sequential_access_hint {
421 PlatformFile::create_sequential(&cfg.path).with_module(mid)?
422 } else {
423 PlatformFile::create(&cfg.path).with_module(mid)?
424 };
425 if let Err(mut e) = file.flock() {
426 if let Err(unlink_err) = file.unlink(&cfg.path) {
427 e.add_suppressed(unlink_err);
428 }
429
430 return Err(e.with_module(mid));
431 }
432
433 if let Err(mut e) = file.grow(0, init_len) {
434 if let Err(unlink_err) = file.unlink(&cfg.path) {
435 e.add_suppressed(unlink_err);
436 }
437
438 return Err(e.with_module(mid));
439 }
440
441 if let Err(mut e) = file.sync() {
442 if let Err(unlink_err) = file.unlink(&cfg.path) {
443 e.add_suppressed(unlink_err);
444 }
445
446 return Err(e.with_module(mid));
447 }
448
449 Self::from_platform_file(cfg, file, init_len)
450 }
451
452 pub fn open(cfg: FileCfg) -> FrozenResult<Self> {
487 let mid = cfg.module_id;
488
489 if cfg.buffer_size == 0 || cfg.initial_available_buffers == 0 {
490 return err::default_error(err::INV).with_module(mid);
491 }
492
493 let init_len = match cfg.buffer_size.checked_mul(cfg.initial_available_buffers) {
494 Some(len) => len,
495 None => return err::default_error(err::INV).with_module(mid),
496 };
497
498 let file = if cfg.sequential_access_hint {
499 PlatformFile::open_sequential(&cfg.path).with_module(mid)?
500 } else {
501 PlatformFile::open(&cfg.path).with_module(mid)?
502 };
503
504 if let Err(mut e) = file.flock() {
505 if let Err(close_err) = file.close() {
506 e.add_suppressed(close_err);
507 }
508
509 return Err(e.with_module(mid));
510 }
511
512 let curr_len = match file.length() {
513 Ok(len) => len,
514 Err(mut e) => {
515 if let Err(close_err) = file.close() {
516 e.add_suppressed(close_err);
517 }
518
519 return Err(e.with_module(mid));
520 }
521 };
522
523 if curr_len < init_len || curr_len % cfg.buffer_size != 0 {
524 let mut e = err::make_error(err::CPT);
525 if let Err(close_err) = file.close() {
526 e.add_suppressed(close_err);
527 }
528
529 return Err(e.with_module(mid));
530 }
531
532 Self::from_platform_file(cfg, file, curr_len)
533 }
534
535 pub fn open_or_create(cfg: FileCfg) -> FrozenResult<Self> {
573 let mid = cfg.module_id;
574
575 if cfg.buffer_size == 0 || cfg.initial_available_buffers == 0 {
576 return err::default_error(err::INV).with_module(mid);
577 }
578
579 let init_len = match cfg.buffer_size.checked_mul(cfg.initial_available_buffers) {
580 Some(len) => len,
581 None => return err::default_error(err::INV).with_module(mid),
582 };
583
584 let file = if cfg.sequential_access_hint {
585 PlatformFile::new_sequential(&cfg.path).with_module(mid)?
586 } else {
587 PlatformFile::new(&cfg.path).with_module(mid)?
588 };
589 if let Err(mut e) = file.flock() {
590 if let Err(close_err) = file.close() {
591 e.add_suppressed(close_err);
592 }
593
594 return Err(e.with_module(mid));
595 }
596
597 let curr_len = match file.length() {
598 Ok(len) => len,
599 Err(mut e) => {
600 if let Err(close_err) = file.close() {
601 e.add_suppressed(close_err);
602 }
603
604 return Err(e.with_module(mid));
605 }
606 };
607
608 if curr_len == 0 {
609 if let Err(mut e) = file.grow(0, init_len) {
610 if let Err(close_err) = file.close() {
611 e.add_suppressed(close_err);
612 }
613
614 return Err(e.with_module(mid));
615 }
616
617 if let Err(mut e) = file.sync() {
618 if let Err(close_err) = file.close() {
619 e.add_suppressed(close_err);
620 }
621
622 return Err(e.with_module(mid));
623 }
624
625 Self::from_platform_file(cfg, file, init_len)
626 } else {
627 if curr_len < init_len || curr_len % cfg.buffer_size != 0 {
628 let mut e = err::make_error(err::CPT);
629 if let Err(close_err) = file.close() {
630 e.add_suppressed(close_err);
631 }
632
633 return Err(e.with_module(mid));
634 }
635
636 Self::from_platform_file(cfg, file, curr_len)
637 }
638 }
639
640 #[inline(always)]
682 pub fn read(&self, buf: &mut [u8], index: usize) -> FrozenResult<()> {
683 let mid = self.cfg.module_id;
684 if buf.is_empty() {
685 return Ok(());
686 }
687
688 if buf.len() % self.cfg.buffer_size != 0 {
689 return err::default_error(err::INV).with_module(mid);
690 }
691
692 let offset = match index.checked_mul(self.cfg.buffer_size) {
693 Some(off) => off,
694 None => return err::default_error(err::INV).with_module(mid),
695 };
696
697 if offset.checked_add(buf.len()).is_none_or(|end| end > self.length()) {
698 return err::default_error(err::HCF).with_module(mid);
699 }
700
701 let guard = self.inner.file.read().unwrap_or_else(|e| e.into_inner());
702 let file = match guard.as_ref() {
703 Some(f) => f,
704 None => return err::default_error(err::INV).with_module(mid),
705 };
706
707 file.pread(buf, offset).with_module(mid)
708 }
709
710 #[inline(always)]
747 pub fn write(&self, buf: &[u8], index: usize) -> FrozenResult<AckTicket> {
748 let mid = self.cfg.module_id;
749 if buf.is_empty() {
750 let current = self.inner.completion.read_current_epoch();
751 return Ok(AckTicket::new(current, self.inner.completion.clone()));
752 }
753
754 if buf.len() % self.cfg.buffer_size != 0 {
755 return err::default_error(err::INV).with_module(mid);
756 }
757
758 let offset = match index.checked_mul(self.cfg.buffer_size) {
759 Some(off) => off,
760 None => return err::default_error(err::INV).with_module(mid),
761 };
762
763 if offset.checked_add(buf.len()).is_none_or(|end| end > self.length()) {
764 return err::default_error(err::HCF).with_module(mid);
765 }
766
767 let guard = self.inner.file.read().unwrap_or_else(|e| e.into_inner());
768 let file = match guard.as_ref() {
769 Some(f) => f,
770 None => return err::default_error(err::INV).with_module(mid),
771 };
772
773 file.pwrite(buf, offset).with_module(mid)?;
774
775 let epoch = self.inner.completion.increment_current_epoch();
776 Ok(AckTicket::new(epoch, self.inner.completion.clone()))
777 }
778
779 pub fn grow(&self, count: usize) -> FrozenResult<()> {
807 let mid = self.cfg.module_id;
808 if count == 0 {
809 return Ok(());
810 }
811
812 let len_to_add = match self.cfg.buffer_size.checked_mul(count) {
813 Some(len) => len,
814 None => return err::default_error(err::GRW).with_module(mid),
815 };
816
817 let guard = self.inner.file.write().unwrap_or_else(|e| e.into_inner());
818 let file = match guard.as_ref() {
819 Some(f) => f,
820 None => return err::default_error(err::INV).with_module(mid),
821 };
822
823 let curr_len = self.inner.current_length.load(atomic::Ordering::Acquire);
824 file.grow(curr_len, len_to_add).with_module(mid)?;
825
826 self.inner.current_length.fetch_add(len_to_add, atomic::Ordering::Release);
827 self.inner.completion.increment_current_epoch();
828
829 Ok(())
830 }
831
832 #[inline]
857 pub fn sync(&self) -> FrozenResult<()> {
858 self.inner.sync_internal()
859 }
860
861 #[cfg(target_os = "linux")]
889 pub fn sync_range(&self, index: usize, count: usize) -> FrozenResult<()> {
890 if count == 0 {
891 return Ok(());
892 }
893
894 let mid = self.cfg.module_id;
895 let offset = match index.checked_mul(self.cfg.buffer_size) {
896 Some(off) => off,
897 None => return err::default_error(err::INV).with_module(mid),
898 };
899 let len_to_sync = match count.checked_mul(self.cfg.buffer_size) {
900 Some(len) => len,
901 None => return err::default_error(err::INV).with_module(mid),
902 };
903
904 let guard = self.inner.file.read().unwrap_or_else(|e| e.into_inner());
905 let file = match guard.as_ref() {
906 Some(f) => f,
907 None => return err::default_error(err::INV).with_module(mid),
908 };
909
910 file.sync_range(offset, len_to_sync).with_module(mid)
911 }
912
913 #[inline]
934 pub fn total_buffers(&self) -> FrozenResult<usize> {
935 let curr_len = self.length();
936 let buffer_size = self.cfg.buffer_size;
937
938 if crate::hints::unlikely(curr_len % buffer_size != 0) {
939 return err::default_error(err::CPT).with_module(self.cfg.module_id);
940 }
941
942 Ok(curr_len / buffer_size)
943 }
944
945 #[inline]
967 pub fn cfg(&self) -> &FileCfg {
968 &self.cfg
969 }
970
971 #[inline]
992 pub fn length(&self) -> usize {
993 self.inner.current_length.load(atomic::Ordering::Acquire)
994 }
995
996 #[inline]
1020 pub fn current_epoch(&self) -> TEpoch {
1021 self.inner.completion.read_current_epoch()
1022 }
1023
1024 #[inline]
1049 pub fn durable_epoch(&self) -> TEpoch {
1050 self.inner.completion.read_durable_epoch()
1051 }
1052
1053 #[inline]
1075 pub fn completion(&self) -> &Arc<Completion> {
1076 &self.inner.completion
1077 }
1078
1079 #[inline]
1104 pub fn exists(&self) -> FrozenResult<bool> {
1105 PlatformFile::exists(&self.cfg.path).with_module(self.cfg.module_id)
1106 }
1107
1108 pub fn delete(mut self) -> FrozenResult<()> {
1143 let mid = self.cfg.module_id;
1144 self.inner.shutdown.store(true, atomic::Ordering::Release);
1145 {
1146 let mut guard = self.inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
1147 *guard = true;
1148 }
1149 self.inner.sync_condvar.notify_all();
1150
1151 if let Some(handle) = self.bg_thread.take() {
1152 let _ = handle.join();
1153 }
1154
1155 let mut guard = self.inner.file.write().unwrap_or_else(|e| e.into_inner());
1156 let file = match guard.take() {
1157 Some(f) => f,
1158 None => return err::default_error(err::INV).with_module(mid),
1159 };
1160
1161 file.unlink(&self.cfg.path).with_module(mid)
1162 }
1163
1164 #[inline]
1188 pub fn fd(&self) -> FileId {
1189 let guard = self.inner.file.read().unwrap_or_else(|e| e.into_inner());
1190 match guard.as_ref() {
1191 Some(f) => f.fd(),
1192 None => PlatformFile::CLOSED_ID,
1193 }
1194 }
1195}
1196
1197impl Drop for File {
1198 fn drop(&mut self) {
1199 self.inner.shutdown.store(true, atomic::Ordering::Release);
1200 {
1201 let mut guard = self.inner.sync_mutex.lock().unwrap_or_else(|e| e.into_inner());
1202 *guard = true;
1203 }
1204 self.inner.sync_condvar.notify_all();
1205
1206 if let Some(handle) = self.bg_thread.take() {
1207 let _ = handle.join();
1208 }
1209
1210 let is_closed = {
1211 let guard = self.inner.file.read().unwrap_or_else(|e| e.into_inner());
1212 match guard.as_ref() {
1213 Some(f) => f.is_closed(),
1214 None => true,
1215 }
1216 };
1217
1218 if is_closed {
1219 return;
1220 }
1221
1222 let _ = self.inner.sync_internal();
1223 }
1224}
1225
1226#[cfg(test)]
1227mod tests {
1228 use super::*;
1229 use std::sync::{Arc, Barrier};
1230
1231 const MID: u8 = 0;
1232 const BUFFER_SIZE: usize = 0x10;
1233 const INIT_BUFFERS: usize = 4;
1234
1235 fn tmp_path() -> (tempfile::TempDir, FileCfg) {
1236 let dir = tempfile::tempdir().unwrap();
1237 let path = dir.path().join("tmp_file");
1238 let cfg = FileCfg {
1239 module_id: MID,
1240 path,
1241 buffer_size: BUFFER_SIZE,
1242 initial_available_buffers: INIT_BUFFERS,
1243 sync_interval: None,
1244 sequential_access_hint: false,
1245 };
1246
1247 (dir, cfg)
1248 }
1249
1250 mod file_new {
1251 use super::*;
1252
1253 #[test]
1254 fn ok_new_creates_file_with_correct_length() {
1255 let (_dir, cfg) = tmp_path();
1256 let file = File::new(cfg.clone()).unwrap();
1257
1258 let expected_len = BUFFER_SIZE * INIT_BUFFERS;
1259 assert_eq!(file.length(), expected_len);
1260 assert_eq!(file.cfg().buffer_size, BUFFER_SIZE);
1261 assert_ne!(file.fd(), PlatformFile::CLOSED_ID);
1262 assert!(cfg.path.exists());
1263 }
1264
1265 #[test]
1266 fn err_new_when_already_exists() {
1267 let (_dir, cfg) = tmp_path();
1268 let file = File::new(cfg.clone()).unwrap();
1269 drop(file);
1270
1271 let err = File::new(cfg).unwrap_err();
1272 assert_eq!(err.reason, err::EXS.reason);
1273 }
1274
1275 #[test]
1276 fn err_new_invalid_cfg() {
1277 let (_dir, mut cfg) = tmp_path();
1278
1279 cfg.buffer_size = 0;
1280 let err = File::new(cfg.clone()).unwrap_err();
1281 assert_eq!(err.reason, err::INV.reason);
1282
1283 cfg.buffer_size = BUFFER_SIZE;
1284 cfg.initial_available_buffers = 0;
1285 let err = File::new(cfg).unwrap_err();
1286 assert_eq!(err.reason, err::INV.reason);
1287 }
1288
1289 #[test]
1290 fn ok_new_sequential_access_hint() {
1291 let (_dir, mut cfg) = tmp_path();
1292 cfg.sequential_access_hint = true;
1293 let file = File::new(cfg.clone()).unwrap();
1294 assert_eq!(file.length(), BUFFER_SIZE * INIT_BUFFERS);
1295 assert!(file.cfg().sequential_access_hint);
1296 drop(file);
1297
1298 let opened = File::open(cfg.clone()).unwrap();
1299 assert_eq!(opened.length(), BUFFER_SIZE * INIT_BUFFERS);
1300 drop(opened);
1301
1302 let open_or_created = File::open_or_create(cfg).unwrap();
1303 assert_eq!(open_or_created.length(), BUFFER_SIZE * INIT_BUFFERS);
1304 }
1305
1306 #[test]
1307 fn err_new_cfg_overflow() {
1308 let (_dir, mut cfg) = tmp_path();
1309 cfg.buffer_size = usize::MAX;
1310 cfg.initial_available_buffers = 2;
1311 let err = File::new(cfg).unwrap_err();
1312 assert_eq!(err.reason, err::INV.reason);
1313 }
1314
1315 #[test]
1316 fn err_new_missing_parent_dir() {
1317 let (_dir, mut cfg) = tmp_path();
1318 cfg.path = cfg.path.join("missing/sub/dir/file.db");
1319
1320 let err = File::new(cfg).unwrap_err();
1321 assert_eq!(err.reason, err::INV.reason);
1322 }
1323
1324 #[test]
1325 fn err_new_cleans_up_on_grow_failure() {
1326 let (_dir, mut cfg) = tmp_path();
1327 cfg.buffer_size = 1 << 0x3F;
1330 cfg.initial_available_buffers = 1;
1331
1332 let err = File::new(cfg.clone()).unwrap_err();
1333 assert_eq!(err.reason, err::GRW.reason);
1334
1335 assert!(!cfg.path.exists());
1337
1338 cfg.buffer_size = BUFFER_SIZE;
1340 cfg.initial_available_buffers = INIT_BUFFERS;
1341 let file = File::new(cfg);
1342 assert!(file.is_ok());
1343 }
1344 }
1345
1346 mod file_open {
1347 use super::*;
1348
1349 #[test]
1350 fn ok_open_existing_valid_file() {
1351 let (_dir, cfg) = tmp_path();
1352
1353 {
1354 let file = File::new(cfg.clone()).unwrap();
1355 assert_eq!(file.length(), BUFFER_SIZE * INIT_BUFFERS);
1356 }
1357
1358 let file = File::open(cfg).unwrap();
1359 assert_eq!(file.length(), BUFFER_SIZE * INIT_BUFFERS);
1360 }
1361
1362 #[test]
1363 fn err_open_missing_file() {
1364 let (_dir, cfg) = tmp_path();
1365
1366 let err = File::open(cfg).unwrap_err();
1367 assert_eq!(err.reason, err::INV.reason);
1368 }
1369
1370 #[test]
1371 fn err_open_invalid_cfg() {
1372 let (_dir, mut cfg) = tmp_path();
1373
1374 cfg.buffer_size = 0;
1375 let err = File::open(cfg.clone()).unwrap_err();
1376 assert_eq!(err.reason, err::INV.reason);
1377
1378 cfg.buffer_size = BUFFER_SIZE;
1379 cfg.initial_available_buffers = 0;
1380 let err = File::open(cfg).unwrap_err();
1381 assert_eq!(err.reason, err::INV.reason);
1382 }
1383
1384 #[test]
1385 fn err_open_cfg_overflow() {
1386 let (_dir, mut cfg) = tmp_path();
1387 cfg.buffer_size = usize::MAX;
1388 cfg.initial_available_buffers = 2;
1389 let err = File::open(cfg).unwrap_err();
1390 assert_eq!(err.reason, err::INV.reason);
1391 }
1392
1393 #[test]
1394 fn err_open_when_file_smaller_than_init_len() {
1395 let (_dir, cfg) = tmp_path();
1396
1397 std::fs::write(&cfg.path, []).unwrap();
1398
1399 let err = File::open(cfg).unwrap_err();
1400 assert_eq!(err.reason, err::CPT.reason);
1401 }
1402
1403 #[test]
1404 fn err_open_when_file_not_buffer_multiple() {
1405 let (_dir, cfg) = tmp_path();
1406
1407 let non_aligned_len = (BUFFER_SIZE * INIT_BUFFERS) + 3;
1408 std::fs::write(&cfg.path, vec![0u8; non_aligned_len]).unwrap();
1409
1410 let err = File::open(cfg).unwrap_err();
1411 assert_eq!(err.reason, err::CPT.reason);
1412 }
1413 }
1414
1415 mod file_open_or_create {
1416 use super::*;
1417
1418 #[test]
1419 fn ok_creates_when_missing() {
1420 let (_dir, cfg) = tmp_path();
1421 assert!(!cfg.path.exists());
1422
1423 let file = File::open_or_create(cfg.clone()).unwrap();
1424 assert!(cfg.path.exists());
1425 assert_eq!(file.length(), BUFFER_SIZE * INIT_BUFFERS);
1426 assert!(file.exists().unwrap());
1427 }
1428
1429 #[test]
1430 fn ok_opens_when_existing() {
1431 let (_dir, cfg) = tmp_path();
1432 let file = File::new(cfg.clone()).unwrap();
1433 let data = [0x5Au8; BUFFER_SIZE];
1434 file.write(&data, 0).unwrap();
1435 file.sync().unwrap();
1436 drop(file);
1437
1438 let opened = File::open_or_create(cfg.clone()).unwrap();
1439 assert_eq!(opened.length(), BUFFER_SIZE * INIT_BUFFERS);
1440 let mut buf = [0u8; BUFFER_SIZE];
1441 opened.read(&mut buf, 0).unwrap();
1442 assert_eq!(buf, data);
1443 }
1444
1445 #[test]
1446 fn err_open_or_create_invalid_cfg() {
1447 let (_dir, mut cfg) = tmp_path();
1448 cfg.buffer_size = 0;
1449 let err = File::open_or_create(cfg.clone()).unwrap_err();
1450 assert_eq!(err.reason, err::INV.reason);
1451
1452 cfg.buffer_size = BUFFER_SIZE;
1453 cfg.initial_available_buffers = 0;
1454 let err = File::open_or_create(cfg).unwrap_err();
1455 assert_eq!(err.reason, err::INV.reason);
1456 }
1457
1458 #[test]
1459 fn err_open_or_create_cfg_overflow() {
1460 let (_dir, mut cfg) = tmp_path();
1461 cfg.buffer_size = usize::MAX;
1462 cfg.initial_available_buffers = 2;
1463 let err = File::open_or_create(cfg).unwrap_err();
1464 assert_eq!(err.reason, err::INV.reason);
1465 }
1466
1467 #[test]
1468 fn err_open_or_create_corrupt_existing() {
1469 let (_dir, cfg) = tmp_path();
1470 let non_aligned = (BUFFER_SIZE * INIT_BUFFERS) + 1;
1471 std::fs::write(&cfg.path, vec![0u8; non_aligned]).unwrap();
1472
1473 let err = File::open_or_create(cfg).unwrap_err();
1474 assert_eq!(err.reason, err::CPT.reason);
1475 }
1476
1477 #[test]
1478 fn err_open_or_create_when_locked() {
1479 let (_dir, cfg) = tmp_path();
1480 let file = File::open_or_create(cfg.clone()).unwrap();
1481
1482 let err = File::open_or_create(cfg).unwrap_err();
1483 assert_eq!(err.reason, err::LCK.reason);
1484 drop(file);
1485 }
1486 }
1487
1488 mod file_toctou_and_concurrency {
1489 use super::*;
1490
1491 #[test]
1492 fn err_concurrent_new_exact_same_path() {
1493 let (_dir, cfg) = tmp_path();
1494 let barrier = Arc::new(Barrier::new(2));
1495
1496 let t1 = {
1497 let cfg = cfg.clone();
1498 let b = barrier.clone();
1499 std::thread::spawn(move || {
1500 b.wait();
1501 File::new(cfg)
1502 })
1503 };
1504
1505 let t2 = {
1506 let b = barrier;
1507 std::thread::spawn(move || {
1508 b.wait();
1509 File::new(cfg)
1510 })
1511 };
1512
1513 let r1 = t1.join().unwrap();
1514 let r2 = t2.join().unwrap();
1515
1516 match (r1, r2) {
1520 (Ok(f), Err(e)) | (Err(e), Ok(f)) => {
1521 assert_eq!(e.reason, err::EXS.reason);
1522 assert_eq!(f.length(), BUFFER_SIZE * INIT_BUFFERS);
1523 }
1524 (Ok(_), Ok(_)) => {
1525 panic!("TOCTOU violation: both concurrent File::new succeeded on same path!");
1526 }
1527 (Err(e1), Err(e2)) => {
1528 panic!("Unexpected failure of both threads: {:?}, {:?}", e1, e2);
1529 }
1530 }
1531 }
1532
1533 #[test]
1534 fn err_concurrent_new_and_open() {
1535 let (_dir, cfg) = tmp_path();
1536 let barrier = Arc::new(Barrier::new(2));
1537
1538 let t_new = {
1539 let cfg = cfg.clone();
1540 let b = barrier.clone();
1541 std::thread::spawn(move || {
1542 b.wait();
1543 File::new(cfg)
1544 })
1545 };
1546
1547 let t_open = {
1548 let b = barrier;
1549 std::thread::spawn(move || {
1550 b.wait();
1551 File::open(cfg)
1552 })
1553 };
1554
1555 let r_new = t_new.join().unwrap();
1556 let r_open = t_open.join().unwrap();
1557
1558 let _file = r_new.expect("File::new should succeed");
1559
1560 if let Err(e) = r_open {
1563 assert!(
1564 e.reason == err::INV.reason
1565 || e.reason == err::LCK.reason
1566 || e.reason == err::CPT.reason,
1567 "unexpected error: {:?}",
1568 e
1569 );
1570 }
1571 }
1572
1573 #[test]
1574 fn err_concurrent_open_same_file() {
1575 let (_dir, cfg) = tmp_path();
1576
1577 {
1578 let file = File::new(cfg.clone()).unwrap();
1579 assert_eq!(file.length(), BUFFER_SIZE * INIT_BUFFERS);
1580 }
1581
1582 let barrier = Arc::new(Barrier::new(2));
1583
1584 let t1 = {
1585 let cfg = cfg.clone();
1586 let b = barrier.clone();
1587 std::thread::spawn(move || {
1588 b.wait();
1589 File::open(cfg)
1590 })
1591 };
1592
1593 let t2 = {
1594 let b = barrier;
1595 std::thread::spawn(move || {
1596 b.wait();
1597 File::open(cfg)
1598 })
1599 };
1600
1601 let r1 = t1.join().unwrap();
1602 let r2 = t2.join().unwrap();
1603
1604 match (r1, r2) {
1608 (Ok(_), Err(e)) | (Err(e), Ok(_)) => {
1609 assert_eq!(e.reason, err::LCK.reason, "error must be LCK: {:?}", e);
1610 }
1611 (Ok(_), Ok(_)) => {
1612 panic!(
1613 "Locking violation: both concurrent File::open acquired exclusive lock!"
1614 );
1615 }
1616 (Err(e1), Err(e2)) => {
1617 panic!("Unexpected failure of both threads: {:?}, {:?}", e1, e2);
1618 }
1619 }
1620 }
1621
1622 #[test]
1623 fn err_new_while_file_open() {
1624 let (_dir, cfg) = tmp_path();
1625 let file = File::new(cfg.clone()).unwrap();
1626
1627 let err = File::new(cfg).unwrap_err();
1628 assert_eq!(err.reason, err::EXS.reason);
1629
1630 drop(file);
1631 }
1632
1633 #[test]
1634 fn ok_reopen_after_drop() {
1635 let (_dir, cfg) = tmp_path();
1636 let file = File::new(cfg.clone()).unwrap();
1637 drop(file);
1638
1639 let reopened = File::open(cfg).unwrap();
1640 assert_eq!(reopened.length(), BUFFER_SIZE * INIT_BUFFERS);
1641 }
1642
1643 #[test]
1644 fn err_concurrent_open_during_and_after_delete() {
1645 let (_dir, cfg) = tmp_path();
1646 let file = File::new(cfg.clone()).unwrap();
1647 file.grow(2).unwrap();
1648
1649 let running = Arc::new(atomic::AtomicBool::new(true));
1650
1651 let opener = {
1653 let cfg = cfg.clone();
1654 let running = running.clone();
1655 std::thread::spawn(move || {
1656 let mut attempts = 0;
1657 while running.load(atomic::Ordering::Relaxed) || attempts < 0x32 {
1658 attempts += 1;
1659 let res = File::open(cfg.clone());
1660 if let Err(err) = res {
1666 assert!(
1667 err.reason == err::LCK.reason
1668 || err.reason == err::INV.reason
1669 || err.reason == err::PRM.reason,
1670 "Unexpected error reason: {:?}",
1671 err
1672 );
1673 }
1674 std::thread::yield_now();
1675 }
1676 })
1677 };
1678
1679 std::thread::sleep(std::time::Duration::from_millis(5));
1681 file.delete().unwrap();
1682 running.store(false, atomic::Ordering::Relaxed);
1683
1684 opener.join().unwrap();
1685
1686 let err = File::open(cfg).unwrap_err();
1688 assert_eq!(err.reason, err::INV.reason);
1689 }
1690
1691 #[test]
1692 fn ok_concurrent_new_fails_before_delete_and_succeeds_after() {
1693 let (_dir, cfg) = tmp_path();
1694 let file = File::new(cfg.clone()).unwrap();
1695 file.grow(2).unwrap();
1696
1697 let deleted = Arc::new(atomic::AtomicBool::new(false));
1698
1699 let creator = {
1700 let cfg = cfg.clone();
1701 let deleted = deleted.clone();
1702 std::thread::spawn(move || {
1703 let mut created = None;
1704 while created.is_none() {
1707 match File::new(cfg.clone()) {
1708 Ok(f) => {
1709 assert!(
1710 deleted.load(atomic::Ordering::Acquire),
1711 "File::new succeeded before file.delete completed!"
1712 );
1713 created = Some(f);
1714 }
1715 Err(e) => {
1716 assert!(
1717 e.reason == err::EXS.reason || e.reason == err::PRM.reason,
1718 "Unexpected error reason during race: {:?}",
1719 e
1720 );
1721 std::thread::yield_now();
1722 }
1723 }
1724 }
1725 created.unwrap()
1726 })
1727 };
1728
1729 std::thread::sleep(std::time::Duration::from_millis(0x0A));
1730 deleted.store(true, atomic::Ordering::Release);
1731 file.delete().unwrap();
1732
1733 let new_file = creator.join().unwrap();
1734 assert!(new_file.exists().unwrap());
1735 new_file.delete().unwrap();
1736 }
1737
1738 #[test]
1739 fn ok_concurrent_open_or_create_same_file() {
1740 let (_dir, cfg) = tmp_path();
1741 let num_threads = 8;
1742 let barrier = Arc::new(Barrier::new(num_threads));
1743 let mut handles = vec![];
1744
1745 for _ in 0..num_threads {
1746 let c = cfg.clone();
1747 let b = barrier.clone();
1748 handles.push(std::thread::spawn(move || {
1749 b.wait();
1750 File::open_or_create(c)
1751 }));
1752 }
1753
1754 let mut opened_files = vec![];
1755 let mut lock_failures = 0;
1756
1757 for h in handles {
1758 match h.join().unwrap() {
1759 Ok(f) => {
1760 assert_eq!(f.length(), BUFFER_SIZE * INIT_BUFFERS);
1761 opened_files.push(f);
1762 }
1763 Err(e) => {
1764 assert_eq!(e.reason, err::LCK.reason, "error must be LCK: {:?}", e);
1765 lock_failures += 1;
1766 }
1767 }
1768 }
1769
1770 assert_eq!(opened_files.len(), 1);
1772 assert_eq!(lock_failures, num_threads - 1);
1773 assert!(cfg.path.exists());
1774 }
1775
1776 #[test]
1777 fn ok_file_send_and_sync() {
1778 fn assert_send_sync<T: Send + Sync>() {}
1779 assert_send_sync::<File>();
1780 }
1781 }
1782
1783 mod file_write_read {
1784 use super::*;
1785
1786 #[test]
1787 fn ok_single_buffer_write_read() {
1788 let (_dir, cfg) = tmp_path();
1789 let file = File::new(cfg).unwrap();
1790
1791 let data = [0xABu8; BUFFER_SIZE];
1792 file.write(&data, 2).unwrap();
1793 file.sync().unwrap();
1794
1795 let mut buf = [0u8; BUFFER_SIZE];
1796 file.read(&mut buf, 2).unwrap();
1797 assert_eq!(buf, data);
1798 }
1799
1800 #[test]
1801 fn ok_multi_buffer_write_read() {
1802 let (_dir, cfg) = tmp_path();
1803 let file = File::new(cfg).unwrap();
1804
1805 let multi_buf_data = [0x77u8; BUFFER_SIZE * 2];
1806 file.write(&multi_buf_data, 1).unwrap();
1807 file.sync().unwrap();
1808
1809 let mut read_buf = [0u8; BUFFER_SIZE * 2];
1810 file.read(&mut read_buf, 1).unwrap();
1811 assert_eq!(read_buf, multi_buf_data);
1812
1813 let mut chunk1 = [0u8; BUFFER_SIZE];
1814 let mut chunk2 = [0u8; BUFFER_SIZE];
1815 file.read(&mut chunk1, 1).unwrap();
1816 file.read(&mut chunk2, 2).unwrap();
1817 assert_eq!(chunk1, [0x77u8; BUFFER_SIZE]);
1818 assert_eq!(chunk2, [0x77u8; BUFFER_SIZE]);
1819 }
1820
1821 #[test]
1822 fn ok_empty_buffer_noop() {
1823 let (_dir, cfg) = tmp_path();
1824 let file = File::new(cfg).unwrap();
1825
1826 assert!(file.write(&[], 0).is_ok());
1827 assert!(file.read(&mut [], 0).is_ok());
1828 }
1829
1830 #[test]
1831 fn err_write_unaligned_buffer_size() {
1832 let (_dir, cfg) = tmp_path();
1833 let file = File::new(cfg).unwrap();
1834
1835 let data = [0u8; BUFFER_SIZE - 1];
1836 let err = file.write(&data, 0).unwrap_err();
1837 assert_eq!(err.reason, err::INV.reason);
1838
1839 let data_multi_unaligned = [0u8; (BUFFER_SIZE * 2) + 1];
1840 let err = file.write(&data_multi_unaligned, 0).unwrap_err();
1841 assert_eq!(err.reason, err::INV.reason);
1842 }
1843
1844 #[test]
1845 fn err_read_unaligned_buffer_size() {
1846 let (_dir, cfg) = tmp_path();
1847 let file = File::new(cfg).unwrap();
1848
1849 let mut buf = [0u8; BUFFER_SIZE - 1];
1850 let err = file.read(&mut buf, 0).unwrap_err();
1851 assert_eq!(err.reason, err::INV.reason);
1852
1853 let mut buf_multi_unaligned = [0u8; (BUFFER_SIZE * 2) + 3];
1854 let err = file.read(&mut buf_multi_unaligned, 0).unwrap_err();
1855 assert_eq!(err.reason, err::INV.reason);
1856 }
1857
1858 #[test]
1859 fn err_read_past_eof() {
1860 let (_dir, cfg) = tmp_path();
1861 let file = File::new(cfg).unwrap();
1862
1863 let mut buf = [0u8; BUFFER_SIZE];
1864 let err = file.read(&mut buf, INIT_BUFFERS).unwrap_err();
1865 assert_eq!(err.reason, err::HCF.reason);
1866
1867 let mut multi_buf = [0u8; BUFFER_SIZE * 2];
1868 let err = file.read(&mut multi_buf, INIT_BUFFERS - 1).unwrap_err();
1869 assert_eq!(err.reason, err::HCF.reason);
1870 }
1871
1872 #[test]
1873 fn err_write_past_eof() {
1874 let (_dir, cfg) = tmp_path();
1875 let file = File::new(cfg).unwrap();
1876
1877 let data = [1u8; BUFFER_SIZE];
1878 let err = file.write(&data, INIT_BUFFERS).unwrap_err();
1879 assert_eq!(err.reason, err::HCF.reason);
1880
1881 let multi_data = [1u8; BUFFER_SIZE * 2];
1882 let err = file.write(&multi_data, INIT_BUFFERS - 1).unwrap_err();
1883 assert_eq!(err.reason, err::HCF.reason);
1884 }
1885
1886 #[test]
1887 fn ok_concurrent_non_overlapping_writes() {
1888 let (_dir, mut cfg) = tmp_path();
1889 cfg.initial_available_buffers = 0x10;
1890 let file = Arc::new(File::new(cfg).unwrap());
1891
1892 let mut handles = vec![];
1893 for i in 0..4 {
1894 let f = file.clone();
1895 handles.push(std::thread::spawn(move || {
1896 let data = [i as u8 + 0x0A; BUFFER_SIZE * 2];
1897 f.write(&data, i * 2).unwrap();
1898 }));
1899 }
1900
1901 for h in handles {
1902 h.join().unwrap();
1903 }
1904
1905 file.sync().unwrap();
1906
1907 for i in 0..4 {
1908 let mut buf = [0u8; BUFFER_SIZE * 2];
1909 file.read(&mut buf, i * 2).unwrap();
1910 assert_eq!(buf, [i as u8 + 0x0A; BUFFER_SIZE * 2]);
1911 }
1912 }
1913
1914 #[test]
1915 fn ok_concurrent_readers() {
1916 let (_dir, cfg) = tmp_path();
1917 let file = Arc::new(File::new(cfg).unwrap());
1918
1919 let data = [0x42u8; BUFFER_SIZE * 2];
1920 file.write(&data, 0).unwrap();
1921 file.sync().unwrap();
1922
1923 let mut handles = vec![];
1924 for _ in 0..8 {
1925 let f = file.clone();
1926 let expected = data;
1927 handles.push(std::thread::spawn(move || {
1928 let mut buf = [0u8; BUFFER_SIZE * 2];
1929 f.read(&mut buf, 0).unwrap();
1930 assert_eq!(buf, expected);
1931 }));
1932 }
1933
1934 for h in handles {
1935 h.join().unwrap();
1936 }
1937 }
1938 }
1939
1940 mod file_grow {
1941 use super::*;
1942
1943 #[test]
1944 fn ok_grow_updates_length() {
1945 let (_dir, cfg) = tmp_path();
1946 let file = File::new(cfg).unwrap();
1947 assert_eq!(file.length(), BUFFER_SIZE * INIT_BUFFERS);
1948 assert_eq!(file.total_buffers().unwrap(), INIT_BUFFERS);
1949
1950 file.grow(0x20).unwrap();
1951 assert_eq!(file.length(), BUFFER_SIZE * (INIT_BUFFERS + 0x20));
1952 assert_eq!(file.total_buffers().unwrap(), INIT_BUFFERS + 0x20);
1953 }
1954
1955 #[test]
1956 fn ok_grow_zero_count() {
1957 let (_dir, cfg) = tmp_path();
1958 let file = File::new(cfg).unwrap();
1959 file.grow(0).unwrap();
1960 assert_eq!(file.length(), BUFFER_SIZE * INIT_BUFFERS);
1961 }
1962
1963 #[test]
1964 fn ok_grow_and_multi_buffer_write_read() {
1965 let (_dir, cfg) = tmp_path();
1966 let file = File::new(cfg).unwrap();
1967
1968 file.grow(4).unwrap();
1969 let new_index = INIT_BUFFERS;
1970
1971 let data = [0x55u8; BUFFER_SIZE * 3];
1972 file.write(&data, new_index).unwrap();
1973 file.sync().unwrap();
1974
1975 let mut buf = [0u8; BUFFER_SIZE * 3];
1976 file.read(&mut buf, new_index).unwrap();
1977 assert_eq!(buf, data);
1978 }
1979
1980 #[test]
1981 fn ok_concurrent_grow_and_write() {
1982 let (_dir, cfg) = tmp_path();
1983 let file = Arc::new(File::new(cfg).unwrap());
1984
1985 let writer = {
1986 let f = file.clone();
1987 std::thread::spawn(move || {
1988 for i in 0..INIT_BUFFERS {
1989 let data = [i as u8; BUFFER_SIZE];
1990 f.write(&data, i).unwrap();
1991 }
1992 })
1993 };
1994
1995 let chunks_to_grow = 0x20;
1996 let grower = {
1997 let f = file.clone();
1998 std::thread::spawn(move || {
1999 f.grow(chunks_to_grow).unwrap();
2000 })
2001 };
2002
2003 writer.join().unwrap();
2004 grower.join().unwrap();
2005
2006 file.sync().unwrap();
2007 assert_eq!(file.length(), BUFFER_SIZE * (INIT_BUFFERS + chunks_to_grow));
2008
2009 for i in 0..INIT_BUFFERS {
2010 let mut buf = [0u8; BUFFER_SIZE];
2011 file.read(&mut buf, i).unwrap();
2012 assert_eq!(buf, [i as u8; BUFFER_SIZE]);
2013 }
2014 }
2015
2016 #[test]
2017 #[cfg(target_os = "linux")]
2018 fn ok_sync_range() {
2019 let (_dir, cfg) = tmp_path();
2020 let file = File::new(cfg).unwrap();
2021
2022 let data = [0x33u8; BUFFER_SIZE * 2];
2023 file.write(&data, 0).unwrap();
2024 file.sync_range(0, 2).unwrap();
2025 file.sync().unwrap();
2026
2027 let mut buf = [0u8; BUFFER_SIZE * 2];
2028 file.read(&mut buf, 0).unwrap();
2029 assert_eq!(buf, data);
2030 }
2031
2032 #[test]
2033 #[cfg(target_os = "linux")]
2034 fn ok_sync_range_zero_count() {
2035 let (_dir, cfg) = tmp_path();
2036 let file = File::new(cfg).unwrap();
2037 assert!(file.sync_range(0, 0).is_ok());
2038 assert!(file.sync_range(5, 0).is_ok());
2039 }
2040
2041 #[test]
2042 #[cfg(target_os = "linux")]
2043 fn err_sync_range_index_overflow() {
2044 let (_dir, cfg) = tmp_path();
2045 let file = File::new(cfg).unwrap();
2046 let err1 = file.sync_range(usize::MAX, 1).unwrap_err();
2047 assert_eq!(err1.reason, err::INV.reason);
2048
2049 let err2 = file.sync_range(0, usize::MAX).unwrap_err();
2050 assert_eq!(err2.reason, err::INV.reason);
2051 }
2052
2053 #[test]
2054 fn ok_concurrent_multiple_growers() {
2055 let (_dir, cfg) = tmp_path();
2056 let file = Arc::new(File::new(cfg).unwrap());
2057 let num_threads = 4;
2058 let chunks_per_thread = 4;
2059 let barrier = Arc::new(Barrier::new(num_threads));
2060 let mut handles = vec![];
2061
2062 for _ in 0..num_threads {
2063 let f = file.clone();
2064 let b = barrier.clone();
2065 handles.push(std::thread::spawn(move || {
2066 b.wait();
2067 f.grow(chunks_per_thread).unwrap();
2068 }));
2069 }
2070
2071 for h in handles {
2072 h.join().unwrap();
2073 }
2074
2075 let expected_total_chunks = INIT_BUFFERS + (num_threads * chunks_per_thread);
2076 assert_eq!(file.length(), BUFFER_SIZE * expected_total_chunks);
2077 assert_eq!(file.total_buffers().unwrap(), expected_total_chunks);
2078 assert_eq!(file.current_epoch(), num_threads as TEpoch);
2079 }
2080
2081 #[test]
2082 fn ok_concurrent_readers_during_grow() {
2083 let (_dir, cfg) = tmp_path();
2084 let file = Arc::new(File::new(cfg).unwrap());
2085 let initial_data = [0x5Au8; BUFFER_SIZE];
2086 file.write(&initial_data, 0).unwrap();
2087 file.sync().unwrap();
2088
2089 let running = Arc::new(atomic::AtomicBool::new(true));
2090 let num_readers = 4;
2091 let mut readers = vec![];
2092
2093 for _ in 0..num_readers {
2094 let f = file.clone();
2095 let r = running.clone();
2096 let expected = initial_data;
2097 readers.push(std::thread::spawn(move || {
2098 let mut buf = [0u8; BUFFER_SIZE];
2099 while r.load(atomic::Ordering::Relaxed) {
2100 f.read(&mut buf, 0).unwrap();
2101 assert_eq!(buf, expected);
2102 std::thread::yield_now();
2103 }
2104 }));
2105 }
2106
2107 for _ in 0..5 {
2109 file.grow(2).unwrap();
2110 std::thread::sleep(std::time::Duration::from_millis(5));
2111 }
2112
2113 running.store(false, atomic::Ordering::Relaxed);
2114 for r in readers {
2115 r.join().unwrap();
2116 }
2117
2118 assert_eq!(file.length(), BUFFER_SIZE * (INIT_BUFFERS + 0x0A));
2119 assert_eq!(file.total_buffers().unwrap(), INIT_BUFFERS + 0x0A);
2120 }
2121
2122 #[test]
2123 fn err_total_buffers_unaligned_file() {
2124 let (_dir, cfg) = tmp_path();
2125 let file = File::new(cfg).unwrap();
2126 assert_eq!(file.total_buffers().unwrap(), INIT_BUFFERS);
2127
2128 file.inner.current_length.fetch_add(1, atomic::Ordering::SeqCst);
2130
2131 let err = file.total_buffers().unwrap_err();
2132 assert_eq!(err.reason, err::CPT.reason);
2133 }
2134 }
2135
2136 mod file_delete_exists {
2137 use super::*;
2138
2139 #[test]
2140 fn ok_exists_true_on_created_file() {
2141 let (_dir, cfg) = tmp_path();
2142 let file = File::new(cfg.clone()).unwrap();
2143
2144 assert!(file.exists().unwrap());
2145 assert!(cfg.path.exists());
2146 }
2147
2148 #[test]
2149 fn ok_delete_removes_file() {
2150 let (_dir, cfg) = tmp_path();
2151 let file = File::new(cfg.clone()).unwrap();
2152 let path = cfg.path.clone();
2153
2154 assert!(file.exists().unwrap());
2155 file.delete().unwrap();
2156
2157 assert!(!path.exists());
2158 assert!(File::new(cfg).is_ok());
2159 }
2160
2161 #[test]
2162 fn ok_open_and_delete() {
2163 let (_dir, cfg) = tmp_path();
2164 let file = File::new(cfg.clone()).unwrap();
2165 drop(file);
2166
2167 let opened = File::open(cfg.clone()).unwrap();
2168 assert!(opened.exists().unwrap());
2169 opened.delete().unwrap();
2170
2171 assert!(!cfg.path.exists());
2172 }
2173
2174 #[test]
2175 fn err_open_after_delete_grown_file() {
2176 let (_dir, cfg) = tmp_path();
2177 let file = File::new(cfg.clone()).unwrap();
2178
2179 file.grow(2).unwrap();
2180 assert_eq!(file.length(), (INIT_BUFFERS + 2) * BUFFER_SIZE);
2181
2182 file.delete().unwrap();
2183 assert!(!cfg.path.exists());
2184
2185 let err = File::open(cfg).unwrap_err();
2186 assert_eq!(err.reason, err::INV.reason);
2187 }
2188
2189 #[test]
2190 #[cfg(unix)]
2191 fn ok_exists_false_after_external_removal() {
2192 let (_dir, cfg) = tmp_path();
2193 let file = File::new(cfg.clone()).unwrap();
2194 assert!(file.exists().unwrap());
2195
2196 std::fs::remove_file(&cfg.path).unwrap();
2197 assert!(!file.exists().unwrap());
2198 }
2199
2200 #[test]
2201 #[cfg(windows)]
2202 fn ok_exists_false_after_external_removal() {
2203 let (_dir, cfg) = tmp_path();
2204 let file = File::new(cfg.clone()).unwrap();
2205 assert!(file.exists().unwrap());
2206 drop(file);
2207
2208 std::fs::remove_file(&cfg.path).unwrap();
2209 assert!(!PlatformFile::exists(&cfg.path).unwrap());
2210 }
2211
2212 #[test]
2213 #[cfg(unix)]
2214 fn err_delete_when_unlinked_externally() {
2215 let (_dir, cfg) = tmp_path();
2216 let file = File::new(cfg.clone()).unwrap();
2217
2218 std::fs::remove_file(&cfg.path).unwrap();
2219 let err = file.delete().unwrap_err();
2220 assert_eq!(err.reason, err::INV.reason);
2221 }
2222
2223 #[test]
2224 #[cfg(windows)]
2225 fn err_delete_when_unlinked_externally() {
2226 let (_dir, cfg) = tmp_path();
2227 let file = File::new(cfg.clone()).unwrap();
2228
2229 std::fs::remove_file(&cfg.path).unwrap();
2235 let err = file.delete().unwrap_err();
2236 assert!(
2237 err.reason == err::INV.reason || err.reason == err::PRM.reason,
2238 "Unexpected error reason: {:?}",
2239 err
2240 );
2241 }
2242
2243 #[test]
2244 #[cfg(unix)]
2245 fn err_delete_permission_denied() {
2246 use std::os::unix::fs::PermissionsExt;
2247
2248 let dir = tempfile::tempdir().unwrap();
2249 let sub_dir = dir.path().join("readonly_dir");
2250 std::fs::create_dir(&sub_dir).unwrap();
2251
2252 let path = sub_dir.join("victim.db");
2253 let cfg = FileCfg {
2254 module_id: MID,
2255 path,
2256 buffer_size: BUFFER_SIZE,
2257 initial_available_buffers: INIT_BUFFERS,
2258 sync_interval: None,
2259 sequential_access_hint: false,
2260 };
2261
2262 let file = File::new(cfg).unwrap();
2263 std::fs::set_permissions(&sub_dir, std::fs::Permissions::from_mode(0o555)).unwrap();
2264
2265 let err = file.delete().unwrap_err();
2266 std::fs::set_permissions(&sub_dir, std::fs::Permissions::from_mode(0o755)).unwrap();
2267
2268 assert_eq!(err.reason, err::PRM.reason);
2269 }
2270
2271 #[test]
2272 #[cfg(windows)]
2273 fn err_delete_permission_denied() {
2274 let (_dir, cfg) = tmp_path();
2275 let file = File::new(cfg.clone()).unwrap();
2276
2277 let orig_perms = std::fs::metadata(&cfg.path).unwrap().permissions();
2278 let mut read_only = orig_perms.clone();
2279 read_only.set_readonly(true);
2280 std::fs::set_permissions(&cfg.path, read_only).unwrap();
2281
2282 let err = file.delete().unwrap_err();
2283
2284 let _ = std::fs::set_permissions(&cfg.path, orig_perms);
2285
2286 assert_eq!(err.reason, err::PRM.reason);
2287 }
2288
2289 #[test]
2290 fn ok_delete_and_recreate_cycle() {
2291 let (_dir, cfg) = tmp_path();
2292
2293 for i in 0..3 {
2294 let file = File::new(cfg.clone()).unwrap();
2295 let data = [i as u8; BUFFER_SIZE];
2296 file.write(&data, 0).unwrap();
2297 assert!(file.exists().unwrap());
2298
2299 file.delete().unwrap();
2300 assert!(!cfg.path.exists());
2301 }
2302 }
2303 }
2304
2305 mod file_drop {
2306 use super::*;
2307
2308 #[test]
2309 fn ok_drop_persists_written_data() {
2310 let (_dir, cfg) = tmp_path();
2311 let data = [0x7Au8; BUFFER_SIZE * 2];
2312
2313 {
2314 let file = File::new(cfg.clone()).unwrap();
2315 file.write(&data, 0).unwrap();
2316 drop(file);
2317 }
2318
2319 {
2320 let opened = File::open(cfg).unwrap();
2321 let mut buf = [0u8; BUFFER_SIZE * 2];
2322 opened.read(&mut buf, 0).unwrap();
2323 assert_eq!(buf, data);
2324 }
2325 }
2326
2327 #[test]
2328 fn ok_drop_releases_exclusive_lock() {
2329 let (_dir, cfg) = tmp_path();
2330
2331 let file = File::new(cfg.clone()).unwrap();
2332 let err = File::open(cfg.clone()).unwrap_err();
2333 assert_eq!(err.reason, err::LCK.reason);
2334
2335 drop(file);
2336
2337 let opened = File::open(cfg);
2338 assert!(opened.is_ok());
2339 }
2340 }
2341
2342 mod file_sync_and_durability {
2343 use super::*;
2344 use std::time::{Duration, Instant};
2345
2346 #[test]
2347 fn ok_manual_sync_advances_epoch_and_tickets() {
2348 let (_dir, cfg) = tmp_path();
2349 let file = File::new(cfg).unwrap();
2350 let data = [0x55u8; BUFFER_SIZE];
2351
2352 assert_eq!(file.current_epoch(), 0);
2353 assert_eq!(file.durable_epoch(), 0);
2354
2355 let t1 = file.write(&data, 0).unwrap();
2356 assert_eq!(t1.epoch(), 1);
2357 assert!(!t1.is_durable());
2358 assert_eq!(file.current_epoch(), 1);
2359 assert_eq!(file.durable_epoch(), 0);
2360
2361 let t2 = file.write(&data, 1).unwrap();
2362 assert_eq!(t2.epoch(), 2);
2363 assert!(!t2.is_durable());
2364 assert_eq!(file.current_epoch(), 2);
2365 assert_eq!(file.durable_epoch(), 0);
2366
2367 file.sync().unwrap();
2368 assert!(t1.is_durable());
2369 assert!(t2.is_durable());
2370 assert_eq!(file.durable_epoch(), 2);
2371 }
2372
2373 #[test]
2374 fn ok_ticket_force_without_background_thread() {
2375 let (_dir, cfg) = tmp_path();
2376 let file = File::new(cfg).unwrap();
2377 let data = [0xAAu8; BUFFER_SIZE];
2378
2379 let ticket = file.write(&data, 0).unwrap();
2380 assert!(!ticket.is_durable());
2381
2382 let durable = ticket.force().unwrap();
2383 assert_eq!(durable, ticket.epoch());
2384 assert!(ticket.is_durable());
2385 assert_eq!(file.durable_epoch(), ticket.epoch());
2386 }
2387
2388 #[test]
2389 fn ok_background_sync_advances_epoch() {
2390 let (_dir, mut cfg) = tmp_path();
2391 cfg.sync_interval = Some(Duration::from_millis(0x19));
2392
2393 let file = File::new(cfg).unwrap();
2394 let data = [0x33u8; BUFFER_SIZE];
2395
2396 let ticket = file.write(&data, 0).unwrap();
2397 let epoch = ticket.wait().unwrap();
2398 assert_eq!(epoch, ticket.epoch());
2399 assert!(ticket.is_durable());
2400 assert!(file.durable_epoch() >= ticket.epoch());
2401 }
2402
2403 #[test]
2404 fn ok_ticket_force_with_background_thread() {
2405 let (_dir, mut cfg) = tmp_path();
2406 cfg.sync_interval = Some(Duration::from_secs(0x0A));
2408
2409 let file = File::new(cfg).unwrap();
2410 let data = [0x44u8; BUFFER_SIZE];
2411
2412 let ticket = file.write(&data, 0).unwrap();
2413 assert!(!ticket.is_durable());
2414
2415 let start = Instant::now();
2416 let epoch = ticket.force().unwrap();
2417 let elapsed = start.elapsed();
2418
2419 assert_eq!(epoch, ticket.epoch());
2420 assert!(ticket.is_durable());
2421 assert!(elapsed < Duration::from_secs(2));
2422 }
2423
2424 #[test]
2425 fn ok_drop_with_background_thread_persists_data() {
2426 let (_dir, mut cfg) = tmp_path();
2427 cfg.sync_interval = Some(Duration::from_secs(0x1E));
2428 let data = [0x88u8; BUFFER_SIZE];
2429
2430 {
2431 let file = File::new(cfg.clone()).unwrap();
2432 let _ticket = file.write(&data, 0).unwrap();
2433 drop(file);
2435 }
2436
2437 {
2438 let opened = File::open(cfg).unwrap();
2439 let mut buf = [0u8; BUFFER_SIZE];
2440 opened.read(&mut buf, 0).unwrap();
2441 assert_eq!(buf, data);
2442 }
2443 }
2444
2445 #[test]
2446 fn ok_delete_with_background_thread() {
2447 let (_dir, mut cfg) = tmp_path();
2448 cfg.sync_interval = Some(Duration::from_millis(0x14));
2449
2450 let file = File::new(cfg.clone()).unwrap();
2451 let data = [0x99u8; BUFFER_SIZE];
2452 let _ = file.write(&data, 0).unwrap();
2453
2454 assert!(file.exists().unwrap());
2455 file.delete().unwrap();
2456 assert!(!cfg.path.exists());
2457 }
2458
2459 #[test]
2460 fn ok_concurrent_writes_distinct_epochs() {
2461 let (_dir, cfg) = tmp_path();
2462 let file = Arc::new(File::new(cfg).unwrap());
2463 let num_threads = 4;
2464 let mut handles = Vec::new();
2465
2466 for i in 0..num_threads {
2467 let f = file.clone();
2468 handles.push(std::thread::spawn(move || {
2469 let data = [i as u8; BUFFER_SIZE];
2470 f.write(&data, i).unwrap()
2471 }));
2472 }
2473
2474 let mut epochs = Vec::new();
2475 for h in handles {
2476 let ticket = h.join().unwrap();
2477 epochs.push(ticket.epoch());
2478 }
2479
2480 epochs.sort();
2481 epochs.dedup();
2482 assert_eq!(epochs.len(), num_threads);
2483 }
2484
2485 #[test]
2486 fn err_ticket_wait_when_file_deleted() {
2487 let (_dir, cfg) = tmp_path();
2488 let file = File::new(cfg).unwrap();
2489 let data = [0x11u8; BUFFER_SIZE];
2490 let ticket = file.write(&data, 0).unwrap();
2491
2492 file.delete().unwrap();
2493
2494 let res = ticket.force();
2496 assert!(res.is_err());
2497 }
2498
2499 #[test]
2500 fn err_ticket_wait_surfaces_sync_failure() {
2501 let (_dir, cfg) = tmp_path();
2502 let file = File::new(cfg).unwrap();
2503 let data = [0x22u8; BUFFER_SIZE];
2504 let ticket = file.write(&data, 0).unwrap();
2505
2506 let sync_err = err::make_error(err::SYN);
2508 file.completion().set_err(sync_err.clone());
2509 file.completion().notify_all_listeners();
2510
2511 let err_wait = ticket.wait().unwrap_err();
2512 assert_eq!(err_wait.reason, err::SYN.reason);
2513
2514 let err_force = ticket.force().unwrap_err();
2515 assert_eq!(err_force.reason, err::SYN.reason);
2516 }
2517
2518 #[test]
2519 fn ok_concurrent_ticket_force_storm() {
2520 let (_dir, mut cfg) = tmp_path();
2521 cfg.initial_available_buffers = 0x20;
2522 cfg.sync_interval = Some(Duration::from_millis(0x0A));
2523 let file = Arc::new(File::new(cfg).unwrap());
2524 let num_threads = 0x10;
2525 let barrier = Arc::new(Barrier::new(num_threads));
2526 let mut handles = Vec::new();
2527
2528 for i in 0..num_threads {
2529 let f = file.clone();
2530 let b = barrier.clone();
2531 handles.push(std::thread::spawn(move || {
2532 b.wait();
2533 let data = [i as u8; BUFFER_SIZE];
2534 let ticket = f.write(&data, i).unwrap();
2535 let durable = ticket.force().unwrap();
2536 assert_eq!(durable, ticket.epoch());
2537 assert!(ticket.is_durable());
2538 }));
2539 }
2540
2541 for h in handles {
2542 h.join().unwrap();
2543 }
2544
2545 assert!(file.durable_epoch() >= num_threads as TEpoch);
2546 }
2547 }
2548
2549 mod module_binding {
2550 use super::*;
2551
2552 #[test]
2553 fn ok_multiple_files_distinct_module_ids() {
2554 let dir = tempfile::tempdir().unwrap();
2555 let cfg1 = FileCfg {
2556 module_id: 0x11,
2557 path: dir.path().join("file1.db"),
2558 buffer_size: BUFFER_SIZE,
2559 initial_available_buffers: INIT_BUFFERS,
2560 sync_interval: None,
2561 sequential_access_hint: false,
2562 };
2563 let cfg2 = FileCfg {
2564 module_id: 0x22,
2565 path: dir.path().join("file2.db"),
2566 buffer_size: BUFFER_SIZE,
2567 initial_available_buffers: INIT_BUFFERS,
2568 sync_interval: None,
2569 sequential_access_hint: false,
2570 };
2571
2572 let file1 = File::new(cfg1.clone()).unwrap();
2573 let file2 = File::new(cfg2.clone()).unwrap();
2574
2575 let mut invalid_buf = [0u8; BUFFER_SIZE - 1];
2577 let err1 = file1.read(&mut invalid_buf, 0).unwrap_err();
2578 assert_eq!(err1.module, 0x11);
2579 assert_eq!(err1.reason, err::INV.reason);
2580
2581 let err1_oob = file1.read(&mut [0u8; BUFFER_SIZE], 0x3E7).unwrap_err();
2582 assert_eq!(err1_oob.module, 0x11);
2583 assert_eq!(err1_oob.reason, err::HCF.reason);
2584
2585 let err2 = file2.read(&mut invalid_buf, 0).unwrap_err();
2587 assert_eq!(err2.module, 0x22);
2588 assert_eq!(err2.reason, err::INV.reason);
2589
2590 let err2_oob = file2.write(&[0u8; BUFFER_SIZE], 0x3E7).unwrap_err();
2591 assert_eq!(err2_oob.module, 0x22);
2592 assert_eq!(err2_oob.reason, err::HCF.reason);
2593
2594 let err1_exs = File::new(cfg1).unwrap_err();
2596 assert_eq!(err1_exs.module, 0x11);
2597 assert_eq!(err1_exs.reason, err::EXS.reason);
2598
2599 let err2_exs = File::new(cfg2).unwrap_err();
2600 assert_eq!(err2_exs.module, 0x22);
2601 assert_eq!(err2_exs.reason, err::EXS.reason);
2602 }
2603 }
2604}