ext4_journal: fix block_tag flag being wrongly parsed...
[lwext4.git] / lwext4 / ext4_journal.c
1 /**
2  * @file  ext4_journal.c
3  * @brief Journalling
4  */
5
6 #include "ext4_config.h"
7 #include "ext4_types.h"
8 #include "ext4_fs.h"
9 #include "ext4_super.h"
10 #include "ext4_errno.h"
11 #include "ext4_blockdev.h"
12 #include "ext4_crc32c.h"
13 #include "ext4_debug.h"
14 #include "tree.h"
15
16 #include <string.h>
17 #include <malloc.h>
18
19 int jbd_inode_bmap(struct jbd_fs *jbd_fs,
20                    ext4_lblk_t iblock,
21                    ext4_fsblk_t *fblock);
22
23 int jbd_sb_write(struct jbd_fs *jbd_fs, struct jbd_sb *s)
24 {
25         int rc;
26         struct ext4_fs *fs = jbd_fs->inode_ref.fs;
27         uint64_t offset;
28         ext4_fsblk_t fblock;
29         rc = jbd_inode_bmap(jbd_fs, 0, &fblock);
30         if (rc != EOK)
31                 return rc;
32
33         offset = fblock * ext4_sb_get_block_size(&fs->sb);
34         return ext4_block_writebytes(fs->bdev, offset, s,
35                                      EXT4_SUPERBLOCK_SIZE);
36 }
37
38 int jbd_sb_read(struct jbd_fs *jbd_fs, struct jbd_sb *s)
39 {
40         int rc;
41         struct ext4_fs *fs = jbd_fs->inode_ref.fs;
42         uint64_t offset;
43         ext4_fsblk_t fblock;
44         rc = jbd_inode_bmap(jbd_fs, 0, &fblock);
45         if (rc != EOK)
46                 return rc;
47
48         offset = fblock * ext4_sb_get_block_size(&fs->sb);
49         return ext4_block_readbytes(fs->bdev, offset, s,
50                                     EXT4_SUPERBLOCK_SIZE);
51 }
52
53 static bool jbd_verify_sb(struct jbd_sb *sb)
54 {
55         struct jbd_bhdr *header = &sb->header;
56         if (jbd_get32(header, magic) != JBD_MAGIC_NUMBER)
57                 return false;
58
59         if (jbd_get32(header, blocktype) != JBD_SUPERBLOCK &&
60             jbd_get32(header, blocktype) != JBD_SUPERBLOCK_V2)
61                 return false;
62
63         return true;
64 }
65
66 int jbd_get_fs(struct ext4_fs *fs,
67                struct jbd_fs *jbd_fs)
68 {
69         int rc;
70         uint32_t journal_ino;
71
72         memset(jbd_fs, 0, sizeof(struct jbd_fs));
73         journal_ino = ext4_get32(&fs->sb, journal_inode_number);
74
75         rc = ext4_fs_get_inode_ref(fs,
76                                    journal_ino,
77                                    &jbd_fs->inode_ref);
78         if (rc != EOK) {
79                 memset(jbd_fs, 0, sizeof(struct jbd_fs));
80                 return rc;
81         }
82         rc = jbd_sb_read(jbd_fs, &jbd_fs->sb);
83         if (rc != EOK) {
84                 memset(jbd_fs, 0, sizeof(struct jbd_fs));
85                 ext4_fs_put_inode_ref(&jbd_fs->inode_ref);
86         }
87
88         return rc;
89 }
90
91 int jbd_put_fs(struct jbd_fs *jbd_fs)
92 {
93         int rc;
94         rc = ext4_fs_put_inode_ref(&jbd_fs->inode_ref);
95         return rc;
96 }
97
98 int jbd_inode_bmap(struct jbd_fs *jbd_fs,
99                    ext4_lblk_t iblock,
100                    ext4_fsblk_t *fblock)
101 {
102         int rc = ext4_fs_get_inode_data_block_index(
103                         &jbd_fs->inode_ref,
104                         iblock,
105                         fblock,
106                         false);
107         return rc;
108 }
109
110 int jbd_block_get(struct jbd_fs *jbd_fs,
111                   struct ext4_block *block,
112                   ext4_fsblk_t fblock)
113 {
114         /* TODO: journal device. */
115         int rc;
116         ext4_lblk_t iblock = (ext4_lblk_t)fblock;
117         rc = jbd_inode_bmap(jbd_fs, iblock,
118                             &fblock);
119         if (rc != EOK)
120                 return rc;
121
122         struct ext4_blockdev *bdev = jbd_fs->inode_ref.fs->bdev;
123         rc = ext4_block_get(bdev, block, fblock);
124         return rc;
125 }
126
127 int jbd_block_get_noread(struct jbd_fs *jbd_fs,
128                          struct ext4_block *block,
129                          ext4_fsblk_t fblock)
130 {
131         /* TODO: journal device. */
132         int rc;
133         ext4_lblk_t iblock = (ext4_lblk_t)fblock;
134         rc = jbd_inode_bmap(jbd_fs, iblock,
135                             &fblock);
136         if (rc != EOK)
137                 return rc;
138
139         struct ext4_blockdev *bdev = jbd_fs->inode_ref.fs->bdev;
140         rc = ext4_block_get_noread(bdev, block, fblock);
141         return rc;
142 }
143
144 int jbd_block_set(struct jbd_fs *jbd_fs,
145                   struct ext4_block *block)
146 {
147         return ext4_block_set(jbd_fs->inode_ref.fs->bdev,
148                               block);
149 }
150
151 /*
152  * helper functions to deal with 32 or 64bit block numbers.
153  */
154 int jbd_tag_bytes(struct jbd_fs *jbd_fs)
155 {
156         int size;
157
158         if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb,
159                                      JBD_FEATURE_INCOMPAT_CSUM_V3))
160                 return sizeof(struct jbd_block_tag3);
161
162         size = sizeof(struct jbd_block_tag);
163
164         if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb,
165                                      JBD_FEATURE_INCOMPAT_CSUM_V2))
166                 size += sizeof(uint16_t);
167
168         if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb,
169                                      JBD_FEATURE_INCOMPAT_64BIT))
170                 return size;
171
172         return size - sizeof(uint32_t);
173 }
174
175 static void
176 jbd_extract_block_tag(struct jbd_fs *jbd_fs,
177                       uint32_t tag_bytes,
178                       void *__tag,
179                       ext4_fsblk_t *block,
180                       bool *uuid_exist,
181                       uint8_t *uuid,
182                       bool *last_tag)
183 {
184         char *uuid_start;
185         *uuid_exist = false;
186         *last_tag = false;
187         if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb,
188                                      JBD_FEATURE_INCOMPAT_CSUM_V3)) {
189                 struct jbd_block_tag3 *tag = __tag;
190                 *block = jbd_get32(tag, blocknr);
191                 if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb,
192                                              JBD_FEATURE_INCOMPAT_64BIT))
193                          *block |= (uint64_t)jbd_get32(tag, blocknr_high) << 32;
194
195                 if (jbd_get32(tag, flags) & JBD_FLAG_ESCAPE)
196                         *block = 0;
197
198                 if (!(jbd_get32(tag, flags) & JBD_FLAG_SAME_UUID)) {
199                         uuid_start = (char *)tag + tag_bytes;
200                         *uuid_exist = true;
201                         memcpy(uuid, uuid_start, UUID_SIZE);
202                 }
203
204                 if (jbd_get32(tag, flags) & JBD_FLAG_LAST_TAG)
205                         *last_tag = true;
206
207         } else {
208                 struct jbd_block_tag *tag = __tag;
209                 *block = jbd_get32(tag, blocknr);
210                 if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb,
211                                              JBD_FEATURE_INCOMPAT_64BIT))
212                          *block |= (uint64_t)jbd_get32(tag, blocknr_high) << 32;
213
214                 if (jbd_get16(tag, flags) & JBD_FLAG_ESCAPE)
215                         *block = 0;
216
217                 if (!(jbd_get16(tag, flags) & JBD_FLAG_SAME_UUID)) {
218                         uuid_start = (char *)tag + tag_bytes;
219                         *uuid_exist = true;
220                         memcpy(uuid, uuid_start, UUID_SIZE);
221                 }
222
223                 if (jbd_get16(tag, flags) & JBD_FLAG_LAST_TAG)
224                         *last_tag = true;
225
226         }
227 }
228
229 static void
230 jbd_iterate_block_table(struct jbd_fs *jbd_fs,
231                         void *__tag_start,
232                         uint32_t tag_tbl_size,
233                         void (*func)(struct jbd_fs * jbd_fs,
234                                         ext4_fsblk_t block,
235                                         uint8_t *uuid,
236                                         void *arg),
237                         void *arg)
238 {
239         ext4_fsblk_t block = 0;
240         uint8_t uuid[UUID_SIZE];
241         char *tag_start, *tag_ptr;
242         uint32_t tag_bytes = jbd_tag_bytes(jbd_fs);
243         tag_start = __tag_start;
244         tag_ptr = tag_start;
245
246         if (JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb,
247                                      JBD_FEATURE_INCOMPAT_CSUM_V2) ||
248             JBD_HAS_INCOMPAT_FEATURE(&jbd_fs->sb,
249                                      JBD_FEATURE_INCOMPAT_CSUM_V3))
250                 tag_tbl_size -= sizeof(struct jbd_block_tail);
251
252         while (tag_ptr - tag_start + tag_bytes <= tag_tbl_size) {
253                 bool uuid_exist;
254                 bool last_tag;
255                 jbd_extract_block_tag(jbd_fs,
256                                       tag_bytes,
257                                       tag_ptr,
258                                       &block,
259                                       &uuid_exist,
260                                       uuid,
261                                       &last_tag);
262                 if (func)
263                         func(jbd_fs, block, uuid, arg);
264
265                 if (last_tag)
266                         break;
267
268                 tag_ptr += tag_bytes;
269                 if (uuid_exist)
270                         tag_ptr += UUID_SIZE;
271
272         }
273 }
274
275 static void jbd_display_block_tags(struct jbd_fs *jbd_fs,
276                                    ext4_fsblk_t block,
277                                    uint8_t *uuid,
278                                    void *arg)
279 {
280         uint32_t *iblock = arg;
281         ext4_dbg(DEBUG_JBD, "Block in block_tag: %" PRIu64 "\n", block);
282         (*iblock)++;
283         (void)jbd_fs;
284         (void)uuid;
285         return;
286 }
287
288 struct revoke_entry {
289         ext4_fsblk_t block;
290         uint32_t trans_id;
291         RB_ENTRY(revoke_entry) revoke_node;
292 };
293
294 /* Make sure we wrap around the log correctly! */
295 #define wrap(sb, var)                                           \
296 do {                                                                    \
297         if (var >= jbd_get32((sb), maxlen))                                     \
298                 var -= (jbd_get32((sb), maxlen) - jbd_get32((sb), first));      \
299 } while (0)
300
301 #define ACTION_SCAN 0
302 #define ACTION_REVOKE 1
303 #define ACTION_RECOVER 2
304
305 struct recover_info {
306         uint32_t start_trans_id;
307         uint32_t last_trans_id;
308         RB_HEAD(jbd_revoke, revoke_entry) revoke_root;
309 };
310
311 static void jbd_build_revoke_root(struct jbd_fs *jbd_fs,
312                                   struct jbd_bhdr *header,
313                                   struct recover_info *info)
314 {
315         struct jbd_revoke_header *revoke_hdr =
316                 (struct jbd_revoke_header *)header;
317
318         jbd_iterate_block_table(jbd_fs,
319                                 revoke_hdr + 1,
320                                 jbd_get32(&jbd_fs->sb, blocksize) -
321                                         sizeof(struct jbd_revoke_header),
322                                 jbd_display_block_tags,
323                                 NULL);
324
325         (void)info;
326 }
327
328 static void jbd_debug_descriptor_block(struct jbd_fs *jbd_fs,
329                                        struct jbd_bhdr *header,
330                                        uint32_t *iblock)
331 {
332         jbd_iterate_block_table(jbd_fs,
333                                 header + 1,
334                                 jbd_get32(&jbd_fs->sb, blocksize) -
335                                         sizeof(struct jbd_bhdr),
336                                 jbd_display_block_tags,
337                                 iblock);
338 }
339
340 int jbd_iterate_log(struct jbd_fs *jbd_fs,
341                     struct recover_info *info,
342                     int action)
343 {
344         int r = EOK;
345         bool log_end = false;
346         struct jbd_sb *sb = &jbd_fs->sb;
347         uint32_t start_trans_id, this_trans_id;
348         uint32_t start_block, this_block;
349
350         start_trans_id = this_trans_id = jbd_get32(sb, sequence);
351         start_block = this_block = jbd_get32(sb, start);
352
353         ext4_dbg(DEBUG_JBD, "Start of journal at trans id: %" PRIu32 "\n",
354                             start_trans_id);
355
356         while (!log_end) {
357                 struct ext4_block block;
358                 struct jbd_bhdr *header;
359                 if (action != ACTION_SCAN)
360                         if (this_trans_id > info->last_trans_id) {
361                                 log_end = true;
362                                 continue;
363                         }
364
365                 r = jbd_block_get(jbd_fs, &block, this_block);
366                 if (r != EOK)
367                         break;
368
369                 header = (struct jbd_bhdr *)block.data;
370                 if (jbd_get32(header, magic) != JBD_MAGIC_NUMBER) {
371                         jbd_block_set(jbd_fs, &block);
372                         log_end = true;
373                         continue;
374                 }
375
376                 if (jbd_get32(header, sequence) != this_trans_id) {
377                         if (action != ACTION_SCAN)
378                                 r = EIO;
379
380                         jbd_block_set(jbd_fs, &block);
381                         log_end = true;
382                         continue;
383                 }
384
385                 switch (jbd_get32(header, blocktype)) {
386                 case JBD_DESCRIPTOR_BLOCK:
387                         ext4_dbg(DEBUG_JBD, "Descriptor block: %u, "
388                                             "trans_id: %u\n",
389                                             this_block, this_trans_id);
390                         jbd_debug_descriptor_block(jbd_fs, header, &this_block);
391                         break;
392                 case JBD_COMMIT_BLOCK:
393                         ext4_dbg(DEBUG_JBD, "Commit block: %u, "
394                                             "trans_id: %u\n",
395                                             this_block, this_trans_id);
396                         this_trans_id++;
397                         break;
398                 case JBD_REVOKE_BLOCK:
399                         ext4_dbg(DEBUG_JBD, "Revoke block: %u, "
400                                             "trans_id: %u\n",
401                                             this_block, this_trans_id);
402                         jbd_build_revoke_root(jbd_fs, header, info);
403                         break;
404                 default:
405                         log_end = true;
406                         break;
407                 }
408                 jbd_block_set(jbd_fs, &block);
409                 this_block++;
410                 wrap(sb, this_block);
411                 if (this_block == start_block)
412                         log_end = true;
413
414         }
415         ext4_dbg(DEBUG_JBD, "End of journal.\n");
416         if (r == EOK && action == ACTION_SCAN) {
417                 info->start_trans_id = start_trans_id;
418                 if (this_trans_id > start_trans_id)
419                         info->last_trans_id = this_trans_id - 1;
420                 else
421                         info->last_trans_id = this_trans_id;
422         }
423
424         return r;
425 }
426
427 int jbd_recover(struct jbd_fs *jbd_fs)
428 {
429         int r;
430         struct recover_info info;
431         struct jbd_sb *sb = &jbd_fs->sb;
432         if (!sb->start)
433                 return EOK;
434
435         r = jbd_iterate_log(jbd_fs, &info, ACTION_SCAN);
436         return r;
437 }