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