RustForge

rust-kernel

Rustbelt: a hobby x86_64 kernel written in Rust

rust-kernel / src / fs / mod.rs
27888 bytes ยท raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
//! VFS (virtual file system): the layer every concrete file system plugs into.
//!
//! Modelled (very loosely) on Linux:
//!
//! * [`Inode`] -- one file or directory *inside some file system*. Knows how to
//!   read/write its bytes at an offset and (for directories) look up, create,
//!   list and remove children. ramfs, FAT32, ext2, exFAT and (read-only)
//!   btrfs implement it.
//! * [`FileSystem`] -- a mounted instance (e.g. "the FAT32 volume on vda1");
//!   gives you its root inode.
//! * the **mount table** -- which file system is attached at which path ("/",
//!   "/tmp", "/mnt/disk0"). `lookup("/tmp/x")` finds the longest matching
//!   mount point and walks the rest of the path from that file system's root.
//! * [`file::File`] -- an *open* file (what a file descriptor points to): an
//!   inode plus a current offset, or the console, or a pipe. The per-process
//!   fd table holds `Arc<dyn File>`s, so syscalls don't care what's behind it.
//!
//! Paths: every path is first turned into a clean absolute path by
//! [`normalize`] (relative paths are joined to the process's current
//! directory; ".", ".." and "//" are resolved by plain string processing).
//! That makes ".." work across mount points for free. Real kernels track
//! parent dentries instead.
//!
//! Symlinks (ext2 has them): [`lookup`] follows them. When a path component
//! is a symlink, its target is joined to the directory the link is in, the
//! rest of the path is appended, and the lookup starts over (at most 8
//! times, then ELOOP). Since ".." is handled as text, `link/..` is the
//! directory holding `link`, not the parent of the link's target. Linux does
//! the latter; ours is simpler and fine for a hobby kernel.

pub mod btrfs;
pub mod exfat;
pub mod lock;
pub mod lzo;
pub mod ext2;
pub mod fat;
pub mod file;
pub mod pipe;
pub mod procfs;
pub mod ramfs;

use crate::errno::Errno;
use alloc::{format, string::String, sync::Arc, vec::Vec};
use spin::Mutex;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InodeKind {
    File,
    Directory,
    /// A symbolic link: a small file holding a path ([`Inode::readlink`]).
    Symlink,
    /// A device file in /dev (opening it gives the driver's own `File`).
    Device,
    /// Stage 18: a Unix socket's name in the file system, made by `bind`.
    /// It holds no data: `connect` uses it to find the listening socket
    /// (net/unix.rs); `open` on it fails with ENXIO, like Linux.
    Socket,
}

/// One entry of a directory listing.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DirEntry {
    pub name: String,
    pub kind: InodeKind,
    /// The child's inode number (stage 17: what getdents64 reports as
    /// d_ino), or 0 if the file system has none (FAT, exFAT, /dev).
    pub ino: u64,
}

/// A file or directory inside a file system.
///
/// Default method bodies return the error a "wrong kind" of inode would give
/// (e.g. `lookup` on a file -> ENOTDIR), so implementations only override
/// what makes sense for them.
pub trait Inode: Send + Sync {
    fn kind(&self) -> InodeKind;
    /// Size in bytes (files) or number of entries (directories).
    fn size(&self) -> usize;
    /// Unix permission bits like 0o644, or 0 if the file system has none
    /// (FAT, ramfs).
    fn mode(&self) -> u32 {
        0
    }
    /// For a device file (stage 19): which device it is, as Linux numbers
    /// it (`makedev(major, minor)`, e.g. 5:1 for /dev/console). stat
    /// reports it as `st_rdev`. 0 for everything else.
    fn rdev(&self) -> u64 {
        0
    }
    /// The target path of a symlink.
    fn readlink(&self) -> Result<String, Errno> {
        Err(Errno::EINVAL)
    }
    /// Read bytes starting at `offset`; returns how many were read (0 = EOF).
    fn read_at(&self, _offset: usize, _buf: &mut [u8]) -> Result<usize, Errno> {
        Err(Errno::EISDIR)
    }
    /// Write bytes at `offset` (growing the file if needed).
    fn write_at(&self, _offset: usize, _buf: &[u8]) -> Result<usize, Errno> {
        Err(Errno::EISDIR)
    }
    /// Cut the file to length 0 (for `open(..., O_TRUNC)`, i.e. shell `>`).
    fn truncate(&self) -> Result<(), Errno> {
        Err(Errno::EISDIR)
    }
    /// Find a child of this directory by name.
    fn lookup(&self, _name: &str) -> Result<Arc<dyn Inode>, Errno> {
        Err(Errno::ENOTDIR)
    }
    /// Create a child of this directory.
    fn create(&self, _name: &str, _kind: InodeKind) -> Result<Arc<dyn Inode>, Errno> {
        Err(Errno::ENOTDIR)
    }
    /// All children of this directory (without "." and "..").
    fn list(&self) -> Result<Vec<DirEntry>, Errno> {
        Err(Errno::ENOTDIR)
    }
    /// Delete a child (a file, or an *empty* directory).
    fn remove(&self, _name: &str) -> Result<(), Errno> {
        Err(Errno::ENOTDIR)
    }
    /// Device files: the `File` that `open` should return (None = an
    /// ordinary inode, opened as an `InodeFile`).
    fn open_device(&self) -> Result<Option<Arc<dyn file::File>>, Errno> {
        Ok(None)
    }

    // --- Stage 16: changing a file's size, permissions and times ---------
    // (truncate/ftruncate, chmod & co., utimensat.)

    /// Make the file exactly `len` bytes long: cut off the end, or grow it
    /// with zero bytes. This default works for any file system that can
    /// truncate to 0 and write: growing writes one zero byte at the new
    /// end (the gap reads as zeros), shrinking keeps the first `len`
    /// bytes, truncates to 0 and writes them back. File systems that can
    /// do better (ramfs, FAT) override it.
    fn set_len(&self, len: usize) -> Result<(), Errno> {
        if self.kind() != InodeKind::File {
            return Err(Errno::EISDIR);
        }
        let size = self.size();
        if len == size {
            return Ok(());
        }
        if len > size {
            self.write_at(len - 1, &[0])?;
            return Ok(());
        }
        let mut keep = alloc::vec![0u8; len];
        let mut done = 0;
        while done < len {
            match self.read_at(done, &mut keep[done..])? {
                0 => break,
                n => done += n,
            }
        }
        self.truncate()?;
        if done > 0 {
            self.write_at(0, &keep[..done])?;
        }
        Ok(())
    }
    /// chmod: set the permission bits (0o7777 part of `mode`). EPERM if the
    /// file system can't store them (FAT, exFAT, /dev), EROFS if it's
    /// read-only (btrfs).
    fn set_mode(&self, _mode: u32) -> Result<(), Errno> {
        Err(Errno::EPERM)
    }
    /// The access, modification and status-change times, each as
    /// [seconds, nanoseconds] since 1970 -- or None if the file system
    /// doesn't keep them (stat then shows 0).
    fn times(&self) -> Option<[[i64; 2]; 3]> {
        None
    }
    /// utimensat: set the access and/or modification time (None = leave
    /// it). The default accepts and forgets them: FAT and exFAT have time
    /// fields, but our drivers don't write them yet. That's what `touch`
    /// needs to not fail.
    fn set_times(&self, _atime: Option<[i64; 2]>, _mtime: Option<[i64; 2]>) -> Result<(), Errno> {
        Ok(())
    }

    // --- Stage 17: links, owners, renames (coreutils / busybox) ----------

    /// A real inode number (ext2's, btrfs's, a ramfs node's address), or
    /// None: stat then hashes the path instead (FAT, exFAT, /dev).
    fn ino(&self) -> Option<u64> {
        None
    }
    /// How many names (hard links) the inode has; None = "1, or 2 for a
    /// directory" (file systems without hard links).
    fn nlink(&self) -> Option<u64> {
        None
    }
    /// Owner (uid, gid). File systems without owners say root (0, 0).
    fn owner(&self) -> (u32, u32) {
        (0, 0)
    }
    /// chown: set the owner and/or group (None = leave it). EPERM where
    /// the file system can't store them (FAT, exFAT, /dev, like Linux's
    /// vfat), EROFS on read-only btrfs.
    fn set_owner(&self, _uid: Option<u32>, _gid: Option<u32>) -> Result<(), Errno> {
        Err(Errno::EPERM)
    }
    /// Create a symlink `name` -> `target` in this directory. EPERM where
    /// the file system has no symlinks (FAT and exFAT: Linux says EPERM too).
    fn symlink(&self, _name: &str, _target: &str) -> Result<Arc<dyn Inode>, Errno> {
        Err(Errno::EPERM)
    }
    /// Hard link: add `name` in this directory for the existing `target`
    /// (same file system, checked by the caller). EPERM where there are no
    /// hard links (FAT, exFAT).
    fn link(&self, _name: &str, _target: &Arc<dyn Inode>) -> Result<(), Errno> {
        Err(Errno::EPERM)
    }
    /// Move entry `old` of this directory to `new` in `new_dir` (same file
    /// system), replacing what's there (the caller checked that's allowed).
    /// ENOSYS = "can't do it directly": `rename` then copies and deletes.
    fn rename_entry(&self, _old: &str, _new_dir: &Arc<dyn Inode>, _new: &str) -> Result<(), Errno> {
        Err(Errno::ENOSYS)
    }
    /// The concrete type behind the trait object, so a file system can
    /// recognise its own inodes (for link and rename). None = don't care.
    fn as_any(self: Arc<Self>) -> Option<Arc<dyn core::any::Any + Send + Sync>> {
        None
    }
}

/// Downcast an inode to a file system's own type (stage 17).
pub fn downcast<T: Send + Sync + 'static>(inode: &Arc<dyn Inode>) -> Option<Arc<T>> {
    inode.clone().as_any()?.downcast::<T>().ok()
}

/// What statfs/fstatfs report about a file system (stage 17).
#[derive(Debug, Clone, Copy, Default)]
pub struct StatFs {
    /// The file system's magic number (f_type), as in <linux/magic.h>.
    pub magic: u64,
    /// Block size in bytes, and the counts in those blocks.
    pub block_size: u64,
    pub blocks: u64,
    pub free_blocks: u64,
    /// Free blocks an ordinary user may use (ext2 keeps some for root).
    pub avail_blocks: u64,
    /// Inodes (0 = the file system doesn't count them).
    pub files: u64,
    pub free_files: u64,
    pub name_max: u64,
    pub read_only: bool,
}

pub const RAMFS_MAGIC: u64 = 0x8584_58f6;
pub const TMPFS_MAGIC: u64 = 0x0102_1994;
pub const EXT2_MAGIC: u64 = 0xEF53;
pub const MSDOS_MAGIC: u64 = 0x4d44;
pub const EXFAT_MAGIC: u64 = 0x2011_BAB0;
pub const BTRFS_MAGIC: u64 = 0x9123_683E;

/// The wall-clock time as [seconds, nanoseconds] since 1970 (file times).
pub fn now_timespec() -> [i64; 2] {
    let us = crate::time::realtime_us();
    [(us / 1_000_000) as i64, (us % 1_000_000 * 1000) as i64]
}

/// `Inode::mode` of a file whose mode was set to exactly 0 with chmod
/// (plain 0 means "this file system has no permissions").
pub const MODE_SET: u32 = 1 << 31;

/// A mounted file system.
pub trait FileSystem: Send + Sync {
    /// Type name, like the `-t` argument of `mount` ("ramfs", "fat32", ...).
    fn name(&self) -> &'static str;
    fn root(&self) -> Arc<dyn Inode>;
    /// Sizes and counts for statfs (stage 17). The default suits a
    /// memory-only file system that keeps no counts (/dev: devtmpfs).
    fn statfs(&self) -> StatFs {
        StatFs { magic: TMPFS_MAGIC, block_size: 4096, name_max: 255, ..Default::default() }
    }
}

struct Mount {
    path: String, // normalized, e.g. "/" or "/tmp"
    fs: Arc<dyn FileSystem>,
}

/// The mount table. Lookups take the lock only briefly (never while doing
/// I/O), so it's fine as a spinlock. Not used from interrupt handlers.
static MOUNTS: Mutex<Vec<Mount>> = Mutex::new(Vec::new());

/// Attach `fs` at `path` (absolute, like "/" or "/tmp").
pub fn mount(path: &str, fs: Arc<dyn FileSystem>) -> Result<(), Errno> {
    if !path.starts_with('/') {
        return Err(Errno::EINVAL);
    }
    let path = normalize("/", path)?;
    let mut mounts = MOUNTS.lock();
    if mounts.iter().any(|m| m.path == path) {
        return Err(Errno::EEXIST);
    }
    mounts.push(Mount { path, fs });
    Ok(())
}

/// (mount path, fs type) of every mount, for printing.
pub fn mounts() -> Vec<(String, &'static str)> {
    MOUNTS.lock().iter().map(|m| (m.path.clone(), m.fs.name())).collect()
}

/// Does mount point `mount` contain `path`? ("/tmp" contains "/tmp/x" but
/// not "/tmpfoo"; "/" contains everything.)
fn mount_contains(mount: &str, path: &str) -> bool {
    mount == "/"
        || path == mount
        || (path.starts_with(mount) && path.as_bytes().get(mount.len()) == Some(&b'/'))
}

/// Turn `path` into a clean absolute path. Relative paths start at `cwd`
/// (which must be absolute). "." is dropped, ".." removes the previous
/// component (".." of "/" is "/", like Unix).
///   normalize("/home", "../etc//motd") == "/etc/motd"
pub fn normalize(cwd: &str, path: &str) -> Result<String, Errno> {
    if path.is_empty() {
        return Err(Errno::ENOENT); // like Linux: "" is not a path
    }
    let mut parts: Vec<&str> = Vec::new();
    let joined = if path.starts_with('/') { String::from(path) } else { format!("{}/{}", cwd, path) };
    for component in joined.split('/') {
        match component {
            "" | "." => {}
            ".." => {
                parts.pop();
            }
            name if name.len() > 255 => return Err(Errno::ENAMETOOLONG),
            name => parts.push(name),
        }
    }
    let result = format!("/{}", parts.join("/"));
    if result.len() > 4096 {
        return Err(Errno::ENAMETOOLONG);
    }
    Ok(result)
}

/// Resolve an absolute path to an inode, following symlinks.
pub fn lookup(path: &str) -> Result<Arc<dyn Inode>, Errno> {
    walk(path, true, 0).map(|(inode, _)| inode)
}

/// Like [`lookup`] (or `lookup_nofollow`), but also say where the inode
/// really is: the path with every symlink on the way replaced by its
/// target (stage 18). stat uses it for st_dev (a link on one file system
/// can point into another), and Unix sockets for their key.
pub fn lookup_resolved(path: &str, follow_last: bool) -> Result<(Arc<dyn Inode>, String), Errno> {
    walk(path, follow_last, 0)
}

/// Like [`lookup`], but if the *last* component is a symlink, return the
/// link itself (for `readlink`, and for deleting a link, not its target).
pub fn lookup_nofollow(path: &str) -> Result<Arc<dyn Inode>, Errno> {
    walk(path, false, 0).map(|(inode, _)| inode)
}

/// The target of the symlink at absolute `path`.
pub fn readlink(path: &str) -> Result<String, Errno> {
    lookup_nofollow(path)?.readlink()
}

/// How many symlinks one lookup may follow (Linux allows 40).
const MAX_SYMLINKS: usize = 8;

fn walk(path: &str, follow_last: bool, links_followed: usize) -> Result<(Arc<dyn Inode>, String), Errno> {
    if !path.starts_with('/') {
        return Err(Errno::EINVAL); // callers resolve relative paths first
    }
    let path = normalize("/", path)?;
    // Longest mount point that contains the path wins.
    let (root, mount_path, rest) = {
        let mounts = MOUNTS.lock();
        let m = mounts
            .iter()
            .filter(|m| mount_contains(&m.path, &path))
            .max_by_key(|m| m.path.len())
            .ok_or(Errno::ENOENT)?;
        let rest = String::from(if m.path == "/" { &path[..] } else { &path[m.path.len()..] });
        (m.fs.root(), m.path.clone(), rest)
    };
    // Walk the remaining components ("a/b/c") from that file system's root.
    let names: Vec<&str> = rest.split('/').filter(|c| !c.is_empty()).collect();
    let mut inode = root;
    let mut dir = mount_path; // absolute path of `inode` so far
    for (i, name) in names.iter().enumerate() {
        let child = inode.lookup(name)?;
        let last = i + 1 == names.len();
        if child.kind() == InodeKind::Symlink && (follow_last || !last) {
            if links_followed == MAX_SYMLINKS {
                return Err(Errno::ELOOP);
            }
            // Replace "dir/link/rest..." by "dir/<target>/rest..." and start over.
            let mut new_path = normalize(&dir, &child.readlink()?)?;
            for rest in &names[i + 1..] {
                new_path.push('/');
                new_path.push_str(rest);
            }
            return walk(&new_path, follow_last, links_followed + 1);
        }
        dir = if dir == "/" { format!("/{}", name) } else { format!("{}/{}", dir, name) };
        inode = child;
    }
    Ok((inode, dir))
}

/// Split a normalized path into (parent directory inode, last component).
fn parent_of(path: &str) -> Result<(Arc<dyn Inode>, String), Errno> {
    let path = normalize("/", path)?;
    let (parent, name) = path.rsplit_once('/').ok_or(Errno::EINVAL)?;
    if name.is_empty() {
        return Err(Errno::EEXIST); // "/" itself
    }
    Ok((lookup(if parent.is_empty() { "/" } else { parent })?, String::from(name)))
}

/// Create a file or directory at absolute `path` (parent must exist).
pub fn create(path: &str, kind: InodeKind) -> Result<Arc<dyn Inode>, Errno> {
    let (parent, name) = parent_of(path)?;
    parent.create(&name, kind)
}

/// Delete the file (`want_dir` false, like `unlink`) or empty directory
/// (`want_dir` true, like `rmdir`) at absolute `path`.
pub fn remove(path: &str, want_dir: bool) -> Result<(), Errno> {
    let normalized = normalize("/", path)?;
    if MOUNTS.lock().iter().any(|m| m.path == normalized) {
        return Err(Errno::EBUSY); // can't delete a mount point
    }
    // Don't follow a symlink here: `rm link` deletes the link.
    let inode = lookup_nofollow(&normalized)?;
    match (inode.kind(), want_dir) {
        (InodeKind::Directory, false) => return Err(Errno::EISDIR),
        (InodeKind::File | InodeKind::Symlink | InodeKind::Socket, true) => return Err(Errno::ENOTDIR),
        _ => {}
    }
    let (parent, name) = parent_of(&normalized)?;
    parent.remove(&name)
}

/// The mount point `path` (absolute, normalized) lives under.
fn mount_of(path: &str) -> Option<String> {
    let mounts = MOUNTS.lock();
    mounts.iter().filter(|m| mount_contains(&m.path, path)).max_by_key(|m| m.path.len()).map(|m| m.path.clone())
}

/// The file system `path` lives on and its place in the mount table
/// (stage 17: statfs, and st_dev = place + 1 so that inode numbers of
/// different file systems never look like the same file).
pub fn fs_of(path: &str) -> Option<(usize, Arc<dyn FileSystem>)> {
    let path = normalize("/", path).ok()?;
    let mounts = MOUNTS.lock();
    mounts
        .iter()
        .enumerate()
        .filter(|(_, m)| mount_contains(&m.path, &path))
        .max_by_key(|(_, m)| m.path.len())
        .map(|(i, m)| (i, m.fs.clone()))
}

/// st_dev for a path: which mounted file system (see `fs_of`).
pub fn dev_of(path: &str) -> u64 {
    fs_of(path).map(|(i, _)| i as u64 + 1).unwrap_or(0)
}

/// symlink(target, path): a new symlink at absolute `path` holding
/// `target` exactly as given (it's resolved when the link is followed).
pub fn symlink(target: &str, path: &str) -> Result<Arc<dyn Inode>, Errno> {
    if target.is_empty() {
        return Err(Errno::ENOENT);
    }
    if lookup_nofollow(path).is_ok() {
        return Err(Errno::EEXIST);
    }
    let (parent, name) = parent_of(path)?;
    parent.symlink(&name, target)
}

/// link(old, new): a second name for the file at `old` (`follow`: if `old`
/// is a symlink, link what it points to, like linkat's AT_SYMLINK_FOLLOW).
pub fn link(old: &str, new: &str, follow: bool) -> Result<(), Errno> {
    let (old, new) = (normalize("/", old)?, normalize("/", new)?);
    let src = if follow { lookup(&old)? } else { lookup_nofollow(&old)? };
    link_inode(&src, &old, &new)
}

/// Hard-link the inode `src` (found at `src_path`) as `new`.
pub fn link_inode(src: &Arc<dyn Inode>, src_path: &str, new: &str) -> Result<(), Errno> {
    if src.kind() == InodeKind::Directory {
        return Err(Errno::EPERM); // no hard links to directories, ever
    }
    let new = normalize("/", new)?;
    if lookup_nofollow(&new).is_ok() {
        return Err(Errno::EEXIST);
    }
    if mount_of(src_path) != mount_of(&new) {
        return Err(Errno::EXDEV);
    }
    let (parent, name) = parent_of(&new)?;
    parent.link(&name, src)
}

/// rename(old, new), and renameat2's RENAME_NOREPLACE (`noreplace`: fail
/// with EEXIST instead of replacing `new`).
///
/// ramfs, ext2 and FAT move the directory entry itself (stage 17,
/// `Inode::rename_entry`): any file, symlink or directory, full or not,
/// and hard links stay links. Other file systems (exFAT) get the long way
/// round, enough for "save to a temp file, rename it over the real one":
///   - a file: copy its bytes to `new` (replacing a file there), then
///     delete `old`;
///   - an empty directory: create `new` (or reuse an empty one), delete `old`;
///   - anything else (a non-empty directory, a symlink), or across file
///     systems: EXDEV, which tells `mv` to copy and delete itself, like
///     Linux does across file systems.
/// Neither is atomic like Linux's: a crash in the middle can leave both.
pub fn rename(old: &str, new: &str, noreplace: bool) -> Result<(), Errno> {
    let (old, new) = (normalize("/", old)?, normalize("/", new)?);
    let src = lookup_nofollow(&old)?;
    if new.starts_with(&old) && new.as_bytes().get(old.len()) == Some(&b'/') {
        return Err(Errno::EINVAL); // a directory into itself
    }
    let existing = lookup_nofollow(&new).ok();
    if noreplace && existing.is_some() {
        return Err(Errno::EEXIST); // (even renaming a file onto itself)
    }
    if old == new {
        return Ok(());
    }
    if MOUNTS.lock().iter().any(|m| m.path == old || m.path == new) {
        return Err(Errno::EBUSY);
    }
    if mount_of(&old) != mount_of(&new) {
        return Err(Errno::EXDEV);
    }
    // What may be replaced: a directory only by a directory, and only an
    // empty one; anything else only by a non-directory.
    if let Some(dst) = &existing {
        if src.ino().is_some() && src.ino() == dst.ino() {
            return Ok(()); // two hard links of one file: nothing to do (POSIX)
        }
        match (src.kind() == InodeKind::Directory, dst.kind() == InodeKind::Directory) {
            (true, false) => return Err(Errno::ENOTDIR),
            (false, true) => return Err(Errno::EISDIR),
            (true, true) if !dst.list()?.is_empty() => return Err(Errno::ENOTEMPTY),
            _ => {}
        }
    }
    let (old_parent, old_name) = parent_of(&old)?;
    let (new_parent, new_name) = parent_of(&new)?;
    match old_parent.rename_entry(&old_name, &new_parent, &new_name) {
        Err(Errno::ENOSYS) => {} // not supported here: the long way below
        other => return other,
    }
    match src.kind() {
        InodeKind::File => {
            if existing.is_some() {
                remove(&new, false)?;
            }
            let data = read_all(&old)?;
            let dst = create(&new, InodeKind::File)?;
            let mut done = 0;
            while done < data.len() {
                done += dst.write_at(done, &data[done..])?;
            }
            remove(&old, false)
        }
        InodeKind::Directory => {
            if !src.list()?.is_empty() {
                return Err(Errno::EXDEV);
            }
            if existing.is_none() {
                create(&new, InodeKind::Directory)?;
            }
            remove(&old, true)
        }
        _ => Err(Errno::EXDEV),
    }
}

/// Read a whole file into memory (used to load programs).
pub fn read_all(path: &str) -> Result<Vec<u8>, Errno> {
    let inode = lookup(path)?;
    if inode.kind() == InodeKind::Directory {
        return Err(Errno::EISDIR);
    }
    let mut data = alloc::vec![0u8; inode.size()];
    let mut done = 0;
    while done < data.len() {
        match inode.read_at(done, &mut data[done..])? {
            0 => break,
            n => done += n,
        }
    }
    data.truncate(done);
    Ok(data)
}

/// Boot-time file system setup: a ramfs as "/", with a few files, and a
/// second, empty ramfs mounted at "/tmp" (to exercise the mount table).
pub fn init() {
    let root = ramfs::RamFs::new();
    let dir = root.root();
    let hello = dir.create("hello.txt", InodeKind::File).unwrap();
    hello.write_at(0, b"Hello from the ramfs! This is /hello.txt.\n").unwrap();
    let etc = dir.create("etc", InodeKind::Directory).unwrap();
    let motd = etc.create("motd", InodeKind::File).unwrap();
    motd.write_at(0, b"Welcome to Tyler's kernel. Next up: SMP, then networking.\n").unwrap();
    dir.create("tmp", InodeKind::Directory).unwrap(); // mount point
    dir.create("mnt", InodeKind::Directory).unwrap(); // disks go to /mnt/diskN
    dir.create("dev", InodeKind::Directory).unwrap(); // device files (dev.rs)
    dir.create("proc", InodeKind::Directory).unwrap(); // stage 18: /proc/self (procfs.rs)
    mount("/", root).unwrap();
    mount("/tmp", ramfs::RamFs::new()).unwrap();
    mount("/dev", Arc::new(crate::dev::DevFs)).unwrap();
    mount("/proc", Arc::new(procfs::ProcFs)).unwrap();
}

/// Try to mount every registered partition (and unpartitioned disk) at
/// /mnt/disk0, /mnt/disk1, ... Returns log lines.
///
/// Which driver? Like Linux's `mount` without `-t`, we look for each file
/// system's *magic number*: ext2 has 0xEF53 at byte 56 of its superblock
/// (which starts at byte 1024); exFAT says "EXFAT   " at byte 3 of its boot
/// sector; btrfs has "_BHRfS_M" at byte 64 of its superblock (at 64 KiB);
/// anything else is tried as FAT (fatfs checks the boot sector itself).
/// A disk whose driver refuses it (say, a btrfs with sha256 checksums)
/// gets a log line saying why, and no /mnt/diskN.
pub fn mount_disks() -> Vec<String> {
    let mut log = Vec::new();
    let devices = crate::block::devices();
    let mut n = 0;
    for (name, dev) in &devices {
        // Skip a whole disk if it has partitions ("vda" when "vda1" exists).
        let has_partitions = devices.iter().any(|(other, _)| other.len() > name.len() && other.starts_with(name.as_str()));
        if has_partitions {
            continue;
        }
        let path = format!("/mnt/disk{}", n);
        let (fs, description): (Arc<dyn FileSystem>, String) = if ext2::probe(&**dev) {
            match ext2::Ext2Fs::mount(dev.clone()) {
                Ok(fs) => {
                    let d = fs.describe();
                    (fs, d)
                }
                Err(e) => {
                    log.push(format!("{}: ext2, but can't mount it: {:?}", name, e));
                    continue;
                }
            }
        } else if exfat::probe(&**dev) {
            match exfat::ExfatFs::mount(dev.clone()) {
                Ok(fs) => {
                    let d = fs.describe();
                    (fs, d)
                }
                Err(e) => {
                    log.push(format!("{}: exFAT, but can't mount it: {:?}", name, e));
                    continue;
                }
            }
        } else if btrfs::probe(&**dev) {
            match btrfs::BtrfsFs::mount(dev.clone()) {
                Ok(fs) => {
                    let d = fs.describe();
                    (fs, d)
                }
                Err(e) => {
                    log.push(format!("{}: btrfs, but can't mount it: {}", name, e));
                    continue;
                }
            }
        } else {
            match fat::FatFs::mount(dev.clone()) {
                Ok(fs) => {
                    let d = format!("FAT32 volume \"{}\"", fs.label());
                    (fs, d)
                }
                Err(e) => {
                    log.push(format!("{}: no file system we know ({:?})", name, e));
                    continue;
                }
            }
        };
        let _ = create(&path, InodeKind::Directory); // mount point (may exist)
        match mount(&path, fs) {
            Ok(()) => log.push(format!("{}: {} mounted at {}", name, description, path)),
            Err(e) => log.push(format!("{}: mount at {} failed: {:?}", name, path, e)),
        }
        n += 1;
    }
    log
}