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
use std::{
ffi::CString,
marker::PhantomData,
os::fd::{IntoRawFd, OwnedFd},
path::Path,
ptr,
};
use librdb_sys::{
self, RdbHandlersDataCallbacks, RdbStatus_RDB_STATUS_ERROR, RdbStatus_RDB_STATUS_OK,
};
use crate::{
handlers::RdbHandlers,
trampoline::{self, HandlerState},
types::{RdbError, Result},
};
/// RAII wrapper around `RdbParser*`.
///
/// `H` implements [`RdbHandlers`] and receives callbacks during parsing.
/// The parser owns the handler and returns it via [`into_handler`](Self::into_handler)
/// after parsing completes.
pub struct Parser<H: RdbHandlers> {
raw: *mut librdb_sys::RdbParser,
state: *mut HandlerState<H>,
_handlers: *mut librdb_sys::RdbHandlers,
parsed: bool,
// The underlying C parser is not thread-safe; Parser must stay on the creating thread.
_not_send: PhantomData<*mut ()>,
}
#[allow(unsafe_code)]
impl<H: RdbHandlers> Parser<H> {
/// Create a new parser that will dispatch callbacks to `handler`.
///
/// # Errors
/// Returns an error if the underlying C parser cannot be created.
pub fn new(handler: H) -> Result<Self> {
let state = Box::new(HandlerState {
handler,
last_error: None,
});
let state_ptr = Box::into_raw(state);
// SAFETY: null memAlloc tells librdb to use default malloc/free.
let raw = unsafe { librdb_sys::RDB_createParserRdb(ptr::null_mut()) };
if raw.is_null() {
// SAFETY: state_ptr was just created by Box::into_raw above.
let _ = unsafe { Box::from_raw(state_ptr) };
return Err(RdbError::Parser {
code: 0,
message: "RDB_createParserRdb returned null".into(),
});
}
let mut callbacks: RdbHandlersDataCallbacks = trampoline::build_callbacks::<H>();
// SAFETY: `raw` is a valid parser. `callbacks` is copied by librdb internally.
// `state_ptr` outlives the parser. We pass `None` for `freeUserData` because
// we reclaim `state_ptr` ourselves in `Drop`.
let handlers = unsafe {
librdb_sys::RDB_createHandlersData(
raw,
std::ptr::from_mut(&mut callbacks),
state_ptr.cast(),
None,
)
};
if handlers.is_null() {
// SAFETY: raw is valid; state_ptr was created above and not yet freed.
unsafe { librdb_sys::RDB_deleteParser(raw) };
let _ = unsafe { Box::from_raw(state_ptr) };
return Err(RdbError::Parser {
code: 0,
message: "RDB_createHandlersData returned null".into(),
});
}
Ok(Self {
raw,
state: state_ptr,
_handlers: handlers,
parsed: false,
_not_send: PhantomData,
})
}
/// Attach a file reader and parse the entire RDB file.
///
/// Can only be called once per parser instance. A second call returns an error
/// because librdb does not support re-parsing after completion.
///
/// # Errors
/// Returns an error if the file cannot be opened, the RDB data is malformed,
/// or a handler callback returns `Err`.
pub fn parse_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
if self.parsed {
return Err(RdbError::Parser {
code: 0,
message: "parser already used; create a new Parser instance".into(),
});
}
let c_path = CString::new(path.as_ref().to_str().ok_or_else(|| RdbError::Parser {
code: 0,
message: "path contains invalid UTF-8".into(),
})?)
.map_err(|_| RdbError::Parser {
code: 0,
message: "path contains null byte".into(),
})?;
// SAFETY: self.raw is a valid parser in CONFIGURING state, c_path is valid.
let reader = unsafe { librdb_sys::RDBX_createReaderFile(self.raw, c_path.as_ptr()) };
if reader.is_null() {
return Err(self.extract_c_error("RDBX_createReaderFile returned null"));
}
self.parsed = true;
self.run_parse()
}
/// Parse RDB data from a file descriptor.
///
/// Takes ownership of the fd. The fd must be in blocking mode.
///
/// # Errors
/// Returns an error if the reader cannot be created, the RDB data is
/// malformed, or a handler callback returns `Err`.
pub fn parse_fd(&mut self, fd: OwnedFd) -> Result<()> {
if self.parsed {
return Err(RdbError::Parser {
code: 0,
message: "parser already used; create a new Parser instance".into(),
});
}
// SAFETY: self.raw is a valid parser in CONFIGURING state.
// OwnedFd guarantees a valid fd; into_raw_fd transfers ownership to
// librdb which will close it (fdCloseWhenDone = 1).
let raw_fd = fd.into_raw_fd();
let reader = unsafe { librdb_sys::RDBX_createReaderFileDesc(self.raw, raw_fd, 1) };
if reader.is_null() {
return Err(self.extract_c_error("RDBX_createReaderFileDesc returned null"));
}
self.parsed = true;
self.run_parse()
}
fn run_parse(&mut self) -> Result<()> {
// SAFETY: parser and reader are valid, callbacks are wired.
let status = unsafe { librdb_sys::RDB_parse(self.raw) };
if status == RdbStatus_RDB_STATUS_OK {
return Ok(());
}
// SAFETY: self.state is valid and exclusively accessed from this thread.
let state = unsafe { &mut *self.state };
if let Some(handler_err) = state.last_error.take() {
return Err(handler_err);
}
if status == RdbStatus_RDB_STATUS_ERROR {
return Err(self.extract_c_error("parse failed"));
}
Err(RdbError::Parser {
code: status,
message: format!("unexpected parse status: {status}"),
})
}
fn extract_c_error(&self, fallback: &str) -> RdbError {
// SAFETY: self.raw is valid. librdb returns a null-terminated C string.
let code = unsafe { librdb_sys::RDB_getErrorCode(self.raw) };
let msg_ptr = unsafe { librdb_sys::RDB_getErrorMessage(self.raw) };
let message = if msg_ptr.is_null() {
fallback.to_owned()
} else {
// SAFETY: msg_ptr is a valid null-terminated string owned by the parser.
unsafe { std::ffi::CStr::from_ptr(msg_ptr) }
.to_string_lossy()
.into_owned()
};
RdbError::Parser { code, message }
}
/// Borrow the handler, e.g. to inspect accumulated state after a parse failure.
#[must_use]
pub fn handler(&self) -> &H {
// SAFETY: self.state is valid and not null while Parser is alive.
unsafe { &(*self.state).handler }
}
/// Mutably borrow the handler.
pub fn handler_mut(&mut self) -> &mut H {
// SAFETY: self.state is valid, not null, and exclusively accessed.
unsafe { &mut (*self.state).handler }
}
/// Consume the parser and return the handler.
#[must_use]
pub fn into_handler(mut self) -> H {
// Null self.state so the subsequent Drop doesn't double-free the Box
// we are about to reclaim here.
let state_ptr = self.state;
self.state = ptr::null_mut();
// SAFETY: state_ptr was created by Box::into_raw in new() and has not been freed.
let state = unsafe { Box::from_raw(state_ptr) };
state.handler
}
}
#[allow(unsafe_code)]
impl<H: RdbHandlers> Drop for Parser<H> {
fn drop(&mut self) {
// SAFETY: RDB_deleteParser frees the parser and all attached handlers/readers.
unsafe { librdb_sys::RDB_deleteParser(self.raw) };
if !self.state.is_null() {
// SAFETY: state was created by Box::into_raw and not yet reclaimed.
let _ = unsafe { Box::from_raw(self.state) };
}
}
}