redb/tree_store/page_store/file_backend/
optimized.rs1use crate::{DatabaseError, Result, StorageBackend};
2use std::fs::{File, TryLockError};
3use std::io;
4
5#[cfg(feature = "logging")]
6use log::warn;
7
8#[cfg(unix)]
9use std::os::unix::fs::FileExt;
10
11#[cfg(windows)]
12use std::os::windows::fs::FileExt;
13
14#[derive(Debug)]
16pub struct FileBackend {
17 lock_supported: bool,
18 file: File,
19}
20
21impl FileBackend {
22 pub fn new(file: File) -> Result<Self, DatabaseError> {
24 Self::new_internal(file, false)
25 }
26
27 pub(crate) fn new_internal(file: File, read_only: bool) -> Result<Self, DatabaseError> {
28 let result = if read_only {
29 file.try_lock_shared()
30 } else {
31 file.try_lock()
32 };
33
34 match result {
35 Ok(()) => Ok(Self {
36 file,
37 lock_supported: true,
38 }),
39 Err(TryLockError::WouldBlock) => Err(DatabaseError::DatabaseAlreadyOpen),
40 Err(TryLockError::Error(err)) if err.kind() == io::ErrorKind::Unsupported => {
41 #[cfg(feature = "logging")]
42 warn!(
43 "File locks not supported on this platform. You must ensure that only a single process opens the database file, at a time"
44 );
45
46 Ok(Self {
47 file,
48 lock_supported: false,
49 })
50 }
51 Err(TryLockError::Error(err)) => Err(err.into()),
52 }
53 }
54}
55
56impl StorageBackend for FileBackend {
57 fn len(&self) -> Result<u64, io::Error> {
58 Ok(self.file.metadata()?.len())
59 }
60
61 #[cfg(unix)]
62 fn read(&self, offset: u64, out: &mut [u8]) -> Result<(), io::Error> {
63 self.file.read_exact_at(out, offset)?;
64 Ok(())
65 }
66
67 #[cfg(target_os = "wasi")]
68 fn read(&self, offset: u64, out: &mut [u8]) -> Result<(), io::Error> {
69 read_exact_at(&self.file, out, offset)?;
70 Ok(())
71 }
72
73 #[cfg(windows)]
74 fn read(&self, mut offset: u64, out: &mut [u8]) -> Result<(), io::Error> {
75 let mut data_offset = 0;
76 while data_offset < out.len() {
77 let read = self.file.seek_read(&mut out[data_offset..], offset)?;
78 offset += read as u64;
79 data_offset += read;
80 }
81 Ok(())
82 }
83
84 fn set_len(&self, len: u64) -> Result<(), io::Error> {
85 self.file.set_len(len)
86 }
87
88 fn sync_data(&self) -> Result<(), io::Error> {
89 self.file.sync_data()
90 }
91
92 #[cfg(unix)]
93 fn write(&self, offset: u64, data: &[u8]) -> Result<(), io::Error> {
94 self.file.write_all_at(data, offset)
95 }
96
97 #[cfg(target_os = "wasi")]
98 fn write(&self, offset: u64, data: &[u8]) -> Result<(), io::Error> {
99 write_all_at(&self.file, data, offset)
100 }
101
102 #[cfg(windows)]
103 fn write(&self, mut offset: u64, data: &[u8]) -> Result<(), io::Error> {
104 let mut data_offset = 0;
105 while data_offset < data.len() {
106 let written = self.file.seek_write(&data[data_offset..], offset)?;
107 offset += written as u64;
108 data_offset += written;
109 }
110 Ok(())
111 }
112
113 fn close(&self) -> Result<(), io::Error> {
114 if self.lock_supported {
115 self.file.unlock()?;
116 }
117
118 Ok(())
119 }
120}
121
122#[cfg(target_os = "wasi")]
125fn read_exact_at(file: &File, mut buf: &mut [u8], mut offset: u64) -> io::Result<()> {
126 use std::os::fd::AsRawFd;
127
128 while !buf.is_empty() {
129 let nbytes = unsafe {
130 libc::pread(
131 file.as_raw_fd(),
132 buf.as_mut_ptr() as _,
133 std::cmp::min(buf.len(), libc::ssize_t::MAX as _),
134 offset as _,
135 )
136 };
137 match nbytes {
138 0 => break,
139 -1 => match io::Error::last_os_error() {
140 err if err.kind() == io::ErrorKind::Interrupted => {}
141 err => return Err(err),
142 },
143 n => {
144 let tmp = buf;
145 buf = &mut tmp[n as usize..];
146 offset += n as u64;
147 }
148 }
149 }
150 if !buf.is_empty() {
151 Err(io::Error::new(
152 io::ErrorKind::UnexpectedEof,
153 "failed to fill whole buffer",
154 ))
155 } else {
156 Ok(())
157 }
158}
159
160#[cfg(target_os = "wasi")]
161fn write_all_at(file: &File, mut buf: &[u8], mut offset: u64) -> io::Result<()> {
162 use std::os::fd::AsRawFd;
163
164 while !buf.is_empty() {
165 let nbytes = unsafe {
166 libc::pwrite(
167 file.as_raw_fd(),
168 buf.as_ptr() as _,
169 std::cmp::min(buf.len(), libc::ssize_t::MAX as _),
170 offset as _,
171 )
172 };
173 match nbytes {
174 0 => {
175 return Err(io::Error::new(
176 io::ErrorKind::WriteZero,
177 "failed to write whole buffer",
178 ));
179 }
180 -1 => match io::Error::last_os_error() {
181 err if err.kind() == io::ErrorKind::Interrupted => {}
182 err => return Err(err),
183 },
184 n => {
185 buf = &buf[n as usize..];
186 offset += n as u64
187 }
188 }
189 }
190 Ok(())
191}