1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#[cfg(all(feature = "fs", target_os = "linux"))]
use std::{
fmt::{self, Debug},
path::PathBuf,
};
use std::os::fd::OwnedFd;
#[cfg(all(feature = "fs", target_os = "linux"))]
use nix::{
errno::Errno,
fcntl::{FcntlArg, fcntl},
libc,
unistd::Uid,
};
#[cfg(all(feature = "fs", target_os = "linux"))]
use crate::FileId;
use crate::{FilesystemId, block::FileId as StoreFileId};
#[cfg(all(feature = "fs", target_os = "linux"))]
#[cfg_attr(coverage_nightly, coverage(off))]
fn runtime_dir() -> PathBuf {
std::env::var_os("XDG_RUNTIME_DIR")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(format!("/run/user/{}", Uid::current())))
}
#[derive(Debug)]
pub enum LockResult {
#[cfg_attr(not(all(feature = "fs", target_os = "linux")), allow(dead_code))]
Acquired(LockHandle),
Locked,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LockType {
Read,
Write,
}
#[derive(Debug, Clone)]
pub struct LockHandler {
#[cfg(all(feature = "fs", target_os = "linux"))]
base_dir: PathBuf,
}
#[derive(Debug)]
pub struct LockHandle {
_fd: OwnedFd,
}
impl LockHandle {
#[cfg_attr(not(all(feature = "fs", target_os = "linux")), allow(dead_code))]
fn new(fd: OwnedFd) -> Self {
Self { _fd: fd }
}
/// Attempt to atomically upgrade this handle from a shared lock to an exclusive lock.
///
/// On success, returns `Ok(Ok(handle))` with the (now exclusive) handle. If another holder has
/// a conflicting lock, returns `Ok(Err(handle))` and the original shared lock is preserved
/// unchanged.
#[cfg(all(feature = "fs", target_os = "linux"))]
#[cfg_attr(not(all(feature = "fuse", target_os = "linux")), allow(dead_code))]
pub fn try_upgrade(self) -> crate::Result<Result<LockHandle, LockHandle>> {
use std::io;
let result = fcntl(
&self._fd,
FcntlArg::F_OFD_SETLK(&libc::flock {
l_type: libc::F_WRLCK as i16,
l_whence: libc::SEEK_SET as i16,
l_start: 0,
l_len: 0,
l_pid: 0,
}),
);
match result {
Ok(_) => Ok(Ok(self)),
Err(Errno::EAGAIN) | Err(Errno::EACCES) => Ok(Err(self)),
Err(e) => Err(crate::Error::from(io::Error::from_raw_os_error(e as i32))),
}
}
}
impl LockHandler {
#[cfg(all(feature = "fs", target_os = "linux"))]
pub fn new(fs: FilesystemId) -> Self {
Self {
base_dir: runtime_dir()
.join("liteboxfs")
.join("fs")
.join(fs.to_string()),
}
}
#[cfg(not(all(feature = "fs", target_os = "linux")))]
pub fn new(_fs: FilesystemId) -> Self {
Self {}
}
#[cfg(all(feature = "fs", target_os = "linux"))]
fn handles_lock_path(&self) -> PathBuf {
self.base_dir.join("handles.lock")
}
#[cfg(all(feature = "fs", target_os = "linux"))]
fn litebox_lock_path(&self) -> PathBuf {
self.base_dir.join("litebox.lock")
}
#[cfg(all(feature = "fs", target_os = "linux"))]
fn open_lock_file(&self, path: &std::path::Path) -> crate::Result<OwnedFd> {
use std::fs;
fs::create_dir_all(&self.base_dir)?;
Ok(fs::File::options()
.create(true)
.read(true)
.append(true)
.open(path)?
.into())
}
// Acquire a lock on the LiteboxFS file with the given ID, returning a handle. The lock is
// released when the handle is dropped.
#[cfg(all(feature = "fs", target_os = "linux"))]
pub fn acquire_file_lock(
&self,
file: StoreFileId,
kind: LockType,
) -> crate::Result<LockResult> {
use std::io;
let lock_fd = self.open_lock_file(&self.handles_lock_path())?;
// We use Linux file descriptor locks, acquiring a lock on a single byte corresponding to
// the file ID. This allows us to lock any number of LiteboxFS files within a single lock
// file on the host filesystem.
let result = fcntl(
&lock_fd,
FcntlArg::F_OFD_SETLK(&libc::flock {
l_type: match kind {
LockType::Read => libc::F_RDLCK as i16,
LockType::Write => libc::F_WRLCK as i16,
},
l_whence: libc::SEEK_SET as i16,
l_start: file.into(),
l_len: 1,
// This is only used when testing for the existence of a lock, not when acquiring
// it. It returns the PID of the locking process, which is not relevant when
// locking. This may remain "unset", which in Rust just means initialized to 0
l_pid: 0,
}),
);
match result {
Ok(_) => Ok(LockResult::Acquired(LockHandle::new(lock_fd))),
// The Linux API can return either of these errors to indicate a lock is already held;
// portable implementations are required to handle both.
Err(Errno::EAGAIN) | Err(Errno::EACCES) => Ok(LockResult::Locked),
Err(e) => Err(crate::Error::from(io::Error::from_raw_os_error(e as i32))),
}
}
#[cfg(not(all(feature = "fs", target_os = "linux")))]
pub fn acquire_file_lock(
&self,
_file: StoreFileId,
_kind: LockType,
) -> crate::Result<LockResult> {
// If the `fs` feature is disabled, we should act as if all files could be open in other
// transactions, because we have no way of knowing.
Ok(LockResult::Locked)
}
// Acquire a whole-litebox lock, used to keep the database stable for the duration of a FUSE
// mount. Connection holders take this as `LockType::Read` (shared); mounting upgrades to
// `LockType::Write` (exclusive) via [`LockHandle::try_upgrade_to_exclusive`].
#[cfg(all(feature = "fs", target_os = "linux"))]
pub fn acquire_litebox_lock(&self, kind: LockType) -> crate::Result<LockResult> {
use std::io;
let lock_fd = self.open_lock_file(&self.litebox_lock_path())?;
let result = fcntl(
&lock_fd,
FcntlArg::F_OFD_SETLK(&libc::flock {
l_type: match kind {
LockType::Read => libc::F_RDLCK as i16,
LockType::Write => libc::F_WRLCK as i16,
},
l_whence: libc::SEEK_SET as i16,
l_start: 0,
l_len: 0,
l_pid: 0,
}),
);
match result {
Ok(_) => Ok(LockResult::Acquired(LockHandle::new(lock_fd))),
Err(Errno::EAGAIN) | Err(Errno::EACCES) => Ok(LockResult::Locked),
Err(e) => Err(crate::Error::from(io::Error::from_raw_os_error(e as i32))),
}
}
}
/// A raw handle to a file which can be used to prevent its deletion.
///
/// This handle can be used to keep a file which has been unlinked with [`Filesystem::unlink`] from
/// being deleted without needing to hold a [`File`], which holds an exclusive reference to the
/// filesystem.
///
/// However, unlike [`File`], **this handle does not automatically delete the file when it is
/// dropped**. For that, you must use [`Filesystem::release`]. If you fail to call
/// [`Filesystem::release`] before dropping this handle, the file will be orphaned and remain on
/// disk until either 1) another connection with an open handle deletes it or 2) it is garbage
/// collected at an unspecified later time.
///
/// You can get a [`RawFd`] by calling [`File::leak_fd`].
///
/// [`File`]: crate::File
/// [`Filesystem::unlink`]: crate::Filesystem::unlink
/// [`File::leak_fd`]: crate::File::leak_fd
/// [`Filesystem::release`]: crate::Filesystem::release
#[cfg(all(feature = "fs", target_os = "linux"))]
pub struct RawFd {
_handle: LockHandle,
file_id: StoreFileId,
filesystem_id: FilesystemId,
}
#[cfg(all(feature = "fs", target_os = "linux"))]
impl RawFd {
pub(crate) fn new(
handle: LockHandle,
file_id: StoreFileId,
filesystem_id: FilesystemId,
) -> Self {
Self {
_handle: handle,
file_id,
filesystem_id,
}
}
pub(crate) fn into_file_id(self) -> StoreFileId {
self.file_id
}
/// The [`FileId`] of the file associated with this raw file descriptor.
pub fn file_id(&self) -> FileId {
FileId::new(self.file_id, self.filesystem_id)
}
}
#[cfg(all(feature = "fs", target_os = "linux"))]
#[cfg_attr(coverage_nightly, coverage(off))]
impl Debug for RawFd {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("RawFd").field(&self.file_id).finish()
}
}