First version
[3rdparty/ote_partner/tlk.git] / lib / fs / ext2 / io.c
1 /*
2  * Copyright (c) 2007 Travis Geiselbrecht
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files
6  * (the "Software"), to deal in the Software without restriction,
7  * including without limitation the rights to use, copy, modify, merge,
8  * publish, distribute, sublicense, and/or sell copies of the Software,
9  * and to permit persons to whom the Software is furnished to do so,
10  * subject to the following conditions:
11  * 
12  * The above copyright notice and this permission notice shall be
13  * included in all copies or substantial portions of the Software.
14  * 
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23
24 #include <string.h>
25 #include <stdlib.h>
26 #include <debug.h>
27 #include <lib/fs/ext2.h>
28 #include "ext2_priv.h"
29
30 #define LOCAL_TRACE 0
31
32 int ext2_read_block(ext2_t *ext2, void *buf, blocknum_t bnum)
33 {
34         return bcache_read_block(ext2->cache, buf, bnum);
35 }
36
37 int ext2_get_block(ext2_t *ext2, void **ptr, blocknum_t bnum)
38 {
39         return bcache_get_block(ext2->cache, ptr, bnum);
40 }
41
42 int ext2_put_block(ext2_t *ext2, blocknum_t bnum)
43 {
44         return bcache_put_block(ext2->cache, bnum);
45 }
46
47 static int ext2_calculate_block_pointer_pos(ext2_t *ext2, blocknum_t block_to_find, uint32_t *level, uint32_t pos[])
48 {
49         uint32_t block_ptr_per_block, block_ptr_per_2nd_block;
50
51         // XXX optimize this
52         
53         // See if it's in the direct blocks
54         if(block_to_find < EXT2_NDIR_BLOCKS) {
55                 *level = 0;
56                 pos[0] = block_to_find;
57                 return 0;
58         }
59         
60         block_ptr_per_block = EXT2_ADDR_PER_BLOCK(ext2->sb);
61         block_to_find -= EXT2_NDIR_BLOCKS;
62         // See if it's in the first indirect block
63         if(block_to_find < block_ptr_per_block) {
64                 *level = 1;
65                 pos[0] = EXT2_IND_BLOCK;        
66                 pos[1] = block_to_find;
67                 return 0;
68         }
69
70         block_to_find -= block_ptr_per_block;
71         block_ptr_per_2nd_block = block_ptr_per_block * block_ptr_per_block;
72         // See if it's in the second indirect block
73         if(block_to_find < (block_ptr_per_2nd_block)) {
74                 *level = 2;
75                 pos[0] = EXT2_DIND_BLOCK;
76                 pos[1] = block_to_find / block_ptr_per_block;
77                 pos[2] = block_to_find % block_ptr_per_block;           
78                 return 0;
79         }
80         
81         block_to_find -= block_ptr_per_2nd_block;
82         // See if it's in the third indirect block
83         if(block_to_find < (block_ptr_per_2nd_block * block_ptr_per_block)) {
84                 *level = 3;
85                 pos[0] = EXT2_TIND_BLOCK;
86                 pos[1] = block_to_find / block_ptr_per_2nd_block;
87                 pos[2] = (block_to_find % block_ptr_per_2nd_block) / block_ptr_per_block;
88                 pos[3] = (block_to_find % block_ptr_per_2nd_block) % block_ptr_per_block;
89                 return 0;       
90         }
91         
92         // The block requested must be too big.
93         return -1;
94 }
95
96 // This function returns a pointer to the cache block that corresponds to the indirect block pointer.
97 int ext2_get_indirect_block_pointer_cache_block(ext2_t *ext2, struct ext2_inode *inode, blocknum_t **cache_block, uint32_t level, uint32_t pos[], uint *block_loaded)
98 {
99         uint32_t current_level = 0;
100         uint current_block = 0, last_block;
101         blocknum_t *block = NULL;
102         int err;
103
104         if ((level > 3) || (level == 0)) {
105                 err = -1;
106                 goto error;
107         }
108
109         // Dig down into the indirect blocks. When done, current_block should point to the target.
110         while (current_level < level) {
111                 if (current_level == 0) {
112                         // read the direct block, simulates a prior loop
113                         current_block = LE32(inode->i_block[pos[0]]);
114                 }
115
116                 if (current_block == 0) {
117                         err = -1;
118                         goto error;
119                 }
120
121                 last_block = current_block;
122                 current_level++;
123                 *block_loaded = current_block;
124
125                 err = ext2_get_block(ext2, (void **)(void *)&block, current_block);
126                 if (err < 0) {
127                         goto error;
128                 } 
129
130                 if (current_level < level) {
131                         current_block = LE32(block[pos[current_level]]);
132                         ext2_put_block(ext2, last_block);
133                 }
134         }
135
136         *cache_block = block;
137         return 0;
138
139 error:
140         *cache_block = NULL;
141         *block_loaded = 0;
142         return err;
143 }
144
145 /* translate a file block to a physical block */
146 static blocknum_t file_block_to_fs_block(ext2_t *ext2, struct ext2_inode *inode, uint fileblock)
147 {
148         int err;
149         blocknum_t block;
150
151         LTRACEF("inode %p, fileblock %u\n", inode, fileblock);  
152
153         uint32_t pos[4];
154         uint32_t level = 0;
155         ext2_calculate_block_pointer_pos(ext2, fileblock, &level, pos);
156
157         LTRACEF("level %d, pos 0x%x 0x%x 0x%x 0x%x\n", level, pos[0], pos[1], pos[2], pos[3]);
158
159         if (level == 0) {
160                 /* direct block, just return it directly */
161                 block = LE32(inode->i_block[fileblock]);
162         } else {
163                 /* at least one level of indirection, get a pointer to the final indirect block table and dereference it */
164                 blocknum_t *ind_table;
165                 blocknum_t phys_block;
166                 err = ext2_get_indirect_block_pointer_cache_block(ext2, inode, &ind_table, level, pos, &phys_block);
167                 if (err < 0)
168                         return 0;
169
170                 /* dereference the final entry in the final table */
171                 block = LE32(ind_table[pos[level]]);
172                 LTRACEF("block %u, indirect_block %u\n", block, phys_block);
173
174                 /* release the ref on the cache block */
175                 ext2_put_block(ext2, phys_block);
176         }
177
178         LTRACEF("returning %u\n", block);
179
180         return block;
181 }
182
183 int ext2_read_inode(ext2_t *ext2, struct ext2_inode *inode, void *_buf, off_t offset, size_t len)
184 {
185         int err = 0;
186         int bytes_read = 0;
187         uint8_t *buf = _buf;
188
189         /* calculate the file size */
190         off_t file_size = ext2_file_len(ext2, inode);
191
192         LTRACEF("inode %p, offset %lld, len %zd, file_size %lld\n", inode, offset, len, file_size);     
193
194         /* trim the read */
195         if (offset > file_size)
196                 return 0;
197         if (offset + len >= file_size)
198                 len = file_size - offset;
199         if (len == 0)
200                 return 0;
201         
202         /* calculate the starting file block */
203         uint file_block = offset / EXT2_BLOCK_SIZE(ext2->sb);
204
205         /* handle partial first block */
206         if ((offset % EXT2_BLOCK_SIZE(ext2->sb)) != 0) {
207                 uint8_t temp[EXT2_BLOCK_SIZE(ext2->sb)];
208
209                 /* calculate the block and read it */
210                 blocknum_t phys_block = file_block_to_fs_block(ext2, inode, file_block);
211                 if (phys_block == 0) {
212                         memset(temp, 0, EXT2_BLOCK_SIZE(ext2->sb));
213                 } else {
214                         ext2_read_block(ext2, temp, phys_block);
215                 }
216
217                 /* copy out what we need */
218                 size_t block_offset = offset % EXT2_BLOCK_SIZE(ext2->sb);
219                 size_t tocopy = MIN(len, EXT2_BLOCK_SIZE(ext2->sb) - block_offset);
220                 memcpy(buf, temp + block_offset, tocopy);
221
222                 /* increment our stuff */
223                 file_block++;
224                 len -= tocopy;
225                 bytes_read += tocopy;
226                 buf += tocopy;
227         }
228
229         /* handle middle blocks */
230         while (len >= EXT2_BLOCK_SIZE(ext2->sb)) {
231                 /* calculate the block and read it */
232                 blocknum_t phys_block = file_block_to_fs_block(ext2, inode, file_block);
233                 if (phys_block == 0) {
234                         memset(buf, 0, EXT2_BLOCK_SIZE(ext2->sb));
235                 } else {
236                         ext2_read_block(ext2, buf, phys_block);
237                 }
238
239                 /* increment our stuff */
240                 file_block++;
241                 len -= EXT2_BLOCK_SIZE(ext2->sb);
242                 bytes_read += EXT2_BLOCK_SIZE(ext2->sb);
243                 buf += EXT2_BLOCK_SIZE(ext2->sb);
244         }
245
246         /* handle partial last block */
247         if (len > 0) {
248                 uint8_t temp[EXT2_BLOCK_SIZE(ext2->sb)];
249
250                 /* calculate the block and read it */
251                 blocknum_t phys_block = file_block_to_fs_block(ext2, inode, file_block);
252                 if (phys_block == 0) {
253                         memset(temp, 0, EXT2_BLOCK_SIZE(ext2->sb));
254                 } else {
255                         ext2_read_block(ext2, temp, phys_block);
256                 }
257
258                 /* copy out what we need */
259                 memcpy(buf, temp, len);
260
261                 /* increment our stuff */
262                 bytes_read += len;
263         }
264
265         LTRACEF("err %d, bytes_read %d\n", err, bytes_read);
266
267         return (err < 0) ? err : bytes_read;
268 }
269