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 #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 { 31 unsigned int len = dirent->rec_len; 32 33 if (fs->blocksize < 65536) 34 *rec_len = len; 35 else if (len == EXT4_MAX_REC_LEN || len == 0) 36 *rec_len = fs->blocksize; 37 else 38 *rec_len = (len & 65532) | ((len & 3) << 16); 39 return 0; 40 } 41 42 errcode_t ext2fs_set_rec_len(ext2_filsys fs, 43 unsigned int len, 44 struct ext2_dir_entry *dirent) 45 { 46 if ((len > fs->blocksize) || (fs->blocksize > (1 << 18)) || (len & 3)) 47 return EINVAL; 48 if (len < 65536) { 49 dirent->rec_len = len; 50 return 0; 51 } 52 if (len == fs->blocksize) { 53 if (fs->blocksize == 65536) 54 dirent->rec_len = EXT4_MAX_REC_LEN; 55 else 56 dirent->rec_len = 0; 57 } else 58 dirent->rec_len = (len & 65532) | ((len >> 16) & 3); 59 return 0; 60 } 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 { 74 struct ext2_dir_entry *dirent; 75 unsigned int rec_len; 76 #define DIRENT_MIN_LENGTH 12 77 78 while ((offset < final_offset) && 79 (offset <= fs->blocksize - DIRENT_MIN_LENGTH)) { 80 dirent = (struct ext2_dir_entry *)(buf + offset); 81 if (ext2fs_get_rec_len(fs, dirent, &rec_len)) 82 return 0; 83 offset += rec_len; 84 if ((rec_len < 8) || 85 ((rec_len % 4) != 0) || 86 ((ext2fs_dirent_name_len(dirent)+8) > (int) rec_len)) 87 return 0; 88 } 89 return (offset == final_offset); 90 } 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 { 105 struct dir_context ctx; 106 errcode_t retval; 107 108 EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); 109 110 retval = ext2fs_check_directory(fs, dir); 111 if (retval) 112 return retval; 113 114 ctx.dir = dir; 115 ctx.flags = flags; 116 if (block_buf) 117 ctx.buf = block_buf; 118 else { 119 retval = ext2fs_get_mem(fs->blocksize, &ctx.buf); 120 if (retval) 121 return retval; 122 } 123 ctx.func = func; 124 ctx.priv_data = priv_data; 125 ctx.errcode = 0; 126 retval = ext2fs_block_iterate3(fs, dir, BLOCK_FLAG_READ_ONLY, 0, 127 ext2fs_process_dir_block, &ctx); 128 if (!block_buf) 129 ext2fs_free_mem(&ctx.buf); 130 if (retval == EXT2_ET_INLINE_DATA_CANT_ITERATE) { 131 (void) ext2fs_inline_data_dir_iterate(fs, dir, &ctx); 132 retval = 0; 133 } 134 if (retval) 135 return retval; 136 return ctx.errcode; 137 } 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 { 153 struct xlate *xl = (struct xlate *) priv_data; 154 155 return (*xl->func)(dirent, offset, blocksize, buf, xl->real_private); 156 } 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 { 169 struct xlate xl; 170 171 xl.real_private = priv_data; 172 xl.func = func; 173 174 return ext2fs_dir_iterate2(fs, dir, flags, block_buf, 175 xlate_func, &xl); 176 } 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 { 190 struct dir_context *ctx = (struct dir_context *) priv_data; 191 unsigned int offset = 0; 192 unsigned int next_real_entry = 0; 193 int ret = 0; 194 int changed = 0; 195 int do_abort = 0; 196 unsigned int rec_len, size, buflen; 197 int entry; 198 struct ext2_dir_entry *dirent; 199 int csum_size = 0; 200 int inline_data; 201 errcode_t retval = 0; 202 203 if (blockcnt < 0) 204 return 0; 205 206 entry = blockcnt ? DIRENT_OTHER_FILE : DIRENT_DOT_FILE; 207 208 /* If a dir has inline data, we don't need to read block */ 209 inline_data = !!(ctx->flags & DIRENT_FLAG_INCLUDE_INLINE_DATA); 210 if (!inline_data) { 211 ctx->errcode = ext2fs_read_dir_block4(fs, *blocknr, ctx->buf, 0, 212 ctx->dir); 213 if (ctx->errcode) 214 return BLOCK_ABORT; 215 /* If we handle a normal dir, we traverse the entire block */ 216 buflen = fs->blocksize; 217 } else { 218 buflen = ctx->buflen; 219 } 220 221 if (ext2fs_has_feature_metadata_csum(fs->super)) 222 csum_size = sizeof(struct ext2_dir_entry_tail); 223 224 while (offset < buflen - 8) { 225 dirent = (struct ext2_dir_entry *) (ctx->buf + offset); 226 if (ext2fs_get_rec_len(fs, dirent, &rec_len)) 227 return BLOCK_ABORT; 228 if (((offset + rec_len) > buflen) || 229 (rec_len < 8) || 230 ((rec_len % 4) != 0) || 231 ((ext2fs_dirent_name_len(dirent)+8) > (int) rec_len)) { 232 ctx->errcode = EXT2_ET_DIR_CORRUPTED; 233 return BLOCK_ABORT; 234 } 235 if (!dirent->inode) { 236 /* 237 * We just need to check metadata_csum when this 238 * dir hasn't inline data. That means that 'buflen' 239 * should be blocksize. 240 */ 241 if (!inline_data && 242 (offset == buflen - csum_size) && 243 (dirent->rec_len == csum_size) && 244 (dirent->name_len == EXT2_DIR_NAME_LEN_CSUM)) { 245 if (!(ctx->flags & DIRENT_FLAG_INCLUDE_CSUM)) 246 goto next; 247 entry = DIRENT_CHECKSUM; 248 } else if (!(ctx->flags & DIRENT_FLAG_INCLUDE_EMPTY)) 249 goto next; 250 } 251 252 ret = (ctx->func)(ctx->dir, 253 (next_real_entry > offset) ? 254 DIRENT_DELETED_FILE : entry, 255 dirent, offset, 256 buflen, ctx->buf, 257 ctx->priv_data); 258 if (entry < DIRENT_OTHER_FILE) 259 entry++; 260 261 if (ret & DIRENT_CHANGED) { 262 if (ext2fs_get_rec_len(fs, dirent, &rec_len)) 263 return BLOCK_ABORT; 264 changed++; 265 } 266 if (ret & DIRENT_ABORT) { 267 do_abort++; 268 break; 269 } 270 next: 271 if (next_real_entry == offset) 272 next_real_entry += rec_len; 273 274 if (ctx->flags & DIRENT_FLAG_INCLUDE_REMOVED) { 275 size = (ext2fs_dirent_name_len(dirent) + 11) & ~3; 276 277 if (rec_len != size) { 278 unsigned int final_offset; 279 280 final_offset = offset + rec_len; 281 offset += size; 282 while (offset < final_offset && 283 !ext2fs_validate_entry(fs, ctx->buf, 284 offset, 285 final_offset)) 286 offset += 4; 287 continue; 288 } 289 } 290 offset += rec_len; 291 } 292 293 if (changed) { 294 if (!inline_data) { 295 ctx->errcode = ext2fs_write_dir_block4(fs, *blocknr, 296 ctx->buf, 297 0, ctx->dir); 298 if (ctx->errcode) 299 return BLOCK_ABORT; 300 } else { 301 /* 302 * return BLOCK_INLINE_DATA_CHANGED to notify caller 303 * that inline data has been changed. 304 */ 305 retval = BLOCK_INLINE_DATA_CHANGED; 306 } 307 } 308 if (do_abort) 309 return retval | BLOCK_ABORT; 310 return retval; 311 }