summaryrefslogtreecommitdiffstats
path: root/contrib/syslinux-4.02/core/fs/ext2/bmap.c
blob: ef2bf649d42e1bb2e42651329264f33300807766 (plain) (blame)
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/*
 * The logical block -> physical block routine.
 *
 * Copyright (C) 2009 Liu Aleaxander -- All rights reserved. This file
 * may be redistributed under the terms of the GNU Public License.
 */

#include <stdio.h>
#include <dprintf.h>
#include <fs.h>
#include <disk.h>
#include <cache.h>
#include "ext2_fs.h"

static const struct ext4_extent_header *
ext4_find_leaf(struct fs_info *fs, const struct ext4_extent_header *eh,
	       block_t block)
{
    struct ext4_extent_idx *index;
    block_t blk;
    int i;

    while (1) {
	if (eh->eh_magic != EXT4_EXT_MAGIC)
	    return NULL;
	if (eh->eh_depth == 0)
	    return eh;

	index = EXT4_FIRST_INDEX(eh);
	for (i = 0; i < (int)eh->eh_entries; i++) {
	    if (block < index[i].ei_block)
		break;
	}
	if (--i < 0)
	    return NULL;

	blk = index[i].ei_leaf_hi;
	blk = (blk << 32) + index[i].ei_leaf_lo;
	eh = get_cache(fs->fs_dev, blk);
    }
}

/* handle the ext4 extents to get the phsical block number */
/* XXX: still need to handle sparse files with extents */
static block_t
bmap_extent(struct inode *inode, uint32_t block, size_t *nblocks)
{
    struct fs_info *fs = inode->fs;
    const struct ext4_extent_header *leaf;
    const struct ext4_extent *ext;
    int i;
    block_t start;

    leaf = ext4_find_leaf(fs, &PVT(inode)->i_extent_hdr, block);
    if (!leaf) {
	printf("ERROR, extent leaf not found\n");
	return 0;
    }

    ext = EXT4_FIRST_EXTENT(leaf);
    for (i = 0; i < leaf->eh_entries; i++) {
	if (block < ext[i].ee_block)
	    break;
    }
    if (--i < 0) {
	printf("ERROR, not find the right block\n");
	return 0;
    }

    /* got it */
    block -= ext[i].ee_block;
    if (block >= ext[i].ee_len)
	return 0;
    start = ((block_t)ext[i].ee_start_hi << 32) + ext[i].ee_start_lo;

    if (nblocks)
	*nblocks = ext[i].ee_len - block;

    return start + block;
}

/*
 * Scan forward in a range of blocks to see if they are contiguous,
 * then return the initial value.
 */
static uint32_t
scan_set_nblocks(const uint32_t *map, unsigned int count, size_t *nblocks)
{
    uint32_t blk = *map;

    if (nblocks) {
	uint32_t skip = blk ? 1 : 0;
	uint32_t next = blk + skip;
	size_t   cnt = 1;

	while (--count) {
	    map++;
	    if (*map == next) {
		cnt++;
		next += skip;
	    } else {
		break;
	    }
	}

	*nblocks = cnt;
    }

    return blk;
}

/*
 * The actual indirect block map handling - the block passed in should
 * be relative to the beginning of the particular block hierarchy.
 */
static block_t
bmap_indirect(struct fs_info *fs, uint32_t start, uint32_t block,
	      int levels, size_t *nblocks)
{
    int addr_shift = BLOCK_SHIFT(fs) - 2;
    uint32_t addr_count = 1 << addr_shift;
    const uint32_t *blk = NULL;
    uint32_t index = 0;

    while (levels--) {
	if (!start) {
	    if (nblocks)
		*nblocks = addr_count << (levels * addr_shift);
	    return 0;
	}
	blk = get_cache(fs->fs_dev, start);
	index = (block >> (levels * addr_shift)) & (addr_count - 1);
	start = blk[index];
    }

    return scan_set_nblocks(blk + index, addr_count - index, nblocks);
}

/*
 * Handle the traditional block map, like indirect, double indirect
 * and triple indirect
 */
static block_t
bmap_traditional(struct inode *inode, block_t block, size_t *nblocks)
{
    struct fs_info *fs = inode->fs;
    const uint32_t addr_per_block = BLOCK_SIZE(fs) >> 2;
    const int shft_per_block      = BLOCK_SHIFT(fs) - 2;
    const uint32_t direct_blocks   = EXT2_NDIR_BLOCKS;
    const uint32_t indirect_blocks = addr_per_block;
    const uint32_t double_blocks   = addr_per_block << shft_per_block;
    const uint32_t triple_blocks   = double_blocks  << shft_per_block;

    /* direct blocks */
    if (block < direct_blocks)
	return scan_set_nblocks(&PVT(inode)->i_block[block],
				direct_blocks - block, nblocks);

    /* indirect blocks */
    block -= direct_blocks;
    if (block < indirect_blocks)
	return bmap_indirect(fs, PVT(inode)->i_block[EXT2_IND_BLOCK],
			     block, 1, nblocks);

    /* double indirect blocks */
    block -= indirect_blocks;
    if (block < double_blocks)
	return bmap_indirect(fs, PVT(inode)->i_block[EXT2_DIND_BLOCK],
			     block, 2, nblocks);

    /* triple indirect block */
    block -= double_blocks;
    if (block < triple_blocks)
	return bmap_indirect(fs, PVT(inode)->i_block[EXT2_TIND_BLOCK],
			     block, 3, nblocks);

    /* This can't happen... */
    return 0;
}


/**
 * Map the logical block to physic block where the file data stores.
 * In EXT4, there are two ways to handle the map process, extents and indirect.
 * EXT4 uses a inode flag to mark extent file and indirect block file.
 *
 * @fs:      the fs_info structure.
 * @inode:   the inode structure.
 * @block:   the logical block to be mapped.
 * @nblocks: optional pointer to number of contiguous blocks (low estimate)
 * @retrun:  the physical block number.
 *
 */
block_t ext2_bmap(struct inode *inode, block_t block, size_t *nblocks)
{
    block_t ret;

    if (inode->flags & EXT4_EXTENTS_FLAG)
	ret = bmap_extent(inode, block, nblocks);
    else
	ret = bmap_traditional(inode, block, nblocks);

    return ret;
}


/*
 * Next extent for getfssec
 */
int ext2_next_extent(struct inode *inode, uint32_t lstart)
{
    struct fs_info *fs = inode->fs;
    int blktosec =  BLOCK_SHIFT(fs) - SECTOR_SHIFT(fs);
    int blkmask = (1 << blktosec) - 1;
    block_t block;
    size_t nblocks = 0;

    block = ext2_bmap(inode, lstart >> blktosec, &nblocks);

    if (!block)
	inode->next_extent.pstart = EXTENT_ZERO;
    else
	inode->next_extent.pstart =
	    ((sector_t)block << blktosec) | (lstart & blkmask);

    inode->next_extent.len = (nblocks << blktosec) - (lstart & blkmask);
    return 0;
}