/src/e2fsprogs/lib/ext2fs/dir_iterate.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * dir_iterate.c --- ext2fs directory iteration operations |
3 | | * |
4 | | * Copyright (C) 1993, 1994, 1994, 1995, 1996, 1997 Theodore Ts'o. |
5 | | * |
6 | | * %Begin-Header% |
7 | | * This file may be redistributed under the terms of the GNU Library |
8 | | * General Public License, version 2. |
9 | | * %End-Header% |
10 | | */ |
11 | | |
12 | | #include "config.h" |
13 | | #include <stdio.h> |
14 | | #include <string.h> |
15 | | #if HAVE_UNISTD_H |
16 | | #include <unistd.h> |
17 | | #endif |
18 | | #if HAVE_ERRNO_H |
19 | | #include <errno.h> |
20 | | #endif |
21 | | |
22 | | #include "ext2_fs.h" |
23 | | #include "ext2fsP.h" |
24 | | |
25 | 0 | #define EXT4_MAX_REC_LEN ((1<<16)-1) |
26 | | |
27 | | errcode_t ext2fs_get_rec_len(ext2_filsys fs, |
28 | | struct ext2_dir_entry *dirent, |
29 | | unsigned int *rec_len) |
30 | 0 | { |
31 | 0 | unsigned int len = dirent->rec_len; |
32 | |
|
33 | 0 | if (fs->blocksize < 65536) |
34 | 0 | *rec_len = len; |
35 | 0 | else if (len == EXT4_MAX_REC_LEN || len == 0) |
36 | 0 | *rec_len = fs->blocksize; |
37 | 0 | else |
38 | 0 | *rec_len = (len & 65532) | ((len & 3) << 16); |
39 | 0 | return 0; |
40 | 0 | } |
41 | | |
42 | | errcode_t ext2fs_set_rec_len(ext2_filsys fs, |
43 | | unsigned int len, |
44 | | struct ext2_dir_entry *dirent) |
45 | 0 | { |
46 | 0 | if ((len > fs->blocksize) || (fs->blocksize > (1 << 18)) || (len & 3)) |
47 | 0 | return EINVAL; |
48 | 0 | if (len < 65536) { |
49 | 0 | dirent->rec_len = len; |
50 | 0 | return 0; |
51 | 0 | } |
52 | 0 | if (len == fs->blocksize) { |
53 | 0 | if (fs->blocksize == 65536) |
54 | 0 | dirent->rec_len = EXT4_MAX_REC_LEN; |
55 | 0 | else |
56 | 0 | dirent->rec_len = 0; |
57 | 0 | } else |
58 | 0 | dirent->rec_len = (len & 65532) | ((len >> 16) & 3); |
59 | 0 | return 0; |
60 | 0 | } |
61 | | |
62 | | /* |
63 | | * This function checks to see whether or not a potential deleted |
64 | | * directory entry looks valid. What we do is check the deleted entry |
65 | | * and each successive entry to make sure that they all look valid and |
66 | | * that the last deleted entry ends at the beginning of the next |
67 | | * undeleted entry. Returns 1 if the deleted entry looks valid, zero |
68 | | * if not valid. |
69 | | */ |
70 | | static int ext2fs_validate_entry(ext2_filsys fs, char *buf, |
71 | | unsigned int offset, |
72 | | unsigned int final_offset) |
73 | 0 | { |
74 | 0 | struct ext2_dir_entry *dirent; |
75 | 0 | unsigned int rec_len; |
76 | 0 | #define DIRENT_MIN_LENGTH 12 |
77 | |
|
78 | 0 | while ((offset < final_offset) && |
79 | 0 | (offset <= fs->blocksize - DIRENT_MIN_LENGTH)) { |
80 | 0 | dirent = (struct ext2_dir_entry *)(buf + offset); |
81 | 0 | if (ext2fs_get_rec_len(fs, dirent, &rec_len)) |
82 | 0 | return 0; |
83 | 0 | offset += rec_len; |
84 | 0 | if ((rec_len < 8) || |
85 | 0 | ((rec_len % 4) != 0) || |
86 | 0 | ((ext2fs_dirent_name_len(dirent)+8) > (int) rec_len)) |
87 | 0 | return 0; |
88 | 0 | } |
89 | 0 | return (offset == final_offset); |
90 | 0 | } |
91 | | |
92 | | errcode_t ext2fs_dir_iterate2(ext2_filsys fs, |
93 | | ext2_ino_t dir, |
94 | | int flags, |
95 | | char *block_buf, |
96 | | int (*func)(ext2_ino_t dir, |
97 | | int entry, |
98 | | struct ext2_dir_entry *dirent, |
99 | | int offset, |
100 | | int blocksize, |
101 | | char *buf, |
102 | | void *priv_data), |
103 | | void *priv_data) |
104 | 0 | { |
105 | 0 | struct dir_context ctx; |
106 | 0 | errcode_t retval; |
107 | |
|
108 | 0 | EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); |
109 | | |
110 | 0 | retval = ext2fs_check_directory(fs, dir); |
111 | 0 | if (retval) |
112 | 0 | return retval; |
113 | | |
114 | 0 | ctx.dir = dir; |
115 | 0 | ctx.flags = flags; |
116 | 0 | if (block_buf) |
117 | 0 | ctx.buf = block_buf; |
118 | 0 | else { |
119 | 0 | retval = ext2fs_get_mem(fs->blocksize, &ctx.buf); |
120 | 0 | if (retval) |
121 | 0 | return retval; |
122 | 0 | } |
123 | 0 | ctx.func = func; |
124 | 0 | ctx.priv_data = priv_data; |
125 | 0 | ctx.errcode = 0; |
126 | 0 | retval = ext2fs_block_iterate3(fs, dir, BLOCK_FLAG_READ_ONLY, 0, |
127 | 0 | ext2fs_process_dir_block, &ctx); |
128 | 0 | if (!block_buf) |
129 | 0 | ext2fs_free_mem(&ctx.buf); |
130 | 0 | if (retval == EXT2_ET_INLINE_DATA_CANT_ITERATE) { |
131 | 0 | (void) ext2fs_inline_data_dir_iterate(fs, dir, &ctx); |
132 | 0 | retval = 0; |
133 | 0 | } |
134 | 0 | if (retval) |
135 | 0 | return retval; |
136 | 0 | return ctx.errcode; |
137 | 0 | } |
138 | | |
139 | | struct xlate { |
140 | | int (*func)(struct ext2_dir_entry *dirent, |
141 | | int offset, |
142 | | int blocksize, |
143 | | char *buf, |
144 | | void *priv_data); |
145 | | void *real_private; |
146 | | }; |
147 | | |
148 | | static int xlate_func(ext2_ino_t dir EXT2FS_ATTR((unused)), |
149 | | int entry EXT2FS_ATTR((unused)), |
150 | | struct ext2_dir_entry *dirent, int offset, |
151 | | int blocksize, char *buf, void *priv_data) |
152 | 0 | { |
153 | 0 | struct xlate *xl = (struct xlate *) priv_data; |
154 | |
|
155 | 0 | return (*xl->func)(dirent, offset, blocksize, buf, xl->real_private); |
156 | 0 | } |
157 | | |
158 | | errcode_t ext2fs_dir_iterate(ext2_filsys fs, |
159 | | ext2_ino_t dir, |
160 | | int flags, |
161 | | char *block_buf, |
162 | | int (*func)(struct ext2_dir_entry *dirent, |
163 | | int offset, |
164 | | int blocksize, |
165 | | char *buf, |
166 | | void *priv_data), |
167 | | void *priv_data) |
168 | 0 | { |
169 | 0 | struct xlate xl; |
170 | |
|
171 | 0 | xl.real_private = priv_data; |
172 | 0 | xl.func = func; |
173 | |
|
174 | 0 | return ext2fs_dir_iterate2(fs, dir, flags, block_buf, |
175 | 0 | xlate_func, &xl); |
176 | 0 | } |
177 | | |
178 | | |
179 | | /* |
180 | | * Helper function which is private to this module. Used by |
181 | | * ext2fs_dir_iterate() and ext2fs_dblist_dir_iterate() |
182 | | */ |
183 | | int ext2fs_process_dir_block(ext2_filsys fs, |
184 | | blk64_t *blocknr, |
185 | | e2_blkcnt_t blockcnt, |
186 | | blk64_t ref_block EXT2FS_ATTR((unused)), |
187 | | int ref_offset EXT2FS_ATTR((unused)), |
188 | | void *priv_data) |
189 | 0 | { |
190 | 0 | struct dir_context *ctx = (struct dir_context *) priv_data; |
191 | 0 | unsigned int offset = 0; |
192 | 0 | unsigned int next_real_entry = 0; |
193 | 0 | int ret = 0; |
194 | 0 | int changed = 0; |
195 | 0 | int do_abort = 0; |
196 | 0 | unsigned int rec_len, size, buflen; |
197 | 0 | int entry; |
198 | 0 | struct ext2_dir_entry *dirent; |
199 | 0 | int csum_size = 0; |
200 | 0 | int inline_data; |
201 | 0 | errcode_t retval = 0; |
202 | |
|
203 | 0 | if (blockcnt < 0) |
204 | 0 | return 0; |
205 | | |
206 | 0 | entry = blockcnt ? DIRENT_OTHER_FILE : DIRENT_DOT_FILE; |
207 | | |
208 | | /* If a dir has inline data, we don't need to read block */ |
209 | 0 | inline_data = !!(ctx->flags & DIRENT_FLAG_INCLUDE_INLINE_DATA); |
210 | 0 | if (!inline_data) { |
211 | 0 | ctx->errcode = ext2fs_read_dir_block4(fs, *blocknr, ctx->buf, 0, |
212 | 0 | ctx->dir); |
213 | 0 | if (ctx->errcode) |
214 | 0 | return BLOCK_ABORT; |
215 | | /* If we handle a normal dir, we traverse the entire block */ |
216 | 0 | buflen = fs->blocksize; |
217 | 0 | } else { |
218 | 0 | buflen = ctx->buflen; |
219 | 0 | } |
220 | | |
221 | 0 | if (ext2fs_has_feature_metadata_csum(fs->super)) |
222 | 0 | csum_size = sizeof(struct ext2_dir_entry_tail); |
223 | |
|
224 | 0 | if (buflen < 8) { |
225 | 0 | ctx->errcode = EXT2_ET_DIR_CORRUPTED; |
226 | 0 | return BLOCK_ABORT; |
227 | 0 | } |
228 | 0 | while (offset < buflen - 8) { |
229 | 0 | dirent = (struct ext2_dir_entry *) (ctx->buf + offset); |
230 | 0 | if (ext2fs_get_rec_len(fs, dirent, &rec_len)) |
231 | 0 | return BLOCK_ABORT; |
232 | 0 | if (((offset + rec_len) > buflen) || |
233 | 0 | (rec_len < 8) || |
234 | 0 | ((rec_len % 4) != 0) || |
235 | 0 | ((ext2fs_dirent_name_len(dirent)+8) > (int) rec_len)) { |
236 | 0 | ctx->errcode = EXT2_ET_DIR_CORRUPTED; |
237 | 0 | return BLOCK_ABORT; |
238 | 0 | } |
239 | 0 | if (!dirent->inode) { |
240 | | /* |
241 | | * We just need to check metadata_csum when this |
242 | | * dir hasn't inline data. That means that 'buflen' |
243 | | * should be blocksize. |
244 | | */ |
245 | 0 | if (!inline_data && |
246 | 0 | (offset == buflen - csum_size) && |
247 | 0 | (dirent->rec_len == csum_size) && |
248 | 0 | (dirent->name_len == EXT2_DIR_NAME_LEN_CSUM)) { |
249 | 0 | if (!(ctx->flags & DIRENT_FLAG_INCLUDE_CSUM)) |
250 | 0 | goto next; |
251 | 0 | entry = DIRENT_CHECKSUM; |
252 | 0 | } else if (!(ctx->flags & DIRENT_FLAG_INCLUDE_EMPTY)) |
253 | 0 | goto next; |
254 | 0 | } |
255 | | |
256 | 0 | ret = (ctx->func)(ctx->dir, |
257 | 0 | (next_real_entry > offset) ? |
258 | 0 | DIRENT_DELETED_FILE : entry, |
259 | 0 | dirent, offset, |
260 | 0 | buflen, ctx->buf, |
261 | 0 | ctx->priv_data); |
262 | 0 | if (entry < DIRENT_OTHER_FILE) |
263 | 0 | entry++; |
264 | |
|
265 | 0 | if (ret & DIRENT_CHANGED) { |
266 | 0 | if (ext2fs_get_rec_len(fs, dirent, &rec_len)) |
267 | 0 | return BLOCK_ABORT; |
268 | 0 | changed++; |
269 | 0 | } |
270 | 0 | if (ret & DIRENT_ABORT) { |
271 | 0 | do_abort++; |
272 | 0 | break; |
273 | 0 | } |
274 | 0 | next: |
275 | 0 | if (next_real_entry == offset) |
276 | 0 | next_real_entry += rec_len; |
277 | |
|
278 | 0 | if (ctx->flags & DIRENT_FLAG_INCLUDE_REMOVED) { |
279 | 0 | size = (ext2fs_dirent_name_len(dirent) + 11) & ~3; |
280 | |
|
281 | 0 | if (rec_len != size) { |
282 | 0 | unsigned int final_offset; |
283 | |
|
284 | 0 | final_offset = offset + rec_len; |
285 | 0 | offset += size; |
286 | 0 | while (offset < final_offset && |
287 | 0 | !ext2fs_validate_entry(fs, ctx->buf, |
288 | 0 | offset, |
289 | 0 | final_offset)) |
290 | 0 | offset += 4; |
291 | 0 | continue; |
292 | 0 | } |
293 | 0 | } |
294 | 0 | offset += rec_len; |
295 | 0 | } |
296 | | |
297 | 0 | if (changed) { |
298 | 0 | if (!inline_data) { |
299 | 0 | ctx->errcode = ext2fs_write_dir_block4(fs, *blocknr, |
300 | 0 | ctx->buf, |
301 | 0 | 0, ctx->dir); |
302 | 0 | if (ctx->errcode) |
303 | 0 | return BLOCK_ABORT; |
304 | 0 | } else { |
305 | | /* |
306 | | * return BLOCK_INLINE_DATA_CHANGED to notify caller |
307 | | * that inline data has been changed. |
308 | | */ |
309 | 0 | retval = BLOCK_INLINE_DATA_CHANGED; |
310 | 0 | } |
311 | 0 | } |
312 | 0 | if (do_abort) |
313 | 0 | return retval | BLOCK_ABORT; |
314 | 0 | return retval; |
315 | 0 | } |