This repository has no description
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}