Эх сурвалжийг харах

Merge pull request #20 from SMS-Derfflinger/ext4-support

Ext4 fs read support. Closes #14
greatbridf 7 сар өмнө
parent
commit
dd114cb94d

+ 11 - 0
Cargo.lock

@@ -144,6 +144,7 @@ dependencies = [
  "eonix_preempt",
  "eonix_runtime",
  "eonix_sync",
+ "ext4_rs",
  "intrusive-collections",
  "intrusive_list",
  "itertools",
@@ -246,6 +247,16 @@ dependencies = [
  "intrusive-collections",
 ]
 
+[[package]]
+name = "ext4_rs"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a1a97344bde15b0ace15e265dab27228d4bdc37a0bfa8548c5645d7cfa6a144"
+dependencies = [
+ "bitflags",
+ "log",
+]
+
 [[package]]
 name = "fdt"
 version = "0.1.5"

+ 1 - 0
Cargo.toml

@@ -30,6 +30,7 @@ itertools = { version = "0.13.0", default-features = false }
 acpi = "5.2.0"
 align_ext = "0.1.0"
 xmas-elf = "0.10.0"
+ext4_rs = "1.3.2"
 
 [target.'cfg(target_arch = "riscv64")'.dependencies]
 virtio-drivers = { version = "0.11.0" }

+ 18 - 5
Makefile.src

@@ -7,6 +7,8 @@ QEMU ?= ##QEMU##
 GDB ?= ##GDB##
 FDISK ?= ##FDISK##
 
+IMG ?= ##IMAGE##
+
 COMMA := ,
 
 PROFILE = $(MODE)
@@ -46,17 +48,21 @@ BINARY_DIR := $(BINARY_DIR_BASE)/$(MODE)
 QEMU_ARGS += \
 	-machine virt -kernel $(BINARY_DIR)/eonix_kernel \
 	-device virtio-blk-device,drive=disk0,bus=virtio-mmio-bus.0 \
-	-device virtio-blk-device,drive=disk1,bus=virtio-mmio-bus.1 \
 	-device virtio-net-device,netdev=mynet0 \
 	-drive id=disk0,file=build/boot-riscv64.img,format=raw,if=none \
-	-drive id=disk1,file=build/fs-riscv64.img,format=raw,if=none \
 	-netdev user,id=mynet0 \
 	-rtc base=utc
 
+ifneq ($(IMG),)
+QEMU_ARGS += \
+	-drive id=disk1,file=$(IMG),format=raw,if=none \
+	-device virtio-blk-device,drive=disk1,bus=virtio-mmio-bus.1
+endif
+
 CARGO_FLAGS += --target riscv64gc-unknown-none-elf
 
 .PHONY: build
-build: $(BINARY_DIR)/eonix_kernel build/boot-riscv64.img build/fs-riscv64.img
+build: $(BINARY_DIR)/eonix_kernel build/boot-riscv64.img
 
 else ifeq ($(ARCH),x86_64)
 
@@ -65,11 +71,18 @@ BINARY_DIR := $(BINARY_DIR_BASE)/$(MODE)
 
 QEMU_ARGS += \
 	-machine q35 \
-	-device ahci,id=ahci -device ide-hd,drive=disk,bus=ahci.0 \
+	-device ahci,id=ahci \
+	-device ide-hd,drive=disk0,bus=ahci.0 \
 	-device e1000e,netdev=mynet0 \
-	-drive id=disk,file=build/boot-x86_64.img,format=raw,if=none \
+	-drive id=disk0,file=build/boot-x86_64.img,format=raw,if=none \
 	-netdev user,id=mynet0
 
+ifneq ($(IMG),)
+QEMU_ARGS += \
+	-drive id=disk1,file=$(IMG),format=raw,if=none \
+	-device ide-hd,drive=disk1,bus=ahci.1
+endif
+
 CARGO_FLAGS += --target x86_64-unknown-none.json
 
 .PHONY: build

+ 2 - 0
README.md

@@ -205,6 +205,7 @@ make tmux-debug
 - `QEMU`: 用于调试运行的 QEMU。默认使用 `qemu-system-$(ARCH)`。
 - `GDB`: 用于 `make debug` 的 GDB。我们将默认查找 `$(ARCH)-elf-gdb` 并检查支持的架构。
 - `FDISK`: 用于创建磁盘镜像分区表的 fdisk 可执行文件,要求使用来自 util-linux 版本的 fdisk。默认使用 `fdisk`。
+- `IMG`: 除启动磁盘以外,额外的磁盘镜像文件。默认不使用。
 
 在运行 make 时可以指定的额外选项:
 
@@ -217,3 +218,4 @@ make tmux-debug
 - `FDISK`: 手动指定 fdisk 路径。
 - `QEMU_ACCEL`: 手动指定要使用的 qemu 加速方法。
 - `DEBUG_TRAPS`: 是否要进行 trap 的调试,使 qemu 输出详细的 trap 日志。
+- `FEATURES`: 手动指定要编译的特性,使用逗号分隔。具体见 `Cargo.toml` 中的 `features` 字段。

+ 8 - 0
configure

@@ -112,9 +112,17 @@ else
     echo `which mkfs.fat`
 fi
 
+event "checking additional image"
+if [ "$IMG" = "" ]; then
+    echo "no"
+else
+    echo "$IMG"
+fi
+
 cp Makefile.src Makefile
 sed -i '' -e "s|##DEFAULT_ARCH##|$DEFAULT_ARCH|" Makefile > /dev/null 2>&1
 sed -i '' -e "s|##GDB##|$GDB|" Makefile > /dev/null 2>&1
 sed -i '' -e "s|##QEMU##|$QEMU|" Makefile > /dev/null 2>&1
 sed -i '' -e "s|##FDISK##|$FDISK|" Makefile > /dev/null 2>&1
+sed -i '' -e "s|##IMAGE##|$IMG|" Makefile > /dev/null 2>&1
 exit 0

+ 6 - 3
src/driver/virtio.rs

@@ -7,7 +7,7 @@ use crate::kernel::{
     block::{make_device, BlockDevice},
     mem::{AsMemoryBlock, MemoryBlock, Page},
 };
-use alloc::sync::Arc;
+use alloc::{sync::Arc, vec::Vec};
 use core::num::NonZero;
 use eonix_hal::{arch_exported::fdt::FDT, mm::ArchPhysAccess};
 use eonix_log::{println_info, println_warn};
@@ -84,7 +84,7 @@ unsafe impl Hal for HAL {
 
 pub fn init_virtio_devices() {
     let mut disk_id = 0;
-    for reg in FDT
+    let mut virtio_devices: Vec<_> = FDT
         .all_nodes()
         .filter(|node| {
             node.compatible()
@@ -92,7 +92,10 @@ pub fn init_virtio_devices() {
         })
         .filter_map(|node| node.reg())
         .flatten()
-    {
+        .collect();
+    virtio_devices.sort_by_key(|reg| reg.starting_address);
+
+    for reg in virtio_devices {
         let base = PAddr::from(reg.starting_address as usize);
         let size = reg.size.expect("Virtio device must have a size");
 

+ 311 - 0
src/fs/ext4.rs

@@ -0,0 +1,311 @@
+use core::sync::atomic::{AtomicU32, AtomicU64};
+
+use crate::{
+    io::{Buffer, ByteBuffer},
+    kernel::{
+        block::BlockDevice,
+        constants::EIO,
+        vfs::{
+            dentry::Dentry,
+            inode::{define_struct_inode, AtomicNlink, Ino, Inode, InodeData},
+            mount::{register_filesystem, Mount, MountCreator},
+            s_isdir, s_isreg,
+            vfs::Vfs,
+            DevId, FsContext, TimeSpec,
+        },
+    },
+    path::Path,
+    prelude::*,
+};
+use alloc::{
+    collections::btree_map::{BTreeMap, Entry},
+    sync::Arc,
+};
+use eonix_runtime::task::Task;
+use eonix_sync::RwLock;
+use ext4_rs::{BlockDevice as Ext4BlockDeviceTrait, Ext4Error};
+use ext4_rs::{Errno, Ext4};
+
+pub struct Ext4BlockDevice {
+    device: Arc<BlockDevice>,
+}
+
+impl Ext4BlockDevice {
+    pub fn new(device: Arc<BlockDevice>) -> Self {
+        Self { device }
+    }
+}
+
+impl Ext4BlockDeviceTrait for Ext4BlockDevice {
+    fn read_offset(&self, offset: usize) -> Vec<u8> {
+        let mut buffer = vec![0u8; 4096];
+        let mut byte_buffer = ByteBuffer::new(buffer.as_mut_slice());
+
+        let _ = self
+            .device
+            .read_some(offset, &mut byte_buffer)
+            .expect("Failed to read from block device");
+
+        buffer
+    }
+
+    fn write_offset(&self, _offset: usize, _data: &[u8]) {
+        todo!()
+    }
+}
+
+impl_any!(Ext4Fs);
+struct Ext4Fs {
+    inner: Ext4,
+    device: Arc<BlockDevice>,
+    icache: RwLock<BTreeMap<Ino, Ext4Inode>>,
+}
+
+impl Vfs for Ext4Fs {
+    fn io_blksize(&self) -> usize {
+        4096
+    }
+
+    fn fs_devid(&self) -> DevId {
+        self.device.devid()
+    }
+
+    fn is_read_only(&self) -> bool {
+        true
+    }
+}
+
+impl Ext4Fs {
+    fn try_get(&self, icache: &BTreeMap<Ino, Ext4Inode>, ino: u64) -> Option<Arc<dyn Inode>> {
+        icache.get(&ino).cloned().map(Ext4Inode::into_inner)
+    }
+
+    fn get_or_insert(
+        &self,
+        icache: &mut BTreeMap<Ino, Ext4Inode>,
+        mut idata: InodeData,
+    ) -> Arc<dyn Inode> {
+        match icache.entry(idata.ino) {
+            Entry::Occupied(occupied) => occupied.get().clone().into_inner(),
+            Entry::Vacant(vacant) => {
+                let mode = *idata.mode.get_mut();
+                if s_isreg(mode) {
+                    vacant
+                        .insert(Ext4Inode::File(Arc::new(FileInode { idata })))
+                        .clone()
+                        .into_inner()
+                } else if s_isdir(mode) {
+                    vacant
+                        .insert(Ext4Inode::Dir(Arc::new(DirInode { idata })))
+                        .clone()
+                        .into_inner()
+                } else {
+                    println_warn!("ext4: Unsupported inode type: {mode:#o}");
+                    vacant
+                        .insert(Ext4Inode::File(Arc::new(FileInode { idata })))
+                        .clone()
+                        .into_inner()
+                }
+            }
+        }
+    }
+}
+
+impl Ext4Fs {
+    pub fn create(device: Arc<BlockDevice>) -> KResult<(Arc<Self>, Arc<dyn Inode>)> {
+        let ext4_device = Ext4BlockDevice::new(device.clone());
+        let ext4 = Ext4::open(Arc::new(ext4_device));
+
+        let ext4fs = Arc::new(Self {
+            inner: ext4,
+            device,
+            icache: RwLock::new(BTreeMap::new()),
+        });
+
+        let root_inode = {
+            let mut icache = Task::block_on(ext4fs.icache.write());
+            let root_inode = ext4fs.inner.get_inode_ref(2);
+
+            ext4fs.get_or_insert(
+                &mut icache,
+                InodeData {
+                    ino: root_inode.inode_num as Ino,
+                    size: AtomicU64::new(root_inode.inode.size()),
+                    nlink: AtomicNlink::new(root_inode.inode.links_count() as _),
+                    uid: AtomicU32::new(root_inode.inode.uid() as _),
+                    gid: AtomicU32::new(root_inode.inode.gid() as _),
+                    mode: AtomicU32::new(root_inode.inode.mode() as _),
+                    atime: Spin::new(TimeSpec {
+                        sec: root_inode.inode.atime() as _,
+                        nsec: root_inode.inode.i_atime_extra() as _,
+                    }),
+                    ctime: Spin::new(TimeSpec {
+                        sec: root_inode.inode.ctime() as _,
+                        nsec: root_inode.inode.i_ctime_extra() as _,
+                    }),
+                    mtime: Spin::new(TimeSpec {
+                        sec: root_inode.inode.mtime() as _,
+                        nsec: root_inode.inode.i_mtime_extra() as _,
+                    }),
+                    rwsem: RwLock::new(()),
+                    vfs: Arc::downgrade(&ext4fs) as _,
+                },
+            )
+        };
+
+        Ok((ext4fs, root_inode))
+    }
+}
+
+#[derive(Clone)]
+enum Ext4Inode {
+    File(Arc<FileInode>),
+    Dir(Arc<DirInode>),
+}
+
+impl Ext4Inode {
+    fn into_inner(self) -> Arc<dyn Inode> {
+        match self {
+            Ext4Inode::File(inode) => inode,
+            Ext4Inode::Dir(inode) => inode,
+        }
+    }
+}
+
+define_struct_inode! {
+    struct FileInode;
+}
+
+define_struct_inode! {
+    struct DirInode;
+}
+
+impl Inode for FileInode {
+    fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize> {
+        let vfs = self.vfs.upgrade().ok_or(EIO)?;
+        let ext4fs = vfs.as_any().downcast_ref::<Ext4Fs>().unwrap();
+
+        let mut temp_buf = vec![0u8; buffer.total()];
+        match ext4fs.inner.read_at(self.ino as u32, offset, &mut temp_buf) {
+            Ok(bytes_read) => {
+                let _ = buffer.fill(&temp_buf[..bytes_read])?;
+                Ok(buffer.wrote())
+            }
+            Err(e) => Err(e.error() as u32),
+        }
+    }
+}
+
+impl Inode for DirInode {
+    fn lookup(&self, dentry: &Arc<Dentry>) -> KResult<Option<Arc<dyn Inode>>> {
+        let vfs = self.vfs.upgrade().ok_or(EIO)?;
+        let ext4fs = vfs.as_any().downcast_ref::<Ext4Fs>().unwrap();
+
+        let name = String::from_utf8_lossy(&dentry.name());
+        let lookup_result = ext4fs.inner.fuse_lookup(self.ino, &name);
+
+        const EXT4_ERROR_ENOENT: Ext4Error = Ext4Error::new(Errno::ENOENT);
+        let attr = match lookup_result {
+            Ok(attr) => attr,
+            Err(EXT4_ERROR_ENOENT) => return Ok(None),
+            Err(error) => return Err(error.error() as u32),
+        };
+
+        // Fast path: if the inode is already in the cache, return it.
+        if let Some(inode) = ext4fs.try_get(&Task::block_on(ext4fs.icache.read()), attr.ino as u64)
+        {
+            return Ok(Some(inode));
+        }
+
+        let extra_perm = attr.perm.bits() as u32 & 0o7000;
+        let perm = attr.perm.bits() as u32 & 0o0700;
+        let real_perm = extra_perm | perm | perm >> 3 | perm >> 6;
+
+        // Create a new inode based on the attributes.
+        let mut icache = Task::block_on(ext4fs.icache.write());
+        let inode = ext4fs.get_or_insert(
+            &mut icache,
+            InodeData {
+                ino: attr.ino as Ino,
+                size: AtomicU64::new(attr.size),
+                nlink: AtomicNlink::new(attr.nlink as _),
+                uid: AtomicU32::new(attr.uid),
+                gid: AtomicU32::new(attr.gid),
+                mode: AtomicU32::new(attr.kind.bits() as u32 | real_perm),
+                atime: Spin::new(TimeSpec {
+                    sec: attr.atime as _,
+                    nsec: 0,
+                }),
+                ctime: Spin::new(TimeSpec {
+                    sec: attr.ctime as _,
+                    nsec: 0,
+                }),
+                mtime: Spin::new(TimeSpec {
+                    sec: attr.mtime as _,
+                    nsec: 0,
+                }),
+                rwsem: RwLock::new(()),
+                vfs: self.vfs.clone(),
+            },
+        );
+
+        Ok(Some(inode))
+    }
+
+    fn do_readdir(
+        &self,
+        offset: usize,
+        callback: &mut dyn FnMut(&[u8], Ino) -> KResult<core::ops::ControlFlow<(), ()>>,
+    ) -> KResult<usize> {
+        let vfs = self.vfs.upgrade().ok_or(EIO)?;
+        let ext4fs = vfs.as_any().downcast_ref::<Ext4Fs>().unwrap();
+
+        let entries = ext4fs
+            .inner
+            .fuse_readdir(self.ino as u64, 0, offset as i64)
+            .map_err(|err| err.error() as u32)?;
+        let mut current_offset = 0;
+
+        for entry in entries {
+            let name_len = entry.name_len as usize;
+            let name = &entry.name[..name_len];
+
+            if callback(name, entry.inode as Ino)?.is_break() {
+                break;
+            }
+
+            current_offset += 1;
+        }
+        Ok(current_offset)
+    }
+}
+
+struct Ext4MountCreator;
+
+impl MountCreator for Ext4MountCreator {
+    fn check_signature(&self, mut first_block: &[u8]) -> KResult<bool> {
+        match first_block.split_off(1080..) {
+            Some([0x53, 0xef, ..]) => Ok(true), // Superblock signature
+            Some(..) => Ok(false),
+            None => Err(EIO),
+        }
+    }
+
+    fn create_mount(&self, source: &str, _flags: u64, mp: &Arc<Dentry>) -> KResult<Mount> {
+        let source = source.as_bytes();
+
+        let path = Path::new(source)?;
+        let device_dentry =
+            Dentry::open_recursive(&FsContext::global(), Dentry::root(), path, true, 0)?;
+        let devid = device_dentry.get_inode()?.devid()?;
+        let device = BlockDevice::get(devid)?;
+
+        let (ext4fs, root_inode) = Ext4Fs::create(device)?;
+
+        Mount::new(mp, ext4fs, root_inode)
+    }
+}
+
+pub fn init() {
+    register_filesystem("ext4", Arc::new(Ext4MountCreator)).unwrap();
+}

+ 8 - 0
src/fs/fat32.rs

@@ -343,6 +343,14 @@ impl Inode for DirInode {
 struct FatMountCreator;
 
 impl MountCreator for FatMountCreator {
+    fn check_signature(&self, mut first_block: &[u8]) -> KResult<bool> {
+        match first_block.split_off(82..) {
+            Some([b'F', b'A', b'T', b'3', b'2', b' ', b' ', b' ', ..]) => Ok(true),
+            Some(..) => Ok(false),
+            None => Err(EIO),
+        }
+    }
+
     fn create_mount(&self, _source: &str, _flags: u64, mp: &Arc<Dentry>) -> KResult<Mount> {
         let (fatfs, root_inode) = FatFs::create(make_device(8, 1))?;
 

+ 1 - 0
src/fs/mod.rs

@@ -1,3 +1,4 @@
 pub mod fat32;
 pub mod procfs;
 pub mod tmpfs;
+pub mod ext4;

+ 4 - 0
src/fs/procfs.rs

@@ -202,6 +202,10 @@ impl MountCreator for ProcFsMountCreator {
         let root_inode = vfs.root_node.clone();
         Mount::new(mp, vfs, root_inode)
     }
+
+    fn check_signature(&self, _: &[u8]) -> KResult<bool> {
+        Ok(true)
+    }
 }
 
 pub fn root() -> ProcFsNode {

+ 4 - 0
src/fs/tmpfs.rs

@@ -383,6 +383,10 @@ impl MountCreator for TmpFsMountCreator {
 
         Mount::new(mp, fs, root_inode)
     }
+
+    fn check_signature(&self, _: &[u8]) -> KResult<bool> {
+        Ok(true)
+    }
 }
 
 pub fn init() {

+ 21 - 13
src/kernel/block.rs

@@ -48,6 +48,21 @@ enum BlockDeviceType {
     },
 }
 
+#[derive(Debug, Clone)]
+pub enum FileSystemType {
+    Ext4,
+    Fat32,
+}
+
+impl FileSystemType {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            FileSystemType::Ext4 => "ext4",
+            FileSystemType::Fat32 => "fat32",
+        }
+    }
+}
+
 pub struct BlockDevice {
     /// Unique device identifier, major and minor numbers
     devid: DevId,
@@ -114,14 +129,14 @@ impl BlockDevice {
         }
     }
 
-    pub fn register_partition(&self, idx: u32, offset: u64, size: u64) -> KResult<Arc<Self>> {
+    pub fn register_partition(&self, idx: usize, offset: u64, size: u64) -> KResult<Arc<Self>> {
         let queue = match &self.dev_type {
             BlockDeviceType::Disk { queue } => queue.clone(),
             BlockDeviceType::Partition { .. } => return Err(EINVAL),
         };
 
         let device = Arc::new(BlockDevice {
-            devid: make_device(self.devid >> 8, idx as u32),
+            devid: make_device(self.devid >> 8, (self.devid & 0xff) + idx as u32 + 1),
             sector_count: size,
             dev_type: BlockDeviceType::Partition {
                 disk_dev: self.devid,
@@ -140,17 +155,10 @@ impl BlockDevice {
         match self.dev_type {
             BlockDeviceType::Partition { .. } => Err(EINVAL),
             BlockDeviceType::Disk { .. } => {
-                let mbr_table = MBRPartTable::from_disk(self).await?;
-
-                for (
-                    idx,
-                    Partition {
-                        lba_offset,
-                        sector_count,
-                    },
-                ) in mbr_table.partitions().enumerate()
-                {
-                    self.register_partition(idx as u32 + 1, lba_offset, sector_count)?;
+                if let Ok(mbr_table) = MBRPartTable::from_disk(self).await {
+                    for (idx, partition) in mbr_table.partitions().enumerate() {
+                        self.register_partition(idx, partition.lba_offset, partition.sector_count)?;
+                    }
                 }
 
                 Ok(())

+ 1 - 0
src/kernel/vfs/mount.rs

@@ -54,6 +54,7 @@ unsafe impl Send for Mount {}
 unsafe impl Sync for Mount {}
 
 pub trait MountCreator: Send + Sync {
+    fn check_signature(&self, first_block: &[u8]) -> KResult<bool>;
     fn create_mount(&self, source: &str, flags: u64, mp: &Arc<Dentry>) -> KResult<Mount>;
 }
 

+ 1 - 0
src/lib.rs

@@ -144,6 +144,7 @@ async fn init_process(early_kstack: PRange) {
     fs::tmpfs::init();
     fs::procfs::init();
     fs::fat32::init();
+    fs::ext4::init();
 
     let load_info = {
         // mount fat32 /mnt directory

+ 9 - 0
user-programs/init_script_riscv64.sh

@@ -25,6 +25,7 @@ do_or_freeze $BUSYBOX mknod -m 666 /dev/null c 1 3
 do_or_freeze $BUSYBOX mknod -m 666 /dev/zero c 1 5
 do_or_freeze $BUSYBOX mknod -m 666 /dev/sda b 8 0
 do_or_freeze $BUSYBOX mknod -m 666 /dev/sda1 b 8 1
+do_or_freeze $BUSYBOX mknod -m 666 /dev/sdb b 8 16
 do_or_freeze $BUSYBOX mknod -m 666 /dev/ttyS0 c 4 64
 do_or_freeze $BUSYBOX mknod -m 666 /dev/ttyS1 c 4 65
 
@@ -41,6 +42,14 @@ echo ok >&2
 do_or_freeze mkdir -p /etc /root /proc
 do_or_freeze mount -t procfs proc proc
 
+# Check if the device /dev/sdb is available and can be read
+if dd if=/dev/sdb of=/dev/null bs=512 count=1; then
+    echo -n -e "Mounting the ext4 image... " >&2
+    do_or_freeze mkdir -p /mnt1
+    do_or_freeze mount -t ext4 /dev/sdb /mnt1
+    echo ok >&2
+fi
+
 cp /mnt/ld-musl-i386.so.1 /lib/ld-musl-i386.so.1
 ln -s /lib/ld-musl-i386.so.1 /lib/libc.so
 

+ 9 - 0
user-programs/init_script_x86_64.sh

@@ -25,6 +25,7 @@ do_or_freeze $BUSYBOX mknod -m 666 /dev/null c 1 3
 do_or_freeze $BUSYBOX mknod -m 666 /dev/zero c 1 5
 do_or_freeze $BUSYBOX mknod -m 666 /dev/sda b 8 0
 do_or_freeze $BUSYBOX mknod -m 666 /dev/sda1 b 8 1
+do_or_freeze $BUSYBOX mknod -m 666 /dev/sdb b 8 16
 do_or_freeze $BUSYBOX mknod -m 666 /dev/ttyS0 c 4 64
 do_or_freeze $BUSYBOX mknod -m 666 /dev/ttyS1 c 4 65
 
@@ -41,6 +42,14 @@ echo ok >&2
 do_or_freeze mkdir -p /etc /root /proc
 do_or_freeze mount -t procfs proc proc
 
+# Check if the device /dev/sdb is available and can be read
+if dd if=/dev/sdb of=/dev/null bs=512 count=1; then
+    echo -n -e "Mounting the ext4 image... " >&2
+    do_or_freeze mkdir -p /mnt1
+    do_or_freeze mount -t ext4 /dev/sdb /mnt1
+    echo ok >&2
+fi
+
 cp /mnt/ld-musl-i386.so.1 /lib/ld-musl-i386.so.1
 ln -s /lib/ld-musl-i386.so.1 /lib/libc.so