an efficient binary archive format
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

c api updates: bindle_read -> bindle_read_buffer

zach 17f7775e 0df2f2d5

+246 -44
+1 -1
Cargo.toml
··· 1 1 [package] 2 2 name = "bindle-file" 3 - version = "0.0.1" 3 + version = "0.0.2" 4 4 edition = "2024" 5 5 authors = ["Zach Shipko <zachshipko@gmail.com>"] 6 6 license = "ISC"
+3 -3
README.md
··· 44 44 bindle_add(bindle, "file.txt", data, len, BindleCompressNone); 45 45 bindle_save(bindle); 46 46 47 - size_t size; 48 - uint8_t* data = bindle_read(bindle, "file.txt", &size); 49 - 47 + size_t len = bindle_entry_size(bindle, "file.txt"); 48 + uint8_t *data = malloc(len); 49 + assert(bindle_read(bindle, "file.txt", data) == len); 50 50 // Or for uncompressed entries, read directly without decompression 51 51 uint8_t* raw = bindle_read_uncompressed_direct(bindle, "file.txt", &size); 52 52
+45 -4
include/bindle.h
··· 181 181 * Pointer to data buffer, or NULL if not found or CRC32 check fails. 182 182 * Must be freed with `bindle_free_buffer()`. 183 183 */ 184 - uint8_t *bindle_read(struct Bindle *ctx_ptr, const char *name, size_t *out_len); 184 + uint8_t *bindle_read_buffer(struct Bindle *ctx_ptr, const char *name, size_t *out_len); 185 185 186 186 /** 187 187 * Frees a buffer returned by `bindle_read()`. ··· 212 212 size_t bindle_length(const struct Bindle *ctx); 213 213 214 214 /** 215 - * Returns the name of the entry at the given index. 215 + * Returns the name of the entry at the given index as a null-terminated C string. 216 216 * 217 217 * Use with `bindle_length()` to iterate over all entries. The pointer is valid as long as the Bindle handle is open. 218 218 * Do NOT free the returned pointer. 219 219 */ 220 220 const char *bindle_entry_name(const struct Bindle *ctx, 221 - size_t index, 222 - size_t *len); 221 + size_t index); 223 222 224 223 /** 225 224 * Reclaims space by removing shadowed data. ··· 299 298 * Closes the reader and frees the handle. 300 299 */ 301 300 void bindle_reader_close(struct BindleReader *reader); 301 + 302 + /** 303 + * Gets the uncompressed size of an entry by name. 304 + * 305 + * # Parameters 306 + * * `ctx` - Bindle handle 307 + * * `name` - NUL-terminated entry name 308 + * 309 + * # Returns 310 + * The uncompressed size in bytes, or 0 if the entry doesn't exist. 311 + * Note: Returns 0 for both non-existent entries and zero-length entries. 312 + */ 313 + size_t bindle_entry_size(const struct Bindle *ctx, const char *name); 314 + 315 + /** 316 + * Gets the compression type of an entry by name. 317 + * 318 + * # Parameters 319 + * * `ctx` - Bindle handle 320 + * * `name` - NUL-terminated entry name 321 + * 322 + * # Returns 323 + * The Compress value (0 = None, 1 = Zstd), or 0 if the entry doesn't exist. 324 + */ 325 + enum BindleCompress bindle_entry_compress(const struct Bindle *ctx, const char *name); 326 + 327 + /** 328 + * Reads an entry into a pre-existing buffer. 329 + * 330 + * Decompresses if needed and verifies CRC32. Reads up to `buffer_len` bytes. 331 + * 332 + * # Parameters 333 + * * `ctx` - Bindle handle 334 + * * `name` - NUL-terminated entry name 335 + * * `buffer` - Pre-allocated buffer to read into 336 + * * `buffer_len` - Maximum number of bytes to read 337 + * 338 + * # Returns 339 + * The number of bytes actually read, or 0 if the entry doesn't exist or CRC32 check fails. 340 + * If the entry is larger than `buffer_len`, only `buffer_len` bytes are read. 341 + */ 342 + size_t bindle_read(const struct Bindle *ctx, const char *name, uint8_t *buffer, size_t buffer_len); 302 343 303 344 #endif /* BINDLE_H */
+22
src/bindle.rs
··· 333 333 Some(data) 334 334 } 335 335 336 + /// Reads an entry into a provided buffer, avoiding allocation. 337 + /// 338 + /// Decompresses if needed and verifies CRC32. Returns the number of bytes read. 339 + /// If the buffer is too small, only reads up to buffer.len() bytes. 340 + /// 341 + /// # Example 342 + /// 343 + /// ```no_run 344 + /// use bindle_file::Bindle; 345 + /// 346 + /// let archive = Bindle::open("data.bndl")?; 347 + /// let mut buffer = vec![0u8; 1024]; 348 + /// let bytes_read = archive.read_into("file.txt", &mut buffer)?; 349 + /// # Ok::<(), std::io::Error>(()) 350 + /// ``` 351 + pub fn read_into(&self, name: &str, buffer: &mut [u8]) -> io::Result<usize> { 352 + let mut reader = self.reader(name)?; 353 + let bytes_read = reader.read(buffer)?; 354 + reader.verify_crc32()?; 355 + Ok(bytes_read) 356 + } 357 + 336 358 /// Reads an entry and writes it to the given writer. 337 359 /// 338 360 /// Returns the number of bytes written. Verifies CRC32 after reading.
+175 -36
src/ffi.rs
··· 1 1 use std::alloc::{Layout, dealloc}; 2 - use std::ffi::CStr; 2 + use std::ffi::{CStr, CString}; 3 3 use std::io::{Read, Write}; 4 4 use std::mem; 5 5 use std::os::raw::c_char; 6 6 use std::slice; 7 7 8 - use crate::{Bindle, Compress, Reader, Writer}; 8 + use crate::{Compress, Reader, Writer}; 9 + 10 + /// FFI wrapper around Bindle that caches null-terminated entry names for C API. 11 + pub struct Bindle { 12 + bindle: crate::Bindle, 13 + entry_names_cache: Vec<CString>, 14 + } 15 + 16 + impl Bindle { 17 + fn new(bindle: crate::Bindle) -> Self { 18 + let mut ffi = Bindle { 19 + bindle, 20 + entry_names_cache: Vec::new(), 21 + }; 22 + ffi.rebuild_cache(); 23 + ffi 24 + } 25 + 26 + fn rebuild_cache(&mut self) { 27 + self.entry_names_cache.clear(); 28 + for (name, _) in &self.bindle.index { 29 + if let Ok(c_str) = CString::new(name.as_str()) { 30 + self.entry_names_cache.push(c_str); 31 + } 32 + } 33 + } 34 + } 9 35 10 36 /// Creates a new archive, overwriting any existing file. 11 37 /// ··· 27 53 } 28 54 }; 29 55 30 - match Bindle::create(path_str) { 31 - Ok(b) => Box::into_raw(Box::new(b)), 56 + match crate::Bindle::create(path_str) { 57 + Ok(b) => Box::into_raw(Box::new(Bindle::new(b))), 32 58 Err(_) => std::ptr::null_mut(), 33 59 } 34 60 } ··· 53 79 } 54 80 }; 55 81 56 - match Bindle::open(path_str) { 57 - Ok(b) => Box::into_raw(Box::new(b)), 82 + match crate::Bindle::open(path_str) { 83 + Ok(b) => Box::into_raw(Box::new(Bindle::new(b))), 58 84 Err(_) => std::ptr::null_mut(), 59 85 } 60 86 } ··· 79 105 } 80 106 }; 81 107 82 - match Bindle::load(path_str) { 83 - Ok(b) => Box::into_raw(Box::new(b)), 108 + match crate::Bindle::load(path_str) { 109 + Ok(b) => Box::into_raw(Box::new(Bindle::new(b))), 84 110 Err(_) => std::ptr::null_mut(), 85 111 } 86 112 } ··· 117 143 let data_slice = slice::from_raw_parts(data, data_len); 118 144 let b = &mut (*ctx); 119 145 120 - b.add(name_str, data_slice, compress).is_ok() 146 + let result = b.bindle.add(name_str, data_slice, compress).is_ok(); 147 + if result { 148 + b.rebuild_cache(); 149 + } 150 + result 121 151 } 122 152 } 123 153 ··· 155 185 156 186 let b = &mut (*ctx); 157 187 158 - b.add_file(name_str, path_str, compress).is_ok() 188 + let result = b.bindle.add_file(name_str, path_str, compress).is_ok(); 189 + if result { 190 + b.rebuild_cache(); 191 + } 192 + result 159 193 } 160 194 } 161 195 ··· 169 203 } 170 204 unsafe { 171 205 let b = &mut (*ctx); 172 - b.save().is_ok() 206 + b.bindle.save().is_ok() 173 207 } 174 208 } 175 209 ··· 195 229 /// Pointer to data buffer, or NULL if not found or CRC32 check fails. 196 230 /// Must be freed with `bindle_free_buffer()`. 197 231 #[unsafe(no_mangle)] 198 - pub unsafe extern "C" fn bindle_read( 232 + pub unsafe extern "C" fn bindle_read_buffer( 199 233 ctx_ptr: *mut Bindle, 200 234 name: *const c_char, 201 235 out_len: *mut usize, ··· 216 250 let ctx = &mut *ctx_ptr; 217 251 218 252 // 3. The actual data retrieval logic 219 - // (Assuming your Rust Bindle has a method like .get(name)) 220 - match ctx.read(name_str) { 253 + match ctx.bindle.read(name_str) { 221 254 Some(bytes) => wrap_in_ffi_header(bytes.as_ref(), out_len), 222 255 None => return std::ptr::null_mut(), 223 256 } ··· 307 340 }; 308 341 309 342 let b = &(*ctx); 310 - if let Some(data) = b.read(name_str) { 343 + if let Some(data) = b.bindle.read(name_str) { 311 344 match data { 312 345 std::borrow::Cow::Borrowed(bytes) => bytes.as_ptr(), 313 346 _ => std::ptr::null_mut(), ··· 324 357 if ctx.is_null() { 325 358 return 0; 326 359 } 327 - unsafe { (*ctx).len() } 360 + unsafe { (*ctx).bindle.len() } 328 361 } 329 362 330 - /// Returns the name of the entry at the given index. 363 + /// Returns the name of the entry at the given index as a null-terminated C string. 331 364 /// 332 365 /// Use with `bindle_length()` to iterate over all entries. The pointer is valid as long as the Bindle handle is open. 333 366 /// Do NOT free the returned pointer. 334 367 #[unsafe(no_mangle)] 335 - pub unsafe extern "C" fn bindle_entry_name( 336 - ctx: *const Bindle, 337 - index: usize, 338 - len: *mut usize, 339 - ) -> *const c_char { 368 + pub unsafe extern "C" fn bindle_entry_name(ctx: *const Bindle, index: usize) -> *const c_char { 340 369 if ctx.is_null() { 341 370 return std::ptr::null(); 342 371 } 343 372 344 373 let b = unsafe { &(*ctx) }; 345 - match b.index.iter().nth(index) { 346 - Some((name, _)) => { 347 - unsafe { 348 - *len = name.as_bytes().len(); 349 - } 350 - name.as_ptr() as *const _ 351 - } 374 + match b.entry_names_cache.get(index) { 375 + Some(c_str) => c_str.as_ptr(), 352 376 None => std::ptr::null(), 353 377 } 354 378 } ··· 362 386 return false; 363 387 } 364 388 let b = unsafe { &mut (*ctx) }; 365 - b.vacuum().is_ok() 389 + let result = b.bindle.vacuum().is_ok(); 390 + if result { 391 + b.rebuild_cache(); 392 + } 393 + result 366 394 } 367 395 368 396 /// Extracts all entries to a destination directory. ··· 373 401 } 374 402 let b = unsafe { &*ctx }; 375 403 let path = unsafe { CStr::from_ptr(dest_path).to_string_lossy() }; 376 - b.unpack(path.as_ref()).is_ok() 404 + b.bindle.unpack(path.as_ref()).is_ok() 377 405 } 378 406 379 407 /// Recursively adds all files from a directory to the archive. ··· 390 418 } 391 419 let b = unsafe { &mut *ctx }; 392 420 let path = unsafe { CStr::from_ptr(src_path).to_string_lossy() }; 393 - b.pack(path.as_ref(), compress).is_ok() 421 + let result = b.bindle.pack(path.as_ref(), compress).is_ok(); 422 + if result { 423 + b.rebuild_cache(); 424 + } 425 + result 394 426 } 395 427 396 428 /// Returns true if an entry with the given name exists. ··· 408 440 } 409 441 }; 410 442 411 - b.exists(name_str) 443 + b.bindle.exists(name_str) 412 444 } 413 445 414 446 /// Removes an entry from the index. ··· 429 461 } 430 462 }; 431 463 432 - b.remove(name_str) 464 + let result = b.bindle.remove(name_str); 465 + if result { 466 + b.rebuild_cache(); 467 + } 468 + result 433 469 } 434 470 435 471 /// Creates a streaming writer for adding an entry. ··· 446 482 let b = &mut *ctx; 447 483 let name_str = CStr::from_ptr(name).to_string_lossy(); 448 484 449 - match b.writer(&name_str, compress) { 485 + match b.bindle.writer(&name_str, compress) { 450 486 Ok(stream) => Box::into_raw(Box::new(std::mem::transmute(stream))), 451 487 Err(_) => std::ptr::null_mut(), 452 488 } ··· 490 526 let b = unsafe { &*ctx }; 491 527 let name_str = unsafe { CStr::from_ptr(name).to_string_lossy() }; 492 528 493 - match b.reader(&name_str) { 529 + match b.bindle.reader(&name_str) { 494 530 Ok(reader) => Box::into_raw(Box::new(reader)), 495 531 Err(_) => std::ptr::null_mut(), 496 532 } ··· 540 576 } 541 577 } 542 578 } 579 + 580 + /// Gets the uncompressed size of an entry by name. 581 + /// 582 + /// # Parameters 583 + /// * `ctx` - Bindle handle 584 + /// * `name` - NUL-terminated entry name 585 + /// 586 + /// # Returns 587 + /// The uncompressed size in bytes, or 0 if the entry doesn't exist. 588 + /// Note: Returns 0 for both non-existent entries and zero-length entries. 589 + #[unsafe(no_mangle)] 590 + pub unsafe extern "C" fn bindle_entry_size(ctx: *const Bindle, name: *const c_char) -> usize { 591 + if ctx.is_null() || name.is_null() { 592 + return 0; 593 + } 594 + 595 + unsafe { 596 + let name_str = match CStr::from_ptr(name).to_str() { 597 + Ok(s) => s, 598 + Err(_) => return 0, 599 + }; 600 + 601 + let b = &*ctx; 602 + match b.bindle.index.get(name_str) { 603 + Some(entry) => entry.uncompressed_size() as usize, 604 + None => 0, 605 + } 606 + } 607 + } 608 + 609 + /// Gets the compression type of an entry by name. 610 + /// 611 + /// # Parameters 612 + /// * `ctx` - Bindle handle 613 + /// * `name` - NUL-terminated entry name 614 + /// 615 + /// # Returns 616 + /// The Compress value (0 = None, 1 = Zstd), or 0 if the entry doesn't exist. 617 + #[unsafe(no_mangle)] 618 + pub unsafe extern "C" fn bindle_entry_compress(ctx: *const Bindle, name: *const c_char) -> Compress { 619 + if ctx.is_null() || name.is_null() { 620 + return Compress::None; 621 + } 622 + 623 + unsafe { 624 + let name_str = match CStr::from_ptr(name).to_str() { 625 + Ok(s) => s, 626 + Err(_) => return Compress::None, 627 + }; 628 + 629 + let b = &*ctx; 630 + match b.bindle.index.get(name_str) { 631 + Some(entry) => { 632 + if entry.compression_type == 1 { 633 + Compress::Zstd 634 + } else { 635 + Compress::None 636 + } 637 + } 638 + None => Compress::None, 639 + } 640 + } 641 + } 642 + 643 + /// Reads an entry into a pre-existing buffer. 644 + /// 645 + /// Decompresses if needed and verifies CRC32. Reads up to `buffer_len` bytes. 646 + /// 647 + /// # Parameters 648 + /// * `ctx` - Bindle handle 649 + /// * `name` - NUL-terminated entry name 650 + /// * `buffer` - Pre-allocated buffer to read into 651 + /// * `buffer_len` - Maximum number of bytes to read 652 + /// 653 + /// # Returns 654 + /// The number of bytes actually read, or 0 if the entry doesn't exist or CRC32 check fails. 655 + /// If the entry is larger than `buffer_len`, only `buffer_len` bytes are read. 656 + #[unsafe(no_mangle)] 657 + pub unsafe extern "C" fn bindle_read( 658 + ctx: *const Bindle, 659 + name: *const c_char, 660 + buffer: *mut u8, 661 + buffer_len: usize, 662 + ) -> usize { 663 + if ctx.is_null() || name.is_null() || buffer.is_null() { 664 + return 0; 665 + } 666 + 667 + unsafe { 668 + let name_str = match CStr::from_ptr(name).to_str() { 669 + Ok(s) => s, 670 + Err(_) => return 0, 671 + }; 672 + 673 + let b = &*ctx; 674 + let buffer_slice = slice::from_raw_parts_mut(buffer, buffer_len); 675 + 676 + match b.bindle.read_into(name_str, buffer_slice) { 677 + Ok(bytes_read) => bytes_read, 678 + Err(_) => 0, 679 + } 680 + } 681 + }