]> code.delx.au - refind/blobdiff - filesystems/fsw_ext4.c
Previous commit broke loading of EFI drivers with SB active; fix it.
[refind] / filesystems / fsw_ext4.c
index 8d145e243da701154cc523df14e1f16bce23e407..7cdca910d1625b54803920d33d6f15551db5437b 100644 (file)
@@ -37,6 +37,10 @@ static fsw_status_t fsw_ext4_dnode_stat(struct fsw_ext4_volume *vol, struct fsw_
                                         struct fsw_dnode_stat *sb);
 static fsw_status_t fsw_ext4_get_extent(struct fsw_ext4_volume *vol, struct fsw_ext4_dnode *dno,
                                         struct fsw_extent *extent);
+static fsw_status_t fsw_ext4_get_by_blkaddr(struct fsw_ext4_volume *vol, struct fsw_ext4_dnode *dno,
+                                        struct fsw_extent *extent);
+static fsw_status_t fsw_ext4_get_by_extent(struct fsw_ext4_volume *vol, struct fsw_ext4_dnode *dno,
+                                        struct fsw_extent *extent);
 
 static fsw_status_t fsw_ext4_dir_lookup(struct fsw_ext4_volume *vol, struct fsw_ext4_dnode *dno,
                                         struct fsw_string *lookup_name, struct fsw_ext4_dnode **child_dno);
@@ -68,6 +72,34 @@ struct fsw_fstype_table   FSW_FSTYPE_TABLE_NAME(ext4) = {
     fsw_ext4_readlink,
 };
 
+
+static __inline int test_root(fsw_u32 a, int b)
+{
+        fsw_u32 num = b;
+
+        while (a > num)
+                num *= b;
+        return num == a;
+}
+
+static int fsw_ext4_group_sparse(fsw_u32 group)
+{
+        if (group <= 1)
+                return 1;
+        if (!(group & 1))
+                return 0;
+        return (test_root(group, 7) || test_root(group, 5) ||
+                test_root(group, 3));
+}
+
+/* calculate the first block number of the group */
+static __inline fsw_u32
+fsw_ext4_group_first_block_no(struct ext4_super_block *sb, fsw_u32 group_no)
+{
+        return group_no * (fsw_u32)EXT4_BLOCKS_PER_GROUP(sb) +
+                sb->s_first_data_block;
+}
+
 /**
  * Mount an ext4 volume. Reads the superblock and constructs the
  * root directory dnode.
@@ -78,7 +110,7 @@ static fsw_status_t fsw_ext4_volume_mount(struct fsw_ext4_volume *vol)
     fsw_status_t    status;
     void            *buffer;
     fsw_u32         blocksize;
-    fsw_u32         groupcnt, groupno, gdesc_per_block, gdesc_bno, gdesc_index;
+    fsw_u32         groupcnt, groupno, gdesc_per_block, gdesc_bno, gdesc_index, metabg_of_gdesc;
     struct ext4_group_desc *gdesc;
     int             i;
     struct fsw_string s;
@@ -106,25 +138,30 @@ static fsw_status_t fsw_ext4_volume_mount(struct fsw_ext4_volume *vol)
     FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_volume_mount: Incompat flag %x\n"), vol->sb->s_feature_incompat));
 
     if (vol->sb->s_rev_level == EXT4_DYNAMIC_REV &&
-        (vol->sb->s_feature_incompat & ~(EXT4_FEATURE_INCOMPAT_FILETYPE | EXT4_FEATURE_INCOMPAT_RECOVER)))
+        (vol->sb->s_feature_incompat & ~(EXT4_FEATURE_INCOMPAT_FILETYPE | EXT4_FEATURE_INCOMPAT_RECOVER |
+                                         EXT4_FEATURE_INCOMPAT_EXTENTS | EXT4_FEATURE_INCOMPAT_FLEX_BG |
+                                         EXT4_FEATURE_INCOMPAT_META_BG)))
         return FSW_UNSUPPORTED;
 
 
-     if (vol->sb->s_rev_level == EXT4_DYNAMIC_REV &&
-         (vol->sb->s_feature_incompat & EXT4_FEATURE_INCOMPAT_RECOVER))
-     {
-         FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_volume_mount: This ext3 file system needs recovery\n")));
-         // Print(L"Ext4 WARNING: This file system needs recovery, trying to use it anyway.\n");
-     }
+    if (vol->sb->s_rev_level == EXT4_DYNAMIC_REV &&
+        (vol->sb->s_feature_incompat & EXT4_FEATURE_INCOMPAT_RECOVER))
+    {
+        FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_volume_mount: This ext3 file system needs recovery\n")));
+        // Print(L"Ext4 WARNING: This file system needs recovery, trying to use it anyway.\n");
+    }
 
-    // set real blocksize
     blocksize = EXT4_BLOCK_SIZE(vol->sb);
+    if (blocksize < EXT4_MIN_BLOCK_SIZE || blocksize > EXT4_MAX_BLOCK_SIZE)
+        return FSW_UNSUPPORTED;
+
+    // set real blocksize
     fsw_set_blocksize(vol, blocksize, blocksize);
 
     // get other info from superblock
     vol->ind_bcnt = EXT4_ADDR_PER_BLOCK(vol->sb);
     vol->dind_bcnt = vol->ind_bcnt * vol->ind_bcnt;
-    vol->inode_size = EXT4_INODE_SIZE(vol->sb);
+    vol->inode_size = vol->sb->s_inode_size;//EXT4_INODE_SIZE(vol->sb);
 
     for (i = 0; i < 16; i++)
         if (vol->sb->s_volume_name[i] == 0)
@@ -136,22 +173,55 @@ static fsw_status_t fsw_ext4_volume_mount(struct fsw_ext4_volume *vol)
     if (status)
         return status;
 
-    // read the group descriptors to get inode table offsets
-    groupcnt = ((vol->sb->s_inodes_count - 2) / vol->sb->s_inodes_per_group) + 1;
-    gdesc_per_block = (vol->g.phys_blocksize / sizeof(struct ext4_group_desc));
+    // size of group descriptor depends on feature....
+    if (!(vol->sb->s_feature_incompat & EXT4_FEATURE_INCOMPAT_64BIT)) {
+        // Default minimal group descriptor size... (this might not be set in old ext2 filesystems, therefor set it!)
+        vol->sb->s_desc_size = EXT4_MIN_DESC_SIZE;
+    }
+
+    // Calculate group descriptor count the way the kernel does it...
+    groupcnt = (vol->sb->s_blocks_count_lo - vol->sb->s_first_data_block + 
+                vol->sb->s_blocks_per_group - 1) / vol->sb->s_blocks_per_group;
 
+    // Descriptors in one block... s_desc_size needs to be set! (Usually 128 since normal block 
+    // descriptors are 32 byte and block size is 4096)
+    gdesc_per_block = EXT4_DESC_PER_BLOCK(vol->sb);
+    
+    // Read the group descriptors to get inode table offsets
     status = fsw_alloc(sizeof(fsw_u32) * groupcnt, &vol->inotab_bno);
     if (status)
         return status;
+
+    // Loop through all block group descriptors in order to get inode table locations
     for (groupno = 0; groupno < groupcnt; groupno++) {
-        // get the block group descriptor
-        gdesc_bno = (vol->sb->s_first_data_block + 1) + groupno / gdesc_per_block;
+
+        // Calculate the block number which contains the block group descriptor we look for
+        if(vol->sb->s_feature_incompat & EXT4_FEATURE_INCOMPAT_META_BG && groupno >= vol->sb->s_first_meta_bg)
+        {
+            // If option meta_bg is set, the block group descriptor is in meta block group...
+            metabg_of_gdesc = (fsw_u32)(groupno / gdesc_per_block) * gdesc_per_block;
+            gdesc_bno = fsw_ext4_group_first_block_no(vol->sb, metabg_of_gdesc);
+            // We need to know if the block group in questition has a super block, if yes, the 
+            // block group descriptors are in the next block number
+            if(!(vol->sb->s_feature_ro_compat & EXT4_FEATURE_RO_COMPAT_SPARSE_SUPER) || fsw_ext4_group_sparse(metabg_of_gdesc))
+                gdesc_bno += 1;
+        }
+        else
+        {
+            // All group descriptors follow the super block (+1)
+            gdesc_bno = (vol->sb->s_first_data_block + 1) + groupno / gdesc_per_block;
+        }
         gdesc_index = groupno % gdesc_per_block;
+
+        // Get block if necessary...
         status = fsw_block_get(vol, gdesc_bno, 1, (void **)&buffer);
         if (status)
             return status;
-        gdesc = ((struct ext4_group_desc *)(buffer)) + gdesc_index;
+
+        // Get group descriptor table and block number of inode table...
+        gdesc = (struct ext4_group_desc *)((char *)buffer + gdesc_index * vol->sb->s_desc_size);
         vol->inotab_bno[groupno] = gdesc->bg_inode_table_lo;
+
         fsw_block_release(vol, gdesc_bno, buffer);
     }
 
@@ -207,15 +277,15 @@ static fsw_status_t fsw_ext4_dnode_fill(struct fsw_ext4_volume *vol, struct fsw_
     if (dno->raw)
         return FSW_SUCCESS;
 
-    FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_dnode_fill: inode %d\n"), dno->g.dnode_id));
 
     // read the inode block
-    groupno = (dno->g.dnode_id - 1) / vol->sb->s_inodes_per_group;
-    ino_in_group = (dno->g.dnode_id - 1) % vol->sb->s_inodes_per_group;
+    groupno = (fsw_u32) (dno->g.dnode_id - 1) / vol->sb->s_inodes_per_group;
+    ino_in_group = (fsw_u32) (dno->g.dnode_id - 1) % vol->sb->s_inodes_per_group;
     ino_bno = vol->inotab_bno[groupno] +
         ino_in_group / (vol->g.phys_blocksize / vol->inode_size);
     ino_index = ino_in_group % (vol->g.phys_blocksize / vol->inode_size);
     status = fsw_block_get(vol, ino_bno, 2, (void **)&buffer);
+
     if (status)
         return status;
 
@@ -237,6 +307,8 @@ static fsw_status_t fsw_ext4_dnode_fill(struct fsw_ext4_volume *vol, struct fsw_
     else
         dno->g.type = FSW_DNODE_TYPE_SPECIAL;
 
+    FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_dnode_fill: inode flags %x\n"), dno->raw->i_flags));
+    FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_dnode_fill: i_mode %x\n"), dno->raw->i_mode));
     return FSW_SUCCESS;
 }
 
@@ -263,10 +335,10 @@ static fsw_status_t fsw_ext4_dnode_stat(struct fsw_ext4_volume *vol, struct fsw_
                                         struct fsw_dnode_stat *sb)
 {
     sb->used_bytes = dno->raw->i_blocks_lo * EXT4_BLOCK_SIZE(vol->sb);   // very, very strange...
-    sb->store_time_posix(sb, FSW_DNODE_STAT_CTIME, dno->raw->i_ctime);
-    sb->store_time_posix(sb, FSW_DNODE_STAT_ATIME, dno->raw->i_atime);
-    sb->store_time_posix(sb, FSW_DNODE_STAT_MTIME, dno->raw->i_mtime);
-    sb->store_attr_posix(sb, dno->raw->i_mode);
+    fsw_store_time_posix(sb, FSW_DNODE_STAT_CTIME, dno->raw->i_ctime);
+    fsw_store_time_posix(sb, FSW_DNODE_STAT_ATIME, dno->raw->i_atime);
+    fsw_store_time_posix(sb, FSW_DNODE_STAT_MTIME, dno->raw->i_mtime);
+    fsw_store_attr_posix(sb, dno->raw->i_mode);
 
     return FSW_SUCCESS;
 }
@@ -278,27 +350,118 @@ static fsw_status_t fsw_ext4_dnode_stat(struct fsw_ext4_volume *vol, struct fsw_
  * on the dnode before. Our task here is to get the physical disk block number for
  * the requested logical block number.
  *
- * TODO...
- * The ext2 file system does not use extents, but stores a list of block numbers
- * using the usual direct, indirect, double-indirect, triple-indirect scheme. To
- * optimize access, this function checks if the following file blocks are mapped
- * to consecutive disk blocks and returns a combined extent if possible.
+ * The ext4 file system usually uses extents do to store those disk block numbers.
+ * However, since ext4 is backward compatible, depending on inode flags the old direct
+ * and indirect addressing scheme can still be in place...
  */
 
 static fsw_status_t fsw_ext4_get_extent(struct fsw_ext4_volume *vol, struct fsw_ext4_dnode *dno,
                                         struct fsw_extent *extent)
 {
-    fsw_status_t    status;
-    fsw_u32         bno, release_bno, buf_bcnt, file_bcnt;
-    fsw_u32         *buffer;
-    int             path[5], i;
-
     // Preconditions: The caller has checked that the requested logical block
     //  is within the file's size. The dnode has complete information, i.e.
     //  fsw_ext4_dnode_read_info was called successfully on it.
-
+    FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_extent: inode %d, block %d\n"), dno->g.dnode_id, extent->log_start));
     extent->type = FSW_EXTENT_TYPE_PHYSBLOCK;
     extent->log_count = 1;
+
+    if(dno->raw->i_flags & 1 << EXT4_INODE_EXTENTS)
+    {
+       FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_extent: inode %d uses extents\n"), dno->g.dnode_id));
+       return fsw_ext4_get_by_extent(vol, dno, extent);
+    }
+    else
+    {
+       FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_extent: inode %d uses direct/indirect block addressing\n"),
+           dno->g.dnode_id));
+       return fsw_ext4_get_by_blkaddr(vol, dno, extent);
+    }
+}
+
+/**
+ * New ext4 extents...
+ */
+static fsw_status_t fsw_ext4_get_by_extent(struct fsw_ext4_volume *vol, struct fsw_ext4_dnode *dno,
+                                        struct fsw_extent *extent)
+{
+    fsw_status_t  status;
+    fsw_u32       bno, buf_offset;
+    int           ext_cnt;
+    void          *buffer;
+
+    struct ext4_extent_header  *ext4_extent_header;
+    struct ext4_extent_idx     *ext4_extent_idx;
+    struct ext4_extent         *ext4_extent;
+
+    // Logical block requested by core...
+    bno = extent->log_start;
+
+    // First buffer is the i_block field from inode...
+    buffer = (void *)dno->raw->i_block;
+    buf_offset = 0;
+    while(1) {
+        ext4_extent_header = (struct ext4_extent_header *)((char *)buffer + buf_offset);
+        buf_offset += sizeof(struct ext4_extent_header);
+        FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: extent header with %d entries\n"), 
+                      ext4_extent_header->eh_entries));
+        if(ext4_extent_header->eh_magic != EXT4_EXT_MAGIC)
+            return FSW_VOLUME_CORRUPTED;
+
+        for(ext_cnt = 0;ext_cnt < ext4_extent_header->eh_entries;ext_cnt++)
+        {
+            if(ext4_extent_header->eh_depth == 0)
+            {
+                // Leaf node, the header follows actual extents
+                ext4_extent = (struct ext4_extent *)((char *)buffer + buf_offset);
+                buf_offset += sizeof(struct ext4_extent);
+                FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: extent node cover %d...\n"), ext4_extent->ee_block));
+
+                // Is the requested block in this extent?
+                if(bno >= ext4_extent->ee_block && bno < ext4_extent->ee_block + ext4_extent->ee_len)
+                {
+                    extent->phys_start = ext4_extent->ee_start_lo + (bno - ext4_extent->ee_block);
+                    extent->log_count = ext4_extent->ee_len - (bno - ext4_extent->ee_block);
+                    return FSW_SUCCESS;
+                }
+            }
+            else
+            {
+                FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: index extents, depth %d\n"), 
+                          ext4_extent_header->eh_depth));
+                ext4_extent_idx = (struct ext4_extent_idx *)((char *)buffer + buf_offset);
+                buf_offset += sizeof(struct ext4_extent_idx);
+
+                FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_get_by_extent: index node covers block %d...\n"),
+                          ext4_extent_idx->ei_block));
+                if(bno >= ext4_extent_idx->ei_block)
+                {
+                    // Follow extent tree...
+                    status = fsw_block_get(vol, ext4_extent_idx->ei_leaf_lo, 1, (void **)&buffer);
+                    if (status)
+                        return status;
+                    buf_offset = 0;
+                    break;
+                }
+            }
+        }
+    }
+
+    return FSW_NOT_FOUND;
+}
+
+/**
+ * The ext2/ext3 file system does not use extents, but stores a list of block numbers
+ * using the usual direct, indirect, double-indirect, triple-indirect scheme. To
+ * optimize access, this function checks if the following file blocks are mapped
+ * to consecutive disk blocks and returns a combined extent if possible.
+ */
+static fsw_status_t fsw_ext4_get_by_blkaddr(struct fsw_ext4_volume *vol, struct fsw_ext4_dnode *dno,
+                                        struct fsw_extent *extent)
+{
+    fsw_status_t    status;
+    fsw_u32         bno, release_bno, buf_bcnt, file_bcnt;
+    int             path[5], i;
+    fsw_u32         *buffer;
     bno = extent->log_start;
 
     // try direct block pointers in the inode
@@ -334,7 +497,7 @@ static fsw_status_t fsw_ext4_get_extent(struct fsw_ext4_volume *vol, struct fsw_
             }
         }
     }
-
+    
     // follow the indirection path
     buffer = dno->raw->i_block;
     buf_bcnt = EXT4_NDIR_BLOCKS;
@@ -448,6 +611,7 @@ static fsw_status_t fsw_ext4_dir_read(struct fsw_ext4_volume *vol, struct fsw_ex
     // Preconditions: The caller has checked that dno is a directory node. The caller
     //  has opened a storage handle to the directory's storage and keeps it around between
     //  calls.
+    FSW_MSG_DEBUG((FSW_MSGSTR("fsw_ext4_dir_read: started reading dir\n")));
 
     while (1) {
         // read next entry