:: commit c9604f1214920937825737a9e377f4b2a0044b75

mintsuki <mintsuki@protonmail.com> — 2023-09-23 22:39

parents: 21e5f65e6c

misc: Converge with 5.x

diff --git a/PHILOSOPHY.md b/PHILOSOPHY.md
index 4babc183..702c055e 100644
--- a/PHILOSOPHY.md
+++ b/PHILOSOPHY.md
@@ -21,6 +21,10 @@ way to modify its own EFI executable to bake in the BLAKE2B checksum of the conf
 a key added to the firmware's keychain. This prevents modifications to the config file (and in turn the checksums contained there)
 from going unnoticed.
 
+### What about ext2/3/4? Why is that supported then?
+
+This is explicitly against the philosophy, but it is a pragmatic compromise since a lot of Linux distros and setups expect it to "work that way".
+
 ### But I don't want to have a separate FAT boot partition! I don't want it!!!
 
 Well tough luck. It is `$year_following_2012` now and most PCs are equipped with UEFI and simply won't boot without a FAT EFI system partition
diff --git a/README.md b/README.md
index d6fab358..7c098a5b 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ Donations welcome, but absolutely not mandatory!
 * Unpartitioned media
 
 ### Supported filesystems
+* ext2/3/4
 * FAT12/16/32
 * ISO9660 (CDs/DVDs)
 
diff --git a/common/fs/ext2.h b/common/fs/ext2.h
new file mode 100644
index 00000000..22f787ac
--- /dev/null
+++ b/common/fs/ext2.h
@@ -0,0 +1,13 @@
+#ifndef __FS__EXT2_H__
+#define __FS__EXT2_H__
+
+#include <stdbool.h>
+#include <lib/part.h>
+#include <fs/file.h>
+
+bool ext2_get_guid(struct guid *guid, struct volume *part);
+char *ext2_get_label(struct volume *part);
+
+struct file_handle *ext2_open(struct volume *part, const char *path);
+
+#endif
diff --git a/common/fs/ext2.s2.c b/common/fs/ext2.s2.c
new file mode 100644
index 00000000..9b56d615
--- /dev/null
+++ b/common/fs/ext2.s2.c
@@ -0,0 +1,685 @@
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <fs/ext2.h>
+#include <drivers/disk.h>
+#include <lib/libc.h>
+#include <lib/misc.h>
+#include <lib/print.h>
+#include <mm/pmm.h>
+
+/* Superblock Fields */
+struct ext2_superblock {
+    uint32_t s_inodes_count;
+    uint32_t s_blocks_count;
+    uint32_t s_r_blocks_count;
+    uint32_t s_free_blocks_count;
+    uint32_t s_free_inodes_count;
+    uint32_t s_first_data_block;
+    uint32_t s_log_block_size;
+    uint32_t s_log_frag_size;
+    uint32_t s_blocks_per_group;
+    uint32_t s_frags_per_group;
+    uint32_t s_inodes_per_group;
+    uint32_t s_mtime;
+    uint32_t s_wtime;
+
+    uint16_t s_mnt_count;
+    uint16_t s_max_mnt_count;
+    uint16_t s_magic;
+    uint16_t s_state;
+    uint16_t s_errors;
+    uint16_t s_minor_rev_level;
+
+    uint32_t s_lastcheck;
+    uint32_t s_checkinterval;
+    uint32_t s_creator_os;
+    uint32_t s_rev_level;
+    uint16_t s_def_resuid;
+    uint16_t s_def_gid;
+
+    // if version number >= 1, we have to use the ext2 extended superblock as well
+
+    /* Extended Superblock */
+    uint32_t s_first_ino;
+
+    uint16_t s_inode_size;
+    uint16_t s_block_group_nr;
+
+    uint32_t s_feature_compat;
+    uint32_t s_feature_incompat;
+    uint32_t s_feature_ro_compat;
+
+    uint64_t s_uuid[2];
+    uint8_t s_volume_name[16];
+
+    uint64_t s_last_mounted[8];
+
+    uint32_t compression_info;
+    uint8_t prealloc_blocks;
+    uint8_t prealloc_dir_blocks;
+    uint16_t reserved_gdt_blocks;
+    uint8_t journal_uuid[16];
+    uint32_t journal_inum;
+    uint32_t journal_dev;
+    uint32_t last_orphan;
+    uint32_t hash_seed[4];
+    uint8_t def_hash_version;
+    uint8_t jnl_backup_type;
+    uint16_t group_desc_size;
+    uint32_t default_mount_opts;
+    uint32_t first_meta_bg;
+    uint32_t mkfs_time;
+    uint32_t jnl_blocks[17];
+} __attribute__((packed));
+
+struct ext2_linux {
+    uint8_t  frag_num;
+    uint8_t  frag_size;
+
+    uint16_t reserved_16;
+    uint16_t user_id_high;
+    uint16_t group_id_high;
+
+    uint32_t reserved_32;
+} __attribute__((packed));
+
+struct ext2_inode {
+    uint16_t i_mode;
+    uint16_t i_uid;
+
+    uint32_t i_size;
+    uint32_t i_atime;
+    uint32_t i_ctime;
+    uint32_t i_mtime;
+    uint32_t i_dtime;
+
+    uint16_t i_gid;
+    uint16_t i_links_count;
+
+    uint32_t i_blocks_count;
+    uint32_t i_flags;
+    uint32_t i_osd1;
+    uint32_t i_blocks[15];
+    uint32_t i_generation;
+
+    /* EXT2 v >= 1.0 */
+    uint32_t i_eab;
+    uint32_t i_maj;
+
+    /* EXT2 vAll */
+    uint32_t i_frag_block;
+
+    struct ext2_linux i_osd2;
+} __attribute__((packed));
+
+struct ext2_file_handle {
+    struct volume *part;
+    struct ext2_superblock sb;
+    int size;
+    struct ext2_inode root_inode;
+    struct ext2_inode inode;
+    uint64_t block_size;
+    uint32_t *alloc_map;
+};
+
+/* Inode types */
+#define S_IFIFO  0x1000
+#define S_IFCHR  0x2000
+#define S_IFDIR  0x4000
+#define S_IFBLK  0x6000
+#define S_IFREG  0x8000
+#define S_IFLNK  0xa000
+#define S_IFSOCK 0xc000
+
+#define FMT_MASK 0xf000
+
+/* EXT2 Filesystem States */
+#define EXT2_FS_UNRECOVERABLE_ERRORS 3
+
+/* Ext2 incompatible features */
+#define EXT2_IF_COMPRESSION 0x01
+#define EXT2_IF_EXTENTS 0x40
+#define EXT2_IF_64BIT 0x80
+#define EXT2_IF_INLINE_DATA 0x8000
+#define EXT2_IF_ENCRYPT 0x10000
+#define EXT2_FEATURE_INCOMPAT_META_BG 0x0010
+
+/* Ext4 flags */
+#define EXT4_EXTENTS_FLAG 0x80000
+
+#define EXT2_S_MAGIC    0xEF53
+
+/* EXT2 Block Group Descriptor */
+struct ext2_bgd {
+    uint32_t bg_block_bitmap;
+    uint32_t bg_inode_bitmap;
+    uint32_t bg_inode_table;
+
+    uint16_t bg_free_blocks_count;
+    uint16_t bg_free_inodes_count;
+    uint16_t bg_dirs_count;
+
+    uint16_t reserved[7];
+} __attribute__((packed));
+
+struct ext4_bgd {
+    uint32_t bg_block_bitmap;
+    uint32_t bg_inode_bitmap;
+    uint32_t bg_inode_table;
+
+    uint16_t bg_free_blocks_count;
+    uint16_t bg_free_inodes_count;
+    uint16_t bg_dirs_count;
+
+    uint16_t pad;
+    uint32_t reserved[3];
+    uint32_t block_id_hi;
+    uint32_t inode_id_hi;
+    uint32_t inode_table_id_hi;
+    uint16_t free_blocks_hi;
+    uint16_t free_inodes_hi;
+    uint16_t used_dirs_hi;
+    uint16_t pad2;
+    uint32_t reserved2[3];
+} __attribute__((packed));
+
+/* EXT2 Inode Types */
+#define EXT2_INO_DIRECTORY  0x4000
+
+/* EXT2 Directory Entry */
+struct ext2_dir_entry {
+    uint32_t inode;
+    uint16_t rec_len;
+    uint8_t  name_len;
+    uint8_t  type;
+} __attribute__((packed));
+
+struct ext4_extent_header {
+    uint16_t magic;
+    uint16_t entries;
+    uint16_t max;
+    uint16_t depth;
+    uint16_t generation;
+} __attribute__((packed));
+
+struct ext4_extent {
+    uint32_t block;
+    uint16_t len;
+    uint16_t start_hi;
+    uint32_t start;
+} __attribute__((packed));
+
+struct ext4_extent_idx {
+    uint32_t block;
+    uint32_t leaf;
+    uint16_t leaf_hi;
+    uint16_t empty;
+} __attribute__((packed));
+
+static int inode_read(void *buf, uint64_t loc, uint64_t count,
+                      struct ext2_inode *inode, struct ext2_file_handle *fd,
+                      uint32_t *alloc_map);
+static bool ext2_parse_dirent(struct ext2_dir_entry *dir, struct ext2_file_handle *fd, const char *path);
+
+// parse an inode given the partition base and inode number
+static bool ext2_get_inode(struct ext2_inode *ret,
+                          struct ext2_file_handle *fd, uint64_t inode) {
+    if (inode == 0)
+        return false;
+
+    struct ext2_superblock *sb = &fd->sb;
+
+    //determine if we need to use 64 bit inode ids
+    bool bit64 = false;
+    if (sb->s_rev_level != 0
+        && (sb->s_feature_incompat & (EXT2_IF_64BIT))
+        && sb->group_desc_size != 0
+        && ((sb->group_desc_size & (sb->group_desc_size - 1)) == 0)) {
+                if(sb->group_desc_size > 32) {
+                    bit64 = true;
+                }
+            }
+
+    const uint64_t ino_blk_grp = (inode - 1) / sb->s_inodes_per_group;
+    const uint64_t ino_tbl_idx = (inode - 1) % sb->s_inodes_per_group;
+
+    const uint64_t block_size = ((uint64_t)1024 << sb->s_log_block_size);
+    uint64_t ino_offset;
+    const uint64_t bgd_start_offset = block_size >= 2048 ? block_size : block_size * 2;
+    const uint64_t ino_size = sb->s_rev_level == 0 ? sizeof(struct ext2_inode) : sb->s_inode_size;
+
+    if (!bit64) {
+        struct ext2_bgd target_descriptor;
+        const uint64_t bgd_offset = bgd_start_offset + (sizeof(struct ext2_bgd) * ino_blk_grp);
+
+        volume_read(fd->part, &target_descriptor, bgd_offset, sizeof(struct ext2_bgd));
+
+        ino_offset = ((target_descriptor.bg_inode_table) * block_size) +
+                                    (ino_size * ino_tbl_idx);
+    } else {
+        struct ext4_bgd target_descriptor;
+        const uint64_t bgd_offset = bgd_start_offset + (sizeof(struct ext4_bgd) * ino_blk_grp);
+
+        volume_read(fd->part, &target_descriptor, bgd_offset, sizeof(struct ext4_bgd));
+
+        ino_offset = ((target_descriptor.bg_inode_table | (bit64 ? ((uint64_t)target_descriptor.inode_id_hi << 32) : 0)) * block_size) +
+                                    (ino_size * ino_tbl_idx);
+    }
+
+    volume_read(fd->part, ret, ino_offset, sizeof(struct ext2_inode));
+
+    return true;
+}
+
+static uint32_t *create_alloc_map(struct ext2_file_handle *fd,
+                                  struct ext2_inode *inode) {
+    if (inode->i_flags & EXT4_EXTENTS_FLAG)
+        return NULL;
+
+    size_t entries_per_block = fd->block_size / sizeof(uint32_t);
+
+    // Cache the map of blocks
+    uint32_t *alloc_map = ext_mem_alloc(inode->i_blocks_count * sizeof(uint32_t));
+    for (uint32_t i = 0; i < inode->i_blocks_count; i++) {
+        uint32_t block = i;
+        if (block < 12) {
+            // Direct block
+            alloc_map[i] = inode->i_blocks[block];
+        } else {
+            // Indirect block
+            block -= 12;
+            if (block >= entries_per_block) {
+                // Double indirect block
+                block -= entries_per_block;
+                uint32_t index = block / entries_per_block;
+                uint32_t indirect_block;
+                if (index >= entries_per_block) {
+                    uint32_t first_index = index / entries_per_block;
+                    uint32_t first_indirect_block;
+                    volume_read(
+                        fd->part, &first_indirect_block,
+                        inode->i_blocks[14] * fd->block_size + first_index * sizeof(uint32_t),
+                        sizeof(uint32_t)
+                    );
+                    uint32_t second_index = index % entries_per_block;
+                    volume_read(
+                        fd->part, &indirect_block,
+                        first_indirect_block * fd->block_size + second_index * sizeof(uint32_t),
+                        sizeof(uint32_t)
+                    );
+                } else {
+                    volume_read(
+                        fd->part, &indirect_block,
+                        inode->i_blocks[13] * fd->block_size + index * sizeof(uint32_t),
+                        sizeof(uint32_t)
+                    );
+                }
+                for (uint32_t j = 0; j < entries_per_block; j++) {
+                    if (i + j >= inode->i_blocks_count)
+                        return alloc_map;
+                    volume_read(
+                        fd->part, &alloc_map[i + j],
+                        indirect_block * fd->block_size + j * sizeof(uint32_t),
+                        sizeof(uint32_t)
+                    );
+                }
+                i += entries_per_block - 1;
+            } else {
+                // Single indirect block
+                volume_read(
+                    fd->part, &alloc_map[i],
+                    inode->i_blocks[12] * fd->block_size + block * sizeof(uint32_t),
+                    sizeof(uint32_t)
+                );
+            }
+        }
+    }
+
+    return alloc_map;
+}
+
+static bool symlink_to_inode(struct ext2_inode *inode, struct ext2_file_handle *fd,
+                             const char *cwd, size_t cwd_len) {
+    // I cannot find whether this is 0-terminated or not, so I'm gonna take the
+    // safe route here and assume it is not.
+    if (inode->i_size < 59) {
+        struct ext2_dir_entry dir;
+        char *symlink = (char *)inode->i_blocks;
+        symlink[59] = 0;
+
+        char *abs = ext_mem_alloc(4096);
+        char *cwd_copy = ext_mem_alloc(cwd_len + 1);
+        memcpy(cwd_copy, cwd, cwd_len);
+        get_absolute_path(abs, symlink, cwd_copy);
+
+        pmm_free(cwd_copy, cwd_len + 1);
+
+        if (!ext2_parse_dirent(&dir, fd, abs)) {
+            pmm_free(abs, 4096);
+            return false;
+        }
+        pmm_free(abs, 4096);
+
+        ext2_get_inode(inode, fd, dir.inode);
+        return true;
+    } else {
+        print("ext2: Symlinks with destination paths longer than 60 chars unsupported\n");
+        return false;
+    }
+}
+
+static bool ext2_parse_dirent(struct ext2_dir_entry *dir, struct ext2_file_handle *fd, const char *path) {
+    if (*path != '/') {
+        panic(true, "ext2: Path does not start in /");
+    }
+
+    path++;
+
+    struct ext2_inode current_inode = fd->root_inode;
+
+    bool escape = false;
+    static char token[256];
+
+    bool ret;
+
+    const char *cwd = path - 1; // because /
+    size_t cwd_len = 1;
+    size_t next_cwd_len = cwd_len;
+
+next:
+    memset(token, 0, 256);
+
+    for (size_t i = 0; i < 255 && *path != '/' && *path != '\0'; i++, path++, next_cwd_len++)
+        token[i] = *path;
+
+    if (*path == '\0')
+        escape = true;
+    else
+        path++, next_cwd_len++;
+
+    uint32_t *alloc_map = create_alloc_map(fd, &current_inode);
+
+    for (uint32_t i = 0; i < current_inode.i_size; ) {
+        // preliminary read
+        inode_read(dir, i, sizeof(struct ext2_dir_entry),
+                   &current_inode, fd, alloc_map);
+
+        // name read
+        char *name = ext_mem_alloc(dir->name_len + 1);
+
+        memset(name, 0, dir->name_len + 1);
+        inode_read(name, i + sizeof(struct ext2_dir_entry), dir->name_len,
+                   &current_inode, fd, alloc_map);
+
+        int (*strcmpfn)(const char *, const char *) = case_insensitive_fopen ? strcasecmp : strcmp;
+
+        int test = strcmpfn(token, name);
+        pmm_free(name, dir->name_len + 1);
+
+        if (test == 0) {
+            if (escape) {
+                ret = true;
+                goto out;
+            } else {
+                // update the current inode
+                ext2_get_inode(&current_inode, fd, dir->inode);
+                while ((current_inode.i_mode & FMT_MASK) != S_IFDIR) {
+                    if ((current_inode.i_mode & FMT_MASK) == S_IFLNK) {
+                        if (!symlink_to_inode(&current_inode, fd, cwd, cwd_len)) {
+                            ret = false;
+                            goto out;
+                        }
+                    } else {
+                        print("ext2: Part of path is not directory nor symlink\n");
+                        ret = false;
+                        goto out;
+                    }
+                }
+                pmm_free(alloc_map, current_inode.i_blocks_count * sizeof(uint32_t));
+                cwd_len = next_cwd_len;
+                goto next;
+            }
+        }
+
+        i += dir->rec_len;
+    }
+
+    ret = false;
+
+out:
+    pmm_free(alloc_map, current_inode.i_blocks_count * sizeof(uint32_t));
+    return ret;
+}
+
+static void ext2_read(struct file_handle *handle, void *buf, uint64_t loc, uint64_t count);
+static void ext2_close(struct file_handle *file);
+
+struct file_handle *ext2_open(struct volume *part, const char *path) {
+    struct ext2_file_handle *ret = ext_mem_alloc(sizeof(struct ext2_file_handle));
+
+    ret->part = part;
+
+    volume_read(ret->part, &ret->sb, 1024, sizeof(struct ext2_superblock));
+
+    struct ext2_superblock *sb = &ret->sb;
+
+    if (sb->s_magic != EXT2_S_MAGIC) {
+        pmm_free(ret, sizeof(struct ext2_file_handle));
+        return NULL;
+    }
+
+    if (sb->s_rev_level != 0 &&
+        (sb->s_feature_incompat & EXT2_IF_COMPRESSION ||
+         sb->s_feature_incompat & EXT2_IF_INLINE_DATA ||
+         sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG)) {
+        print("ext2: filesystem has unsupported features %x\n", sb->s_feature_incompat);
+        pmm_free(ret, sizeof(struct ext2_file_handle));
+        return NULL;
+    }
+
+    if (sb->s_rev_level != 0 && sb->s_feature_incompat & EXT2_IF_ENCRYPT) {
+        print("ext2: WARNING: File system has encryption feature on, stuff may misbehave\n");
+    }
+
+    if (sb->s_state == EXT2_FS_UNRECOVERABLE_ERRORS) {
+        print("ext2: unrecoverable errors found\n");
+        pmm_free(ret, sizeof(struct ext2_file_handle));
+        return NULL;
+    }
+
+    ret->block_size = ((uint64_t)1024 << ret->sb.s_log_block_size);
+
+    ext2_get_inode(&ret->root_inode, ret, 2);
+
+    struct ext2_dir_entry entry;
+
+    size_t cwd_len = 0;
+    char *cwd = ext_mem_alloc(4096);
+    for (int i = strlen(path) - 1; i > 0; i--) {
+        if (path[i] == '/' || path[i] == 0) {
+            cwd_len = i;
+            break;
+        }
+    }
+    memcpy(cwd, path, cwd_len);
+
+    if (!ext2_parse_dirent(&entry, ret, path)) {
+        pmm_free(cwd, 4096);
+        pmm_free(ret, sizeof(struct ext2_file_handle));
+        return NULL;
+    }
+
+    ext2_get_inode(&ret->inode, ret, entry.inode);
+
+    while ((ret->inode.i_mode & FMT_MASK) != S_IFREG) {
+        if ((ret->inode.i_mode & FMT_MASK) == S_IFLNK) {
+            if (!symlink_to_inode(&ret->inode, ret, cwd, cwd_len)) {
+                pmm_free(cwd, 4096);
+                pmm_free(ret, sizeof(struct ext2_file_handle));
+                return NULL;
+            }
+        } else {
+            print("ext2: Entity is not regular file nor symlink\n");
+            pmm_free(cwd, 4096);
+            pmm_free(ret, sizeof(struct ext2_file_handle));
+            return NULL;
+        }
+    }
+
+    pmm_free(cwd, 4096);
+
+    ret->size = ret->inode.i_size;
+
+    ret->alloc_map = create_alloc_map(ret, &ret->inode);
+
+    struct file_handle *handle = ext_mem_alloc(sizeof(struct file_handle));
+
+    handle->fd = ret;
+    handle->read = (void *)ext2_read;
+    handle->close = (void *)ext2_close;
+    handle->size = ret->size;
+    handle->vol = part;
+#if defined (UEFI)
+    handle->efi_part_handle = part->efi_part_handle;
+#endif
+
+    return handle;
+}
+
+static void ext2_close(struct file_handle *file) {
+    struct ext2_file_handle *f = file->fd;
+    if (f->alloc_map != NULL) {
+        pmm_free(f->alloc_map, f->inode.i_blocks_count * sizeof(uint32_t));
+    }
+    pmm_free(f, sizeof(struct ext2_file_handle));
+}
+
+static void ext2_read(struct file_handle *file, void *buf, uint64_t loc, uint64_t count) {
+    struct ext2_file_handle *f = file->fd;
+    inode_read(buf, loc, count, &f->inode, f, f->alloc_map);
+}
+
+static struct ext4_extent_header *ext4_find_leaf(struct ext4_extent_header *ext_block, uint32_t read_block, uint64_t block_size, struct volume *part) {
+    struct ext4_extent_idx *index;
+
+    void *buf = ext_mem_alloc(block_size);
+    memcpy(buf, ext_block, block_size);
+    ext_block = buf;
+
+    for (;;) {
+        index = (struct ext4_extent_idx *)((size_t)ext_block + 12);
+
+        #define EXT4_EXT_MAGIC 0xf30a
+        if (ext_block->magic != EXT4_EXT_MAGIC)
+            panic(false, "invalid extent magic");
+
+        if (ext_block->depth == 0) {
+            return ext_block;
+        }
+
+        int i;
+        for (i = 0; i < ext_block->entries; i++) {
+            if (read_block < index[i].block)
+                break;
+        }
+
+        if (--i < 0)
+            panic(false, "extent not found");
+
+        uint64_t block = ((uint64_t)index[i].leaf_hi << 32) | index[i].leaf;
+
+        volume_read(part, buf, (block * block_size), block_size);
+        ext_block = buf;
+    }
+}
+
+static int inode_read(void *buf, uint64_t loc, uint64_t count,
+                      struct ext2_inode *inode, struct ext2_file_handle *fd,
+                      uint32_t *alloc_map) {
+    for (uint64_t progress = 0; progress < count;) {
+        uint64_t block = (loc + progress) / fd->block_size;
+
+        uint64_t chunk = count - progress;
+        uint64_t offset = (loc + progress) % fd->block_size;
+        if (chunk > fd->block_size - offset)
+            chunk = fd->block_size - offset;
+
+        uint32_t block_index;
+
+        if (inode->i_flags & EXT4_EXTENTS_FLAG) {
+            struct ext4_extent_header *leaf;
+            struct ext4_extent *ext;
+            int i;
+
+            leaf = ext4_find_leaf((struct ext4_extent_header *)inode->i_blocks, block, fd->block_size, fd->part);
+
+            if (!leaf)
+                panic(false, "invalid extent");
+            ext = (struct ext4_extent*)((size_t)leaf + 12);
+
+            for (i = 0; i < leaf->entries; i++) {
+                if (block < ext[i].block) {
+                    break;
+                }
+            }
+
+            if (--i >= 0) {
+                block -= ext[i].block;
+                if (block >= ext[i].len) {
+                    panic(false, "block longer than extent");
+                } else {
+                    uint64_t start = ((uint64_t)ext[i].start_hi << 32) + ext[i].start;
+                    block_index = start + block;
+                }
+            } else {
+                panic(false, "extent for block not found");
+            }
+
+            pmm_free(leaf, fd->block_size);
+        } else {
+            block_index = alloc_map[block];
+        }
+
+        volume_read(fd->part, buf + progress, (block_index * fd->block_size) + offset, chunk);
+
+        progress += chunk;
+    }
+
+    return 0;
+}
+
+bool ext2_get_guid(struct guid *guid, struct volume *part) {
+    struct ext2_superblock sb;
+    volume_read(part, &sb, 1024, sizeof(struct ext2_superblock));
+
+    if (sb.s_magic != EXT2_S_MAGIC)
+        return false;
+
+    ((uint64_t *)guid)[0] = sb.s_uuid[0];
+    ((uint64_t *)guid)[1] = sb.s_uuid[1];
+
+    return true;
+}
+
+char *ext2_get_label(struct volume *part) {
+    struct ext2_superblock sb;
+    volume_read(part, &sb, 1024, sizeof(struct ext2_superblock));
+
+    if (sb.s_magic != EXT2_S_MAGIC) {
+        return NULL;
+    }
+
+    if (sb.s_rev_level < 1) {
+        return NULL;
+    }
+
+    size_t label_len = strlen((char *)sb.s_volume_name);
+    if (label_len == 0) {
+        return NULL;
+    }
+    char *ret = ext_mem_alloc(label_len + 1);
+    strcpy(ret, (char *)sb.s_volume_name);
+
+    return ret;
+}
diff --git a/common/fs/file.s2.c b/common/fs/file.s2.c
index 643f19c3..aa603e6f 100644
--- a/common/fs/file.s2.c
+++ b/common/fs/file.s2.c
@@ -1,6 +1,7 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <fs/file.h>
+#include <fs/ext2.h>
 #include <fs/fat32.h>
 #include <fs/iso9660.h>
 #include <lib/print.h>
@@ -16,13 +17,18 @@ char *fs_get_label(struct volume *part) {
     if ((ret = fat32_get_label(part)) != NULL) {
         return ret;
     }
+    if ((ret = ext2_get_label(part)) != NULL) {
+        return ret;
+    }
 
     return NULL;
 }
 
 bool fs_get_guid(struct guid *guid, struct volume *part) {
-    (void)guid;
-    (void)part;
+    if (ext2_get_guid(guid, part) == true) {
+        return true;
+    }
+
     return false;
 }
 
@@ -50,6 +56,9 @@ struct file_handle *fopen(struct volume *part, const char *filename) {
         return ret;
     }
 
+    if ((ret = ext2_open(part, filename)) != NULL) {
+        goto success;
+    }
     if ((ret = iso9660_open(part, filename)) != NULL) {
         goto success;
     }
diff --git a/test.mk b/test.mk
index d899ecaf..993e6d4f 100644
--- a/test.mk
+++ b/test.mk
@@ -32,6 +32,29 @@ mbrtest.hdd:
 	dd if=/dev/zero bs=1M count=0 seek=64 of=mbrtest.hdd
 	echo -e "o\nn\np\n1\n2048\n\nt\n6\na\nw\n" | fdisk mbrtest.hdd -H 16 -S 63
 
+.PHONY: ext2-test
+ext2-test:
+	$(MAKE) test-clean
+	$(MAKE) test.hdd
+	$(MAKE) limine-bios
+	$(MAKE) limine
+	$(MAKE) -C test TOOLCHAIN_FILE='$(call SHESCAPE,$(BUILDDIR))/toolchain-files/uefi-x86_64-toolchain.mk'
+	rm -rf test_image/
+	mkdir test_image
+	sudo losetup -Pf --show test.hdd > loopback_dev
+	sudo partprobe `cat loopback_dev`
+	sudo mkfs.ext2 `cat loopback_dev`p1
+	sudo mount `cat loopback_dev`p1 test_image
+	sudo mkdir test_image/boot
+	sudo cp -rv $(BINDIR)/* test_image/boot/
+	sudo cp -rv test/* test_image/boot/
+	sync
+	sudo umount test_image/
+	sudo losetup -d `cat loopback_dev`
+	rm -rf test_image loopback_dev
+	$(BINDIR)/limine bios-install test.hdd
+	qemu-system-x86_64 -net none -smp 4   -hda test.hdd -debugcon stdio
+
 .PHONY: fat12-test
 fat12-test:
 	$(MAKE) test-clean
tab: 248 wrap: offon