redb/tree_store/page_store/file_backend/
unix.rs

1// TODO once Rust's libc has flock implemented for WASI, this file needs to be revisited.
2// What needs to be changed is commented below.
3// See also: https://github.com/WebAssembly/wasi-filesystem/issues/2
4
5// Remove this line once wasi-libc has flock
6#![cfg_attr(target_os = "wasi", allow(unused_imports))]
7
8use crate::{DatabaseError, Result, StorageBackend};
9use std::fs::File;
10use std::io;
11
12#[cfg(unix)]
13use std::os::unix::{fs::FileExt, io::AsRawFd};
14
15#[cfg(target_os = "wasi")]
16use std::os::wasi::{fs::FileExt, io::AsRawFd};
17
18/// Stores a database as a file on-disk.
19#[derive(Debug)]
20pub struct FileBackend {
21    file: File,
22}
23
24impl FileBackend {
25    /// Creates a new backend which stores data to the given file.
26    // This is a no-op until we get flock in wasi-libc.
27    // Delete this function when we get flock.
28    #[cfg(target_os = "wasi")]
29    pub fn new(file: File) -> Result<Self, DatabaseError> {
30        Ok(Self { file })
31    }
32
33    /// Creates a new backend which stores data to the given file.
34    #[cfg(unix)] // remove this line when wasi-libc gets flock
35    pub fn new(file: File) -> Result<Self, DatabaseError> {
36        let fd = file.as_raw_fd();
37        let result = unsafe { libc::flock(fd, libc::LOCK_EX | libc::LOCK_NB) };
38        if result != 0 {
39            let err = io::Error::last_os_error();
40            if err.kind() == io::ErrorKind::WouldBlock {
41                Err(DatabaseError::DatabaseAlreadyOpen)
42            } else {
43                Err(err.into())
44            }
45        } else {
46            Ok(Self { file })
47        }
48    }
49}
50
51impl StorageBackend for FileBackend {
52    fn len(&self) -> Result<u64, io::Error> {
53        Ok(self.file.metadata()?.len())
54    }
55
56    fn read(&self, offset: u64, len: usize) -> Result<Vec<u8>, io::Error> {
57        let mut buffer = vec![0; len];
58        self.file.read_exact_at(&mut buffer, offset)?;
59        Ok(buffer)
60    }
61
62    fn set_len(&self, len: u64) -> Result<(), io::Error> {
63        self.file.set_len(len)
64    }
65
66    #[cfg(not(target_os = "macos"))]
67    fn sync_data(&self, _: bool) -> Result<(), io::Error> {
68        self.file.sync_data()
69    }
70
71    #[cfg(target_os = "macos")]
72    fn sync_data(&self, eventual: bool) -> Result<(), io::Error> {
73        if eventual {
74            let code = unsafe { libc::fcntl(self.file.as_raw_fd(), libc::F_BARRIERFSYNC) };
75            if code == -1 {
76                Err(io::Error::last_os_error())
77            } else {
78                Ok(())
79            }
80        } else {
81            self.file.sync_data()
82        }
83    }
84
85    fn write(&self, offset: u64, data: &[u8]) -> Result<(), io::Error> {
86        self.file.write_all_at(data, offset)
87    }
88}
89
90#[cfg(unix)] // remove this line when wasi-libc gets flock
91impl Drop for FileBackend {
92    fn drop(&mut self) {
93        unsafe { libc::flock(self.file.as_raw_fd(), libc::LOCK_UN) };
94    }
95}