squashfs_ng/
lib.rs

1//! This crate provides Rust bindings for the [squashfs-tools-ng][] library, providing support for
2//! SquashFS as an embeddable archive format without the need for kernel support.  It also tries to
3//! provide a level of safety and abstraction on top of the C library.  Cross-platform usability is a
4//! secondary goal.
5//!
6//! # Installation
7//!
8//! Currently, the underlying [squashfs-tools-ng][] library must be installed on the system both to
9//! build and to use this library.  The development headers (`/usr/include/sqfs/...`) are required
10//! to build, and the shared library (`/usr/lib/libsquashfs.so`) to run.  The project's GitHub page
11//! asserts that packages are available in many Linux distributions' repositories.
12//!
13//! Once the dependencies are in place, this should function like most other Rust libraries, and
14//! `cargo build` should suffice to build the library.
15//!
16//! # Usage
17//!
18//! The [`read`] and [`write`](module@write) modules below provide support for reading and writing
19//! SquashFS files, respectively.  Check them out for further documentation.
20//!
21//! [squashfs-tools-ng]: https://github.com/AgentD/squashfs-tools-ng/
22
23#[macro_use] extern crate lazy_static;
24extern crate libc;
25extern crate memmap;
26extern crate num_derive;
27extern crate num_traits;
28extern crate owning_ref;
29extern crate walkdir;
30extern crate xattr;
31
32use std::mem::MaybeUninit;
33use std::ffi::{OsStr, OsString};
34use std::path::PathBuf;
35use std::ptr;
36use num_derive::FromPrimitive;
37use num_traits::FromPrimitive;
38use thiserror::Error;
39
40#[cfg(not(feature = "hermetic"))]
41mod bindings {
42	#![allow(non_camel_case_types)]
43	#![allow(non_snake_case)]
44	#![allow(non_upper_case_globals)]
45	#![allow(dead_code)]
46	include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
47}
48#[cfg(feature = "hermetic")]
49mod bindings;
50
51use bindings::*;
52
53pub mod read;
54pub mod write;
55
56type BoxedError = Box<dyn std::error::Error + std::marker::Send + std::marker::Sync>;
57
58/// Errors raised by the underlying library.
59///
60/// This error type reflects all errors raised by the squashfs-tools-ng library.  This should
61/// always be wrapped in a [`SquashfsError`] before being returned from any of the functions in
62/// this library.
63#[derive(Error, Debug, FromPrimitive)]
64#[repr(i32)]
65pub enum LibError {
66	#[error("Failed to allocate memory")] Alloc = SQFS_ERROR_SQFS_ERROR_ALLOC,
67	#[error("Generic I/O failure")] Io = SQFS_ERROR_SQFS_ERROR_IO,
68	#[error("Compressor failed to extract data")] Compressor = SQFS_ERROR_SQFS_ERROR_COMPRESSOR,
69	#[error("Internal error")] Internal = SQFS_ERROR_SQFS_ERROR_INTERNAL,
70	#[error("Archive file appears to be corrupted")] Corrupted = SQFS_ERROR_SQFS_ERROR_CORRUPTED,
71	#[error("Unsupported feature used")] Unsupported = SQFS_ERROR_SQFS_ERROR_UNSUPPORTED,
72	#[error("Archive would overflow memory")] Overflow = SQFS_ERROR_SQFS_ERROR_OVERFLOW,
73	#[error("Out-of-bounds access attempted")] OutOfBounds = SQFS_ERROR_SQFS_ERROR_OUT_OF_BOUNDS,
74	#[error("Superblock magic number incorrect")] SuperMagic = SQFS_ERROR_SFQS_ERROR_SUPER_MAGIC,
75	#[error("Unsupported archive version")] SuperVersion = SQFS_ERROR_SFQS_ERROR_SUPER_VERSION,
76	#[error("Archive block size is invalid")] SuperBlockSize = SQFS_ERROR_SQFS_ERROR_SUPER_BLOCK_SIZE,
77	#[error("Not a directory")] NotDir = SQFS_ERROR_SQFS_ERROR_NOT_DIR,
78	#[error("Path does not exist")] NoEntry = SQFS_ERROR_SQFS_ERROR_NO_ENTRY,
79	#[error("Hard link loop detected")] LinkLoop = SQFS_ERROR_SQFS_ERROR_LINK_LOOP,
80	#[error("Not a regular file")] NotFile = SQFS_ERROR_SQFS_ERROR_NOT_FILE,
81	#[error("Invalid argument passed")] ArgInvalid = SQFS_ERROR_SQFS_ERROR_ARG_INVALID,
82	#[error("Library operations performed in incorrect order")] Sequence = SQFS_ERROR_SQFS_ERROR_SEQUENCE,
83}
84
85/// Errors encountered while reading or writing an archive.
86///
87/// This wraps all errors that might be encountered by the library during its normal course of
88/// operation.
89#[derive(Error, Debug)]
90pub enum SquashfsError {
91	#[error("Input contains an invalid null character")] NullInput(#[from] std::ffi::NulError),
92	#[error("Encoded string is not valid UTF-8")] Utf8(#[from] std::string::FromUtf8Error),
93	#[error("OS string is not valid UTF-8")] OsUtf8(OsString),
94	#[error("{0}: {1}")] LibraryError(String, LibError),
95	#[error("{0}: Unknown error {1} in SquashFS library")] UnknownLibraryError(String, i32),
96	#[error("{0}: Squashfs library did not return expected value")] LibraryReturnError(String),
97	#[error("{0}")] LibraryNullError(String),
98	#[error("Symbolic link chain exceeds {0} elements")] LinkChain(i32), // Can I use a const in the formatting string?
99	#[error("Symbolic link loop detected containing {0}")] LinkLoop(PathBuf),
100	#[error("Dangling symbolic link from {0} to {1}")] DanglingLink(PathBuf, PathBuf),
101	#[error("{0} is type {1}, not {2}")] WrongType(String, String, String),
102	#[error("Tried to copy an object that can't be copied")] Copy,
103	#[error("Tried to get parent of a node with an unknown path")] NoPath,
104	#[error("Inode index {0} is not within limits 1..{1}")] Range(u64, u64),
105	#[error("Couldn't read file: {0}")] Read(#[from] std::io::Error),
106	#[error("The filesystem does not support the feature: {0}")] Unsupported(String),
107	#[error("Memory mapping failed: {0}")] Mmap(std::io::Error),
108	#[error("Couldn't get the current system time: {0}")] Time(#[from] std::time::SystemTimeError),
109	#[error("Refusing to create empty archive")] Empty,
110	#[error("Tried to write parent directory before child node {0}")] WriteOrder(u32),
111	#[error("Tried to write unknown or unsupported file type")] WriteType(std::fs::FileType),
112	#[error("Callback returned an error")] WrappedError(BoxedError),
113	#[error("Failed to retrieve xattrs for {0}: {1}")] Xattr(PathBuf, std::io::Error),
114	#[error("Tried to add files to a writer that was already finished")] Finished,
115	#[error("Internal error: {0}")] Internal(String),
116}
117
118/// Result type returned by SquashFS library operations.
119pub type Result<T> = std::result::Result<T, SquashfsError>;
120
121fn sfs_check(code: i32, desc: &str) -> Result<i32> {
122	match code {
123		i if i >= 0 => Ok(i),
124		i => match FromPrimitive::from_i32(i) {
125			Some(e) => Err(SquashfsError::LibraryError(desc.to_string(), e)),
126			None => Err(SquashfsError::UnknownLibraryError(desc.to_string(), i)),
127		},
128	}
129}
130
131fn sfs_destroy<T>(x: *mut T) {
132	unsafe {
133		let obj = x as *mut sqfs_object_t;
134		((*obj).destroy.expect("SquashFS object did not provide a destroy callback"))(obj);
135	}
136}
137
138fn libc_free<T>(x: *mut T) {
139	unsafe { libc::free(x as *mut _ as *mut libc::c_void); }
140}
141
142fn rust_dealloc<T>(x: *mut T) {
143	unsafe { std::alloc::dealloc(x as *mut u8, std::alloc::Layout::new::<T>()) }
144}
145
146fn unpack_meta_ref(meta_ref: u64) -> (u64, u64) {
147	(meta_ref >> 16 & 0xffffffff, meta_ref & 0xffff)
148}
149
150fn os_to_string(s: &OsStr) -> Result<String> {
151	Ok(s.to_str().ok_or_else(|| SquashfsError::OsUtf8(s.to_os_string()))?.to_string())
152}
153
154const NO_XATTRS: u32 = 0xffffffff;
155const LOCK_ERR: &str = "A thread panicked while holding a lock"; // Because poisoned locks only happen when a thread panics, we probably want to panic too.
156const LINK_MAX: i32 = 1000;
157const BLOCK_BUF_SIZE: usize = 4096;
158const PAD_TO: usize = 4096;
159
160struct ManagedPointer<T> {
161	ptr: *mut T,
162	destroy: fn(*mut T),
163}
164
165impl<T> ManagedPointer<T> {
166	fn null(destroy: fn(*mut T)) -> Self {
167		Self { ptr: ptr::null_mut(), destroy: destroy }
168	}
169
170	fn new(ptr: *mut T, destroy: fn(*mut T)) -> Self {
171		Self { ptr: ptr, destroy: destroy }
172	}
173	
174	fn as_const(&self) -> *const T {
175		self.ptr as *const T
176	}
177}
178
179impl<T> std::ops::Deref for ManagedPointer<T> {
180	type Target = *mut T;
181
182	fn deref(&self) -> &Self::Target {
183		&self.ptr
184	}
185}
186
187impl<T> std::ops::DerefMut for ManagedPointer<T> {
188	fn deref_mut(&mut self) -> &mut Self::Target {
189		&mut self.ptr
190	}
191}
192
193impl<T> Drop for ManagedPointer<T> {
194	fn drop(&mut self) {
195		(self.destroy)(**self)
196	}
197}
198
199impl<T> std::fmt::Debug for ManagedPointer<T> {
200	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201		write!(f, "ManagedPointer({:?})", self.ptr)
202	}
203}
204
205fn sfs_init<T>(init: &dyn Fn(*mut T) -> i32, err: &str) -> Result<T> {
206	let mut ret: MaybeUninit<T> = MaybeUninit::uninit();
207	sfs_check(init(ret.as_mut_ptr()), err)?;
208	Ok(unsafe { ret.assume_init() })
209}
210
211fn sfs_init_ptr<T>(init: &dyn Fn(*mut *mut T) -> i32, err: &str, destroy: fn(*mut T)) -> Result<ManagedPointer<T>> {
212	let mut ret: *mut T = ptr::null_mut();
213	sfs_check(init(&mut ret), err)?;
214	if ret.is_null() { Err(SquashfsError::LibraryReturnError(err.to_string())) }
215	else { Ok(ManagedPointer::new(ret, destroy)) }
216}
217
218fn sfs_init_check_null<T>(init: &dyn Fn() -> *mut T, err: &str, destroy: fn(*mut T)) -> Result<ManagedPointer<T>> {
219	let ret = init();
220	if ret.is_null() { Err(SquashfsError::LibraryNullError(err.to_string())) }
221	else { Ok(ManagedPointer::new(ret, destroy)) }
222}