A working example where tables are displayed and rows and shown as files. Very hacky and doesn't feel right. Rust is rust smh.
use rusqlite::{Connection};
use std::collections::HashMap;
use std::time::{Duration, UNIX_EPOCH};
use std::ffi::OsStr;
use fuser::{
FileAttr, FileType, Filesystem, ReplyAttr, ReplyDirectory, ReplyEntry, Request, MountOption
};
#[derive(Debug)]
struct Table {
name: String,
}
struct RFS {
name_map: HashMap<String, FileAttr>,
inode_map: HashMap<u64, String>,
}
const TTL: Duration = Duration::from_secs(1);
fn generate_inode_dir(ino: u64) -> FileAttr {
FileAttr {
ino: ino,
size: 4096,
blocks: 0,
atime: UNIX_EPOCH,
mtime: UNIX_EPOCH,
ctime: UNIX_EPOCH,
crtime: UNIX_EPOCH,
kind: FileType::Directory,
perm: 0o755,
nlink: 2,
uid: 1000,
gid: 1000,
rdev: 0,
flags: 0,
blksize: 512,
padding: 0,
}
}
const DEFAULT_DIR_ATTR: FileAttr = FileAttr {
ino: 2,
size: 4096,
blocks: 0,
atime: UNIX_EPOCH,
mtime: UNIX_EPOCH,
ctime: UNIX_EPOCH,
crtime: UNIX_EPOCH,
kind: FileType::Directory,
perm: 0o755,
nlink: 2,
uid: 1000,
gid: 1000,
rdev: 0,
flags: 0,
blksize: 512,
padding: 0,
};
const DEFAULT_FILE_ATTR: FileAttr = FileAttr {
ino: 3,
size: 4096,
blocks: 1,
atime: UNIX_EPOCH,
mtime: UNIX_EPOCH,
ctime: UNIX_EPOCH,
crtime: UNIX_EPOCH,
kind: FileType::RegularFile,
perm: 0o644,
nlink: 1,
uid: 1000,
gid: 1000,
rdev: 0,
flags: 0,
blksize: 512,
padding: 0,
};
impl Filesystem for RFS {
fn getattr(&mut self, _req: &Request, ino: u64, reply: ReplyAttr) {
println!("Calling getattr: {}", ino);
if let Some(_) = self.inode_map.get(&ino) {
reply.attr(&TTL, &DEFAULT_DIR_ATTR);
} else {
if ino == 1 {
reply.attr(&TTL, &DEFAULT_DIR_ATTR);
} else {
reply.attr(&TTL, &DEFAULT_FILE_ATTR);
}
}
}
fn lookup(&mut self, _req: &Request, parent: u64, name: &OsStr, reply: ReplyEntry) {
println!("Calling lookup: {:?}, {:?}", name, parent);
let x: String = name.to_string_lossy().to_string();
if let Some(t) = self.name_map.get(&x) {
reply.entry(&TTL, &t, 0);
} else {
reply.entry(&TTL, &DEFAULT_FILE_ATTR, 0);
}
}
fn readdir(&mut self, _req: &Request, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory) {
println!("Calling readdir: {}", ino);
if ino == 1 {
let mut entries = vec![
(1, FileType::Directory, String::from(".")),
(1, FileType::Directory, String::from("..")),
];
let conn = Connection::open("example.db").unwrap();
let mut stmt = conn.prepare(
"select name from sqlite_master
where type = 'table'
and name not like 'sqlite_%';"
).unwrap();
let tables = stmt.query_map([], |row| {
Ok(Table {
name: row.get(0)?
})
}).unwrap();
let mut inode = 2;
let mut name_map = HashMap::new();
let mut inode_map = HashMap::new();
for table in tables {
if let Ok(t) = table {
let attr = generate_inode_dir(inode);
name_map.insert(t.name.clone(), attr);
inode_map.insert(inode, t.name.clone());
inode = inode + 1;
}
}
self.name_map = name_map;
self.inode_map = inode_map;
for (name, _) in &self.name_map {
entries.push((1, FileType::Directory, name.to_string()));
}
for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) {
if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) {
break;
}
}
reply.ok();
} else {
if let Some(table) = self.inode_map.get(&ino) {
println!("Reading...: {}", table);
let mut entries = vec![
(1, FileType::Directory, String::from(".")),
(1, FileType::Directory, String::from("..")),
];
let conn = Connection::open("example.db").unwrap();
let query = format!("select * from {};", table);
let mut stmt = conn.prepare(&query).unwrap();
let tables = stmt.query_map([], |row| {
Ok(Table {
name: row.get(0)?
})
}).unwrap();
let inode = 2;
let mut name_map = HashMap::new();
for table in tables {
if let Ok(t) = table {
let attr = generate_inode_dir(inode);
name_map.insert(t.name.clone(), attr);
}
}
for (name, _) in name_map {
entries.push((3, FileType::RegularFile, name.to_string()));
}
for (i, entry) in entries.into_iter().enumerate().skip(offset as usize) {
if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) {
break;
}
}
reply.ok();
} else {
println!("Not a directory!");
}
}
}
}
fn main() {
let mountpoint = "dir";
let mut options = vec![];
options.push(MountOption::AutoUnmount);
let conn = Connection::open("example.db").unwrap();
let mut stmt = conn.prepare(
"select name from sqlite_master
where type = 'table'
and name not like 'sqlite_%';"
).unwrap();
let tables = stmt.query_map([], |row| {
Ok(Table {
name: row.get(0)?
})
}).unwrap();
let mut inode = 2;
let mut name_map = HashMap::new();
let mut inode_map = HashMap::new();
for table in tables {
if let Ok(t) = table {
let attr = generate_inode_dir(inode);
name_map.insert(t.name.clone(), attr);
inode_map.insert(inode, t.name.clone());
inode = inode + 1;
}
}
fuser::mount2(RFS { name_map, inode_map }, mountpoint, &options).unwrap();
}