static int check_dotdot(e2fsck_t ctx, struct ext2_dir_entry *dirent, ext2_ino_t ino, struct problem_context *pctx) { int rec_len, problem = 0; if (!dirent->inode) problem = PR_2_MISSING_DOT_DOT; else if (((dirent->name_len & 0xFF) != 2) || (dirent->name[0] != '.') || (dirent->name[1] != '.')) problem = PR_2_2ND_NOT_DOT_DOT; else if (dirent->name[2] != '\0') problem = PR_2_DOT_DOT_NULL_TERM; (void) ext2fs_get_rec_len(ctx->fs, dirent, &rec_len); if (problem) { if (fix_problem(ctx, problem, pctx)) { if (rec_len < 12) dirent->rec_len = 12; dirent->inode = EXT2_ROOT_INO; dirent->name_len = 2; dirent->name[0] = '.'; dirent->name[1] = '.'; dirent->name[2] = '\0'; return 1; } return 0; } if (e2fsck_dir_info_set_dotdot(ctx, ino, dirent->inode)) { fix_problem(ctx, PR_2_NO_DIRINFO, pctx); return -1; } return 0; }
static void salvage_directory(ext2_filsys fs, struct ext2_dir_entry *dirent, struct ext2_dir_entry *prev, unsigned int *offset) { char *cp = (char *) dirent; int left; unsigned int rec_len, prev_rec_len; unsigned int name_len = dirent->name_len & 0xFF; (void) ext2fs_get_rec_len(fs, dirent, &rec_len); left = fs->blocksize - *offset - rec_len; if ((left >= 12) && (rec_len == 8)) { memmove(cp, cp+8, left); memset(cp + left, 0, 8); return; } if ((left < 0) && ((int) rec_len + left > 8) && (name_len + 8 <= (int) rec_len + left) && dirent->inode <= fs->super->s_inodes_count && strnlen(dirent->name, name_len) == name_len) { (void) ext2fs_set_rec_len(fs, (int) rec_len + left, dirent); return; } if (prev && rec_len && (rec_len % 4) == 0 && (*offset + rec_len <= fs->blocksize)) { (void) ext2fs_get_rec_len(fs, prev, &prev_rec_len); prev_rec_len += rec_len; (void) ext2fs_set_rec_len(fs, prev_rec_len, prev); *offset += rec_len; return; } if (prev) { (void) ext2fs_get_rec_len(fs, prev, &prev_rec_len); prev_rec_len += fs->blocksize - *offset; (void) ext2fs_set_rec_len(fs, prev_rec_len, prev); *offset = fs->blocksize; } else { rec_len = fs->blocksize - *offset; (void) ext2fs_set_rec_len(fs, rec_len, dirent); dirent->name_len = 0; dirent->inode = 0; } }
/* * Make sure the first entry in the directory is '.', and that the * directory entry is sane. */ static int check_dot(e2fsck_t ctx, struct ext2_dir_entry *dirent, ext2_ino_t ino, struct problem_context *pctx) { struct ext2_dir_entry *nextdir; unsigned int rec_len, new_len; int status = 0; int created = 0; problem_t problem = 0; if (!dirent->inode) problem = PR_2_MISSING_DOT; else if ((ext2fs_dirent_name_len(dirent) != 1) || (dirent->name[0] != '.')) problem = PR_2_1ST_NOT_DOT; else if (dirent->name[1] != '\0') problem = PR_2_DOT_NULL_TERM; (void) ext2fs_get_rec_len(ctx->fs, dirent, &rec_len); if (problem) { if (fix_problem(ctx, problem, pctx)) { if (rec_len < 12) rec_len = dirent->rec_len = 12; dirent->inode = ino; ext2fs_dirent_set_name_len(dirent, 1); ext2fs_dirent_set_file_type(dirent, EXT2_FT_UNKNOWN); dirent->name[0] = '.'; dirent->name[1] = '\0'; status = 1; created = 1; } } if (dirent->inode != ino) { if (fix_problem(ctx, PR_2_BAD_INODE_DOT, pctx)) { dirent->inode = ino; status = 1; } } if (rec_len > 12) { new_len = rec_len - 12; if (new_len > 12) { if (created || fix_problem(ctx, PR_2_SPLIT_DOT, pctx)) { nextdir = (struct ext2_dir_entry *) ((char *) dirent + 12); dirent->rec_len = 12; (void) ext2fs_set_rec_len(ctx->fs, new_len, nextdir); nextdir->inode = 0; ext2fs_dirent_set_name_len(nextdir, 0); ext2fs_dirent_set_file_type(nextdir, EXT2_FT_UNKNOWN); status = 1; } } } return status; }
/* * This function expands '%dX' expressions */ static _INLINE_ void expand_dirent_expression(ext2_filsys fs, char ch, struct problem_context *ctx) { struct ext2_dir_entry *dirent; unsigned int rec_len; int len; if (!ctx || !ctx->dirent) goto no_dirent; dirent = ctx->dirent; switch (ch) { case 'i': printf("%u", dirent->inode); break; case 'n': len = dirent->name_len & 0xFF; if (len > EXT2_NAME_LEN) len = EXT2_NAME_LEN; if ((ext2fs_get_rec_len(fs, dirent, &rec_len) == 0) && (len > rec_len)) len = rec_len; safe_print(dirent->name, len); break; case 'r': (void) ext2fs_get_rec_len(fs, dirent, &rec_len); printf("%u", rec_len); break; case 'l': printf("%u", dirent->name_len & 0xFF); break; case 't': printf("%u", dirent->name_len >> 8); break; default: no_dirent: printf("%%D%c", ch); break; } }
/* * This function expands '%dX' expressions */ static _INLINE_ void expand_dirent_expression(FILE *f, ext2_filsys fs, char ch, struct problem_context *ctx) { struct ext2_dir_entry *dirent; unsigned int rec_len, len; if (!ctx || !ctx->dirent) goto no_dirent; dirent = ctx->dirent; switch (ch) { case 'i': fprintf(f, "%u", dirent->inode); break; case 'n': len = ext2fs_dirent_name_len(dirent); if ((ext2fs_get_rec_len(fs, dirent, &rec_len) == 0) && (len > rec_len)) len = rec_len; safe_print(f, dirent->name, len); break; case 'r': (void) ext2fs_get_rec_len(fs, dirent, &rec_len); fprintf(f, "%u", rec_len); break; case 'l': fprintf(f, "%u", ext2fs_dirent_name_len(dirent)); break; case 't': fprintf(f, "%u", ext2fs_dirent_file_type(dirent)); break; default: no_dirent: fprintf(f, "%%D%c", ch); break; } }
static int search_dir_block(ext2_filsys fs, blk_t *blocknr, e2_blkcnt_t blockcnt, blk_t ref_blk EXT2FS_ATTR((unused)), int ref_offset EXT2FS_ATTR((unused)), void *priv_data) { struct process_block_struct *p; struct ext2_dir_entry *dirent; errcode_t errcode; unsigned int offset = 0; unsigned int rec_len; if (blockcnt < 0) return 0; p = (struct process_block_struct *) priv_data; errcode = io_channel_read_blk(current_fs->io, *blocknr, 1, p->buf); if (errcode) { com_err("search_dir_block", errcode, "while reading block %lu", (unsigned long) *blocknr); return BLOCK_ABORT; } while (offset < fs->blocksize) { dirent = (struct ext2_dir_entry *) (p->buf + offset); errcode = ext2fs_get_rec_len(fs, dirent, &rec_len); if (errcode) { com_err("htree_dump_leaf_inode", errcode, "while getting rec_len for block %lu", (unsigned long) *blocknr); return BLOCK_ABORT; } if (dirent->inode && p->len == (dirent->name_len & 0xFF) && strncmp(p->search_name, dirent->name, p->len) == 0) { printf("Entry found at logical block %lld, " "phys %u, offset %u\n", (long long)blockcnt, *blocknr, offset); printf("offset %u\n", offset); return BLOCK_ABORT; } offset += rec_len; } return 0; }
/* * Make sure the second entry in the directory is '..', and that the * directory entry is sane. We do not check the inode number of '..' * here; this gets done in pass 3. */ static int check_dotdot(e2fsck_t ctx, struct ext2_dir_entry *dirent, ext2_ino_t ino, struct problem_context *pctx) { problem_t problem = 0; unsigned int rec_len; if (!dirent->inode) problem = PR_2_MISSING_DOT_DOT; else if ((ext2fs_dirent_name_len(dirent) != 2) || (dirent->name[0] != '.') || (dirent->name[1] != '.')) problem = PR_2_2ND_NOT_DOT_DOT; else if (dirent->name[2] != '\0') problem = PR_2_DOT_DOT_NULL_TERM; (void) ext2fs_get_rec_len(ctx->fs, dirent, &rec_len); if (problem) { if (fix_problem(ctx, problem, pctx)) { if (rec_len < 12) dirent->rec_len = 12; /* * Note: we don't have the parent inode just * yet, so we will fill it in with the root * inode. This will get fixed in pass 3. */ dirent->inode = EXT2_ROOT_INO; ext2fs_dirent_set_name_len(dirent, 2); ext2fs_dirent_set_file_type(dirent, EXT2_FT_UNKNOWN); dirent->name[0] = '.'; dirent->name[1] = '.'; dirent->name[2] = '\0'; return 1; } return 0; } if (e2fsck_dir_info_set_dotdot(ctx, ino, dirent->inode)) { fix_problem(ctx, PR_2_NO_DIRINFO, pctx); return -1; } return 0; }
errcode_t ext2fs_write_dir_block3(ext2_filsys fs, blk64_t block, void *inbuf, int flags EXT2FS_ATTR((unused))) { #ifdef WORDS_BIGENDIAN errcode_t retval; char *p, *end; char *buf = 0; unsigned int rec_len; struct ext2_dir_entry *dirent; retval = ext2fs_get_mem(fs->blocksize, &buf); if (retval) return retval; memcpy(buf, inbuf, fs->blocksize); p = buf; end = buf + fs->blocksize; while (p < end) { dirent = (struct ext2_dir_entry *) p; if ((retval = ext2fs_get_rec_len(fs, dirent, &rec_len)) != 0) return retval; if ((rec_len < 8) || (rec_len % 4)) { ext2fs_free_mem(&buf); return (EXT2_ET_DIR_CORRUPTED); } p += rec_len; dirent->inode = ext2fs_swab32(dirent->inode); dirent->rec_len = ext2fs_swab16(dirent->rec_len); dirent->name_len = ext2fs_swab16(dirent->name_len); if (flags & EXT2_DIRBLOCK_V2_STRUCT) dirent->name_len = ext2fs_swab16(dirent->name_len); } retval = io_channel_write_blk64(fs->io, block, 1, buf); ext2fs_free_mem(&buf); return retval; #else return io_channel_write_blk(fs->io, block, 1, (char *) inbuf); #endif }
errcode_t ext2fs_read_dir_block3(ext2_filsys fs, blk64_t block, void *buf, int flags EXT2FS_ATTR((unused))) { errcode_t retval; char *p, *end; struct ext2_dir_entry *dirent; unsigned int name_len, rec_len; retval = io_channel_read_blk64(fs->io, block, 1, buf); if (retval) return retval; p = (char *) buf; end = (char *) buf + fs->blocksize; while (p < end-8) { dirent = (struct ext2_dir_entry *) p; #ifdef WORDS_BIGENDIAN dirent->inode = ext2fs_swab32(dirent->inode); dirent->rec_len = ext2fs_swab16(dirent->rec_len); dirent->name_len = ext2fs_swab16(dirent->name_len); #endif name_len = dirent->name_len; #ifdef WORDS_BIGENDIAN if (flags & EXT2_DIRBLOCK_V2_STRUCT) dirent->name_len = ext2fs_swab16(dirent->name_len); #endif if ((retval = ext2fs_get_rec_len(fs, dirent, &rec_len)) != 0) return retval; if ((rec_len < 8) || (rec_len % 4)) { rec_len = 8; retval = EXT2_ET_DIR_CORRUPTED; } else if (((name_len & 0xFF) + 8) > rec_len) retval = EXT2_ET_DIR_CORRUPTED; p += rec_len; } return retval; }
static void htree_dump_leaf_node(ext2_filsys fs, ext2_ino_t ino, struct ext2_inode *inode, struct ext2_dx_root_info * rootnode, blk_t blk, char *buf) { errcode_t errcode; struct ext2_dir_entry *dirent; int thislen, col = 0; unsigned int offset = 0; char name[EXT2_NAME_LEN + 1]; char tmp[EXT2_NAME_LEN + 16]; blk_t pblk; ext2_dirhash_t hash, minor_hash; unsigned int rec_len; int hash_alg; errcode = ext2fs_bmap(fs, ino, inode, buf, 0, blk, &pblk); if (errcode) { com_err("htree_dump_leaf_node", errcode, "while mapping logical block %u\n", blk); return; } printf("Reading directory block %lu, phys %lu\n", (unsigned long) blk, (unsigned long) pblk); errcode = ext2fs_read_dir_block2(current_fs, pblk, buf, 0); if (errcode) { com_err("htree_dump_leaf_node", errcode, "while reading block %lu (%lu)\n", (unsigned long) blk, (unsigned long) pblk); return; } hash_alg = rootnode->hash_version; if ((hash_alg <= EXT2_HASH_TEA) && (fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH)) hash_alg += 3; while (offset < fs->blocksize) { dirent = (struct ext2_dir_entry *) (buf + offset); errcode = ext2fs_get_rec_len(fs, dirent, &rec_len); if (errcode) { com_err("htree_dump_leaf_inode", errcode, "while getting rec_len for block %lu", (unsigned long) blk); return; } if (((offset + rec_len) > fs->blocksize) || (rec_len < 8) || ((rec_len % 4) != 0) || ((((unsigned) dirent->name_len & 0xFF)+8) > rec_len)) { fprintf(pager, "Corrupted directory block (%u)!\n", blk); break; } thislen = ((dirent->name_len & 0xFF) < EXT2_NAME_LEN) ? (dirent->name_len & 0xFF) : EXT2_NAME_LEN; strncpy(name, dirent->name, thislen); name[thislen] = '\0'; errcode = ext2fs_dirhash(hash_alg, name, thislen, fs->super->s_hash_seed, &hash, &minor_hash); if (errcode) com_err("htree_dump_leaf_node", errcode, "while calculating hash"); sprintf(tmp, "%u 0x%08x-%08x (%d) %s ", dirent->inode, hash, minor_hash, rec_len, name); thislen = strlen(tmp); if (col + thislen > 80) { fprintf(pager, "\n"); col = 0; } fprintf(pager, "%s", tmp); col += thislen; offset += rec_len; } fprintf(pager, "\n"); }
static int link_proc(struct ext2_dir_entry *dirent, int offset, int blocksize, char *buf, void *priv_data) { struct link_struct *ls = (struct link_struct *) priv_data; struct ext2_dir_entry *next; unsigned int rec_len, min_rec_len, curr_rec_len; int ret = 0; printf("Trying to link inode %lu at offset %lu\n", ls->inode, offset); rec_len = EXT2_DIR_REC_LEN(ls->namelen); ls->err = ext2fs_get_rec_len(ls->fs, dirent, &curr_rec_len); if (ls->err) { printf("Error: Problem with reading current rec len\n"); return DIRENT_ABORT; } printf("Current rec len: %d\n", curr_rec_len); /* * See if the following directory entry (if any) is unused; * if so, absorb it into this one. */ next = (struct ext2_dir_entry *) (buf + offset + curr_rec_len); if ((offset + curr_rec_len < blocksize - 8) && (next->inode == 0) && (offset + curr_rec_len + next->rec_len <= blocksize)) { printf("Absorbing following dirent into this one\n"); curr_rec_len += next->rec_len; ls->err = ext2fs_set_rec_len(ls->fs, curr_rec_len, dirent); if (ls->err) { printf("Problem with setting rec len for new dirent\n"); return DIRENT_ABORT; } ret = DIRENT_CHANGED; } /* * If the directory entry is used, see if we can split the * directory entry to make room for the new name. If so, * truncate it and return. */ if (dirent->inode) { printf("Handling used dirent\n"); min_rec_len = EXT2_DIR_REC_LEN(dirent->name_len & 0xFF); if (curr_rec_len < (min_rec_len + rec_len)) return ret; rec_len = curr_rec_len - min_rec_len; ls->err = ext2fs_set_rec_len(ls->fs, min_rec_len, dirent); if (ls->err) return DIRENT_ABORT; next = (struct ext2_dir_entry *) (buf + offset + dirent->rec_len); next->inode = 0; next->name_len = 0; ls->err = ext2fs_set_rec_len(ls->fs, rec_len, next); if (ls->err) return DIRENT_ABORT; return DIRENT_CHANGED; } /* * If we get this far, then the directory entry is not used. * See if we can fit the request entry in. If so, do it. */ if (curr_rec_len < rec_len) return ret; printf("Setting dirent based on ls information\n"); dirent->inode = ls->inode; dirent->name_len = ls->namelen; strncpy(dirent->name, ls->name, ls->namelen); if (ls->sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) dirent->name_len |= (ls->flags & 0x7) << 8; ls->done++; return DIRENT_ABORT|DIRENT_CHANGED; }
static int link_proc(struct ext2_dir_entry *dirent, int offset, int blocksize, char *buf, void *priv_data) { struct link_struct *ls = (struct link_struct *) priv_data; struct ext2_dir_entry *next; unsigned int rec_len, min_rec_len, curr_rec_len; int ret = 0; rec_len = EXT2_DIR_REC_LEN(ls->namelen); ls->err = ext2fs_get_rec_len(ls->fs, dirent, &curr_rec_len); if (ls->err) return DIRENT_ABORT; /* * See if the following directory entry (if any) is unused; * if so, absorb it into this one. */ next = (struct ext2_dir_entry *) (buf + offset + curr_rec_len); if ((offset + (int) curr_rec_len < blocksize - 8) && (next->inode == 0) && (offset + (int) curr_rec_len + (int) next->rec_len <= blocksize)) { curr_rec_len += next->rec_len; ls->err = ext2fs_set_rec_len(ls->fs, curr_rec_len, dirent); if (ls->err) return DIRENT_ABORT; ret = DIRENT_CHANGED; } /* * If the directory entry is used, see if we can split the * directory entry to make room for the new name. If so, * truncate it and return. */ if (dirent->inode) { min_rec_len = EXT2_DIR_REC_LEN(dirent->name_len & 0xFF); if (curr_rec_len < (min_rec_len + rec_len)) return ret; rec_len = curr_rec_len - min_rec_len; ls->err = ext2fs_set_rec_len(ls->fs, min_rec_len, dirent); if (ls->err) return DIRENT_ABORT; next = (struct ext2_dir_entry *) (buf + offset + dirent->rec_len); next->inode = 0; next->name_len = 0; ls->err = ext2fs_set_rec_len(ls->fs, rec_len, next); if (ls->err) return DIRENT_ABORT; return DIRENT_CHANGED; } /* * If we get this far, then the directory entry is not used. * See if we can fit the request entry in. If so, do it. */ if (curr_rec_len < rec_len) return ret; dirent->inode = ls->inode; dirent->name_len = ls->namelen; strncpy(dirent->name, ls->name, ls->namelen); if (ls->sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) dirent->name_len |= (ls->flags & 0x7) << 8; ls->done++; return DIRENT_ABORT|DIRENT_CHANGED; }
static int fill_dir_block(ext2_filsys fs, blk64_t *block_nr, e2_blkcnt_t blockcnt, blk64_t ref_block EXT2FS_ATTR((unused)), int ref_offset EXT2FS_ATTR((unused)), void *priv_data) { struct fill_dir_struct *fd = (struct fill_dir_struct *) priv_data; struct hash_entry *new_array, *ent; struct ext2_dir_entry *dirent; char *dir; unsigned int offset, dir_offset, rec_len, name_len; int hash_alg; if (blockcnt < 0) return 0; offset = blockcnt * fs->blocksize; if (offset + fs->blocksize > fd->inode->i_size) { fd->err = EXT2_ET_DIR_CORRUPTED; return BLOCK_ABORT; } dir = (fd->buf+offset); if (*block_nr == 0) { memset(dir, 0, fs->blocksize); dirent = (struct ext2_dir_entry *) dir; (void) ext2fs_set_rec_len(fs, fs->blocksize, dirent); } else { int flags = fs->flags; fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS; fd->err = ext2fs_read_dir_block4(fs, *block_nr, dir, 0, fd->dir); fs->flags = (flags & EXT2_FLAG_IGNORE_CSUM_ERRORS) | (fs->flags & ~EXT2_FLAG_IGNORE_CSUM_ERRORS); if (fd->err) return BLOCK_ABORT; } hash_alg = fs->super->s_def_hash_version; if ((hash_alg <= EXT2_HASH_TEA) && (fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH)) hash_alg += 3; /* While the directory block is "hot", index it. */ dir_offset = 0; while (dir_offset < fs->blocksize) { dirent = (struct ext2_dir_entry *) (dir + dir_offset); (void) ext2fs_get_rec_len(fs, dirent, &rec_len); name_len = ext2fs_dirent_name_len(dirent); if (((dir_offset + rec_len) > fs->blocksize) || (rec_len < 8) || ((rec_len % 4) != 0) || (name_len + 8 > rec_len)) { fd->err = EXT2_ET_DIR_CORRUPTED; return BLOCK_ABORT; } dir_offset += rec_len; if (dirent->inode == 0) continue; if (!fd->compress && (name_len == 1) && (dirent->name[0] == '.')) continue; if (!fd->compress && (name_len == 2) && (dirent->name[0] == '.') && (dirent->name[1] == '.')) { fd->parent = dirent->inode; continue; } if (fd->num_array >= fd->max_array) { new_array = realloc(fd->harray, sizeof(struct hash_entry) * (fd->max_array+500)); if (!new_array) { fd->err = ENOMEM; return BLOCK_ABORT; } fd->harray = new_array; fd->max_array += 500; } ent = fd->harray + fd->num_array++; ent->dir = dirent; fd->dir_size += EXT2_DIR_REC_LEN(name_len); ent->ino = dirent->inode; if (fd->compress) ent->hash = ent->minor_hash = 0; else { fd->err = ext2fs_dirhash(hash_alg, dirent->name, name_len, fs->super->s_hash_seed, &ent->hash, &ent->minor_hash); if (fd->err) return BLOCK_ABORT; } } return 0; }
int ext2fs_inline_data_dir_iterate(ext2_filsys fs, ext2_ino_t ino, void *priv_data) { struct dir_context *ctx; struct ext2_inode inode; struct ext2_dir_entry dirent; struct ext2_inline_data data; int ret = BLOCK_ABORT; e2_blkcnt_t blockcnt = 0; char *old_buf; unsigned int old_buflen; int old_flags; ctx = (struct dir_context *)priv_data; old_buf = ctx->buf; old_buflen = ctx->buflen; old_flags = ctx->flags; ctx->flags |= DIRENT_FLAG_INCLUDE_INLINE_DATA; ctx->errcode = ext2fs_read_inode(fs, ino, &inode); if (ctx->errcode) goto out; if (!(inode.i_flags & EXT4_INLINE_DATA_FL)) { ctx->errcode = EXT2_ET_NO_INLINE_DATA; goto out; } if (!LINUX_S_ISDIR(inode.i_mode)) { ctx->errcode = EXT2_ET_NO_DIRECTORY; goto out; } ret = 0; /* we first check '.' and '..' dir */ dirent.inode = ino; dirent.name_len = 1; ext2fs_set_rec_len(fs, EXT2_DIR_REC_LEN(2), &dirent); dirent.name[0] = '.'; dirent.name[1] = '\0'; ctx->buf = (char *)&dirent; ext2fs_get_rec_len(fs, &dirent, &ctx->buflen); ret |= ext2fs_process_dir_block(fs, 0, blockcnt++, 0, 0, priv_data); if (ret & BLOCK_ABORT) goto out; dirent.inode = ext2fs_le32_to_cpu(inode.i_block[0]); dirent.name_len = 2; ext2fs_set_rec_len(fs, EXT2_DIR_REC_LEN(3), &dirent); dirent.name[0] = '.'; dirent.name[1] = '.'; dirent.name[2] = '\0'; ctx->buf = (char *)&dirent; ext2fs_get_rec_len(fs, &dirent, &ctx->buflen); ret |= ext2fs_process_dir_block(fs, 0, blockcnt++, 0, 0, priv_data); if (ret & BLOCK_INLINE_DATA_CHANGED) { errcode_t err; inode.i_block[0] = ext2fs_cpu_to_le32(dirent.inode); err = ext2fs_write_inode(fs, ino, &inode); if (err) goto out; ret &= ~BLOCK_INLINE_DATA_CHANGED; } if (ret & BLOCK_ABORT) goto out; ctx->buf = (char *)inode.i_block + EXT4_INLINE_DATA_DOTDOT_SIZE; ctx->buflen = EXT4_MIN_INLINE_DATA_SIZE - EXT4_INLINE_DATA_DOTDOT_SIZE; #ifdef WORDS_BIGENDIAN ctx->errcode = ext2fs_dirent_swab_in2(fs, ctx->buf, ctx->buflen, 0); if (ctx->errcode) { ret |= BLOCK_ABORT; goto out; } #endif ret |= ext2fs_process_dir_block(fs, 0, blockcnt++, 0, 0, priv_data); if (ret & BLOCK_INLINE_DATA_CHANGED) { #ifdef WORDS_BIGENDIAN ctx->errcode = ext2fs_dirent_swab_out2(fs, ctx->buf, ctx->buflen, 0); if (ctx->errcode) { ret |= BLOCK_ABORT; goto out; } #endif ctx->errcode = ext2fs_write_inode(fs, ino, &inode); if (ctx->errcode) ret |= BLOCK_ABORT; ret &= ~BLOCK_INLINE_DATA_CHANGED; } if (ret & BLOCK_ABORT) goto out; data.fs = fs; data.ino = ino; ctx->errcode = ext2fs_inline_data_ea_get(&data); if (ctx->errcode) { ret |= BLOCK_ABORT; goto out; } if (data.ea_size <= 0) goto out1; ctx->buf = data.ea_data; ctx->buflen = data.ea_size; #ifdef WORDS_BIGENDIAN ctx->errcode = ext2fs_dirent_swab_in2(fs, ctx->buf, ctx->buflen, 0); if (ctx->errcode) { ret |= BLOCK_ABORT; goto out1; } #endif ret |= ext2fs_process_dir_block(fs, 0, blockcnt++, 0, 0, priv_data); if (ret & BLOCK_INLINE_DATA_CHANGED) { #ifdef WORDS_BIGENDIAN ctx->errcode = ext2fs_dirent_swab_out2(fs, ctx->buf, ctx->buflen, 0); if (ctx->errcode) { ret |= BLOCK_ABORT; goto out1; } #endif ctx->errcode = ext2fs_inline_data_ea_set(&data); if (ctx->errcode) ret |= BLOCK_ABORT; } out1: ext2fs_free_mem(&data.ea_data); out: ctx->buf = old_buf; ctx->buflen = old_buflen; ctx->flags = old_flags; ret &= ~(BLOCK_ABORT | BLOCK_INLINE_DATA_CHANGED); return ret; }
static errcode_t ext2fs_inline_data_convert_dir(ext2_filsys fs, ext2_ino_t ino, char *bbuf, char *ibuf, int size) { struct ext2_dir_entry *dir, *dir2; struct ext2_dir_entry_tail *t; errcode_t retval; unsigned int offset, rec_len; int csum_size = 0; int filetype = 0; if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) csum_size = sizeof(struct ext2_dir_entry_tail); /* Create '.' and '..' */ if (EXT2_HAS_INCOMPAT_FEATURE(fs->super, EXT2_FEATURE_INCOMPAT_FILETYPE)) filetype = EXT2_FT_DIR; /* * Set up entry for '.' */ dir = (struct ext2_dir_entry *) bbuf; dir->inode = ino; ext2fs_dirent_set_name_len(dir, 1); ext2fs_dirent_set_file_type(dir, filetype); dir->name[0] = '.'; rec_len = (fs->blocksize - csum_size) - EXT2_DIR_REC_LEN(1); dir->rec_len = EXT2_DIR_REC_LEN(1); /* * Set up entry for '..' */ dir = (struct ext2_dir_entry *) (bbuf + dir->rec_len); dir->rec_len = EXT2_DIR_REC_LEN(2); dir->inode = ext2fs_le32_to_cpu(((__u32 *)ibuf)[0]); ext2fs_dirent_set_name_len(dir, 2); ext2fs_dirent_set_file_type(dir, filetype); dir->name[0] = '.'; dir->name[1] = '.'; /* * Ajust the last rec_len */ offset = EXT2_DIR_REC_LEN(1) + EXT2_DIR_REC_LEN(2); dir = (struct ext2_dir_entry *) (bbuf + offset); memcpy(bbuf + offset, ibuf + EXT4_INLINE_DATA_DOTDOT_SIZE, size - EXT4_INLINE_DATA_DOTDOT_SIZE); size += EXT2_DIR_REC_LEN(1) + EXT2_DIR_REC_LEN(2) - EXT4_INLINE_DATA_DOTDOT_SIZE; do { dir2 = dir; retval = ext2fs_get_rec_len(fs, dir, &rec_len); if (retval) goto err; offset += rec_len; dir = (struct ext2_dir_entry *) (bbuf + offset); } while (offset < size); rec_len += fs->blocksize - csum_size - offset; retval = ext2fs_set_rec_len(fs, rec_len, dir2); if (retval) goto err; if (csum_size) { t = EXT2_DIRENT_TAIL(bbuf, fs->blocksize); ext2fs_initialize_dirent_tail(fs, t); } err: return retval; }
static int check_dir_block(ext2_filsys fs, struct ext2_db_entry2 *db, void *priv_data) { struct dx_dir_info *dx_dir; #ifdef ENABLE_HTREE struct dx_dirblock_info *dx_db = 0; #endif /* ENABLE_HTREE */ struct ext2_dir_entry *dirent, *prev; ext2_dirhash_t hash; unsigned int offset = 0; int dir_modified = 0; int dot_state; unsigned int rec_len; blk64_t block_nr = db->blk; ext2_ino_t ino = db->ino; ext2_ino_t subdir_parent; __u16 links; struct check_dir_struct *cd; char *buf; e2fsck_t ctx; int problem; struct ext2_dx_root_info *root; struct ext2_dx_countlimit *limit; static dict_t de_dict; struct problem_context pctx; int dups_found = 0; int ret; int dx_csum_size = 0, de_csum_size = 0; int failed_csum = 0; int is_leaf = 1; cd = (struct check_dir_struct *) priv_data; buf = cd->buf; ctx = cd->ctx; if (ctx->flags & E2F_FLAG_SIGNAL_MASK || ctx->flags & E2F_FLAG_RESTART) return DIRENT_ABORT; if (ctx->progress && (ctx->progress)(ctx, 2, cd->count++, cd->max)) return DIRENT_ABORT; if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) { dx_csum_size = sizeof(struct ext2_dx_tail); de_csum_size = sizeof(struct ext2_dir_entry_tail); } /* * Make sure the inode is still in use (could have been * deleted in the duplicate/bad blocks pass. */ if (!(ext2fs_test_inode_bitmap2(ctx->inode_used_map, ino))) return 0; cd->pctx.ino = ino; cd->pctx.blk = block_nr; cd->pctx.blkcount = db->blockcnt; cd->pctx.ino2 = 0; cd->pctx.dirent = 0; cd->pctx.num = 0; if (db->blk == 0) { if (allocate_dir_block(ctx, db, buf, &cd->pctx)) return 0; block_nr = db->blk; } if (db->blockcnt) dot_state = 2; else dot_state = 0; if (ctx->dirs_to_hash && ext2fs_u32_list_test(ctx->dirs_to_hash, ino)) dups_found++; #if 0 printf("In process_dir_block block %lu, #%d, inode %lu\n", block_nr, db->blockcnt, ino); #endif ehandler_operation(_("reading directory block")); cd->pctx.errcode = ext2fs_read_dir_block4(fs, block_nr, buf, 0, ino); ehandler_operation(0); if (cd->pctx.errcode == EXT2_ET_DIR_CORRUPTED) cd->pctx.errcode = 0; /* We'll handle this ourselves */ else if (cd->pctx.errcode == EXT2_ET_DIR_CSUM_INVALID) { cd->pctx.errcode = 0; /* We'll handle this ourselves */ failed_csum = 1; } if (cd->pctx.errcode) { char *buf2; if (!fix_problem(ctx, PR_2_READ_DIRBLOCK, &cd->pctx)) { ctx->flags |= E2F_FLAG_ABORT; return DIRENT_ABORT; } ext2fs_new_dir_block(fs, db->blockcnt == 0 ? ino : 0, EXT2_ROOT_INO, &buf2); memcpy(buf, buf2, fs->blocksize); ext2fs_free_mem(&buf2); } #ifdef ENABLE_HTREE dx_dir = e2fsck_get_dx_dir_info(ctx, ino); if (dx_dir && dx_dir->numblocks) { if (db->blockcnt >= dx_dir->numblocks) { if (fix_problem(ctx, PR_2_UNEXPECTED_HTREE_BLOCK, &pctx)) { clear_htree(ctx, ino); dx_dir->numblocks = 0; dx_db = 0; goto out_htree; } fatal_error(ctx, _("Can not continue.")); } dx_db = &dx_dir->dx_block[db->blockcnt]; dx_db->type = DX_DIRBLOCK_LEAF; dx_db->phys = block_nr; dx_db->min_hash = ~0; dx_db->max_hash = 0; dirent = (struct ext2_dir_entry *) buf; (void) ext2fs_get_rec_len(fs, dirent, &rec_len); limit = (struct ext2_dx_countlimit *) (buf+8); if (db->blockcnt == 0) { root = (struct ext2_dx_root_info *) (buf + 24); dx_db->type = DX_DIRBLOCK_ROOT; dx_db->flags |= DX_FLAG_FIRST | DX_FLAG_LAST; if ((root->reserved_zero || root->info_length < 8 || root->indirect_levels > 1) && fix_problem(ctx, PR_2_HTREE_BAD_ROOT, &cd->pctx)) { clear_htree(ctx, ino); dx_dir->numblocks = 0; dx_db = 0; } dx_dir->hashversion = root->hash_version; if ((dx_dir->hashversion <= EXT2_HASH_TEA) && (fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH)) dx_dir->hashversion += 3; dx_dir->depth = root->indirect_levels + 1; } else if ((dirent->inode == 0) && (rec_len == fs->blocksize) && (dirent->name_len == 0) && (ext2fs_le16_to_cpu(limit->limit) == ((fs->blocksize - (8 + dx_csum_size)) / sizeof(struct ext2_dx_entry)))) dx_db->type = DX_DIRBLOCK_NODE; is_leaf = 0; } out_htree: #endif /* ENABLE_HTREE */ /* Verify checksum. */ if (is_leaf && de_csum_size) { /* No space for csum? Rebuild dirs in pass 3A. */ if (!ext2fs_dirent_has_tail(fs, (struct ext2_dir_entry *)buf)) { de_csum_size = 0; if (e2fsck_dir_will_be_rehashed(ctx, ino)) goto skip_checksum; if (!fix_problem(cd->ctx, PR_2_LEAF_NODE_MISSING_CSUM, &cd->pctx)) goto skip_checksum; e2fsck_rehash_dir_later(ctx, ino); goto skip_checksum; } if (failed_csum) { char *buf2; if (!fix_problem(cd->ctx, PR_2_LEAF_NODE_CSUM_INVALID, &cd->pctx)) goto skip_checksum; ext2fs_new_dir_block(fs, db->blockcnt == 0 ? ino : 0, EXT2_ROOT_INO, &buf2); memcpy(buf, buf2, fs->blocksize); ext2fs_free_mem(&buf2); dir_modified++; failed_csum = 0; } } /* htree nodes don't use fake dirents to store checksums */ if (!is_leaf) de_csum_size = 0; skip_checksum: dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp); prev = 0; do { int group; ext2_ino_t first_unused_inode; problem = 0; dirent = (struct ext2_dir_entry *) (buf + offset); (void) ext2fs_get_rec_len(fs, dirent, &rec_len); cd->pctx.dirent = dirent; cd->pctx.num = offset; if (((offset + rec_len) > fs->blocksize) || (rec_len < 12) || ((rec_len % 4) != 0) || (((dirent->name_len & (unsigned) 0xFF)+8) > rec_len)) { if (fix_problem(ctx, PR_2_DIR_CORRUPTED, &cd->pctx)) { salvage_directory(fs, dirent, prev, &offset); dir_modified++; continue; } else goto abort_free_dict; } if (dot_state == 0) { if (check_dot(ctx, dirent, ino, &cd->pctx)) dir_modified++; } else if (dot_state == 1) { ret = check_dotdot(ctx, dirent, ino, &cd->pctx); if (ret < 0) goto abort_free_dict; if (ret) dir_modified++; } else if (dirent->inode == ino) { problem = PR_2_LINK_DOT; if (fix_problem(ctx, PR_2_LINK_DOT, &cd->pctx)) { dirent->inode = 0; dir_modified++; goto next; } } if (!dirent->inode) goto next; /* * Make sure the inode listed is a legal one. */ if (((dirent->inode != EXT2_ROOT_INO) && (dirent->inode < EXT2_FIRST_INODE(fs->super))) || (dirent->inode > fs->super->s_inodes_count)) { problem = PR_2_BAD_INO; } else if (ctx->inode_bb_map && (ext2fs_test_inode_bitmap2(ctx->inode_bb_map, dirent->inode))) { /* * If the inode is in a bad block, offer to * clear it. */ problem = PR_2_BB_INODE; } else if ((dot_state > 1) && ((dirent->name_len & 0xFF) == 1) && (dirent->name[0] == '.')) { /* * If there's a '.' entry in anything other * than the first directory entry, it's a * duplicate entry that should be removed. */ problem = PR_2_DUP_DOT; } else if ((dot_state > 1) && ((dirent->name_len & 0xFF) == 2) && (dirent->name[0] == '.') && (dirent->name[1] == '.')) { /* * If there's a '..' entry in anything other * than the second directory entry, it's a * duplicate entry that should be removed. */ problem = PR_2_DUP_DOT_DOT; } else if ((dot_state > 1) && (dirent->inode == EXT2_ROOT_INO)) { /* * Don't allow links to the root directory. * We check this specially to make sure we * catch this error case even if the root * directory hasn't been created yet. */ problem = PR_2_LINK_ROOT; } else if ((dot_state > 1) && (dirent->name_len & 0xFF) == 0) { /* * Don't allow zero-length directory names. */ problem = PR_2_NULL_NAME; } if (problem) { if (fix_problem(ctx, problem, &cd->pctx)) { dirent->inode = 0; dir_modified++; goto next; } else { ext2fs_unmark_valid(fs); if (problem == PR_2_BAD_INO) goto next; } } /* * If the inode was marked as having bad fields in * pass1, process it and offer to fix/clear it. * (We wait until now so that we can display the * pathname to the user.) */ if (ctx->inode_bad_map && ext2fs_test_inode_bitmap2(ctx->inode_bad_map, dirent->inode)) { if (e2fsck_process_bad_inode(ctx, ino, dirent->inode, buf + fs->blocksize)) { dirent->inode = 0; dir_modified++; goto next; } if (ctx->flags & E2F_FLAG_SIGNAL_MASK) return DIRENT_ABORT; } group = ext2fs_group_of_ino(fs, dirent->inode); first_unused_inode = group * fs->super->s_inodes_per_group + 1 + fs->super->s_inodes_per_group - ext2fs_bg_itable_unused(fs, group); cd->pctx.group = group; /* * Check if the inode was missed out because * _INODE_UNINIT flag was set or bg_itable_unused was * incorrect. If so, clear the _INODE_UNINIT flag and * restart e2fsck. In the future it would be nice if * we could call a function in pass1.c that checks the * newly visible inodes. */ if (ext2fs_bg_flags_test(fs, group, EXT2_BG_INODE_UNINIT)) { pctx.num = dirent->inode; if (fix_problem(ctx, PR_2_INOREF_BG_INO_UNINIT, &cd->pctx)){ ext2fs_bg_flags_clear(fs, group, EXT2_BG_INODE_UNINIT); ext2fs_mark_super_dirty(fs); ctx->flags |= E2F_FLAG_RESTART_LATER; } else { ext2fs_unmark_valid(fs); if (problem == PR_2_BAD_INO) goto next; } } else if (dirent->inode >= first_unused_inode) { pctx.num = dirent->inode; if (fix_problem(ctx, PR_2_INOREF_IN_UNUSED, &cd->pctx)){ ext2fs_bg_itable_unused_set(fs, group, 0); ext2fs_mark_super_dirty(fs); ctx->flags |= E2F_FLAG_RESTART_LATER; } else { ext2fs_unmark_valid(fs); if (problem == PR_2_BAD_INO) goto next; } } /* * Offer to clear unused inodes; if we are going to be * restarting the scan due to bg_itable_unused being * wrong, then don't clear any inodes to avoid zapping * inodes that were skipped during pass1 due to an * incorrect bg_itable_unused; we'll get any real * problems after we restart. */ if (!(ctx->flags & E2F_FLAG_RESTART_LATER) && !(ext2fs_test_inode_bitmap2(ctx->inode_used_map, dirent->inode))) problem = PR_2_UNUSED_INODE; if (problem) { if (fix_problem(ctx, problem, &cd->pctx)) { dirent->inode = 0; dir_modified++; goto next; } else { ext2fs_unmark_valid(fs); if (problem == PR_2_BAD_INO) goto next; } } if (check_name(ctx, dirent, ino, &cd->pctx)) dir_modified++; if (check_filetype(ctx, dirent, ino, &cd->pctx)) dir_modified++; #ifdef ENABLE_HTREE if (dx_db) { ext2fs_dirhash(dx_dir->hashversion, dirent->name, (dirent->name_len & 0xFF), fs->super->s_hash_seed, &hash, 0); if (hash < dx_db->min_hash) dx_db->min_hash = hash; if (hash > dx_db->max_hash) dx_db->max_hash = hash; } #endif /* * If this is a directory, then mark its parent in its * dir_info structure. If the parent field is already * filled in, then this directory has more than one * hard link. We assume the first link is correct, * and ask the user if he/she wants to clear this one. */ if ((dot_state > 1) && (ext2fs_test_inode_bitmap2(ctx->inode_dir_map, dirent->inode))) { if (e2fsck_dir_info_get_parent(ctx, dirent->inode, &subdir_parent)) { cd->pctx.ino = dirent->inode; fix_problem(ctx, PR_2_NO_DIRINFO, &cd->pctx); goto abort_free_dict; } if (subdir_parent) { cd->pctx.ino2 = subdir_parent; if (fix_problem(ctx, PR_2_LINK_DIR, &cd->pctx)) { dirent->inode = 0; dir_modified++; goto next; } cd->pctx.ino2 = 0; } else { (void) e2fsck_dir_info_set_parent(ctx, dirent->inode, ino); } } if (dups_found) { ; } else if (dict_lookup(&de_dict, dirent)) { clear_problem_context(&pctx); pctx.ino = ino; pctx.dirent = dirent; fix_problem(ctx, PR_2_REPORT_DUP_DIRENT, &pctx); e2fsck_rehash_dir_later(ctx, ino); dups_found++; } else dict_alloc_insert(&de_dict, dirent, dirent); ext2fs_icount_increment(ctx->inode_count, dirent->inode, &links); if (links > 1) ctx->fs_links_count++; ctx->fs_total_count++; next: prev = dirent; if (dir_modified) (void) ext2fs_get_rec_len(fs, dirent, &rec_len); offset += rec_len; dot_state++; } while (offset < fs->blocksize - de_csum_size); #if 0 printf("\n"); #endif #ifdef ENABLE_HTREE if (dx_db) { #ifdef DX_DEBUG printf("db_block %d, type %d, min_hash 0x%0x, max_hash 0x%0x\n", db->blockcnt, dx_db->type, dx_db->min_hash, dx_db->max_hash); #endif cd->pctx.dir = cd->pctx.ino; if ((dx_db->type == DX_DIRBLOCK_ROOT) || (dx_db->type == DX_DIRBLOCK_NODE)) parse_int_node(fs, db, cd, dx_dir, buf, failed_csum); } #endif /* ENABLE_HTREE */ if (offset != fs->blocksize - de_csum_size) { cd->pctx.num = rec_len - (fs->blocksize - de_csum_size) + offset; if (fix_problem(ctx, PR_2_FINAL_RECLEN, &cd->pctx)) { dirent->rec_len = cd->pctx.num; dir_modified++; } } if (dir_modified) { /* leaf block with no tail? Rehash dirs later. */ if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) && is_leaf && !ext2fs_dirent_has_tail(fs, (struct ext2_dir_entry *)buf)) e2fsck_rehash_dir_later(ctx, ino); write_and_fix: if (e2fsck_dir_will_be_rehashed(ctx, ino)) ctx->fs->flags |= EXT2_FLAG_IGNORE_CSUM_ERRORS; cd->pctx.errcode = ext2fs_write_dir_block4(fs, block_nr, buf, 0, ino); if (e2fsck_dir_will_be_rehashed(ctx, ino)) ctx->fs->flags &= ~EXT2_FLAG_IGNORE_CSUM_ERRORS; if (cd->pctx.errcode) { if (!fix_problem(ctx, PR_2_WRITE_DIRBLOCK, &cd->pctx)) goto abort_free_dict; } ext2fs_mark_changed(fs); } else if (is_leaf && failed_csum && !dir_modified) { /* * If a leaf node that fails csum makes it this far without * alteration, ask the user if the checksum should be fixed. */ if (fix_problem(ctx, PR_2_LEAF_NODE_ONLY_CSUM_INVALID, &cd->pctx)) goto write_and_fix; } dict_free_nodes(&de_dict); return 0; abort_free_dict: ctx->flags |= E2F_FLAG_ABORT; dict_free_nodes(&de_dict); return DIRENT_ABORT; }
/* * Given a busted directory, try to salvage it somehow. * */ static void salvage_directory(ext2_filsys fs, struct ext2_dir_entry *dirent, struct ext2_dir_entry *prev, unsigned int *offset) { char *cp = (char *) dirent; int left; unsigned int rec_len, prev_rec_len; unsigned int name_len = dirent->name_len & 0xFF; (void) ext2fs_get_rec_len(fs, dirent, &rec_len); left = fs->blocksize - *offset - rec_len; /* * Special case of directory entry of size 8: copy what's left * of the directory block up to cover up the invalid hole. */ if ((left >= 12) && (rec_len == 8)) { memmove(cp, cp+8, left); memset(cp + left, 0, 8); return; } /* * If the directory entry overruns the end of the directory * block, and the name is small enough to fit, then adjust the * record length. */ if ((left < 0) && ((int) rec_len + left > 8) && ((int) name_len + 8 <= (int) rec_len + left) && dirent->inode <= fs->super->s_inodes_count && strnlen(dirent->name, name_len) == name_len) { (void) ext2fs_set_rec_len(fs, (int) rec_len + left, dirent); return; } /* * If the record length of the directory entry is a multiple * of four, and not too big, such that it is valid, let the * previous directory entry absorb the invalid one. */ if (prev && rec_len && (rec_len % 4) == 0 && (*offset + rec_len <= fs->blocksize)) { (void) ext2fs_get_rec_len(fs, prev, &prev_rec_len); prev_rec_len += rec_len; (void) ext2fs_set_rec_len(fs, prev_rec_len, prev); *offset += rec_len; return; } /* * Default salvage method --- kill all of the directory * entries for the rest of the block. We will either try to * absorb it into the previous directory entry, or create a * new empty directory entry the rest of the directory block. */ if (prev) { (void) ext2fs_get_rec_len(fs, prev, &prev_rec_len); prev_rec_len += fs->blocksize - *offset; (void) ext2fs_set_rec_len(fs, prev_rec_len, prev); *offset = fs->blocksize; } else { rec_len = fs->blocksize - *offset; (void) ext2fs_set_rec_len(fs, rec_len, dirent); dirent->name_len = 0; dirent->inode = 0; } }
static int link_proc(struct ext2_dir_entry *dirent, int offset, int blocksize, char *buf, void *priv_data) { struct link_struct *ls = (struct link_struct *) priv_data; struct ext2_dir_entry *next; unsigned int rec_len, min_rec_len, curr_rec_len; int ret = 0; int csum_size = 0; struct ext2_dir_entry_tail *t; if (ls->done) return DIRENT_ABORT; rec_len = EXT2_DIR_REC_LEN(ls->namelen); ls->err = ext2fs_get_rec_len(ls->fs, dirent, &curr_rec_len); if (ls->err) return DIRENT_ABORT; if (EXT2_HAS_RO_COMPAT_FEATURE(ls->fs->super, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) csum_size = sizeof(struct ext2_dir_entry_tail); /* * See if the following directory entry (if any) is unused; * if so, absorb it into this one. */ next = (struct ext2_dir_entry *) (buf + offset + curr_rec_len); if ((offset + (int) curr_rec_len < blocksize - (8 + csum_size)) && (next->inode == 0) && (offset + (int) curr_rec_len + (int) next->rec_len <= blocksize)) { curr_rec_len += next->rec_len; ls->err = ext2fs_set_rec_len(ls->fs, curr_rec_len, dirent); if (ls->err) return DIRENT_ABORT; ret = DIRENT_CHANGED; } /* * Since ext2fs_link blows away htree data, we need to be * careful -- if metadata_csum is enabled and we're passed in * a dirent that contains htree data, we need to create the * fake entry at the end of the block that hides the checksum. */ /* De-convert a dx_node block */ if (csum_size && curr_rec_len == ls->fs->blocksize && !dirent->inode) { curr_rec_len -= csum_size; ls->err = ext2fs_set_rec_len(ls->fs, curr_rec_len, dirent); if (ls->err) return DIRENT_ABORT; t = EXT2_DIRENT_TAIL(buf, ls->fs->blocksize); ext2fs_initialize_dirent_tail(ls->fs, t); ret = DIRENT_CHANGED; } /* De-convert a dx_root block */ if (csum_size && curr_rec_len == ls->fs->blocksize - EXT2_DIR_REC_LEN(1) && offset == EXT2_DIR_REC_LEN(1) && dirent->name[0] == '.' && dirent->name[1] == '.') { curr_rec_len -= csum_size; ls->err = ext2fs_set_rec_len(ls->fs, curr_rec_len, dirent); if (ls->err) return DIRENT_ABORT; t = EXT2_DIRENT_TAIL(buf, ls->fs->blocksize); ext2fs_initialize_dirent_tail(ls->fs, t); ret = DIRENT_CHANGED; } /* * If the directory entry is used, see if we can split the * directory entry to make room for the new name. If so, * truncate it and return. */ if (dirent->inode) { min_rec_len = EXT2_DIR_REC_LEN(ext2fs_dirent_name_len(dirent)); if (curr_rec_len < (min_rec_len + rec_len)) return ret; rec_len = curr_rec_len - min_rec_len; ls->err = ext2fs_set_rec_len(ls->fs, min_rec_len, dirent); if (ls->err) return DIRENT_ABORT; next = (struct ext2_dir_entry *) (buf + offset + dirent->rec_len); next->inode = 0; ext2fs_dirent_set_name_len(next, 0); ext2fs_dirent_set_file_type(next, 0); ls->err = ext2fs_set_rec_len(ls->fs, rec_len, next); if (ls->err) return DIRENT_ABORT; return DIRENT_CHANGED; } /* * If we get this far, then the directory entry is not used. * See if we can fit the request entry in. If so, do it. */ if (curr_rec_len < rec_len) return ret; dirent->inode = ls->inode; ext2fs_dirent_set_name_len(dirent, ls->namelen); strncpy(dirent->name, ls->name, ls->namelen); if (ls->sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) ext2fs_dirent_set_file_type(dirent, ls->flags & 0x7); ls->done++; return DIRENT_ABORT|DIRENT_CHANGED; }
static void htree_dump_leaf_node(ext2_filsys fs, ext2_ino_t ino, struct ext2_inode *inode, struct ext2_dx_root_info * rootnode, blk64_t blk, char *buf) { errcode_t errcode; struct ext2_dir_entry *dirent; int thislen, col = 0; unsigned int offset = 0; char name[EXT2_NAME_LEN + 1]; char tmp[EXT2_NAME_LEN + 64]; blk64_t pblk; ext2_dirhash_t hash, minor_hash; unsigned int rec_len; int hash_alg; int csum_size = 0; if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) csum_size = sizeof(struct ext2_dir_entry_tail); errcode = ext2fs_bmap2(fs, ino, inode, buf, 0, blk, 0, &pblk); if (errcode) { com_err("htree_dump_leaf_node", errcode, "while mapping logical block %llu\n", blk); return; } fprintf(pager, "Reading directory block %llu, phys %llu\n", blk, pblk); errcode = ext2fs_read_dir_block4(current_fs, pblk, buf, 0, ino); if (errcode) { com_err("htree_dump_leaf_node", errcode, "while reading block %llu (%llu)\n", blk, pblk); return; } hash_alg = rootnode->hash_version; if ((hash_alg <= EXT2_HASH_TEA) && (fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH)) hash_alg += 3; while (offset < fs->blocksize) { dirent = (struct ext2_dir_entry *) (buf + offset); errcode = ext2fs_get_rec_len(fs, dirent, &rec_len); if (errcode) { com_err("htree_dump_leaf_inode", errcode, "while getting rec_len for block %lu", (unsigned long) blk); return; } if (((offset + rec_len) > fs->blocksize) || (rec_len < 8) || ((rec_len % 4) != 0) || ((((unsigned) dirent->name_len & 0xFF)+8) > rec_len)) { fprintf(pager, "Corrupted directory block (%llu)!\n", blk); break; } thislen = dirent->name_len & 0xFF; strncpy(name, dirent->name, thislen); name[thislen] = '\0'; errcode = ext2fs_dirhash(hash_alg, name, thislen, fs->super->s_hash_seed, &hash, &minor_hash); if (errcode) com_err("htree_dump_leaf_node", errcode, "while calculating hash"); if ((offset == fs->blocksize - csum_size) && (dirent->inode == 0) && (dirent->rec_len == csum_size) && (dirent->name_len == EXT2_DIR_NAME_LEN_CSUM)) { struct ext2_dir_entry_tail *t; t = (struct ext2_dir_entry_tail *) dirent; snprintf(tmp, EXT2_NAME_LEN + 64, "leaf block checksum: 0x%08x ", t->det_checksum); } else { snprintf(tmp, EXT2_NAME_LEN + 64, "%u 0x%08x-%08x (%d) %s ", dirent->inode, hash, minor_hash, rec_len, name); } thislen = strlen(tmp); if (col + thislen > 80) { fprintf(pager, "\n"); col = 0; } fprintf(pager, "%s", tmp); col += thislen; offset += rec_len; } fprintf(pager, "\n"); }
static int check_dir_block(ext2_filsys fs, struct ext2_db_entry *db, void *priv_data) { struct dx_dir_info *dx_dir; #ifdef ENABLE_HTREE struct dx_dirblock_info *dx_db = 0; #endif struct ext2_dir_entry *dirent, *prev; ext2_dirhash_t hash; unsigned int offset = 0; const char * old_op; int dir_modified = 0; int dot_state; unsigned int rec_len; blk_t block_nr = db->blk; ext2_ino_t ino = db->ino; ext2_ino_t subdir_parent; __u16 links; struct check_dir_struct *cd; char *buf; e2fsck_t ctx; int problem; struct ext2_dx_root_info *root; struct ext2_dx_countlimit *limit; static dict_t de_dict; struct problem_context pctx; int dups_found = 0; int ret; cd = (struct check_dir_struct *) priv_data; buf = cd->buf; ctx = cd->ctx; if (ctx->flags & E2F_FLAG_SIGNAL_MASK || ctx->flags & E2F_FLAG_RESTART) return DIRENT_ABORT; if (ctx->progress && (ctx->progress)(ctx, 2, cd->count++, cd->max)) return DIRENT_ABORT; if (!(ext2fs_test_inode_bitmap(ctx->inode_used_map, ino))) return 0; cd->pctx.ino = ino; cd->pctx.blk = block_nr; cd->pctx.blkcount = db->blockcnt; cd->pctx.ino2 = 0; cd->pctx.dirent = 0; cd->pctx.num = 0; if (db->blk == 0) { if (allocate_dir_block(ctx, db, buf, &cd->pctx)) return 0; block_nr = db->blk; } if (db->blockcnt) dot_state = 2; else dot_state = 0; if (ctx->dirs_to_hash && ext2fs_u32_list_test(ctx->dirs_to_hash, ino)) dups_found++; #if 0 printf("In process_dir_block block %lu, #%d, inode %lu\n", block_nr, db->blockcnt, ino); #endif old_op = ehandler_operation(_("reading directory block")); cd->pctx.errcode = ext2fs_read_dir_block(fs, block_nr, buf); ehandler_operation(0); if (cd->pctx.errcode == EXT2_ET_DIR_CORRUPTED) cd->pctx.errcode = 0; if (cd->pctx.errcode) { if (!fix_problem(ctx, PR_2_READ_DIRBLOCK, &cd->pctx)) { ctx->flags |= E2F_FLAG_ABORT; return DIRENT_ABORT; } memset(buf, 0, fs->blocksize); } #ifdef ENABLE_HTREE dx_dir = e2fsck_get_dx_dir_info(ctx, ino); if (dx_dir && dx_dir->numblocks) { if (db->blockcnt >= dx_dir->numblocks) { if (fix_problem(ctx, PR_2_UNEXPECTED_HTREE_BLOCK, &pctx)) { clear_htree(ctx, ino); dx_dir->numblocks = 0; dx_db = 0; goto out_htree; } fatal_error(ctx, _("Can not continue.")); } dx_db = &dx_dir->dx_block[db->blockcnt]; dx_db->type = DX_DIRBLOCK_LEAF; dx_db->phys = block_nr; dx_db->min_hash = ~0; dx_db->max_hash = 0; dirent = (struct ext2_dir_entry *) buf; (void) ext2fs_get_rec_len(fs, dirent, &rec_len); limit = (struct ext2_dx_countlimit *) (buf+8); if (db->blockcnt == 0) { root = (struct ext2_dx_root_info *) (buf + 24); dx_db->type = DX_DIRBLOCK_ROOT; dx_db->flags |= DX_FLAG_FIRST | DX_FLAG_LAST; if ((root->reserved_zero || root->info_length < 8 || root->indirect_levels > 1) && fix_problem(ctx, PR_2_HTREE_BAD_ROOT, &cd->pctx)) { clear_htree(ctx, ino); dx_dir->numblocks = 0; dx_db = 0; } dx_dir->hashversion = root->hash_version; if ((dx_dir->hashversion <= EXT2_HASH_TEA) && (fs->super->s_flags & EXT2_FLAGS_UNSIGNED_HASH)) dx_dir->hashversion += 3; dx_dir->depth = root->indirect_levels + 1; } else if ((dirent->inode == 0) && (rec_len == fs->blocksize) && (dirent->name_len == 0) && (ext2fs_le16_to_cpu(limit->limit) == ((fs->blocksize-8) / sizeof(struct ext2_dx_entry)))) dx_db->type = DX_DIRBLOCK_NODE; } out_htree: #endif dict_init(&de_dict, DICTCOUNT_T_MAX, dict_de_cmp); prev = 0; do { int group; ext2_ino_t first_unused_inode; problem = 0; dirent = (struct ext2_dir_entry *) (buf + offset); (void) ext2fs_get_rec_len(fs, dirent, &rec_len); cd->pctx.dirent = dirent; cd->pctx.num = offset; if (((offset + rec_len) > fs->blocksize) || (rec_len < 12) || ((rec_len % 4) != 0) || (((dirent->name_len & (unsigned) 0xFF)+8) > rec_len)) { if (fix_problem(ctx, PR_2_DIR_CORRUPTED, &cd->pctx)) { salvage_directory(fs, dirent, prev, &offset); dir_modified++; continue; } else goto abort_free_dict; } if ((dirent->name_len & 0xFF) > EXT2_NAME_LEN) { if (fix_problem(ctx, PR_2_FILENAME_LONG, &cd->pctx)) { dirent->name_len = EXT2_NAME_LEN; dir_modified++; } } if (dot_state == 0) { if (check_dot(ctx, dirent, ino, &cd->pctx)) dir_modified++; } else if (dot_state == 1) { ret = check_dotdot(ctx, dirent, ino, &cd->pctx); if (ret < 0) goto abort_free_dict; if (ret) dir_modified++; } else if (dirent->inode == ino) { problem = PR_2_LINK_DOT; if (fix_problem(ctx, PR_2_LINK_DOT, &cd->pctx)) { dirent->inode = 0; dir_modified++; goto next; } } if (!dirent->inode) goto next; if (((dirent->inode != EXT2_ROOT_INO) && (dirent->inode < EXT2_FIRST_INODE(fs->super))) || (dirent->inode > fs->super->s_inodes_count)) { problem = PR_2_BAD_INO; } else if (ctx->inode_bb_map && (ext2fs_test_inode_bitmap(ctx->inode_bb_map, dirent->inode))) { problem = PR_2_BB_INODE; } else if ((dot_state > 1) && ((dirent->name_len & 0xFF) == 1) && (dirent->name[0] == '.')) { problem = PR_2_DUP_DOT; } else if ((dot_state > 1) && ((dirent->name_len & 0xFF) == 2) && (dirent->name[0] == '.') && (dirent->name[1] == '.')) { problem = PR_2_DUP_DOT_DOT; } else if ((dot_state > 1) && (dirent->inode == EXT2_ROOT_INO)) { problem = PR_2_LINK_ROOT; } else if ((dot_state > 1) && (dirent->name_len & 0xFF) == 0) { problem = PR_2_NULL_NAME; } if (problem) { if (fix_problem(ctx, problem, &cd->pctx)) { dirent->inode = 0; dir_modified++; goto next; } else { ext2fs_unmark_valid(fs); if (problem == PR_2_BAD_INO) goto next; } } if (ctx->inode_bad_map && ext2fs_test_inode_bitmap(ctx->inode_bad_map, dirent->inode)) { if (e2fsck_process_bad_inode(ctx, ino, dirent->inode, buf + fs->blocksize)) { dirent->inode = 0; dir_modified++; goto next; } if (ctx->flags & E2F_FLAG_SIGNAL_MASK) return DIRENT_ABORT; } group = ext2fs_group_of_ino(fs, dirent->inode); first_unused_inode = group * fs->super->s_inodes_per_group + 1 + fs->super->s_inodes_per_group - fs->group_desc[group].bg_itable_unused; cd->pctx.group = group; if (fs->group_desc[group].bg_flags & EXT2_BG_INODE_UNINIT) { pctx.num = dirent->inode; if (fix_problem(ctx, PR_2_INOREF_BG_INO_UNINIT, &cd->pctx)){ fs->group_desc[group].bg_flags &= ~EXT2_BG_INODE_UNINIT; ext2fs_mark_super_dirty(fs); ctx->flags |= E2F_FLAG_RESTART_LATER; } else { ext2fs_unmark_valid(fs); if (problem == PR_2_BAD_INO) goto next; } } else if (dirent->inode >= first_unused_inode) { pctx.num = dirent->inode; if (fix_problem(ctx, PR_2_INOREF_IN_UNUSED, &cd->pctx)){ fs->group_desc[group].bg_itable_unused = 0; ext2fs_mark_super_dirty(fs); ctx->flags |= E2F_FLAG_RESTART_LATER; } else { ext2fs_unmark_valid(fs); if (problem == PR_2_BAD_INO) goto next; } } if (!(ctx->flags & E2F_FLAG_RESTART_LATER) && !(ext2fs_test_inode_bitmap(ctx->inode_used_map, dirent->inode))) problem = PR_2_UNUSED_INODE; if (problem) { if (fix_problem(ctx, problem, &cd->pctx)) { dirent->inode = 0; dir_modified++; goto next; } else { ext2fs_unmark_valid(fs); if (problem == PR_2_BAD_INO) goto next; } } if (check_name(ctx, dirent, ino, &cd->pctx)) dir_modified++; if (check_filetype(ctx, dirent, ino, &cd->pctx)) dir_modified++; #ifdef ENABLE_HTREE if (dx_db) { ext2fs_dirhash(dx_dir->hashversion, dirent->name, (dirent->name_len & 0xFF), fs->super->s_hash_seed, &hash, 0); if (hash < dx_db->min_hash) dx_db->min_hash = hash; if (hash > dx_db->max_hash) dx_db->max_hash = hash; } #endif if ((dot_state > 1) && (ext2fs_test_inode_bitmap(ctx->inode_dir_map, dirent->inode))) { if (e2fsck_dir_info_get_parent(ctx, dirent->inode, &subdir_parent)) { cd->pctx.ino = dirent->inode; fix_problem(ctx, PR_2_NO_DIRINFO, &cd->pctx); goto abort_free_dict; } if (subdir_parent) { cd->pctx.ino2 = subdir_parent; if (fix_problem(ctx, PR_2_LINK_DIR, &cd->pctx)) { dirent->inode = 0; dir_modified++; goto next; } cd->pctx.ino2 = 0; } else { (void) e2fsck_dir_info_set_parent(ctx, dirent->inode, ino); } } if (dups_found) { ; } else if (dict_lookup(&de_dict, dirent)) { clear_problem_context(&pctx); pctx.ino = ino; pctx.dirent = dirent; fix_problem(ctx, PR_2_REPORT_DUP_DIRENT, &pctx); if (!ctx->dirs_to_hash) ext2fs_u32_list_create(&ctx->dirs_to_hash, 50); if (ctx->dirs_to_hash) ext2fs_u32_list_add(ctx->dirs_to_hash, ino); dups_found++; } else dict_alloc_insert(&de_dict, dirent, dirent); ext2fs_icount_increment(ctx->inode_count, dirent->inode, &links); if (links > 1) ctx->fs_links_count++; ctx->fs_total_count++; next: prev = dirent; if (dir_modified) (void) ext2fs_get_rec_len(fs, dirent, &rec_len); offset += rec_len; dot_state++; } while (offset < fs->blocksize); #if 0 printf("\n"); #endif #ifdef ENABLE_HTREE if (dx_db) { #ifdef DX_DEBUG printf("db_block %d, type %d, min_hash 0x%0x, max_hash 0x%0x\n", db->blockcnt, dx_db->type, dx_db->min_hash, dx_db->max_hash); #endif cd->pctx.dir = cd->pctx.ino; if ((dx_db->type == DX_DIRBLOCK_ROOT) || (dx_db->type == DX_DIRBLOCK_NODE)) parse_int_node(fs, db, cd, dx_dir, buf); } #endif if (offset != fs->blocksize) { cd->pctx.num = rec_len - fs->blocksize + offset; if (fix_problem(ctx, PR_2_FINAL_RECLEN, &cd->pctx)) { dirent->rec_len = cd->pctx.num; dir_modified++; } } if (dir_modified) { cd->pctx.errcode = ext2fs_write_dir_block(fs, block_nr, buf); if (cd->pctx.errcode) { if (!fix_problem(ctx, PR_2_WRITE_DIRBLOCK, &cd->pctx)) goto abort_free_dict; } ext2fs_mark_changed(fs); } dict_free_nodes(&de_dict); return 0; abort_free_dict: ctx->flags |= E2F_FLAG_ABORT; dict_free_nodes(&de_dict); return DIRENT_ABORT; }