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