|
| 1 | +#[cfg(not(windows))] |
| 2 | +use std::os::unix::prelude::OsStrExt; |
| 3 | +use std::{ |
| 4 | + collections::BTreeMap, |
| 5 | + ffi::{CString, OsStr}, |
| 6 | + path::Path, |
| 7 | + sync::RwLock, |
| 8 | + time::{Duration, SystemTime}, |
| 9 | +}; |
| 10 | + |
| 11 | +use fuse_mt::{ |
| 12 | + CallbackResult, DirectoryEntry, FileAttr, FileType, FilesystemMT, RequestInfo, ResultData, |
| 13 | + ResultEmpty, ResultEntry, ResultOpen, ResultReaddir, ResultSlice, ResultXattr, Xattr, |
| 14 | +}; |
| 15 | +use itertools::Itertools; |
| 16 | +use rustic_core::{ |
| 17 | + repofile::{Node, NodeType}, |
| 18 | + OpenFile, |
| 19 | +}; |
| 20 | +use rustic_core::{Id, IndexedFull, Repository}; |
| 21 | + |
| 22 | +pub(super) struct RusticFS<P, S> { |
| 23 | + repo: Repository<P, S>, |
| 24 | + root: Id, |
| 25 | + open_files: RwLock<BTreeMap<u64, OpenFile>>, |
| 26 | + now: SystemTime, |
| 27 | +} |
| 28 | + |
| 29 | +impl<P, S: IndexedFull> RusticFS<P, S> { |
| 30 | + pub(crate) fn from_node(repo: Repository<P, S>, node: Node) -> anyhow::Result<Self> { |
| 31 | + let open_files = RwLock::new(BTreeMap::new()); |
| 32 | + |
| 33 | + Ok(Self { |
| 34 | + repo, |
| 35 | + root: node.subtree.unwrap(), |
| 36 | + open_files, |
| 37 | + now: SystemTime::now(), |
| 38 | + }) |
| 39 | + } |
| 40 | + |
| 41 | + fn node_from_path(&self, path: &Path) -> Result<Node, i32> { |
| 42 | + Ok(self |
| 43 | + .repo |
| 44 | + .node_from_path(self.root, path) |
| 45 | + .map_err(|_| libc::ENOENT)?) |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +fn node_to_filetype(node: &Node) -> FileType { |
| 50 | + match node.node_type { |
| 51 | + NodeType::File => FileType::RegularFile, |
| 52 | + NodeType::Dir => FileType::Directory, |
| 53 | + NodeType::Symlink { .. } => FileType::Symlink, |
| 54 | + NodeType::Chardev { .. } => FileType::CharDevice, |
| 55 | + NodeType::Dev { .. } => FileType::BlockDevice, |
| 56 | + NodeType::Fifo => FileType::NamedPipe, |
| 57 | + NodeType::Socket => FileType::Socket, |
| 58 | + } |
| 59 | +} |
| 60 | + |
| 61 | +fn node_type_to_rdev(tpe: &NodeType) -> u32 { |
| 62 | + u32::try_from(match tpe { |
| 63 | + NodeType::Dev { device } => *device, |
| 64 | + NodeType::Chardev { device } => *device, |
| 65 | + _ => 0, |
| 66 | + }) |
| 67 | + .unwrap() |
| 68 | +} |
| 69 | + |
| 70 | +impl<P, S: IndexedFull> FilesystemMT for RusticFS<P, S> { |
| 71 | + fn getattr(&self, _req: RequestInfo, path: &Path, _fh: Option<u64>) -> ResultEntry { |
| 72 | + let node = self.node_from_path(path)?; |
| 73 | + Ok(( |
| 74 | + Duration::from_secs(1), |
| 75 | + FileAttr { |
| 76 | + /// Size in bytes |
| 77 | + size: node.meta.size, |
| 78 | + /// Size in blocks |
| 79 | + blocks: 0, |
| 80 | + // Time of last access |
| 81 | + atime: node.meta.atime.map(SystemTime::from).unwrap_or(self.now), |
| 82 | + /// Time of last modification |
| 83 | + mtime: node.meta.mtime.map(SystemTime::from).unwrap_or(self.now), |
| 84 | + /// Time of last metadata change |
| 85 | + ctime: node.meta.ctime.map(SystemTime::from).unwrap_or(self.now), |
| 86 | + /// Time of creation (macOS only) |
| 87 | + crtime: self.now, |
| 88 | + /// Kind of file (directory, file, pipe, etc.) |
| 89 | + kind: node_to_filetype(&node), |
| 90 | + /// Permissions |
| 91 | + perm: node.meta.mode.unwrap_or(0) as u16, |
| 92 | + /// Number of hard links |
| 93 | + nlink: node.meta.links.try_into().unwrap_or(1), |
| 94 | + /// User ID |
| 95 | + uid: node.meta.uid.unwrap_or(0), |
| 96 | + /// Group ID |
| 97 | + gid: node.meta.gid.unwrap_or(0), |
| 98 | + /// Device ID (if special file) |
| 99 | + rdev: node_type_to_rdev(&node.node_type), |
| 100 | + /// Flags (macOS only; see chflags(2)) |
| 101 | + flags: 0, |
| 102 | + }, |
| 103 | + )) |
| 104 | + } |
| 105 | + |
| 106 | + #[cfg(not(windows))] |
| 107 | + fn readlink(&self, _req: RequestInfo, path: &Path) -> ResultData { |
| 108 | + let node = self.node_from_path(path)?; |
| 109 | + if node.is_symlink() { |
| 110 | + let target = node.node_type.to_link().as_os_str().as_bytes(); |
| 111 | + Ok(target.to_vec()) |
| 112 | + } else { |
| 113 | + Err(libc::ENOSYS) |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + fn open(&self, _req: RequestInfo, path: &Path, _flags: u32) -> ResultOpen { |
| 118 | + let node = self.node_from_path(path)?; |
| 119 | + let open = self.repo.open_file(&node).map_err(|_| libc::ENOSYS)?; |
| 120 | + let mut open_files = self.open_files.write().unwrap(); |
| 121 | + let fh = open_files.first_key_value().map(|(fh, _)| *fh).unwrap_or(0); |
| 122 | + _ = open_files.insert(fh, open); |
| 123 | + Ok((fh, 0)) |
| 124 | + } |
| 125 | + |
| 126 | + fn release( |
| 127 | + &self, |
| 128 | + _req: RequestInfo, |
| 129 | + _path: &Path, |
| 130 | + fh: u64, |
| 131 | + _flags: u32, |
| 132 | + _lock_owner: u64, |
| 133 | + _flush: bool, |
| 134 | + ) -> ResultEmpty { |
| 135 | + _ = self.open_files.write().unwrap().remove(&fh); |
| 136 | + Ok(()) |
| 137 | + } |
| 138 | + |
| 139 | + fn read( |
| 140 | + &self, |
| 141 | + _req: RequestInfo, |
| 142 | + _path: &Path, |
| 143 | + fh: u64, |
| 144 | + offset: u64, |
| 145 | + size: u32, |
| 146 | + |
| 147 | + callback: impl FnOnce(ResultSlice<'_>) -> CallbackResult, |
| 148 | + ) -> CallbackResult { |
| 149 | + if let Some(open_file) = self.open_files.read().unwrap().get(&fh) { |
| 150 | + if let Ok(data) = |
| 151 | + self.repo |
| 152 | + .read_file_at(open_file, offset.try_into().unwrap(), size as usize) |
| 153 | + { |
| 154 | + return callback(Ok(&data)); |
| 155 | + } |
| 156 | + } |
| 157 | + callback(Err(libc::ENOSYS)) |
| 158 | + } |
| 159 | + |
| 160 | + fn opendir(&self, _req: RequestInfo, _path: &Path, _flags: u32) -> ResultOpen { |
| 161 | + Ok((0, 0)) |
| 162 | + } |
| 163 | + |
| 164 | + fn readdir(&self, _req: RequestInfo, path: &Path, _fh: u64) -> ResultReaddir { |
| 165 | + let node = self.node_from_path(path)?; |
| 166 | + |
| 167 | + let tree = self |
| 168 | + .repo |
| 169 | + .get_tree(&node.subtree.unwrap()) |
| 170 | + .map_err(|_| libc::ENOSYS)?; |
| 171 | + |
| 172 | + let result = tree |
| 173 | + .nodes |
| 174 | + .into_iter() |
| 175 | + .map(|node| DirectoryEntry { |
| 176 | + name: node.name(), |
| 177 | + kind: node_to_filetype(&node), |
| 178 | + }) |
| 179 | + .collect(); |
| 180 | + Ok(result) |
| 181 | + } |
| 182 | + |
| 183 | + fn releasedir(&self, _req: RequestInfo, _path: &Path, _fh: u64, _flags: u32) -> ResultEmpty { |
| 184 | + Ok(()) |
| 185 | + } |
| 186 | + |
| 187 | + fn listxattr(&self, _req: RequestInfo, path: &Path, size: u32) -> ResultXattr { |
| 188 | + let node = self.node_from_path(path)?; |
| 189 | + let xattrs = node |
| 190 | + .meta |
| 191 | + .extended_attributes |
| 192 | + .into_iter() |
| 193 | + // convert into null-terminated [u8] |
| 194 | + .map(|a| CString::new(a.name).unwrap().into_bytes_with_nul()) |
| 195 | + .concat(); |
| 196 | + |
| 197 | + if size == 0 { |
| 198 | + Ok(Xattr::Size(u32::try_from(xattrs.len()).unwrap())) |
| 199 | + } else { |
| 200 | + Ok(Xattr::Data(xattrs)) |
| 201 | + } |
| 202 | + } |
| 203 | + |
| 204 | + fn getxattr(&self, _req: RequestInfo, path: &Path, name: &OsStr, size: u32) -> ResultXattr { |
| 205 | + let node = self.node_from_path(path)?; |
| 206 | + match node |
| 207 | + .meta |
| 208 | + .extended_attributes |
| 209 | + .into_iter() |
| 210 | + .find(|a| name == OsStr::new(&a.name)) |
| 211 | + { |
| 212 | + None => Err(libc::ENOSYS), |
| 213 | + Some(attr) => { |
| 214 | + if size == 0 { |
| 215 | + Ok(Xattr::Size(u32::try_from(attr.value.len()).unwrap())) |
| 216 | + } else { |
| 217 | + Ok(Xattr::Data(attr.value)) |
| 218 | + } |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | +} |
0 commit comments