• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use super::Blob;
2 
3 use std::convert::TryFrom;
4 use std::mem::MaybeUninit;
5 use std::slice::from_raw_parts_mut;
6 
7 use crate::ffi;
8 use crate::{Error, Result};
9 
10 impl<'conn> Blob<'conn> {
11     /// Write `buf` to `self` starting at `write_start`, returning an error if
12     /// `write_start + buf.len()` is past the end of the blob.
13     ///
14     /// If an error is returned, no data is written.
15     ///
16     /// Note: the blob cannot be resized using this function -- that must be
17     /// done using SQL (for example, an `UPDATE` statement).
18     ///
19     /// Note: This is part of the positional I/O API, and thus takes an absolute
20     /// position write to, instead of using the internal position that can be
21     /// manipulated by the `std::io` traits.
22     ///
23     /// Unlike the similarly named [`FileExt::write_at`][fext_write_at] function
24     /// (from `std::os::unix`), it's always an error to perform a "short write".
25     ///
26     /// [fext_write_at]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#tymethod.write_at
27     #[inline]
write_at(&mut self, buf: &[u8], write_start: usize) -> Result<()>28     pub fn write_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> {
29         let len = self.len();
30 
31         if buf.len().saturating_add(write_start) > len {
32             return Err(Error::BlobSizeError);
33         }
34         // We know `len` fits in an `i32`, so either:
35         //
36         // 1. `buf.len() + write_start` overflows, in which case we'd hit the
37         //    return above (courtesy of `saturating_add`).
38         //
39         // 2. `buf.len() + write_start` doesn't overflow but is larger than len,
40         //    in which case ditto.
41         //
42         // 3. `buf.len() + write_start` doesn't overflow but is less than len.
43         //    This means that both `buf.len()` and `write_start` can also be
44         //    losslessly converted to i32, since `len` came from an i32.
45         // Sanity check the above.
46         debug_assert!(i32::try_from(write_start).is_ok() && i32::try_from(buf.len()).is_ok());
47         unsafe {
48             check!(ffi::sqlite3_blob_write(
49                 self.blob,
50                 buf.as_ptr() as *const _,
51                 buf.len() as i32,
52                 write_start as i32,
53             ));
54         }
55         Ok(())
56     }
57 
58     /// An alias for `write_at` provided for compatibility with the conceptually
59     /// equivalent [`std::os::unix::FileExt::write_all_at`][write_all_at]
60     /// function from libstd:
61     ///
62     /// [write_all_at]: https://doc.rust-lang.org/std/os/unix/fs/trait.FileExt.html#method.write_all_at
63     #[inline]
write_all_at(&mut self, buf: &[u8], write_start: usize) -> Result<()>64     pub fn write_all_at(&mut self, buf: &[u8], write_start: usize) -> Result<()> {
65         self.write_at(buf, write_start)
66     }
67 
68     /// Read as much as possible from `offset` to `offset + buf.len()` out of
69     /// `self`, writing into `buf`. On success, returns the number of bytes
70     /// written.
71     ///
72     /// If there's insufficient data in `self`, then the returned value will be
73     /// less than `buf.len()`.
74     ///
75     /// See also [`Blob::raw_read_at`], which can take an uninitialized buffer,
76     /// or [`Blob::read_at_exact`] which returns an error if the entire `buf` is
77     /// not read.
78     ///
79     /// Note: This is part of the positional I/O API, and thus takes an absolute
80     /// position to read from, instead of using the internal position that can
81     /// be manipulated by the `std::io` traits. Consequently, it does not change
82     /// that value either.
83     #[inline]
read_at(&self, buf: &mut [u8], read_start: usize) -> Result<usize>84     pub fn read_at(&self, buf: &mut [u8], read_start: usize) -> Result<usize> {
85         // Safety: this is safe because `raw_read_at` never stores uninitialized
86         // data into `as_uninit`.
87         let as_uninit: &mut [MaybeUninit<u8>] =
88             unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut _, buf.len()) };
89         self.raw_read_at(as_uninit, read_start).map(|s| s.len())
90     }
91 
92     /// Read as much as possible from `offset` to `offset + buf.len()` out of
93     /// `self`, writing into `buf`. On success, returns the portion of `buf`
94     /// which was initialized by this call.
95     ///
96     /// If there's insufficient data in `self`, then the returned value will be
97     /// shorter than `buf`.
98     ///
99     /// See also [`Blob::read_at`], which takes a `&mut [u8]` buffer instead of
100     /// a slice of `MaybeUninit<u8>`.
101     ///
102     /// Note: This is part of the positional I/O API, and thus takes an absolute
103     /// position to read from, instead of using the internal position that can
104     /// be manipulated by the `std::io` traits. Consequently, it does not change
105     /// that value either.
106     #[inline]
raw_read_at<'a>( &self, buf: &'a mut [MaybeUninit<u8>], read_start: usize, ) -> Result<&'a mut [u8]>107     pub fn raw_read_at<'a>(
108         &self,
109         buf: &'a mut [MaybeUninit<u8>],
110         read_start: usize,
111     ) -> Result<&'a mut [u8]> {
112         let len = self.len();
113 
114         let read_len = match len.checked_sub(read_start) {
115             None | Some(0) => 0,
116             Some(v) => v.min(buf.len()),
117         };
118 
119         if read_len == 0 {
120             // We could return `Ok(&mut [])`, but it seems confusing that the
121             // pointers don't match, so fabricate a empty slice of u8 with the
122             // same base pointer as `buf`.
123             let empty = unsafe { from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, 0) };
124             return Ok(empty);
125         }
126 
127         // At this point we believe `read_start as i32` is lossless because:
128         //
129         // 1. `len as i32` is known to be lossless, since it comes from a SQLite
130         //    api returning an i32.
131         //
132         // 2. If we got here, `len.checked_sub(read_start)` was Some (or else
133         //    we'd have hit the `if read_len == 0` early return), so `len` must
134         //    be larger than `read_start`, and so it must fit in i32 as well.
135         debug_assert!(i32::try_from(read_start).is_ok());
136 
137         // We also believe that `read_start + read_len <= len` because:
138         //
139         // 1. This is equivalent to `read_len <= len - read_start` via algebra.
140         // 2. We know that `read_len` is `min(len - read_start, buf.len())`
141         // 3. Expanding, this is `min(len - read_start, buf.len()) <= len - read_start`,
142         //    or `min(A, B) <= A` which is clearly true.
143         //
144         // Note that this stuff is in debug_assert so no need to use checked_add
145         // and such -- we'll always panic on overflow in debug builds.
146         debug_assert!(read_start + read_len <= len);
147 
148         // These follow naturally.
149         debug_assert!(buf.len() >= read_len);
150         debug_assert!(i32::try_from(buf.len()).is_ok());
151         debug_assert!(i32::try_from(read_len).is_ok());
152 
153         unsafe {
154             check!(ffi::sqlite3_blob_read(
155                 self.blob,
156                 buf.as_mut_ptr() as *mut _,
157                 read_len as i32,
158                 read_start as i32,
159             ));
160 
161             Ok(from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, read_len))
162         }
163     }
164 
165     /// Equivalent to [`Blob::read_at`], but returns a `BlobSizeError` if `buf`
166     /// is not fully initialized.
167     #[inline]
read_at_exact(&self, buf: &mut [u8], read_start: usize) -> Result<()>168     pub fn read_at_exact(&self, buf: &mut [u8], read_start: usize) -> Result<()> {
169         let n = self.read_at(buf, read_start)?;
170         if n != buf.len() {
171             Err(Error::BlobSizeError)
172         } else {
173             Ok(())
174         }
175     }
176 
177     /// Equivalent to [`Blob::raw_read_at`], but returns a `BlobSizeError` if
178     /// `buf` is not fully initialized.
179     #[inline]
raw_read_at_exact<'a>( &self, buf: &'a mut [MaybeUninit<u8>], read_start: usize, ) -> Result<&'a mut [u8]>180     pub fn raw_read_at_exact<'a>(
181         &self,
182         buf: &'a mut [MaybeUninit<u8>],
183         read_start: usize,
184     ) -> Result<&'a mut [u8]> {
185         let buflen = buf.len();
186         let initted = self.raw_read_at(buf, read_start)?;
187         if initted.len() != buflen {
188             Err(Error::BlobSizeError)
189         } else {
190             Ok(initted)
191         }
192     }
193 }
194 
195 #[cfg(test)]
196 mod test {
197     use crate::{Connection, DatabaseName, NO_PARAMS};
198     // to ensure we don't modify seek pos
199     use std::io::Seek as _;
200 
201     #[test]
test_pos_io()202     fn test_pos_io() {
203         let db = Connection::open_in_memory().unwrap();
204         db.execute_batch("CREATE TABLE test_table(content BLOB);")
205             .unwrap();
206         db.execute(
207             "INSERT INTO test_table(content) VALUES (ZEROBLOB(10))",
208             NO_PARAMS,
209         )
210         .unwrap();
211 
212         let rowid = db.last_insert_rowid();
213         let mut blob = db
214             .blob_open(DatabaseName::Main, "test_table", "content", rowid, false)
215             .unwrap();
216         // modify the seek pos to ensure we aren't using it or modifying it.
217         blob.seek(std::io::SeekFrom::Start(1)).unwrap();
218 
219         let one2ten: [u8; 10] = [1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10];
220         blob.write_at(&one2ten, 0).unwrap();
221 
222         let mut s = [0u8; 10];
223         blob.read_at_exact(&mut s, 0).unwrap();
224         assert_eq!(&s, &one2ten, "write should go through");
225         assert!(blob.read_at_exact(&mut s, 1).is_err());
226 
227         blob.read_at_exact(&mut s, 0).unwrap();
228         assert_eq!(&s, &one2ten, "should be unchanged");
229 
230         let mut fives = [0u8; 5];
231         blob.read_at_exact(&mut fives, 0).unwrap();
232         assert_eq!(&fives, &[1u8, 2, 3, 4, 5]);
233 
234         blob.read_at_exact(&mut fives, 5).unwrap();
235         assert_eq!(&fives, &[6u8, 7, 8, 9, 10]);
236         assert!(blob.read_at_exact(&mut fives, 7).is_err());
237         assert!(blob.read_at_exact(&mut fives, 12).is_err());
238         assert!(blob.read_at_exact(&mut fives, 10).is_err());
239         assert!(blob.read_at_exact(&mut fives, i32::MAX as usize).is_err());
240         assert!(blob
241             .read_at_exact(&mut fives, i32::MAX as usize + 1)
242             .is_err());
243 
244         // zero length writes are fine if in bounds
245         blob.read_at_exact(&mut [], 10).unwrap();
246         blob.read_at_exact(&mut [], 0).unwrap();
247         blob.read_at_exact(&mut [], 5).unwrap();
248 
249         blob.write_all_at(&[16, 17, 18, 19, 20], 5).unwrap();
250         blob.read_at_exact(&mut s, 0).unwrap();
251         assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);
252 
253         assert!(blob.write_at(&[100, 99, 98, 97, 96], 6).is_err());
254         assert!(blob
255             .write_at(&[100, 99, 98, 97, 96], i32::MAX as usize)
256             .is_err());
257         assert!(blob
258             .write_at(&[100, 99, 98, 97, 96], i32::MAX as usize + 1)
259             .is_err());
260 
261         blob.read_at_exact(&mut s, 0).unwrap();
262         assert_eq!(&s, &[1u8, 2, 3, 4, 5, 16, 17, 18, 19, 20]);
263 
264         let mut s2: [std::mem::MaybeUninit<u8>; 10] = [std::mem::MaybeUninit::uninit(); 10];
265         {
266             let read = blob.raw_read_at_exact(&mut s2, 0).unwrap();
267             assert_eq!(read, &s);
268             assert!(std::ptr::eq(read.as_ptr(), s2.as_ptr().cast()));
269         }
270 
271         let mut empty = [];
272         assert!(std::ptr::eq(
273             blob.raw_read_at_exact(&mut empty, 0).unwrap().as_ptr(),
274             empty.as_ptr().cast(),
275         ));
276         assert!(blob.raw_read_at_exact(&mut s2, 5).is_err());
277 
278         let end_pos = blob.seek(std::io::SeekFrom::Current(0)).unwrap();
279         assert_eq!(end_pos, 1);
280     }
281 }
282