1#[macro_use]
5extern crate bitflags;
6extern crate gdbm_sys;
7extern crate libc;
8
9use std::error::Error as StdError;
10use std::io::Error;
11use std::fmt;
12use std::ffi::{CStr, CString, IntoStringError, NulError};
13use std::os::unix::ffi::OsStrExt;
14use std::os::unix::io::{AsRawFd, RawFd};
15use std::path::Path;
16use std::str::Utf8Error;
17use std::string::FromUtf8Error;
18
19use libc::{c_uint, c_void, free};
20
21use gdbm_sys::*;
22
23#[derive(Debug)]
25pub enum GdbmError {
26 FromUtf8Error(FromUtf8Error),
27 Utf8Error(Utf8Error),
28 NulError(NulError),
29 Error(String),
30 IoError(Error),
31 IntoStringError(IntoStringError),
32}
33
34impl fmt::Display for GdbmError {
35 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
36 write!(f, "{}", self)
37 }
38}
39
40impl StdError for GdbmError {
41 fn description(&self) -> &str {
42 match *self {
43 GdbmError::FromUtf8Error(ref _e) => "invalid utf-8 sequence",
44 GdbmError::Utf8Error(ref _e) => "invalid utf-8 sequence",
45 GdbmError::NulError(ref _e) => "nul byte found in provided data",
46 GdbmError::Error(ref _e) => "gdbm error",
47 GdbmError::IoError(ref _e) => "I/O error",
48 GdbmError::IntoStringError(ref _e) => "error",
49 }
50 }
51 fn cause(&self) -> Option<&dyn StdError> {
52 match *self {
53 GdbmError::FromUtf8Error(ref e) => e.source(),
54 GdbmError::Utf8Error(ref e) => e.source(),
55 GdbmError::NulError(ref e) => e.source(),
56 GdbmError::Error(_) => None,
57 GdbmError::IoError(ref e) => e.source(),
58 GdbmError::IntoStringError(ref e) => e.source(),
59 }
60 }
61}
62impl GdbmError {
63 fn new(err: impl Into<String>) -> GdbmError {
65 GdbmError::Error(err.into())
66 }
67
68 pub fn to_string(&self) -> String {
70 match *self {
71 GdbmError::FromUtf8Error(ref err) => err.utf8_error().to_string(),
72 GdbmError::Utf8Error(ref err) => err.to_string(),
73 GdbmError::NulError(ref err) => err.to_string(),
74 GdbmError::Error(ref err) => err.to_string(),
75 GdbmError::IoError(ref err) => err.to_string(),
76 GdbmError::IntoStringError(ref err) => err.to_string(),
77 }
78 }
79}
80
81impl From<NulError> for GdbmError {
82 fn from(err: NulError) -> GdbmError {
83 GdbmError::NulError(err)
84 }
85}
86impl From<FromUtf8Error> for GdbmError {
87 fn from(err: FromUtf8Error) -> GdbmError {
88 GdbmError::FromUtf8Error(err)
89 }
90}
91impl From<::std::str::Utf8Error> for GdbmError {
92 fn from(err: ::std::str::Utf8Error) -> GdbmError {
93 GdbmError::Utf8Error(err)
94 }
95}
96impl From<IntoStringError> for GdbmError {
97 fn from(err: IntoStringError) -> GdbmError {
98 GdbmError::IntoStringError(err)
99 }
100}
101impl From<Error> for GdbmError {
102 fn from(err: Error) -> GdbmError {
103 GdbmError::IoError(err)
104 }
105}
106
107
108fn get_error() -> String {
109 unsafe {
110 let error_ptr = gdbm_strerror(*gdbm_errno_location());
111 let err_string = CStr::from_ptr(error_ptr);
112 return err_string.to_string_lossy().into_owned();
113 }
114}
115
116fn datum(what: &str, data: impl AsRef<[u8]>) -> Result<datum, GdbmError> {
117 let data = data.as_ref();
118 if data.len() > i32::MAX as usize {
119 return Err(GdbmError::new(format!("{} too large", what)));
120 }
121 Ok(datum {
126 dptr: data.as_ptr() as *mut i8,
127 dsize: data.len() as i32,
128 })
129}
130
131bitflags! {
132 pub struct Open: c_uint {
134 const READER = 0;
136 const WRITER = 1;
138 const WRCREAT = 2;
140 const NEWDB = 3;
142 const FAST = 16;
143 const SYNC = 32;
145 const NOLOCK = 64;
147 }
148}
149
150bitflags! {
151 struct Store: c_uint {
152 const INSERT = 0;
153 const REPLACE = 1;
154 }
155}
156
157#[derive(Debug)]
163pub struct Gdbm {
164 db_handle: GDBM_FILE, }
172
173unsafe impl Send for Gdbm {}
177
178impl Drop for Gdbm {
179 fn drop(&mut self) {
180 if self.db_handle.is_null() {
181 return;
183 }
184 unsafe {
185 gdbm_close(self.db_handle);
186 }
187 }
188}
189
190impl AsRawFd for Gdbm {
194 fn as_raw_fd(&self) -> RawFd {
195 unsafe {
196 gdbm_fdesc(self.db_handle) as RawFd
197 }
198 }
199}
200
201impl Gdbm {
202 pub fn new(
208 path: impl AsRef<Path>,
209 block_size: u32,
210 flags: Open,
211 mode: u32
212 ) -> Result<Gdbm, GdbmError> {
213 if block_size > i32::MAX as u32 {
214 return Err(GdbmError::new("block_size too large"));
215 }
216 if mode > i32::MAX as u32 {
217 return Err(GdbmError::new("invalid mode"));
218 }
219 let path = CString::new(path.as_ref().as_os_str().as_bytes())?;
220 unsafe {
221 let db_ptr = gdbm_open(path.as_ptr() as *mut i8,
222 block_size as i32,
223 flags.bits as i32,
224 mode as i32,
225 None);
226 if db_ptr.is_null() {
227 return Err(GdbmError::new("gdbm_open failed".to_string()));
228 }
229 Ok(Gdbm { db_handle: db_ptr })
230 }
231 }
232
233 pub fn store(
239 &self,
240 key: impl AsRef<[u8]>,
241 content: impl AsRef<[u8]>,
242 replace: bool,
243 ) -> Result<bool, GdbmError> {
244 let key_datum = datum("key", key)?;
245 let content_datum = datum("content", content)?;
246 let flag = if replace { Store::REPLACE } else { Store::INSERT };
247 let result = unsafe {
248 gdbm_store(self.db_handle, key_datum, content_datum, flag.bits as i32)
249 };
250 if result < 0 {
251 return Err(GdbmError::new(get_error()));
252 }
253 Ok(result == 0)
254 }
255
256 pub fn fetch_data(&self, key: impl AsRef<[u8]>) -> Result<Vec<u8>, GdbmError> {
258 let key_datum = datum("key", key)?;
260 unsafe {
261 let content = gdbm_fetch(self.db_handle, key_datum);
262 if content.dptr.is_null() {
263 return Err(GdbmError::new(get_error()));
264 } else if content.dsize < 0 {
265 return Err(GdbmError::new("content has negative size"));
266 } else {
267 let ptr = content.dptr as *const u8;
268 let len = content.dsize as usize;
269 let slice = std::slice::from_raw_parts(ptr, len);
270 let vec = slice.to_vec();
271
272 free(content.dptr as *mut c_void);
275
276 return Ok(vec);
277 }
278 }
279 }
280
281 pub fn fetch_string(&self, key: impl AsRef<[u8]>) -> Result<String, GdbmError> {
286 let vec = self.fetch_data(key)?;
287 let s = String::from_utf8(vec)?;
288 Ok(s)
289 }
290
291 pub fn fetch_cstring(&self, key: impl AsRef<[u8]>) -> Result<String, GdbmError> {
296 let vec = self.fetch_data(key)?;
297 let mut s = String::from_utf8(vec)?;
298 if s.ends_with("\0") {
299 s.pop();
300 }
301 Ok(s)
302 }
303
304 pub fn delete(&self, key: impl AsRef<[u8]>) -> Result<bool, GdbmError> {
309 let key_datum = datum("key", key)?;
310 let result = unsafe {
311 gdbm_delete(self.db_handle, key_datum)
312 };
313 if result < 0 {
314 if unsafe { *gdbm_errno_location() } == 0 {
315 return Ok(false);
316 }
317 return Err(GdbmError::new(get_error()));
318 }
319 Ok(true)
320 }
321
322 pub fn sync(&self) {
348 unsafe {
349 gdbm_sync(self.db_handle);
350 }
351 }
352
353 pub fn exists(&self, key: impl AsRef<[u8]>) -> Result<bool, GdbmError> {
355 let key_datum = datum("key", key)?;
356 unsafe {
357 let result = gdbm_exists(self.db_handle, key_datum);
358 if result == 0 {
359 Ok(true)
360 } else {
361 if *gdbm_errno_location() == 0 {
362 return Ok(true);
363 } else {
364 return Err(GdbmError::new(get_error()));
365 }
366 }
367 }
368 }
369 }