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 <stdio.h> 13 #include <string.h> 14 #if HAVE_UNISTD_H 15 #include <unistd.h> 16 #endif 17 #if HAVE_ERRNO_H 18 #include <errno.h> 19 #endif 20 21 #include "ext2_fs.h" 22 #include "ext2fsP.h" 23 24 #define EXT4_MAX_REC_LEN ((1<<16)-1) 25 26 errcode_t ext2fs_get_rec_len(ext2_filsys fs, 27 struct ext2_dir_entry *dirent, 28 unsigned int *rec_len) 29 { 30 unsigned int len = dirent->rec_len; 31 32 if (fs->blocksize < 65536) 33 *rec_len = len; 34 else if (len == EXT4_MAX_REC_LEN || len == 0) 35 *rec_len = fs->blocksize; 36 else 37 *rec_len = (len & 65532) | ((len & 3) << 16); 38 return 0; 39 } 40 41 errcode_t ext2fs_set_rec_len(ext2_filsys fs, 42 unsigned int len, 43 struct ext2_dir_entry *dirent) 44 { 45 if ((len > fs->blocksize) || (fs->blocksize > (1 << 18)) || (len & 3)) 46 return EINVAL; 47 if (len < 65536) { 48 dirent->rec_len = len; 49 return 0; 50 } 51 if (len == fs->blocksize) { 52 if (fs->blocksize == 65536) 53 dirent->rec_len = EXT4_MAX_REC_LEN; 54 else 55 dirent->rec_len = 0; 56 } else 57 dirent->rec_len = (len & 65532) | ((len >> 16) & 3); 58 return 0; 59 } 60 61 /* 62 * This function checks to see whether or not a potential deleted 63 * directory entry looks valid. What we do is check the deleted entry 64 * and each successive entry to make sure that they all look valid and 65 * that the last deleted entry ends at the beginning of the next 66 * undeleted entry. Returns 1 if the deleted entry looks valid, zero 67 * if not valid. 68 */ 69 static int ext2fs_validate_entry(ext2_filsys fs, char *buf, 70 unsigned int offset, 71 unsigned int final_offset) 72 { 73 struct ext2_dir_entry *dirent; 74 unsigned int rec_len; 75 #define DIRENT_MIN_LENGTH 12 76 77 while ((offset < final_offset) && 78 (offset <= fs->blocksize - DIRENT_MIN_LENGTH)) { 79 dirent = (struct ext2_dir_entry *)(buf + offset); 80 if (ext2fs_get_rec_len(fs, dirent, &rec_len)) 81 return 0; 82 offset += rec_len; 83 if ((rec_len < 8) || 84 ((rec_len % 4) != 0) || 85 ((((unsigned) dirent->name_len & 0xFF)+8) > rec_len)) 86 return 0; 87 } 88 return (offset == final_offset); 89 } 90 91 errcode_t ext2fs_dir_iterate2(ext2_filsys fs, 92 ext2_ino_t dir, 93 int flags, 94 char *block_buf, 95 int (*func)(ext2_ino_t dir, 96 int entry, 97 struct ext2_dir_entry *dirent, 98 int offset, 99 int blocksize, 100 char *buf, 101 void *priv_data), 102 void *priv_data) 103 { 104 struct dir_context ctx; 105 errcode_t retval; 106 107 EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); 108 109 retval = ext2fs_check_directory(fs, dir); 110 if (retval) 111 return retval; 112 113 ctx.dir = dir; 114 ctx.flags = flags; 115 if (block_buf) 116 ctx.buf = block_buf; 117 else { 118 retval = ext2fs_get_mem(fs->blocksize, &ctx.buf); 119 if (retval) 120 return retval; 121 } 122 ctx.func = func; 123 ctx.priv_data = priv_data; 124 ctx.errcode = 0; 125 retval = ext2fs_block_iterate2(fs, dir, BLOCK_FLAG_READ_ONLY, 0, 126 ext2fs_process_dir_block, &ctx); 127 if (!block_buf) 128 ext2fs_free_mem(&ctx.buf); 129 if (retval) 130 return retval; 131 return ctx.errcode; 132 } 133 134 struct xlate { 135 int (*func)(struct ext2_dir_entry *dirent, 136 int offset, 137 int blocksize, 138 char *buf, 139 void *priv_data); 140 void *real_private; 141 }; 142 143 static int xlate_func(ext2_ino_t dir EXT2FS_ATTR((unused)), 144 int entry EXT2FS_ATTR((unused)), 145 struct ext2_dir_entry *dirent, int offset, 146 int blocksize, char *buf, void *priv_data) 147 { 148 struct xlate *xl = (struct xlate *) priv_data; 149 150 return (*xl->func)(dirent, offset, blocksize, buf, xl->real_private); 151 } 152 153 extern errcode_t ext2fs_dir_iterate(ext2_filsys fs, 154 ext2_ino_t dir, 155 int flags, 156 char *block_buf, 157 int (*func)(struct ext2_dir_entry *dirent, 158 int offset, 159 int blocksize, 160 char *buf, 161 void *priv_data), 162 void *priv_data) 163 { 164 struct xlate xl; 165 166 xl.real_private = priv_data; 167 xl.func = func; 168 169 return ext2fs_dir_iterate2(fs, dir, flags, block_buf, 170 xlate_func, &xl); 171 } 172 173 174 /* 175 * Helper function which is private to this module. Used by 176 * ext2fs_dir_iterate() and ext2fs_dblist_dir_iterate() 177 */ 178 int ext2fs_process_dir_block(ext2_filsys fs, 179 blk_t *blocknr, 180 e2_blkcnt_t blockcnt, 181 blk_t ref_block EXT2FS_ATTR((unused)), 182 int ref_offset EXT2FS_ATTR((unused)), 183 void *priv_data) 184 { 185 struct dir_context *ctx = (struct dir_context *) priv_data; 186 unsigned int offset = 0; 187 unsigned int next_real_entry = 0; 188 int ret = 0; 189 int changed = 0; 190 int do_abort = 0; 191 unsigned int rec_len, size; 192 int entry; 193 struct ext2_dir_entry *dirent; 194 195 if (blockcnt < 0) 196 return 0; 197 198 entry = blockcnt ? DIRENT_OTHER_FILE : DIRENT_DOT_FILE; 199 200 ctx->errcode = ext2fs_read_dir_block(fs, *blocknr, ctx->buf); 201 if (ctx->errcode) 202 return BLOCK_ABORT; 203 204 while (offset < fs->blocksize) { 205 dirent = (struct ext2_dir_entry *) (ctx->buf + offset); 206 if (ext2fs_get_rec_len(fs, dirent, &rec_len)) 207 return BLOCK_ABORT; 208 if (((offset + rec_len) > fs->blocksize) || 209 (rec_len < 8) || 210 ((rec_len % 4) != 0) || 211 ((((unsigned) dirent->name_len & 0xFF)+8) > rec_len)) { 212 ctx->errcode = EXT2_ET_DIR_CORRUPTED; 213 return BLOCK_ABORT; 214 } 215 if (!dirent->inode && 216 !(ctx->flags & DIRENT_FLAG_INCLUDE_EMPTY)) 217 goto next; 218 219 ret = (ctx->func)(ctx->dir, 220 (next_real_entry > offset) ? 221 DIRENT_DELETED_FILE : entry, 222 dirent, offset, 223 fs->blocksize, ctx->buf, 224 ctx->priv_data); 225 if (entry < DIRENT_OTHER_FILE) 226 entry++; 227 228 if (ret & DIRENT_CHANGED) { 229 if (ext2fs_get_rec_len(fs, dirent, &rec_len)) 230 return BLOCK_ABORT; 231 changed++; 232 } 233 if (ret & DIRENT_ABORT) { 234 do_abort++; 235 break; 236 } 237 next: 238 if (next_real_entry == offset) 239 next_real_entry += rec_len; 240 241 if (ctx->flags & DIRENT_FLAG_INCLUDE_REMOVED) { 242 size = ((dirent->name_len & 0xFF) + 11) & ~3; 243 244 if (rec_len != size) { 245 unsigned int final_offset; 246 247 final_offset = offset + rec_len; 248 offset += size; 249 while (offset < final_offset && 250 !ext2fs_validate_entry(fs, ctx->buf, 251 offset, 252 final_offset)) 253 offset += 4; 254 continue; 255 } 256 } 257 offset += rec_len; 258 } 259 260 if (changed) { 261 ctx->errcode = ext2fs_write_dir_block(fs, *blocknr, ctx->buf); 262 if (ctx->errcode) 263 return BLOCK_ABORT; 264 } 265 if (do_abort) 266 return BLOCK_ABORT; 267 return 0; 268 } 269 270