#![allow(non_camel_case_types)]
use crate::dir::DirEntry;
use crate::error::Error;
use crate::fs::Filesystem;
use crate::inode::{FileType, Inode};
use fs_core::{BlockRead, FileDevice};
use std::cell::RefCell;
use std::ffi::{c_char, c_int, c_void, CStr, CString};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::Arc;
thread_local! {
static LAST_ERROR: RefCell<(CString, c_int)> =
RefCell::new((CString::new("no error").unwrap(), 0));
}
const ENOENT: c_int = 2;
const EIO: c_int = 5;
const ENOTDIR: c_int = 20;
const EISDIR: c_int = 21;
const EINVAL: c_int = 22;
const EROFS_ERRNO: c_int = 30;
const ERANGE: c_int = 34;
const fn enotsup() -> c_int {
if cfg!(target_os = "macos") {
45
} else {
95
}
}
fn errno_for(e: &Error) -> c_int {
match e {
Error::NotFound => ENOENT,
Error::NotADirectory => ENOTDIR,
Error::NotAFile => EISDIR,
Error::ReadOnly => EROFS_ERRNO,
Error::InvalidGeometry(_) => EINVAL,
Error::UnsupportedFeature(_)
| Error::UnsupportedProfile(_)
| Error::UnsupportedChecksum(_) => enotsup(),
Error::NotBtrfs { .. }
| Error::BadSuperblock(_)
| Error::BadChunkItem(_)
| Error::ChecksumMismatch { .. }
| Error::BlockIdentityMismatch { .. }
| Error::UnmappedLogical(_)
| Error::DirtyLog
| Error::Io(_) => EIO,
}
}
fn set_error(message: String, errno: c_int) {
let c = CString::new(message).unwrap_or_else(|_| CString::new("error").unwrap());
LAST_ERROR.with(|e| *e.borrow_mut() = (c, errno));
}
fn record(e: &Error) {
set_error(e.to_string(), errno_for(e));
}
fn clear_error() {
LAST_ERROR.with(|e| *e.borrow_mut() = (CString::new("no error").unwrap(), 0));
}
fn guard<T>(fallback: T, f: impl FnOnce() -> T) -> T {
clear_error();
release_guard(fallback, f)
}
fn release_guard<T>(fallback: T, f: impl FnOnce() -> T) -> T {
match catch_unwind(AssertUnwindSafe(f)) {
Ok(v) => v,
Err(_) => {
set_error("internal error: the driver panicked".into(), EIO);
fallback
}
}
}
pub struct fs_btrfs_fs {
fs: Filesystem,
}
pub struct fs_btrfs_dir_iter {
entries: Vec<DirEntry>,
next: usize,
current: fs_btrfs_dirent_t,
}
#[repr(C)]
pub struct fs_btrfs_attr_t {
pub inode: u64,
pub mode: u32,
pub uid: u32,
pub gid: u32,
pub size: u64,
pub nbytes: u64,
pub atime: i64,
pub mtime: i64,
pub ctime: i64,
pub otime: i64,
pub link_count: u32,
pub file_type: u32,
}
#[repr(C)]
pub struct fs_btrfs_dirent_t {
pub inode: u64,
pub file_type: u8,
pub name_len: u8,
pub is_subvolume: u8,
pub name: [c_char; 256],
}
#[repr(C)]
pub struct fs_btrfs_volume_info_t {
pub sector_size: u32,
pub node_size: u32,
pub total_bytes: u64,
pub bytes_used: u64,
pub num_devices: u64,
pub csum_type: u16,
pub label: [c_char; 256],
pub fsid: [u8; 16],
pub metadata_uuid: [u8; 16],
pub feature_compat: u64,
pub feature_compat_ro: u64,
pub feature_incompat: u64,
}
pub type fs_btrfs_read_fn =
Option<unsafe extern "C" fn(*mut c_void, *mut c_void, u64, u64) -> c_int>;
#[repr(C)]
pub struct fs_btrfs_blockdev_cfg_t {
pub read: fs_btrfs_read_fn,
pub context: *mut c_void,
pub size_bytes: u64,
pub block_size: u32,
}
fn file_type_code(t: Option<FileType>) -> u32 {
match t {
Some(FileType::Regular) => 1,
Some(FileType::Directory) => 2,
Some(FileType::CharDevice) => 3,
Some(FileType::BlockDevice) => 4,
Some(FileType::Fifo) => 5,
Some(FileType::Socket) => 6,
Some(FileType::Symlink) => 7,
None => 0,
}
}
#[no_mangle]
pub extern "C" fn fs_btrfs_last_error() -> *const c_char {
LAST_ERROR.with(|e| e.borrow().0.as_ptr())
}
#[no_mangle]
pub extern "C" fn fs_btrfs_last_errno() -> c_int {
LAST_ERROR.with(|e| e.borrow().1)
}
unsafe fn borrow_bytes<'a>(p: *const c_char, what: &str) -> Option<&'a [u8]> {
if p.is_null() {
set_error(format!("{what} is NULL"), ENOENT);
return None;
}
Some(unsafe { CStr::from_ptr(p) }.to_bytes())
}
fn shown(path: &[u8]) -> std::borrow::Cow<'_, str> {
String::from_utf8_lossy(path)
}
unsafe fn borrow_str<'a>(p: *const c_char, what: &str) -> Option<&'a str> {
if p.is_null() {
set_error(format!("{what} is NULL"), ENOENT);
return None;
}
match unsafe { CStr::from_ptr(p) }.to_str() {
Ok(s) => Some(s),
Err(_) => {
set_error(format!("{what} is not valid UTF-8"), ENOENT);
None
}
}
}
fn mount_device(device: Arc<dyn BlockRead>) -> *mut fs_btrfs_fs {
match Filesystem::mount(device) {
Ok(fs) => Box::into_raw(Box::new(fs_btrfs_fs { fs })),
Err(e) => {
record(&e);
std::ptr::null_mut()
}
}
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_mount(device_path: *const c_char) -> *mut fs_btrfs_fs {
guard(std::ptr::null_mut(), || {
let Some(path) = (unsafe { borrow_str(device_path, "device_path") }) else {
return std::ptr::null_mut();
};
match FileDevice::open(path) {
Ok(dev) => mount_device(Arc::new(dev)),
Err(e) => {
set_error(format!("opening {path} failed: {e}"), ENOENT);
std::ptr::null_mut()
}
}
})
}
struct CallbackDevice {
read: unsafe extern "C" fn(*mut c_void, *mut c_void, u64, u64) -> c_int,
context: *mut c_void,
size: u64,
}
unsafe impl Send for CallbackDevice {}
unsafe impl Sync for CallbackDevice {}
impl BlockRead for CallbackDevice {
fn read_at(&self, offset: u64, buf: &mut [u8]) -> fs_core::Result<()> {
let rc = unsafe {
(self.read)(
self.context,
buf.as_mut_ptr().cast::<c_void>(),
offset,
buf.len() as u64,
)
};
if rc == 0 {
Ok(())
} else {
Err(fs_core::Error::Io(std::io::Error::other(format!(
"the caller's read callback returned {rc} for {} bytes at offset {offset}",
buf.len()
))))
}
}
fn size_bytes(&self) -> u64 {
self.size
}
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_mount_with_callbacks(
cfg: *const fs_btrfs_blockdev_cfg_t,
) -> *mut fs_btrfs_fs {
guard(std::ptr::null_mut(), || {
if cfg.is_null() {
set_error("cfg is NULL".into(), EIO);
return std::ptr::null_mut();
}
let cfg = unsafe { &*cfg };
let Some(read) = cfg.read else {
set_error("cfg.read is NULL".into(), EIO);
return std::ptr::null_mut();
};
mount_device(Arc::new(CallbackDevice {
read,
context: cfg.context,
size: cfg.size_bytes,
}))
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_mount_with_fs_core_device(
handle: *mut fs_core::ffi::FsCoreDevice,
) -> *mut fs_btrfs_fs {
guard(std::ptr::null_mut(), || {
if handle.is_null() {
set_error("fs_core handle is NULL".into(), EIO);
return std::ptr::null_mut();
}
let dev: std::sync::Arc<dyn fs_core::BlockDevice> = unsafe { (*handle).inner().clone() };
let read: std::sync::Arc<dyn BlockRead> = dev;
mount_device(read)
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_umount(fs: *mut fs_btrfs_fs) {
if fs.is_null() {
return;
}
release_guard((), || drop(unsafe { Box::from_raw(fs) }));
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_get_volume_info(
fs: *mut fs_btrfs_fs,
out: *mut fs_btrfs_volume_info_t,
) -> c_int {
guard(-1, || {
if fs.is_null() || out.is_null() {
set_error("fs or out is NULL".into(), EIO);
return -1;
}
let sb = unsafe { &*fs }.fs.superblock();
let mut label = [0 as c_char; 256];
for (slot, b) in label.iter_mut().zip(sb.label.as_bytes()).take(255) {
*slot = *b as c_char;
}
unsafe {
*out = fs_btrfs_volume_info_t {
sector_size: sb.sectorsize,
node_size: sb.nodesize,
total_bytes: sb.total_bytes,
bytes_used: sb.bytes_used,
num_devices: sb.num_devices,
csum_type: sb.csum_type.to_raw(),
label,
fsid: sb.fsid,
metadata_uuid: sb.metadata_uuid,
feature_compat: sb.compat_flags,
feature_compat_ro: sb.compat_ro_flags,
feature_incompat: sb.incompat_flags,
};
}
0
})
}
fn fill_attr(inode: &Inode, out: *mut fs_btrfs_attr_t) {
unsafe {
*out = fs_btrfs_attr_t {
inode: inode.ino,
mode: inode.mode,
uid: inode.uid,
gid: inode.gid,
size: inode.size,
nbytes: inode.nbytes,
atime: inode.atime.sec,
mtime: inode.mtime.sec,
ctime: inode.ctime.sec,
otime: inode.otime.sec,
link_count: inode.nlink,
file_type: file_type_code(inode.file_type()),
};
}
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_stat(
fs: *mut fs_btrfs_fs,
path: *const c_char,
out: *mut fs_btrfs_attr_t,
) -> c_int {
guard(-1, || {
if fs.is_null() || out.is_null() {
set_error("fs or out is NULL".into(), EIO);
return -1;
}
let Some(path) = (unsafe { borrow_bytes(path, "path") }) else {
return -1;
};
match unsafe { &*fs }.fs.lookup_path_bytes(path) {
Ok(inode) => {
fill_attr(&inode, out);
0
}
Err(e) => {
record(&e);
-1
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_stat_ino(
fs: *mut fs_btrfs_fs,
inode: u64,
out: *mut fs_btrfs_attr_t,
) -> c_int {
guard(-1, || {
if fs.is_null() || out.is_null() {
set_error("fs or out is NULL".into(), EIO);
return -1;
}
match unsafe { &*fs }.fs.read_inode(inode) {
Ok(i) => {
fill_attr(&i, out);
0
}
Err(e) => {
record(&e);
-1
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_dir_open(
fs: *mut fs_btrfs_fs,
path: *const c_char,
) -> *mut fs_btrfs_dir_iter {
guard(std::ptr::null_mut(), || {
if fs.is_null() {
set_error("fs is NULL".into(), EIO);
return std::ptr::null_mut();
}
let Some(path) = (unsafe { borrow_bytes(path, "path") }) else {
return std::ptr::null_mut();
};
match unsafe { &*fs }.fs.list_path_bytes(path) {
Ok(entries) => Box::into_raw(Box::new(fs_btrfs_dir_iter {
entries,
next: 0,
current: unsafe { std::mem::zeroed() },
})),
Err(e) => {
record(&e);
std::ptr::null_mut()
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_dir_next(
iter: *mut fs_btrfs_dir_iter,
) -> *const fs_btrfs_dirent_t {
guard(std::ptr::null(), || {
if iter.is_null() {
set_error("iter is NULL".into(), EIO);
return std::ptr::null();
}
let it = unsafe { &mut *iter };
let Some(e) = it.entries.get(it.next) else {
return std::ptr::null();
};
it.next += 1;
let mut name = [0 as c_char; 256];
let n = e.name.len().min(255);
for (slot, b) in name.iter_mut().zip(&e.name[..n]) {
*slot = *b as c_char;
}
it.current = fs_btrfs_dirent_t {
inode: e.ino,
file_type: file_type_code(e.ftype) as u8,
name_len: n as u8,
is_subvolume: u8::from(!e.is_inode()),
name,
};
&it.current
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_dir_close(iter: *mut fs_btrfs_dir_iter) {
if iter.is_null() {
return;
}
release_guard((), || drop(unsafe { Box::from_raw(iter) }));
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_read_file(
fs: *mut fs_btrfs_fs,
path: *const c_char,
buf: *mut c_void,
offset: u64,
length: u64,
) -> i64 {
guard(-1, || {
if fs.is_null() || buf.is_null() {
set_error("fs or buf is NULL".into(), EIO);
return -1;
}
let Some(path) = (unsafe { borrow_bytes(path, "path") }) else {
return -1;
};
let fs = &unsafe { &*fs }.fs;
let found = match fs.lookup_path_bytes(path) {
Ok(i) => i,
Err(e) => {
record(&e);
return -1;
}
};
let out = unsafe { std::slice::from_raw_parts_mut(buf.cast::<u8>(), length as usize) };
match fs.read_at(found.ino, offset, out) {
Ok(n) => n as i64,
Err(e) => {
record(&e);
-1
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_readlink(
fs: *mut fs_btrfs_fs,
path: *const c_char,
buf: *mut c_char,
bufsize: usize,
) -> c_int {
guard(-1, || {
if fs.is_null() || path.is_null() || buf.is_null() {
set_error("readlink: fs, path or buf is NULL".into(), EINVAL);
return -1;
}
let Some(path) = (unsafe { borrow_bytes(path, "path") }) else {
return -1;
};
let fs = &unsafe { &*fs }.fs;
let found = match fs.lookup_path_bytes(path) {
Ok(i) => i,
Err(e) => {
record(&e);
return -1;
}
};
if !found.is_symlink() {
set_error(
format!("readlink: {} is not a symbolic link", shown(path)),
EINVAL,
);
return -1;
}
match fs.read_link(found.ino) {
Ok(target) => {
let needed = target.len() + 1;
if needed > bufsize {
set_error(
format!(
"readlink buffer holds {bufsize} bytes, need {needed} for the target \
and its terminator"
),
ERANGE,
);
return -1;
}
let Ok(len) = c_int::try_from(target.len()) else {
set_error(
format!("readlink target of {} bytes is too long", target.len()),
EIO,
);
return -1;
};
let out = unsafe { std::slice::from_raw_parts_mut(buf.cast::<u8>(), needed) };
out[..target.len()].copy_from_slice(&target);
out[target.len()] = 0;
len
}
Err(e) => {
record(&e);
-1
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_listxattr(
fs: *mut fs_btrfs_fs,
path: *const c_char,
buf: *mut c_char,
bufsize: usize,
) -> i64 {
guard(-1, || {
if fs.is_null() {
set_error("fs is NULL".into(), EIO);
return -1;
}
let Some(path) = (unsafe { borrow_bytes(path, "path") }) else {
return -1;
};
let fs = &unsafe { &*fs }.fs;
let found = match fs.lookup_path_bytes(path) {
Ok(i) => i,
Err(e) => {
record(&e);
return -1;
}
};
let entries = match fs.list_xattrs(found.ino) {
Ok(v) => v,
Err(e) => {
record(&e);
return -1;
}
};
let required: usize = entries.iter().map(|e| e.name.len() + 1).sum();
if !buf.is_null() && bufsize > 0 {
let out = unsafe { std::slice::from_raw_parts_mut(buf.cast::<u8>(), bufsize) };
let mut pos = 0usize;
for e in &entries {
let needed = e.name.len() + 1;
if pos + needed > bufsize {
break;
}
out[pos..pos + e.name.len()].copy_from_slice(&e.name);
out[pos + e.name.len()] = 0;
pos += needed;
}
}
required as i64
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_getxattr(
fs: *mut fs_btrfs_fs,
path: *const c_char,
name: *const c_char,
buf: *mut c_void,
bufsize: usize,
) -> i64 {
guard(-1, || {
if fs.is_null() {
set_error("fs is NULL".into(), EIO);
return -1;
}
let Some(path) = (unsafe { borrow_bytes(path, "path") }) else {
return -1;
};
if name.is_null() {
set_error("name is NULL".into(), ENOENT);
return -1;
}
let name = unsafe { CStr::from_ptr(name) }.to_bytes();
let fs = &unsafe { &*fs }.fs;
let found = match fs.lookup_path_bytes(path) {
Ok(i) => i,
Err(e) => {
record(&e);
return -1;
}
};
let value = match fs.get_xattr(found.ino, name) {
Ok(Some(v)) => v,
Ok(None) => {
set_error(
format!(
"{} has no attribute {}",
shown(path),
String::from_utf8_lossy(name)
),
ENOENT,
);
return -1;
}
Err(e) => {
record(&e);
return -1;
}
};
if !buf.is_null() && bufsize > 0 {
let take = value.len().min(bufsize);
let out = unsafe { std::slice::from_raw_parts_mut(buf.cast::<u8>(), bufsize) };
out[..take].copy_from_slice(&value[..take]);
}
value.len() as i64
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_mount_rw(device_path: *const c_char) -> *mut fs_btrfs_fs {
guard(std::ptr::null_mut(), || {
let Some(path) = (unsafe { borrow_str(device_path, "device_path") }) else {
return std::ptr::null_mut();
};
match FileDevice::open_rw(path) {
Ok(dev) => match Filesystem::mount_rw(Arc::new(dev)) {
Ok(fs) => Box::into_raw(Box::new(fs_btrfs_fs { fs })),
Err(e) => {
record(&e);
std::ptr::null_mut()
}
},
Err(e) => {
set_error(format!("opening {path} for writing failed: {e}"), EIO);
std::ptr::null_mut()
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_is_writable(fs: *mut fs_btrfs_fs) -> c_int {
guard(0, || {
if fs.is_null() {
return 0;
}
c_int::from(unsafe { &*fs }.fs.is_writable())
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_can_write_in_place(
fs: *mut fs_btrfs_fs,
path: *const c_char,
) -> c_int {
guard(-1, || {
if fs.is_null() {
set_error("fs is NULL".into(), EIO);
return -1;
}
let Some(path) = (unsafe { borrow_bytes(path, "path") }) else {
return -1;
};
let fs = &unsafe { &*fs }.fs;
let found = match fs.lookup_path_bytes(path) {
Ok(i) => i,
Err(e) => {
record(&e);
return -1;
}
};
match fs.can_write_in_place(found.ino) {
Ok(yes) => c_int::from(yes),
Err(e) => {
record(&e);
-1
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn fs_btrfs_write_file(
fs: *mut fs_btrfs_fs,
path: *const c_char,
buf: *const c_void,
offset: u64,
length: u64,
) -> i64 {
guard(-1, || {
if fs.is_null() || buf.is_null() {
set_error("fs or buf is NULL".into(), EIO);
return -1;
}
let Some(path) = (unsafe { borrow_bytes(path, "path") }) else {
return -1;
};
let fs = &unsafe { &*fs }.fs;
let found = match fs.lookup_path_bytes(path) {
Ok(i) => i,
Err(e) => {
record(&e);
return -1;
}
};
let data = unsafe { std::slice::from_raw_parts(buf.cast::<u8>(), length as usize) };
match fs.write_at(found.ino, offset, data) {
Ok(n) => n as i64,
Err(e) => {
record(&e);
-1
}
}
})
}