/* * 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; }
static struct ext2_dx_root_info *set_root_node(ext2_filsys fs, char *buf, ext2_ino_t ino, ext2_ino_t parent) { struct ext2_dir_entry *dir; struct ext2_dx_root_info *root; struct ext2_dx_countlimit *limits; int filetype = 0; int csum_size = 0; if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) filetype = EXT2_FT_DIR; memset(buf, 0, fs->blocksize); dir = (struct ext2_dir_entry *) buf; dir->inode = ino; dir->name[0] = '.'; ext2fs_dirent_set_name_len(dir, 1); ext2fs_dirent_set_file_type(dir, filetype); dir->rec_len = 12; dir = (struct ext2_dir_entry *) (buf + 12); dir->inode = parent; dir->name[0] = '.'; dir->name[1] = '.'; ext2fs_dirent_set_name_len(dir, 2); ext2fs_dirent_set_file_type(dir, filetype); dir->rec_len = fs->blocksize - 12; root = (struct ext2_dx_root_info *) (buf+24); root->reserved_zero = 0; root->hash_version = fs->super->s_def_hash_version; root->info_length = 8; root->indirect_levels = 0; root->unused_flags = 0; if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) csum_size = sizeof(struct ext2_dx_tail); limits = (struct ext2_dx_countlimit *) (buf+32); limits->limit = (fs->blocksize - (32 + csum_size)) / sizeof(struct ext2_dx_entry); limits->count = 0; return root; }
/* * Check the directory filetype (if present) */ static _INLINE_ int check_filetype(e2fsck_t ctx, struct ext2_dir_entry *dirent, ext2_ino_t dir_ino EXT2FS_ATTR((unused)), struct problem_context *pctx) { int filetype = ext2fs_dirent_file_type(dirent); int should_be = EXT2_FT_UNKNOWN; struct ext2_inode inode; if (!(ctx->fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE)) { if (filetype == 0 || !fix_problem(ctx, PR_2_CLEAR_FILETYPE, pctx)) return 0; ext2fs_dirent_set_file_type(dirent, EXT2_FT_UNKNOWN); return 1; } if (ext2fs_test_inode_bitmap2(ctx->inode_dir_map, dirent->inode)) { should_be = EXT2_FT_DIR; } else if (ext2fs_test_inode_bitmap2(ctx->inode_reg_map, dirent->inode)) { should_be = EXT2_FT_REG_FILE; } else if (ctx->inode_bad_map && ext2fs_test_inode_bitmap2(ctx->inode_bad_map, dirent->inode)) should_be = 0; else { e2fsck_read_inode(ctx, dirent->inode, &inode, "check_filetype"); should_be = ext2_file_type(inode.i_mode); } if (filetype == should_be) return 0; pctx->num = should_be; if (fix_problem(ctx, filetype ? PR_2_BAD_FILETYPE : PR_2_SET_FILETYPE, pctx) == 0) return 0; ext2fs_dirent_set_file_type(dirent, should_be); return 1; }
/* * 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; }
static errcode_t copy_dir_entries(e2fsck_t ctx, struct fill_dir_struct *fd, struct out_dir *outdir) { ext2_filsys fs = ctx->fs; errcode_t retval; char *block_start; struct hash_entry *ent; struct ext2_dir_entry *dirent; unsigned int rec_len, prev_rec_len, left, slack, offset; int i; ext2_dirhash_t prev_hash; int csum_size = 0; struct ext2_dir_entry_tail *t; if (ctx->htree_slack_percentage == 255) { profile_get_uint(ctx->profile, "options", "indexed_dir_slack_percentage", 0, 20, &ctx->htree_slack_percentage); if (ctx->htree_slack_percentage > 100) ctx->htree_slack_percentage = 20; } if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) csum_size = sizeof(struct ext2_dir_entry_tail); outdir->max = 0; retval = alloc_size_dir(fs, outdir, (fd->dir_size / fs->blocksize) + 2); if (retval) return retval; outdir->num = fd->compress ? 0 : 1; offset = 0; outdir->hashes[0] = 0; prev_hash = 1; if ((retval = get_next_block(fs, outdir, &block_start))) return retval; dirent = (struct ext2_dir_entry *) block_start; prev_rec_len = 0; rec_len = 0; left = fs->blocksize - csum_size; slack = fd->compress ? 12 : ((fs->blocksize - csum_size) * ctx->htree_slack_percentage)/100; if (slack < 12) slack = 12; for (i = 0; i < fd->num_array; i++) { ent = fd->harray + i; if (ent->dir->inode == 0) continue; rec_len = EXT2_DIR_REC_LEN(ext2fs_dirent_name_len(ent->dir)); if (rec_len > left) { if (left) { left += prev_rec_len; retval = ext2fs_set_rec_len(fs, left, dirent); if (retval) return retval; } if (csum_size) { t = EXT2_DIRENT_TAIL(block_start, fs->blocksize); ext2fs_initialize_dirent_tail(fs, t); } if ((retval = get_next_block(fs, outdir, &block_start))) return retval; offset = 0; } left = (fs->blocksize - csum_size) - offset; dirent = (struct ext2_dir_entry *) (block_start + offset); if (offset == 0) { if (ent->hash == prev_hash) outdir->hashes[outdir->num-1] = ent->hash | 1; else outdir->hashes[outdir->num-1] = ent->hash; } dirent->inode = ent->dir->inode; ext2fs_dirent_set_name_len(dirent, ext2fs_dirent_name_len(ent->dir)); ext2fs_dirent_set_file_type(dirent, ext2fs_dirent_file_type(ent->dir)); retval = ext2fs_set_rec_len(fs, rec_len, dirent); if (retval) return retval; prev_rec_len = rec_len; memcpy(dirent->name, ent->dir->name, ext2fs_dirent_name_len(dirent)); offset += rec_len; left -= rec_len; if (left < slack) { prev_rec_len += left; retval = ext2fs_set_rec_len(fs, prev_rec_len, dirent); if (retval) return retval; offset += left; left = 0; } prev_hash = ent->hash; } if (left) retval = ext2fs_set_rec_len(fs, rec_len + left, dirent); if (csum_size) { t = EXT2_DIRENT_TAIL(block_start, fs->blocksize); ext2fs_initialize_dirent_tail(fs, t); } return retval; }
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 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; }
/* * Create new directory block */ errcode_t ext2fs_new_dir_block(ext2_filsys fs, ext2_ino_t dir_ino, ext2_ino_t parent_ino, char **block) { struct ext2_dir_entry *dir = NULL; errcode_t retval; char *buf; int rec_len; int filetype = 0; struct ext2_dir_entry_tail *t; int csum_size = 0; EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); retval = ext2fs_get_mem(fs->blocksize, &buf); if (retval) return retval; memset(buf, 0, fs->blocksize); dir = (struct ext2_dir_entry *) buf; if (EXT2_HAS_RO_COMPAT_FEATURE(fs->super, EXT4_FEATURE_RO_COMPAT_METADATA_CSUM)) csum_size = sizeof(struct ext2_dir_entry_tail); retval = ext2fs_set_rec_len(fs, fs->blocksize - csum_size, dir); if (retval) return retval; if (dir_ino) { if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) filetype = EXT2_FT_DIR; /* * Set up entry for '.' */ dir->inode = dir_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 *) (buf + dir->rec_len); retval = ext2fs_set_rec_len(fs, rec_len, dir); if (retval) return retval; dir->inode = parent_ino; ext2fs_dirent_set_name_len(dir, 2); ext2fs_dirent_set_file_type(dir, filetype); dir->name[0] = '.'; dir->name[1] = '.'; } if (csum_size) { t = EXT2_DIRENT_TAIL(buf, fs->blocksize); ext2fs_initialize_dirent_tail(fs, t); } *block = buf; return 0; }
/* * 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 = ext2fs_dirent_name_len(dirent); (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); ext2fs_dirent_set_name_len(dirent, 0); ext2fs_dirent_set_file_type(dirent, EXT2_FT_UNKNOWN); dirent->inode = 0; } }