1use crate::vfs::{
2 VfsError, VfsResult, VirtualDirEntry, VirtualFileSystem, VirtualStat, VirtualUtimeSpec,
3};
4use getrandom::getrandom;
5use web_time::{SystemTime, UNIX_EPOCH};
6
7const DEVICE_PATHS: &[&str] = &[
8 "/dev/null",
9 "/dev/zero",
10 "/dev/stdin",
11 "/dev/stdout",
12 "/dev/stderr",
13 "/dev/urandom",
14];
15
16const DEVICE_DIRS: &[&str] = &["/dev/fd", "/dev/pts"];
17const DEFAULT_STREAM_DEVICE_READ_BYTES: usize = 4096;
18const DEV_DIR_ENTRIES: &[(&str, bool)] = &[
19 ("null", false),
20 ("zero", false),
21 ("stdin", false),
22 ("stdout", false),
23 ("stderr", false),
24 ("urandom", false),
25 ("fd", true),
26];
27
28#[derive(Debug, Clone)]
29pub struct DeviceLayer<V> {
30 inner: V,
31}
32
33pub fn create_device_layer<V>(vfs: V) -> DeviceLayer<V> {
34 DeviceLayer { inner: vfs }
35}
36
37impl<V> DeviceLayer<V> {
38 pub fn into_inner(self) -> V {
39 self.inner
40 }
41
42 pub fn inner(&self) -> &V {
43 &self.inner
44 }
45
46 pub fn inner_mut(&mut self) -> &mut V {
47 &mut self.inner
48 }
49}
50
51impl<V: VirtualFileSystem> VirtualFileSystem for DeviceLayer<V> {
52 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
53 if let Some(bytes) = read_stream_device(path, DEFAULT_STREAM_DEVICE_READ_BYTES) {
54 return bytes;
55 }
56
57 if self
58 .inner
59 .stat(path)
60 .is_ok_and(|stat| is_null_character_device(&stat))
61 {
62 return Ok(Vec::new());
63 }
64
65 self.inner.read_file(path)
66 }
67
68 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
69 if path == "/dev" {
70 return Ok(DEV_DIR_ENTRIES
71 .iter()
72 .map(|(name, _)| String::from(*name))
73 .collect());
74 }
75 if DEVICE_DIRS.contains(&path) {
76 return Ok(Vec::new());
77 }
78 self.inner.read_dir(path)
79 }
80
81 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
82 if path == "/dev" {
83 let entries = DEV_DIR_ENTRIES
84 .iter()
85 .map(|(name, _)| String::from(*name))
86 .collect::<Vec<_>>();
87 if entries.len() > max_entries {
88 return Err(VfsError::new(
89 "ENOMEM",
90 format!(
91 "directory listing for '{path}' exceeds configured limit of {max_entries} entries"
92 ),
93 ));
94 }
95 return Ok(entries);
96 }
97 if DEVICE_DIRS.contains(&path) {
98 return Ok(Vec::new());
99 }
100 self.inner.read_dir_limited(path, max_entries)
101 }
102
103 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
104 if path == "/dev" {
105 return Ok(DEV_DIR_ENTRIES
106 .iter()
107 .map(|(name, is_directory)| VirtualDirEntry {
108 name: String::from(*name),
109 is_directory: *is_directory,
110 is_symbolic_link: false,
111 })
112 .collect());
113 }
114 if DEVICE_DIRS.contains(&path) {
115 return Ok(Vec::new());
116 }
117 self.inner.read_dir_with_types(path)
118 }
119
120 fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
121 if is_sink_device_path(path)
122 || self
123 .inner
124 .stat(path)
125 .is_ok_and(|stat| is_null_character_device(&stat))
126 {
127 let _ = content.into();
128 return Ok(());
129 }
130 self.inner.write_file(path, content)
131 }
132
133 fn create_file_exclusive(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
134 if is_device_path(path) || is_device_dir(path) {
135 let _ = content.into();
136 return Err(VfsError::new(
137 "EEXIST",
138 format!("file already exists, open '{path}'"),
139 ));
140 }
141 self.inner.create_file_exclusive(path, content)
142 }
143
144 fn append_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<u64> {
145 if is_sink_device_path(path)
146 || self
147 .inner
148 .stat(path)
149 .is_ok_and(|stat| is_null_character_device(&stat))
150 {
151 return Ok(content.into().len() as u64);
152 }
153 self.inner.append_file(path, content)
154 }
155
156 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
157 if is_device_dir(path) {
158 return Ok(());
159 }
160 self.inner.create_dir(path)
161 }
162
163 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
164 if is_device_dir(path) {
165 return Ok(());
166 }
167 self.inner.mkdir(path, recursive)
168 }
169
170 fn mknod(&mut self, path: &str, mode: u32, rdev: u64) -> VfsResult<()> {
171 if is_device_path(path) || is_device_dir(path) {
172 return Err(VfsError::new(
173 "EEXIST",
174 format!("device already exists: {path}"),
175 ));
176 }
177 self.inner.mknod(path, mode, rdev)
178 }
179
180 fn exists(&self, path: &str) -> bool {
181 if is_device_path(path) || is_device_dir(path) {
182 return true;
183 }
184 self.inner.exists(path)
185 }
186
187 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
188 if is_device_path(path) {
189 return Ok(device_stat(path));
190 }
191 if is_device_dir(path) {
192 return Ok(device_dir_stat(path));
193 }
194 self.inner.stat(path)
195 }
196
197 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
198 if is_device_path(path) {
199 return Err(VfsError::permission_denied("unlink", path));
200 }
201 self.inner.remove_file(path)
202 }
203
204 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
205 if is_device_dir(path) {
206 return Err(VfsError::permission_denied("rmdir", path));
207 }
208 self.inner.remove_dir(path)
209 }
210
211 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
212 if is_device_path(old_path) || is_device_path(new_path) {
213 return Err(VfsError::permission_denied("rename", old_path));
214 }
215 self.inner.rename(old_path, new_path)
216 }
217
218 fn realpath(&self, path: &str) -> VfsResult<String> {
219 if is_device_path(path) || is_device_dir(path) {
220 return Ok(String::from(path));
221 }
222 self.inner.realpath(path)
223 }
224
225 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
226 self.inner.symlink(target, link_path)
227 }
228
229 fn read_link(&self, path: &str) -> VfsResult<String> {
230 self.inner.read_link(path)
231 }
232
233 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
234 if is_device_path(path) {
235 return Ok(device_stat(path));
236 }
237 if is_device_dir(path) {
238 return Ok(device_dir_stat(path));
239 }
240 self.inner.lstat(path)
241 }
242
243 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
244 if is_device_path(old_path) {
245 return Err(VfsError::permission_denied("link", old_path));
246 }
247 self.inner.link(old_path, new_path)
248 }
249
250 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
251 if is_device_path(path) {
252 return Ok(());
253 }
254 self.inner.chmod(path, mode)
255 }
256
257 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
258 if is_device_path(path) {
259 return Ok(());
260 }
261 self.inner.chown(path, uid, gid)
262 }
263
264 fn chown_spec(
265 &mut self,
266 path: &str,
267 uid: u32,
268 gid: u32,
269 follow_symlinks: bool,
270 ) -> VfsResult<()> {
271 if is_device_path(path) {
272 return Ok(());
273 }
274 self.inner.chown_spec(path, uid, gid, follow_symlinks)
275 }
276
277 fn get_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
278 if is_device_path(path) || is_device_dir(path) {
279 return Err(VfsError::new(
280 "EOPNOTSUPP",
281 format!("extended attributes are not supported for device {path}"),
282 ));
283 }
284 self.inner.get_xattr(path, name, follow_symlinks)
285 }
286
287 fn list_xattrs(&mut self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
288 if is_device_path(path) || is_device_dir(path) {
289 return Err(VfsError::new(
290 "EOPNOTSUPP",
291 format!("extended attributes are not supported for device {path}"),
292 ));
293 }
294 self.inner.list_xattrs(path, follow_symlinks)
295 }
296
297 fn set_xattr(
298 &mut self,
299 path: &str,
300 name: &str,
301 value: Vec<u8>,
302 flags: u32,
303 follow_symlinks: bool,
304 ) -> VfsResult<()> {
305 if is_device_path(path) || is_device_dir(path) {
306 return Err(VfsError::new(
307 "EOPNOTSUPP",
308 format!("extended attributes are not supported for device {path}"),
309 ));
310 }
311 self.inner
312 .set_xattr(path, name, value, flags, follow_symlinks)
313 }
314
315 fn remove_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<()> {
316 if is_device_path(path) || is_device_dir(path) {
317 return Err(VfsError::new(
318 "EOPNOTSUPP",
319 format!("extended attributes are not supported for device {path}"),
320 ));
321 }
322 self.inner.remove_xattr(path, name, follow_symlinks)
323 }
324
325 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
326 if is_device_path(path) {
327 return Ok(());
328 }
329 self.inner.utimes(path, atime_ms, mtime_ms)
330 }
331
332 fn utimes_spec(
333 &mut self,
334 path: &str,
335 atime: VirtualUtimeSpec,
336 mtime: VirtualUtimeSpec,
337 follow_symlinks: bool,
338 ) -> VfsResult<()> {
339 if is_device_path(path) {
340 return Ok(());
341 }
342 self.inner.utimes_spec(path, atime, mtime, follow_symlinks)
343 }
344
345 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
346 if is_sink_device_path(path)
347 || self
348 .inner
349 .stat(path)
350 .is_ok_and(|stat| is_null_character_device(&stat))
351 {
352 let _ = length;
353 return Ok(());
354 }
355 self.inner.truncate(path, length)
356 }
357
358 fn sync(&mut self, path: &str) -> VfsResult<()> {
359 if is_device_path(path) || is_device_dir(path) {
360 return Err(VfsError::new(
361 "EINVAL",
362 format!("device does not support sync: {path}"),
363 ));
364 }
365 self.inner.sync(path)
366 }
367
368 fn allocate(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
369 if is_device_path(path) || is_device_dir(path) {
370 return Err(VfsError::new(
371 "EINVAL",
372 format!("device does not support allocation: {path}"),
373 ));
374 }
375 self.inner.allocate(path, offset, length)
376 }
377
378 fn insert_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
379 if is_device_path(path) || is_device_dir(path) {
380 return Err(VfsError::new(
381 "EINVAL",
382 format!("device does not support insert range: {path}"),
383 ));
384 }
385 self.inner.insert_range(path, offset, length)
386 }
387
388 fn collapse_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
389 if is_device_path(path) || is_device_dir(path) {
390 return Err(VfsError::new(
391 "EINVAL",
392 format!("device does not support collapse range: {path}"),
393 ));
394 }
395 self.inner.collapse_range(path, offset, length)
396 }
397
398 fn zero_range(
399 &mut self,
400 path: &str,
401 offset: u64,
402 length: u64,
403 keep_size: bool,
404 ) -> VfsResult<()> {
405 if is_device_path(path) || is_device_dir(path) {
406 return Err(VfsError::new(
407 "EINVAL",
408 format!("device does not support zero range: {path}"),
409 ));
410 }
411 self.inner.zero_range(path, offset, length, keep_size)
412 }
413
414 fn punch_hole(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
415 if is_device_path(path) || is_device_dir(path) {
416 return Err(VfsError::new(
417 "EINVAL",
418 format!("device does not support hole punching: {path}"),
419 ));
420 }
421 self.inner.punch_hole(path, offset, length)
422 }
423
424 fn allocated_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
425 if is_device_path(path) || is_device_dir(path) {
426 return Err(VfsError::new(
427 "EINVAL",
428 format!("device does not support extent mapping: {path}"),
429 ));
430 }
431 self.inner.allocated_ranges(path)
432 }
433
434 fn unwritten_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
435 if is_device_path(path) || is_device_dir(path) {
436 return Err(VfsError::new(
437 "EINVAL",
438 format!("device does not support extent mapping: {path}"),
439 ));
440 }
441 self.inner.unwritten_ranges(path)
442 }
443
444 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
445 if let Some(bytes) = read_stream_device(path, length) {
446 return bytes;
447 }
448
449 if self
450 .inner
451 .stat(path)
452 .is_ok_and(|stat| is_null_character_device(&stat))
453 {
454 return Ok(Vec::new());
455 }
456
457 self.inner.pread(path, offset, length)
458 }
459
460 fn pwrite(&mut self, path: &str, content: impl Into<Vec<u8>>, offset: u64) -> VfsResult<()> {
461 if is_sink_device_path(path)
462 || self
463 .inner
464 .stat(path)
465 .is_ok_and(|stat| is_null_character_device(&stat))
466 {
467 let _ = (content.into(), offset);
468 return Ok(());
469 }
470 self.inner.pwrite(path, content, offset)
471 }
472}
473
474fn is_device_path(path: &str) -> bool {
475 DEVICE_PATHS.contains(&path) || path.starts_with("/dev/fd/") || path.starts_with("/dev/pts/")
476}
477
478pub fn is_standard_device_path(path: &str) -> bool {
487 DEVICE_PATHS.contains(&path)
488}
489
490fn is_sink_device_path(path: &str) -> bool {
491 matches!(
492 path,
493 "/dev/null" | "/dev/zero" | "/dev/stdout" | "/dev/stderr" | "/dev/urandom"
494 )
495}
496
497fn is_device_dir(path: &str) -> bool {
498 path == "/dev" || DEVICE_DIRS.contains(&path)
499}
500
501fn device_stat(path: &str) -> VirtualStat {
502 let now = now_ms();
503 VirtualStat {
504 mode: 0o020666,
505 size: 0,
506 blocks: 0,
507 dev: 2,
508 rdev: device_rdev(path),
509 is_directory: false,
510 is_symbolic_link: false,
511 atime_ms: now,
512 atime_nsec: 0,
513 mtime_ms: now,
514 mtime_nsec: 0,
515 ctime_ms: now,
516 ctime_nsec: 0,
517 birthtime_ms: now,
518 ino: device_ino(path),
519 nlink: 1,
520 uid: 0,
521 gid: 0,
522 }
523}
524
525fn device_dir_stat(path: &str) -> VirtualStat {
526 let now = now_ms();
527 VirtualStat {
528 mode: 0o040755,
529 size: 0,
530 blocks: 0,
531 dev: 2,
532 rdev: 0,
533 is_directory: true,
534 is_symbolic_link: false,
535 atime_ms: now,
536 atime_nsec: 0,
537 mtime_ms: now,
538 mtime_nsec: 0,
539 ctime_ms: now,
540 ctime_nsec: 0,
541 birthtime_ms: now,
542 ino: device_ino(path),
543 nlink: 2,
544 uid: 0,
545 gid: 0,
546 }
547}
548
549fn device_ino(path: &str) -> u64 {
550 match path {
551 "/dev/null" => 0xffff_0001,
552 "/dev/zero" => 0xffff_0002,
553 "/dev/stdin" => 0xffff_0003,
554 "/dev/stdout" => 0xffff_0004,
555 "/dev/stderr" => 0xffff_0005,
556 "/dev/urandom" => 0xffff_0006,
557 _ => 0xffff_0000,
558 }
559}
560
561fn device_rdev(path: &str) -> u64 {
562 match path {
563 "/dev/null" => encode_device_id(1, 3),
564 "/dev/zero" => encode_device_id(1, 5),
565 "/dev/stdin" => encode_device_id(5, 0),
566 "/dev/stdout" => encode_device_id(5, 1),
567 "/dev/stderr" => encode_device_id(5, 2),
568 "/dev/urandom" => encode_device_id(1, 9),
569 _ => 0,
570 }
571}
572
573fn encode_device_id(major: u64, minor: u64) -> u64 {
574 (major << 8) | minor
575}
576
577fn is_null_character_device(stat: &VirtualStat) -> bool {
578 stat.mode & 0o170000 == 0o020000 && stat.rdev == encode_device_id(1, 3)
579}
580
581fn random_bytes(length: usize) -> VfsResult<Vec<u8>> {
582 let mut buffer = vec![0; length];
583 getrandom(&mut buffer)
584 .map_err(|error| VfsError::io(format!("failed to read system random bytes: {error}")))?;
585 Ok(buffer)
586}
587
588fn read_stream_device(path: &str, length: usize) -> Option<VfsResult<Vec<u8>>> {
589 match path {
590 "/dev/null" => Some(Ok(Vec::new())),
591 "/dev/zero" => Some(Ok(vec![0; length])),
592 "/dev/urandom" => Some(random_bytes(length)),
593 _ => None,
594 }
595}
596
597fn now_ms() -> u64 {
598 SystemTime::now()
599 .duration_since(UNIX_EPOCH)
600 .unwrap_or_default()
601 .as_millis() as u64
602}