dir_iterate.c revision f404167dda29a59d2be2882328aeb074b9899669
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 27errcode_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 42errcode_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 */ 70static 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 ((((unsigned) dirent->name_len & 0xFF)+8) > rec_len)) 87 return 0; 88 } 89 return (offset == final_offset); 90} 91 92errcode_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) 131 return retval; 132 return ctx.errcode; 133} 134 135struct xlate { 136 int (*func)(struct ext2_dir_entry *dirent, 137 int offset, 138 int blocksize, 139 char *buf, 140 void *priv_data); 141 void *real_private; 142}; 143 144static int xlate_func(ext2_ino_t dir EXT2FS_ATTR((unused)), 145 int entry EXT2FS_ATTR((unused)), 146 struct ext2_dir_entry *dirent, int offset, 147 int blocksize, char *buf, void *priv_data) 148{ 149 struct xlate *xl = (struct xlate *) priv_data; 150 151 return (*xl->func)(dirent, offset, blocksize, buf, xl->real_private); 152} 153 154errcode_t ext2fs_dir_iterate(ext2_filsys fs, 155 ext2_ino_t dir, 156 int flags, 157 char *block_buf, 158 int (*func)(struct ext2_dir_entry *dirent, 159 int offset, 160 int blocksize, 161 char *buf, 162 void *priv_data), 163 void *priv_data) 164{ 165 struct xlate xl; 166 167 xl.real_private = priv_data; 168 xl.func = func; 169 170 return ext2fs_dir_iterate2(fs, dir, flags, block_buf, 171 xlate_func, &xl); 172} 173 174 175/* 176 * Helper function which is private to this module. Used by 177 * ext2fs_dir_iterate() and ext2fs_dblist_dir_iterate() 178 */ 179int ext2fs_process_dir_block(ext2_filsys fs, 180 blk64_t *blocknr, 181 e2_blkcnt_t blockcnt, 182 blk64_t ref_block EXT2FS_ATTR((unused)), 183 int ref_offset EXT2FS_ATTR((unused)), 184 void *priv_data) 185{ 186 struct dir_context *ctx = (struct dir_context *) priv_data; 187 unsigned int offset = 0; 188 unsigned int next_real_entry = 0; 189 int ret = 0; 190 int changed = 0; 191 int do_abort = 0; 192 unsigned int rec_len, size; 193 int entry; 194 struct ext2_dir_entry *dirent; 195 196 if (blockcnt < 0) 197 return 0; 198 199 entry = blockcnt ? DIRENT_OTHER_FILE : DIRENT_DOT_FILE; 200 201 ctx->errcode = ext2fs_read_dir_block3(fs, *blocknr, ctx->buf, 0); 202 if (ctx->errcode) 203 return BLOCK_ABORT; 204 205 while (offset < fs->blocksize) { 206 dirent = (struct ext2_dir_entry *) (ctx->buf + offset); 207 if (ext2fs_get_rec_len(fs, dirent, &rec_len)) 208 return BLOCK_ABORT; 209 if (((offset + rec_len) > fs->blocksize) || 210 (rec_len < 8) || 211 ((rec_len % 4) != 0) || 212 ((((unsigned) dirent->name_len & 0xFF)+8) > rec_len)) { 213 ctx->errcode = EXT2_ET_DIR_CORRUPTED; 214 return BLOCK_ABORT; 215 } 216 if (!dirent->inode && 217 !(ctx->flags & DIRENT_FLAG_INCLUDE_EMPTY)) 218 goto next; 219 220 ret = (ctx->func)(ctx->dir, 221 (next_real_entry > offset) ? 222 DIRENT_DELETED_FILE : entry, 223 dirent, offset, 224 fs->blocksize, ctx->buf, 225 ctx->priv_data); 226 if (entry < DIRENT_OTHER_FILE) 227 entry++; 228 229 if (ret & DIRENT_CHANGED) { 230 if (ext2fs_get_rec_len(fs, dirent, &rec_len)) 231 return BLOCK_ABORT; 232 changed++; 233 } 234 if (ret & DIRENT_ABORT) { 235 do_abort++; 236 break; 237 } 238next: 239 if (next_real_entry == offset) 240 next_real_entry += rec_len; 241 242 if (ctx->flags & DIRENT_FLAG_INCLUDE_REMOVED) { 243 size = ((dirent->name_len & 0xFF) + 11) & ~3; 244 245 if (rec_len != size) { 246 unsigned int final_offset; 247 248 final_offset = offset + rec_len; 249 offset += size; 250 while (offset < final_offset && 251 !ext2fs_validate_entry(fs, ctx->buf, 252 offset, 253 final_offset)) 254 offset += 4; 255 continue; 256 } 257 } 258 offset += rec_len; 259 } 260 261 if (changed) { 262 ctx->errcode = ext2fs_write_dir_block3(fs, *blocknr, ctx->buf, 263 0); 264 if (ctx->errcode) 265 return BLOCK_ABORT; 266 } 267 if (do_abort) 268 return BLOCK_ABORT; 269 return 0; 270} 271 272