atproto repo as vfs
1use easy_fuser::{prelude::*, templates::DefaultFuseHandler};
2use tokio::runtime::Handle;
3
4use std::{
5 ffi::{OsStr, OsString},
6 io::{Cursor, Read, Seek, SeekFrom},
7 path::PathBuf,
8 sync::Arc,
9 time::UNIX_EPOCH,
10};
11
12use crate::{AtpFS, FileType};
13
14pub struct AtpFuse {
15 pub fs: Arc<AtpFS>,
16 pub inner: DefaultFuseHandler,
17 pub runtime: Handle,
18}
19
20impl AtpFuse {
21 fn path_to_str(&self, path: &std::path::Path) -> String {
22 // Strip leading '/' for VFS compatibility
23 path.to_str()
24 .unwrap_or("")
25 .trim_start_matches('/')
26 .to_string()
27 }
28
29 fn fileattr_for_root(&self) -> FileAttribute {
30 FileAttribute {
31 size: 0,
32 blocks: 0,
33 atime: UNIX_EPOCH,
34 mtime: UNIX_EPOCH,
35 ctime: UNIX_EPOCH,
36 crtime: UNIX_EPOCH,
37 kind: FileKind::Directory,
38 perm: 0o755,
39 nlink: 2,
40 uid: 1000,
41 gid: 1000,
42 rdev: 0,
43 flags: 0,
44 blksize: 512,
45 ttl: None,
46 generation: None,
47 }
48 }
49
50 fn vfs_metadata_attr(&self, vfs_path: &str) -> FuseResult<FileAttribute> {
51 let meta = self
52 .runtime
53 .block_on(self.fs.metadata(vfs_path))
54 .map_err(|_| ErrorKind::FileNotFound.to_error("Not found"))?;
55
56 let (kind, perm, nlink) = match meta.file_type {
57 FileType::Directory => (FileKind::Directory, 0o755, 2),
58 FileType::File => (FileKind::RegularFile, 0o644, 1),
59 };
60
61 Ok(FileAttribute {
62 size: meta.len,
63 blocks: (meta.len + 511) / 512,
64 atime: UNIX_EPOCH,
65 mtime: UNIX_EPOCH,
66 ctime: UNIX_EPOCH,
67 crtime: UNIX_EPOCH,
68 kind,
69 perm,
70 nlink,
71 uid: 1000,
72 gid: 1000,
73 rdev: 0,
74 flags: 0,
75 blksize: 512,
76 ttl: None,
77 generation: None,
78 })
79 }
80}
81
82impl FuseHandler<PathBuf> for AtpFuse {
83 fn get_inner(&self) -> &dyn FuseHandler<PathBuf> {
84 &self.inner
85 }
86
87 fn lookup(
88 &self,
89 _req: &RequestInfo,
90 parent_id: PathBuf,
91 name: &OsStr,
92 ) -> FuseResult<FileAttribute> {
93 let path = parent_id.join(name);
94 let vfs_path = self.path_to_str(&path);
95 self.vfs_metadata_attr(&vfs_path)
96 }
97
98 fn getattr(
99 &self,
100 _req: &RequestInfo,
101 file_id: PathBuf,
102 _fh: Option<BorrowedFileHandle>,
103 ) -> FuseResult<FileAttribute> {
104 // Root handling
105 if file_id.as_os_str().is_empty() || file_id == PathBuf::from("/") {
106 return Ok(self.fileattr_for_root());
107 }
108
109 let vfs_path = self.path_to_str(&file_id);
110 self.vfs_metadata_attr(&vfs_path)
111 }
112
113 fn readdir(
114 &self,
115 _req: &RequestInfo,
116 file_id: PathBuf,
117 _fh: BorrowedFileHandle,
118 ) -> FuseResult<Vec<(OsString, FileKind)>> {
119 let vfs_path = self.path_to_str(&file_id);
120
121 let stream = self
122 .runtime
123 .block_on(self.fs.read_dir(&vfs_path))
124 .map_err(|_| ErrorKind::InputOutputError.to_error("Read dir failed"))?;
125
126 let mut entries = vec![
127 (OsString::from("."), FileKind::Directory),
128 (OsString::from(".."), FileKind::Directory),
129 ];
130
131 for name in stream {
132 let kind = if name.ends_with(".json") {
133 FileKind::RegularFile
134 } else {
135 FileKind::Directory
136 };
137 entries.push((OsString::from(name), kind));
138 }
139
140 Ok(entries)
141 }
142
143 fn read(
144 &self,
145 _req: &RequestInfo,
146 file_id: PathBuf,
147 _fh: BorrowedFileHandle,
148 seek: SeekFrom,
149 size: u32,
150 _flags: FUSEOpenFlags,
151 _lock_owner: Option<u64>,
152 ) -> FuseResult<Vec<u8>> {
153 // Only support absolute start seeks for now.
154 let pos = match seek {
155 SeekFrom::Start(p) => p,
156 SeekFrom::Current(_) | SeekFrom::End(_) => {
157 return Err(ErrorKind::InputOutputError.to_error("Unsupported seek"))
158 }
159 };
160
161 if size == 0 {
162 return Ok(Vec::new());
163 }
164
165 let vfs_path = self.path_to_str(&file_id);
166 let data = self
167 .runtime
168 .block_on(self.fs.open_file(&vfs_path))
169 .map_err(|_| ErrorKind::FileNotFound.to_error("File not found"))?;
170 let mut reader = Cursor::new(data.as_slice());
171
172 // Seek to the requested position.
173 reader
174 .seek(SeekFrom::Start(pos))
175 .map_err(|_| ErrorKind::InputOutputError.to_error("Seek failed"))?;
176
177 // Read up to `size` bytes into the buffer.
178 let mut buf = vec![0u8; size as usize];
179 let n = reader
180 .read(&mut buf)
181 .map_err(|_| ErrorKind::InputOutputError.to_error("Read failed"))?;
182
183 buf.truncate(n);
184 Ok(buf)
185 }
186}