summarylogtreecommitdiffstats
path: root/ext4_block_group.patch
blob: 37f161a08ffe9c3f21d1090aedf7d2785d956f2d (plain)
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
Description: Fix ext4 block group handling
 Backported distantly from grub2, particularly:
   https://git.savannah.gnu.org/cgit/grub.git/commit/?id=e20aa39ea4298011ba716087713cff26c6c52006
Author: Colin Watson <cjwatson@debian.org>
Bug-Debian: https://bugs.debian.org/511121
Last-Update: 2018-10-25

Index: b/stage2/fsys_ext2fs.c
===================================================================
--- a/stage2/fsys_ext2fs.c
+++ b/stage2/fsys_ext2fs.c
@@ -41,6 +41,8 @@
 typedef unsigned short __u16;
 typedef __signed__ int __s32;
 typedef unsigned int __u32;
+typedef __signed__ long long __s64;
+typedef unsigned long long __u64;
 
 /*
  * Constants relative to the data blocks, from ext2_fs.h
@@ -51,6 +53,13 @@
 #define EXT2_TIND_BLOCK                 (EXT2_DIND_BLOCK + 1)
 #define EXT2_N_BLOCKS                   (EXT2_TIND_BLOCK + 1)
 
+/* Superblock filesystem feature flags (RO compatible) */
+#define EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER 0x0001
+
+/* Superblock filesystem feature flags (back-incompatible) */
+#define EXT2_FEATURE_INCOMPAT_META_BG       0x0010
+#define EXT4_FEATURE_INCOMPAT_64BIT         0x0080
+
 /* Inode flags */
 #define EXT4_EXTENTS_FL                 0x00080000 /* Inode uses extents */
 
@@ -122,7 +131,7 @@
     __u32 s_hash_seed[4];	/* HTREE hash seed */
     __u8  s_def_hash_version;	/* Default hash version to use */
     __u8  s_jnl_backup_type; 	/* Default type of journal backup */
-    __u16 s_reserved_word_pad;
+    __u16 s_desc_size;		/* Group desc. size: INCOMPAT_64BIT */
     __u32 s_default_mount_opts;
     __u32 s_first_meta_bg;	/* First metablock group */
     __u32 s_mkfs_time;		/* When the filesystem was created */
@@ -284,13 +293,20 @@
 #define PATH_MAX                1024	/* include/linux/limits.h */
 #define MAX_LINK_COUNT             5	/* number of symbolic links to follow */
 
+/* Information about a "mounted" ext2 filesystem.  */
+struct ext2_data
+  {
+    int log_group_desc_size;
+  };
+
 /* made up, these are pointers into FSYS_BUF */
 /* read once, always stays there: */
 #define SUPERBLOCK \
     ((struct ext2_super_block *)(FSYS_BUF))
+#define FS_DATA \
+    ((struct ext2_data *)((int)SUPERBLOCK + sizeof(struct ext2_super_block)))
 #define GROUP_DESC \
-    ((struct ext2_group_desc *) \
-     ((int)SUPERBLOCK + sizeof(struct ext2_super_block)))
+    ((int)((int)FS_DATA + sizeof(struct ext2_data)))
 #define INODE \
     ((struct ext2_inode *)((int)GROUP_DESC + EXT2_BLOCK_SIZE(SUPERBLOCK)))
 #define DATABLOCK1 \
@@ -357,6 +373,20 @@
       || SUPERBLOCK->s_magic != EXT2_SUPER_MAGIC)
       retval = 0;
 
+  if (SUPERBLOCK->s_rev_level != EXT2_GOOD_OLD_REV
+      && (SUPERBLOCK->s_feature_incompat & EXT4_FEATURE_INCOMPAT_64BIT)
+      && SUPERBLOCK->s_desc_size != 0
+      && (SUPERBLOCK->s_desc_size & (SUPERBLOCK->s_desc_size - 1)) == 0
+      && (SUPERBLOCK->s_desc_size & 0x1fe0))
+    {
+      __u16 b = SUPERBLOCK->s_desc_size;
+      for (FS_DATA->log_group_desc_size = 0;
+	   b != (1 << FS_DATA->log_group_desc_size);
+	   FS_DATA->log_group_desc_size++);
+    }
+  else
+    FS_DATA->log_group_desc_size = 5;
+
   return retval;
 }
 
@@ -623,6 +653,32 @@
   return INODE->i_blocks == ea_blocks;
 }
 
+static int
+is_power_of (__u64 a, __u64 b)
+{
+  __u64 c;
+  /* Prevent overflow assuming b < 8.  */
+  if (a >= (1LL << 60))
+    return 0;
+  for (c = 1; c <= a; c *= b);
+  return (c == a);
+}
+
+static int
+ext2fs_group_has_super_block (__u64 group)
+{
+  if (!(SUPERBLOCK->s_feature_ro_compat & EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER))
+    return 1;
+  /* Algorithm looked up in Linux source.  */
+  if (group <= 1)
+    return 1;
+  /* Even number is never a power of odd number.  */
+  if (!(group & 1))
+    return 0;
+  return (is_power_of (group, 7) || is_power_of (group, 5) ||
+	  is_power_of (group, 3));
+}
+
 /* preconditions: ext2fs_mount already executed, therefore supblk in buffer
  *   known as SUPERBLOCK
  * returns: 0 if error, nonzero iff we were able to find the file successfully
@@ -635,8 +691,9 @@
 {
   int current_ino = EXT2_ROOT_INO;	/* start at the root */
   int updir_ino = current_ino;	/* the parent of the current directory */
-  int group_id;			/* which group the inode is in */
-  int group_desc;		/* fs pointer to that group */
+  __u64 full_offset;
+  __u64 group_id;		/* which group the inode is in */
+  __u64 group_desc;		/* fs pointer to that group */
   int desc;			/* index within that group */
   int ino_blk;			/* fs pointer of the inode's information */
   int str_chk = 0;		/* used to hold the results of a string compare */
@@ -675,23 +732,37 @@
 
       /* look up an inode */
       group_id = (current_ino - 1) / (SUPERBLOCK->s_inodes_per_group);
-      group_desc = group_id >> log2 (EXT2_DESC_PER_BLOCK (SUPERBLOCK));
-      desc = group_id & (EXT2_DESC_PER_BLOCK (SUPERBLOCK) - 1);
+      full_offset = group_id << FS_DATA->log_group_desc_size;
+      group_desc = full_offset >> EXT2_BLOCK_SIZE_BITS (SUPERBLOCK);
+      desc = full_offset & (EXT2_BLOCK_SIZE (SUPERBLOCK) - 1);
+      if ((SUPERBLOCK->s_feature_incompat & EXT2_FEATURE_INCOMPAT_META_BG)
+	  && group_desc >= SUPERBLOCK->s_first_meta_bg)
+	{
+	  __u64 first_block_group;
+	  /* Find the first block group for which a descriptor is stored in
+	     the given block.  */
+	  first_block_group = (group_desc << (EXT2_BLOCK_SIZE_BITS (SUPERBLOCK)
+					      - FS_DATA->log_group_desc_size));
+	  group_desc = first_block_group * SUPERBLOCK->s_blocks_per_group;
+	  if (ext2fs_group_has_super_block (first_block_group))
+	    group_desc++;
+	}
+      else
+	/* Superblock.  */
+	group_desc++;
 #ifdef E2DEBUG
       printf ("ipg=%d, dpb=%d\n", SUPERBLOCK->s_inodes_per_group,
 	      EXT2_DESC_PER_BLOCK (SUPERBLOCK));
       printf ("group_id=%d group_desc=%d desc=%d\n", group_id, group_desc, desc);
 #endif /* E2DEBUG */
-      if (!ext2_rdfsb (
-			(WHICH_SUPER + group_desc + SUPERBLOCK->s_first_data_block),
-			(int) GROUP_DESC))
+      if (!ext2_rdfsb (SUPERBLOCK->s_first_data_block + group_desc, GROUP_DESC))
 	{
 	  return 0;
 	}
-      gdp = GROUP_DESC;
+      gdp = (struct ext2_group_desc *)(GROUP_DESC + desc);
       inodes_per_block =  EXT2_BLOCK_SIZE (SUPERBLOCK) / EXT2_INODE_SIZE(SUPERBLOCK);
       inode_offset = ((current_ino - 1) % (SUPERBLOCK->s_inodes_per_group));
-      ino_blk = gdp[desc].bg_inode_table + (inode_offset / inodes_per_block);
+      ino_blk = gdp->bg_inode_table + (inode_offset / inodes_per_block);
 #ifdef E2DEBUG
       printf ("inode table fsblock=%d\n", ino_blk);
 #endif /* E2DEBUG */