This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

superconfig / tools / lazy-store / src / storefs.rs
16 kB 558 lines
1//! The file system mounted on /nix/store: the runner's real store (upper, 2//! PassthroughFs, all writes go there) merged with the lazy store paths 3//! (lower, LazyFs). 4//! 5//! Unlike a general overlay, the two only meet in the root directory: every 6//! entry of the store dir, i.e. every store path, is entirely in one layer 7//! (the upper one if it is in both). So nothing ever needs copying up, 8//! below the root each request goes straight to the layer of its inode, and 9//! both layers can hand their open files to the kernel (FUSE passthrough). 10//! Lower inodes are the LazyFs ones with LOWER set; PassthroughFs inode 11//! numbers stay below 2^56. 12 13use std::collections::{HashMap, HashSet}; 14use std::ffi::CStr; 15use std::io; 16use std::os::unix::fs::{DirEntryExt, FileTypeExt}; 17use std::path::PathBuf; 18use std::sync::atomic::{AtomicU64, Ordering}; 19use std::sync::{Arc, Mutex}; 20use std::time::Duration; 21 22use fuse_backend_rs::abi::fuse_abi::{stat64, statvfs64, CreateIn, FsOptions, SetattrValid}; 23use fuse_backend_rs::api::filesystem::{ 24 Context, DirEntry, Entry, FileSystem, GetxattrReply, ListxattrReply, OpenOptions, 25 ZeroCopyReader, ZeroCopyWriter, ROOT_ID, 26}; 27use fuse_backend_rs::passthrough::PassthroughFs; 28 29use crate::lazy::{LazyFs, INO_TAG}; 30 31const LOWER: u64 = INO_TAG; 32 33fn err(e: i32) -> io::Error { 34 io::Error::from_raw_os_error(e) 35} 36 37fn is_lower(ino: u64) -> bool { 38 ino & LOWER != 0 39} 40 41fn errno(e: &io::Error) -> i32 { 42 e.raw_os_error().unwrap_or(0) 43} 44 45struct RootEnt { 46 name: Vec<u8>, 47 ino: u64, 48 type_: u32, 49} 50 51pub struct StoreFs { 52 upper: PassthroughFs<()>, 53 upper_dir: PathBuf, 54 pub lower: LazyFs, 55 /// open handles of the root directory: a snapshot of the merged listing 56 root_dirs: Mutex<HashMap<u64, Arc<Vec<RootEnt>>>>, 57 next_root_handle: AtomicU64, 58} 59 60macro_rules! by_layer { 61 ($self:ident, $ino:expr, |$fs:ident, $i:ident| $e:expr) => { 62 if is_lower($ino) { 63 let $fs = &$self.lower; 64 let $i = $ino & !LOWER; 65 $e 66 } else { 67 let $fs = &$self.upper; 68 let $i = $ino; 69 $e 70 } 71 }; 72} 73 74/// Writes to the lower layer. 75macro_rules! upper_only { 76 ($self:ident, $ino:expr, |$fs:ident| $e:expr) => { 77 if is_lower($ino) { 78 Err(err(libc::EROFS)) 79 } else { 80 let $fs = &$self.upper; 81 $e 82 } 83 }; 84} 85 86fn tag(mut e: Entry) -> Entry { 87 if e.inode != 0 { 88 e.inode |= LOWER; 89 } 90 e 91} 92 93impl StoreFs { 94 pub fn new(upper: PassthroughFs<()>, upper_dir: PathBuf, lower: LazyFs) -> StoreFs { 95 StoreFs { 96 upper, 97 upper_dir, 98 lower, 99 root_dirs: Mutex::new(HashMap::new()), 100 next_root_handle: AtomicU64::new(1), 101 } 102 } 103 104 /// Creating `name` in `parent`: fail if it would hide a lower store path. 105 fn check_new(&self, parent: u64, name: &CStr) -> io::Result<()> { 106 if is_lower(parent) { 107 return Err(err(libc::EROFS)); 108 } 109 if parent == ROOT_ID && self.lower.has_root_entry(name) { 110 return Err(err(libc::EEXIST)); 111 } 112 Ok(()) 113 } 114 115 /// Removing `name` from `parent` in the upper layer failed with `e`. 116 fn rm_err(&self, parent: u64, name: &CStr, e: io::Error) -> io::Error { 117 if parent == ROOT_ID && errno(&e) == libc::ENOENT && self.lower.has_root_entry(name) { 118 err(libc::EROFS) 119 } else { 120 e 121 } 122 } 123 124 fn root_listing(&self) -> io::Result<Vec<RootEnt>> { 125 let mut out = Vec::new(); 126 let mut seen = HashSet::new(); 127 for d in std::fs::read_dir(&self.upper_dir)? { 128 let d = d?; 129 let t = d.file_type()?; 130 let type_ = if t.is_dir() { 131 libc::DT_DIR 132 } else if t.is_symlink() { 133 libc::DT_LNK 134 } else if t.is_file() { 135 libc::DT_REG 136 } else if t.is_char_device() { 137 libc::DT_CHR 138 } else { 139 libc::DT_UNKNOWN 140 }; 141 let name = d.file_name().into_encoded_bytes(); 142 seen.insert(name.clone()); 143 out.push(RootEnt { 144 name, 145 ino: d.ino(), 146 type_: type_ as u32, 147 }); 148 } 149 for (name, ino, type_) in self.lower.root_entries() { 150 if !seen.contains(name.as_bytes()) { 151 out.push(RootEnt { 152 name: name.as_bytes().to_vec(), 153 ino: ino | LOWER, 154 type_, 155 }); 156 } 157 } 158 Ok(out) 159 } 160} 161 162impl FileSystem for StoreFs { 163 type Inode = u64; 164 type Handle = u64; 165 166 fn init(&self, capable: FsOptions) -> io::Result<FsOptions> { 167 let mut opts = self.upper.init(capable)?; 168 let pt = opts.contains(FsOptions::PASSTHROUGH); 169 self.lower.set_passthrough(pt); 170 eprintln!( 171 "init: kernel FUSE passthrough {}", 172 if pt { 173 "on" 174 } else if capable.contains(FsOptions::PASSTHROUGH) { 175 "offered by the kernel but off" 176 } else { 177 "not offered by the kernel" 178 } 179 ); 180 // the merged root has no readdirplus 181 opts.remove( 182 FsOptions::DO_READDIRPLUS | FsOptions::READDIRPLUS_AUTO | FsOptions::PERFILE_DAX, 183 ); 184 Ok(opts) 185 } 186 187 fn destroy(&self) { 188 eprintln!("FUSE connection destroyed"); 189 } 190 191 fn lookup(&self, ctx: &Context, parent: u64, name: &CStr) -> io::Result<Entry> { 192 if parent == ROOT_ID { 193 let up = self.upper.lookup(ctx, ROOT_ID, name); 194 match &up { 195 Ok(e) if e.inode != 0 => return up, 196 Ok(_) => {} 197 Err(e) if errno(e) == libc::ENOENT => {} 198 Err(_) => return up, 199 } 200 return match self.lower.lookup(ctx, 1, name) { 201 Ok(e) => Ok(tag(e)), 202 Err(_) => up, 203 }; 204 } 205 if is_lower(parent) { 206 return self.lower.lookup(ctx, parent & !LOWER, name).map(tag); 207 } 208 self.upper.lookup(ctx, parent, name) 209 } 210 211 fn forget(&self, ctx: &Context, ino: u64, count: u64) { 212 if !is_lower(ino) { 213 self.upper.forget(ctx, ino, count) 214 } 215 } 216 217 fn batch_forget(&self, ctx: &Context, requests: Vec<(u64, u64)>) { 218 let up: Vec<_> = requests 219 .into_iter() 220 .filter(|(i, _)| !is_lower(*i)) 221 .collect(); 222 self.upper.batch_forget(ctx, up) 223 } 224 225 fn getattr(&self, ctx: &Context, ino: u64, h: Option<u64>) -> io::Result<(stat64, Duration)> { 226 by_layer!(self, ino, |fs, i| fs.getattr(ctx, i, h)) 227 } 228 229 fn setattr( 230 &self, 231 ctx: &Context, 232 ino: u64, 233 attr: stat64, 234 h: Option<u64>, 235 valid: SetattrValid, 236 ) -> io::Result<(stat64, Duration)> { 237 upper_only!(self, ino, |fs| fs.setattr(ctx, ino, attr, h, valid)) 238 } 239 240 fn readlink(&self, ctx: &Context, ino: u64) -> io::Result<Vec<u8>> { 241 by_layer!(self, ino, |fs, i| fs.readlink(ctx, i)) 242 } 243 244 fn symlink( 245 &self, 246 ctx: &Context, 247 linkname: &CStr, 248 parent: u64, 249 name: &CStr, 250 ) -> io::Result<Entry> { 251 self.check_new(parent, name)?; 252 self.upper.symlink(ctx, linkname, parent, name) 253 } 254 255 fn mknod( 256 &self, 257 ctx: &Context, 258 parent: u64, 259 name: &CStr, 260 mode: u32, 261 rdev: u32, 262 umask: u32, 263 ) -> io::Result<Entry> { 264 self.check_new(parent, name)?; 265 self.upper.mknod(ctx, parent, name, mode, rdev, umask) 266 } 267 268 fn mkdir( 269 &self, 270 ctx: &Context, 271 parent: u64, 272 name: &CStr, 273 mode: u32, 274 umask: u32, 275 ) -> io::Result<Entry> { 276 self.check_new(parent, name)?; 277 self.upper.mkdir(ctx, parent, name, mode, umask) 278 } 279 280 fn unlink(&self, ctx: &Context, parent: u64, name: &CStr) -> io::Result<()> { 281 upper_only!(self, parent, |fs| fs 282 .unlink(ctx, parent, name) 283 .map_err(|e| self.rm_err(parent, name, e))) 284 } 285 286 fn rmdir(&self, ctx: &Context, parent: u64, name: &CStr) -> io::Result<()> { 287 upper_only!(self, parent, |fs| fs 288 .rmdir(ctx, parent, name) 289 .map_err(|e| self.rm_err(parent, name, e))) 290 } 291 292 fn rename( 293 &self, 294 ctx: &Context, 295 olddir: u64, 296 oldname: &CStr, 297 newdir: u64, 298 newname: &CStr, 299 flags: u32, 300 ) -> io::Result<()> { 301 if is_lower(olddir) || is_lower(newdir) { 302 return Err(err(libc::EXDEV)); 303 } 304 // Renaming onto a lower store path hides it, as an overlay would. 305 self.upper 306 .rename(ctx, olddir, oldname, newdir, newname, flags) 307 .map_err(|e| match self.rm_err(olddir, oldname, e) { 308 e if errno(&e) == libc::EROFS => err(libc::EXDEV), 309 e => e, 310 }) 311 } 312 313 fn link(&self, ctx: &Context, ino: u64, newparent: u64, newname: &CStr) -> io::Result<Entry> { 314 if is_lower(ino) { 315 return Err(err(libc::EXDEV)); 316 } 317 self.check_new(newparent, newname)?; 318 self.upper.link(ctx, ino, newparent, newname) 319 } 320 321 fn open( 322 &self, 323 ctx: &Context, 324 ino: u64, 325 flags: u32, 326 fuse_flags: u32, 327 ) -> io::Result<(Option<u64>, OpenOptions, Option<u32>)> { 328 by_layer!(self, ino, |fs, i| fs.open(ctx, i, flags, fuse_flags)) 329 } 330 331 fn create( 332 &self, 333 ctx: &Context, 334 parent: u64, 335 name: &CStr, 336 args: CreateIn, 337 ) -> io::Result<(Entry, Option<u64>, OpenOptions, Option<u32>)> { 338 self.check_new(parent, name)?; 339 self.upper.create(ctx, parent, name, args) 340 } 341 342 fn read( 343 &self, 344 ctx: &Context, 345 ino: u64, 346 handle: u64, 347 w: &mut dyn ZeroCopyWriter, 348 size: u32, 349 offset: u64, 350 lock_owner: Option<u64>, 351 flags: u32, 352 ) -> io::Result<usize> { 353 by_layer!(self, ino, |fs, i| fs 354 .read(ctx, i, handle, w, size, offset, lock_owner, flags)) 355 } 356 357 fn write( 358 &self, 359 ctx: &Context, 360 ino: u64, 361 handle: u64, 362 r: &mut dyn ZeroCopyReader, 363 size: u32, 364 offset: u64, 365 lock_owner: Option<u64>, 366 delayed_write: bool, 367 flags: u32, 368 fuse_flags: u32, 369 ) -> io::Result<usize> { 370 upper_only!(self, ino, |fs| fs.write( 371 ctx, 372 ino, 373 handle, 374 r, 375 size, 376 offset, 377 lock_owner, 378 delayed_write, 379 flags, 380 fuse_flags 381 )) 382 } 383 384 fn flush(&self, ctx: &Context, ino: u64, handle: u64, lock_owner: u64) -> io::Result<()> { 385 by_layer!(self, ino, |fs, i| fs.flush(ctx, i, handle, lock_owner)) 386 } 387 388 fn fsync(&self, ctx: &Context, ino: u64, datasync: bool, handle: u64) -> io::Result<()> { 389 by_layer!(self, ino, |fs, i| fs.fsync(ctx, i, datasync, handle)) 390 } 391 392 fn fallocate( 393 &self, 394 ctx: &Context, 395 ino: u64, 396 handle: u64, 397 mode: u32, 398 offset: u64, 399 length: u64, 400 ) -> io::Result<()> { 401 upper_only!(self, ino, |fs| fs 402 .fallocate(ctx, ino, handle, mode, offset, length)) 403 } 404 405 fn release( 406 &self, 407 ctx: &Context, 408 ino: u64, 409 flags: u32, 410 handle: u64, 411 flush: bool, 412 flock_release: bool, 413 lock_owner: Option<u64>, 414 ) -> io::Result<()> { 415 by_layer!(self, ino, |fs, i| fs.release( 416 ctx, 417 i, 418 flags, 419 handle, 420 flush, 421 flock_release, 422 lock_owner 423 )) 424 } 425 426 fn statfs(&self, ctx: &Context, _ino: u64) -> io::Result<statvfs64> { 427 self.upper.statfs(ctx, ROOT_ID) 428 } 429 430 fn setxattr( 431 &self, 432 ctx: &Context, 433 ino: u64, 434 name: &CStr, 435 value: &[u8], 436 flags: u32, 437 ) -> io::Result<()> { 438 upper_only!(self, ino, |fs| fs.setxattr(ctx, ino, name, value, flags)) 439 } 440 441 fn getxattr( 442 &self, 443 ctx: &Context, 444 ino: u64, 445 name: &CStr, 446 size: u32, 447 ) -> io::Result<GetxattrReply> { 448 by_layer!(self, ino, |fs, i| fs.getxattr(ctx, i, name, size)) 449 } 450 451 fn listxattr(&self, ctx: &Context, ino: u64, size: u32) -> io::Result<ListxattrReply> { 452 by_layer!(self, ino, |fs, i| fs.listxattr(ctx, i, size)) 453 } 454 455 fn removexattr(&self, ctx: &Context, ino: u64, name: &CStr) -> io::Result<()> { 456 upper_only!(self, ino, |fs| fs.removexattr(ctx, ino, name)) 457 } 458 459 fn opendir( 460 &self, 461 ctx: &Context, 462 ino: u64, 463 flags: u32, 464 ) -> io::Result<(Option<u64>, OpenOptions)> { 465 if ino == ROOT_ID { 466 let h = self.next_root_handle.fetch_add(1, Ordering::Relaxed); 467 let l = Arc::new(self.root_listing()?); 468 self.root_dirs.lock().unwrap().insert(h, l); 469 return Ok((Some(h), OpenOptions::empty())); 470 } 471 by_layer!(self, ino, |fs, i| fs.opendir(ctx, i, flags)) 472 } 473 474 fn readdir( 475 &self, 476 ctx: &Context, 477 ino: u64, 478 handle: u64, 479 size: u32, 480 offset: u64, 481 add_entry: &mut dyn FnMut(DirEntry) -> io::Result<usize>, 482 ) -> io::Result<()> { 483 if ino == ROOT_ID { 484 let l = self 485 .root_dirs 486 .lock() 487 .unwrap() 488 .get(&handle) 489 .cloned() 490 .ok_or_else(|| err(libc::EBADF))?; 491 for (i, e) in l.iter().enumerate().skip(offset as usize) { 492 let n = add_entry(DirEntry { 493 ino: e.ino, 494 offset: i as u64 + 1, 495 type_: e.type_, 496 name: &e.name, 497 })?; 498 if n == 0 { 499 break; 500 } 501 } 502 return Ok(()); 503 } 504 by_layer!(self, ino, |fs, i| fs 505 .readdir(ctx, i, handle, size, offset, add_entry)) 506 } 507 508 fn fsyncdir(&self, ctx: &Context, ino: u64, datasync: bool, handle: u64) -> io::Result<()> { 509 if ino == ROOT_ID { 510 return std::fs::File::open(&self.upper_dir)?.sync_all(); 511 } 512 by_layer!(self, ino, |fs, i| fs.fsyncdir(ctx, i, datasync, handle)) 513 } 514 515 fn releasedir(&self, ctx: &Context, ino: u64, flags: u32, handle: u64) -> io::Result<()> { 516 if ino == ROOT_ID { 517 self.root_dirs.lock().unwrap().remove(&handle); 518 return Ok(()); 519 } 520 by_layer!(self, ino, |fs, i| fs.releasedir(ctx, i, flags, handle)) 521 } 522 523 fn access(&self, ctx: &Context, ino: u64, mask: u32) -> io::Result<()> { 524 by_layer!(self, ino, |fs, i| fs.access(ctx, i, mask)) 525 } 526 527 fn lseek( 528 &self, 529 ctx: &Context, 530 ino: u64, 531 handle: u64, 532 offset: u64, 533 whence: u32, 534 ) -> io::Result<u64> { 535 by_layer!(self, ino, |fs, i| fs.lseek(ctx, i, handle, offset, whence)) 536 } 537 538 fn copy_file_range( 539 &self, 540 ctx: &Context, 541 inode_in: u64, 542 fh_in: u64, 543 offset_in: u64, 544 inode_out: u64, 545 fh_out: u64, 546 offset_out: u64, 547 len: u64, 548 flags: u64, 549 ) -> io::Result<u32> { 550 if is_lower(inode_in) || is_lower(inode_out) { 551 // makes the caller fall back to read/write 552 return Err(err(libc::EXDEV)); 553 } 554 self.upper.copy_file_range( 555 ctx, inode_in, fh_in, offset_in, inode_out, fh_out, offset_out, len, flags, 556 ) 557 } 558}