int ext2_check_dir_entry (const char * function, struct inode * dir, struct ext2_dir_entry_2 * de, struct buffer_head * bh, unsigned long offset) { const char * error_msg = NULL; if (le16_to_cpu(de->rec_len) < EXT2_DIR_REC_LEN(1)) error_msg = "rec_len is smaller than minimal"; else if (le16_to_cpu(de->rec_len) % 4 != 0) error_msg = "rec_len % 4 != 0"; else if (le16_to_cpu(de->rec_len) < EXT2_DIR_REC_LEN(de->name_len)) error_msg = "rec_len is too small for name_len"; else if (dir && ((char *) de - bh->b_data) + le16_to_cpu(de->rec_len) > dir->i_sb->s_blocksize) error_msg = "directory entry across blocks"; else if (dir && le32_to_cpu(de->inode) > le32_to_cpu(dir->i_sb->u.ext2_sb.s_es->s_inodes_count)) error_msg = "inode out of bounds"; if (error_msg != NULL) ext2_error (dir->i_sb, function, "bad entry in directory #%lu: %s - " "offset=%lu, inode=%lu, rec_len=%d, name_len=%d", dir->i_ino, error_msg, offset, (unsigned long) le32_to_cpu(de->inode), le16_to_cpu(de->rec_len), de->name_len); return error_msg == NULL ? 1 : 0; }
/* * changed so that it confirms to ext2_check_dir_entry */ static int ext2_dirbadentry(struct vnode *dp, struct ext2_dir_entry_2 *de, int entryoffsetinblock) { int DIRBLKSIZ = VTOI(dp)->i_e2fs->s_blocksize; char * error_msg = NULL; if (de->rec_len < EXT2_DIR_REC_LEN(1)) error_msg = "rec_len is smaller than minimal"; else if (de->rec_len % 4 != 0) error_msg = "rec_len % 4 != 0"; else if (de->rec_len < EXT2_DIR_REC_LEN(de->name_len)) error_msg = "reclen is too small for name_len"; else if (entryoffsetinblock + de->rec_len > DIRBLKSIZ) error_msg = "directory entry across blocks"; /* else LATER if (de->inode > dir->i_sb->u.ext2_sb.s_es->s_inodes_count) error_msg = "inode out of bounds"; */ if (error_msg != NULL) { kprintf("bad directory entry: %s\n", error_msg); kprintf("offset=%d, inode=%lu, rec_len=%u, name_len=%u\n", entryoffsetinblock, (unsigned long)de->inode, de->rec_len, de->name_len); } return error_msg == NULL ? 0 : 1; }
int ext2_check_dir_entry (char * function, struct inode * dir, struct ext2_dir_entry * de, struct buffer_head * bh, unsigned long offset) { char * error_msg = NULL; if (de->rec_len < EXT2_DIR_REC_LEN(1)) error_msg = "rec_len is smaller than minimal"; else if (de->rec_len % 4 != 0) error_msg = "rec_len % 4 != 0"; else if (de->rec_len < EXT2_DIR_REC_LEN(de->name_len)) error_msg = "rec_len is too small for name_len"; else if (dir && ((char *) de - bh->b_data) + de->rec_len > dir->i_sb->s_blocksize) error_msg = "directory entry across blocks"; else if (dir && de->inode > dir->i_sb->u.ext2_sb.s_es->s_inodes_count) error_msg = "inode out of bounds"; if (error_msg != NULL) ext2_error (dir->i_sb, function, "bad directory entry: %s\n" "offset=%lu, inode=%lu, rec_len=%d, name_len=%d", error_msg, offset, de->inode, de->rec_len, de->name_len); return error_msg == NULL ? 1 : 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; int rec_len, min_rec_len; int ret = 0; rec_len = EXT2_DIR_REC_LEN(ls->namelen); /* * See if the following directory entry (if any) is unused; * if so, absorb it into this one. */ next = (struct ext2_dir_entry *) (buf + offset + dirent->rec_len); if ((offset + dirent->rec_len < blocksize - 8) && (next->inode == 0) && (offset + dirent->rec_len + next->rec_len <= blocksize)) { dirent->rec_len += next->rec_len; 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 (dirent->rec_len < (min_rec_len + rec_len)) return ret; rec_len = dirent->rec_len - min_rec_len; dirent->rec_len = min_rec_len; next = (struct ext2_dir_entry *) (buf + offset + dirent->rec_len); next->inode = 0; next->name_len = 0; next->rec_len = rec_len; 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 (dirent->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 uint32_t ext2_htree_root_limit(struct inode *ip, int len) { uint32_t space; space = ip->i_e2fs->e2fs_bsize - EXT2_DIR_REC_LEN(1) - EXT2_DIR_REC_LEN(2) - len; return (space / sizeof(struct ext2fs_htree_entry)); }
/* * Append an entry to the end of the directory block. */ static void ext2_append_entry(char *block, uint32_t blksize, struct ext2fs_direct_2 *last_entry, struct ext2fs_direct_2 *new_entry) { uint16_t entry_len; entry_len = EXT2_DIR_REC_LEN(last_entry->e2d_namlen); last_entry->e2d_reclen = entry_len; last_entry = (struct ext2fs_direct_2 *)((char *)last_entry + entry_len); new_entry->e2d_reclen = block + blksize - (char *)last_entry; memcpy(last_entry, new_entry, EXT2_DIR_REC_LEN(new_entry->e2d_namlen)); }
/* * 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; 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; retval = ext2fs_set_rec_len(fs, fs->blocksize, dir); if (retval) return retval; if (dir_ino) { if (fs->super->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) filetype = EXT2_FT_DIR << 8; /* * Set up entry for '.' */ dir->inode = dir_ino; dir->name_len = 1 | filetype; dir->name[0] = '.'; rec_len = fs->blocksize - 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; dir->name_len = 2 | filetype; dir->name[0] = '.'; dir->name[1] = '.'; } *block = buf; return 0; }
/* * Create new directory block */ bool ext2_new_dir_block(PEXT2_FILESYS fs, ULONG dir_ino, ULONG parent_ino, char **block) { PEXT2_DIR_ENTRY dir = NULL; char *buf; int rec_len; int filetype = 0; buf = (char *)RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, fs->blocksize); if (!buf) return false; dir = (PEXT2_DIR_ENTRY) buf; dir->rec_len = fs->blocksize; if (dir_ino) { if (fs->ext2_sb->s_feature_incompat & EXT2_FEATURE_INCOMPAT_FILETYPE) filetype = EXT2_FT_DIR << 8; /* * Set up entry for '.' */ dir->inode = dir_ino; dir->name_len = 1 | filetype; dir->name[0] = '.'; rec_len = dir->rec_len - 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); dir->rec_len = rec_len; dir->inode = parent_ino; dir->name_len = 2 | filetype; dir->name[0] = '.'; dir->name[1] = '.'; } *block = buf; return true; }
static uint32_t ext2_htree_node_limit(struct inode *ip) { struct m_ext2fs *fs; uint32_t space; fs = ip->i_e2fs; space = fs->e2fs_bsize - EXT2_DIR_REC_LEN(0); return (space / sizeof(struct ext2fs_htree_entry)); }
/* * Create new directory block */ errcode_t ext2fs_new_dir_block(ext2_filsys fs, ino_t dir_ino, ino_t parent_ino, char **block) { char *buf; struct ext2_dir_entry *dir = NULL; int rec_len; EXT2_CHECK_MAGIC(fs, EXT2_ET_MAGIC_EXT2FS_FILSYS); buf = malloc(fs->blocksize); if (!buf) return ENOMEM; memset(buf, 0, fs->blocksize); dir = (struct ext2_dir_entry *) buf; dir->rec_len = fs->blocksize; if (dir_ino) { /* * Set up entry for '.' */ dir->inode = dir_ino; dir->name_len = 1; dir->name[0] = '.'; rec_len = dir->rec_len - EXT2_DIR_REC_LEN(dir->name_len); dir->rec_len = EXT2_DIR_REC_LEN(dir->name_len); /* * Set up entry for '..' */ dir = (struct ext2_dir_entry *) (buf + dir->rec_len); dir->rec_len = rec_len; dir->inode = parent_ino; dir->name_len = 2; dir->name[0] = '.'; dir->name[1] = '.'; } *block = buf; return 0; }
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; }
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; }
/* * Move half of entries from the old directory block to the new one. */ static int ext2_htree_split_dirblock(char *block1, char *block2, uint32_t blksize, uint32_t *hash_seed, uint8_t hash_version, uint32_t *split_hash, struct ext2fs_direct_2 *entry) { int entry_cnt = 0; int size = 0; int i, k; uint32_t offset; uint16_t entry_len = 0; uint32_t entry_hash; struct ext2fs_direct_2 *ep, *last; char *dest; struct ext2fs_htree_sort_entry *sort_info; ep = (struct ext2fs_direct_2 *)block1; dest = block2; sort_info = (struct ext2fs_htree_sort_entry *) ((char *)block2 + blksize); /* * Calculate name hash value for the entry which is to be added. */ ext2_htree_hash(entry->e2d_name, entry->e2d_namlen, hash_seed, hash_version, &entry_hash, NULL); /* * Fill in directory entry sort descriptors. */ while ((char *)ep < block1 + blksize) { if (ep->e2d_ino && ep->e2d_namlen) { entry_cnt++; sort_info--; sort_info->h_size = ep->e2d_reclen; sort_info->h_offset = (char *)ep - block1; ext2_htree_hash(ep->e2d_name, ep->e2d_namlen, hash_seed, hash_version, &sort_info->h_hash, NULL); } ep = (struct ext2fs_direct_2 *) ((char *)ep + ep->e2d_reclen); } /* * Sort directory entry descriptors by name hash value. */ qsort(sort_info, entry_cnt, sizeof(struct ext2fs_htree_sort_entry), ext2_htree_cmp_sort_entry); /* * Count the number of entries to move to directory block 2. */ for (i = entry_cnt - 1; i >= 0; i--) { if (sort_info[i].h_size + size > blksize / 2) break; size += sort_info[i].h_size; } *split_hash = sort_info[i + 1].h_hash; /* * Set collision bit. */ if (*split_hash == sort_info[i].h_hash) *split_hash += 1; /* * Move half of directory entries from block 1 to block 2. */ for (k = i + 1; k < entry_cnt; k++) { ep = (struct ext2fs_direct_2 *)((char *)block1 + sort_info[k].h_offset); entry_len = EXT2_DIR_REC_LEN(ep->e2d_namlen); memcpy(dest, ep, entry_len); ((struct ext2fs_direct_2 *)dest)->e2d_reclen = entry_len; /* Mark directory entry as unused. */ ep->e2d_ino = 0; dest += entry_len; } dest -= entry_len; /* Shrink directory entries in block 1. */ last = (struct ext2fs_direct_2 *)block1; entry_len = EXT2_DIR_REC_LEN(last->e2d_namlen); for (offset = last->e2d_reclen; offset < blksize; ) { ep = (struct ext2fs_direct_2 *)(block1 + offset); offset += ep->e2d_reclen; if (last->e2d_ino) { /* Trim the existing slot */ last->e2d_reclen = entry_len; last = (struct ext2fs_direct_2 *) ((char *)last + entry_len); } entry_len = EXT2_DIR_REC_LEN(ep->e2d_namlen); memcpy((void *)last, (void *)ep, entry_len); } if (entry_hash >= *split_hash) { /* Add entry to block 2. */ ext2_append_entry(block2, blksize, (struct ext2fs_direct_2 *)dest, entry); /* Adjust length field of last entry of block 1. */ last->e2d_reclen = block1 + blksize - (char *)last; } else { /* Add entry to block 1. */ ext2_append_entry(block1, blksize, last, entry); /* Adjust length field of last entry of block 2. */ ((struct ext2fs_direct_2 *)dest)->e2d_reclen = block2 + blksize - dest; } return (0); }
static int ext2_readdir(struct file * filp, void * dirent, filldir_t filldir) { int error = 0; unsigned long offset, blk; int i, num, stored; struct buffer_head * bh, * tmp, * bha[16]; struct ext2_dir_entry_2 * de; struct super_block * sb; int err; struct inode *inode = filp->f_dentry->d_inode; if (!inode || !S_ISDIR(inode->i_mode)) return -EBADF; sb = inode->i_sb; stored = 0; bh = NULL; offset = filp->f_pos & (sb->s_blocksize - 1); while (!error && !stored && filp->f_pos < inode->i_size) { blk = (filp->f_pos) >> EXT2_BLOCK_SIZE_BITS(sb); bh = ext2_bread (inode, blk, 0, &err); if (!bh) { ext2_error (sb, "ext2_readdir", "directory #%lu contains a hole at offset %lu", inode->i_ino, (unsigned long)filp->f_pos); filp->f_pos += sb->s_blocksize - offset; continue; } /* * Do the readahead */ if (!offset) { for (i = 16 >> (EXT2_BLOCK_SIZE_BITS(sb) - 9), num = 0; i > 0; i--) { tmp = ext2_getblk (inode, ++blk, 0, &err); if (tmp && !buffer_uptodate(tmp) && !buffer_locked(tmp)) bha[num++] = tmp; else brelse (tmp); } if (num) { ll_rw_block (READA, num, bha); for (i = 0; i < num; i++) brelse (bha[i]); } } revalidate: /* If the dir block has changed since the last call to * readdir(2), then we might be pointing to an invalid * dirent right now. Scan from the start of the block * to make sure. */ if (filp->f_version != inode->i_version) { for (i = 0; i < sb->s_blocksize && i < offset; ) { de = (struct ext2_dir_entry_2 *) (bh->b_data + i); /* It's too expensive to do a full * dirent test each time round this * loop, but we do have to test at * least that it is non-zero. A * failure will be detected in the * dirent test below. */ if (le16_to_cpu(de->rec_len) < EXT2_DIR_REC_LEN(1)) break; i += le16_to_cpu(de->rec_len); } offset = i; filp->f_pos = (filp->f_pos & ~(sb->s_blocksize - 1)) | offset; filp->f_version = inode->i_version; } while (!error && filp->f_pos < inode->i_size && offset < sb->s_blocksize) { de = (struct ext2_dir_entry_2 *) (bh->b_data + offset); if (!ext2_check_dir_entry ("ext2_readdir", inode, de, bh, offset)) { /* On error, skip the f_pos to the next block. */ filp->f_pos = (filp->f_pos & (sb->s_blocksize - 1)) + sb->s_blocksize; brelse (bh); return stored; } offset += le16_to_cpu(de->rec_len); if (le32_to_cpu(de->inode)) { /* We might block in the next section * if the data destination is * currently swapped out. So, use a * version stamp to detect whether or * not the directory has been modified * during the copy operation. */ unsigned long version = inode->i_version; error = filldir(dirent, de->name, de->name_len, filp->f_pos, le32_to_cpu(de->inode)); if (error) break; if (version != inode->i_version) goto revalidate; stored ++; } filp->f_pos += le16_to_cpu(de->rec_len); } offset = 0; brelse (bh); } UPDATE_ATIME(inode); return 0; }
/* * Create an HTree index for a directory */ int ext2_htree_create_index(struct vnode *vp, struct componentname *cnp, struct ext2fs_direct_2 *new_entry) { struct buf *bp = NULL; struct inode *dp; struct ext2fs *fs; struct m_ext2fs *m_fs; struct ext2fs_direct_2 *ep, *dotdot; struct ext2fs_htree_root *root; struct ext2fs_htree_lookup_info info; uint32_t blksize, dirlen, split_hash; uint8_t hash_version; char *buf1 = NULL; char *buf2 = NULL; int error = 0; dp = VTOI(vp); fs = dp->i_e2fs->e2fs; m_fs = dp->i_e2fs; blksize = m_fs->e2fs_bsize; buf1 = malloc(blksize, M_TEMP, M_WAITOK | M_ZERO); buf2 = malloc(blksize, M_TEMP, M_WAITOK | M_ZERO); if ((error = ext2_blkatoff(vp, 0, NULL, &bp)) != 0) goto out; root = (struct ext2fs_htree_root *)bp->b_data; dotdot = (struct ext2fs_direct_2 *)((char *)&(root->h_dotdot)); ep = (struct ext2fs_direct_2 *)((char *)dotdot + dotdot->e2d_reclen); dirlen = (char *)root + blksize - (char *)ep; memcpy(buf1, ep, dirlen); ep = (struct ext2fs_direct_2 *)buf1; while ((char *)ep < buf1 + dirlen) ep = (struct ext2fs_direct_2 *) ((char *)ep + ep->e2d_reclen); ep->e2d_reclen = buf1 + blksize - (char *)ep; dp->i_flag |= IN_E4INDEX; /* * Initialize index root. */ dotdot->e2d_reclen = blksize - EXT2_DIR_REC_LEN(1); memset(&root->h_info, 0, sizeof(root->h_info)); root->h_info.h_hash_version = fs->e3fs_def_hash_version; root->h_info.h_info_len = sizeof(root->h_info); ext2_htree_set_block(root->h_entries, 1); ext2_htree_set_count(root->h_entries, 1); ext2_htree_set_limit(root->h_entries, ext2_htree_root_limit(dp, sizeof(root->h_info))); memset(&info, 0, sizeof(info)); info.h_levels_num = 1; info.h_levels[0].h_entries = root->h_entries; info.h_levels[0].h_entry = root->h_entries; hash_version = root->h_info.h_hash_version; if (hash_version <= EXT2_HTREE_TEA) hash_version += m_fs->e2fs_uhash; ext2_htree_split_dirblock(buf1, buf2, blksize, fs->e3fs_hash_seed, hash_version, &split_hash, new_entry); ext2_htree_insert_entry(&info, split_hash, 2); /* * Write directory block 0. */ if (DOINGASYNC(vp)) { bdwrite(bp); error = 0; } else { error = bwrite(bp); } dp->i_flag |= IN_CHANGE | IN_UPDATE; if (error) goto out; /* * Write directory block 1. */ error = ext2_htree_append_block(vp, buf1, cnp, blksize); if (error) goto out1; /* * Write directory block 2. */ error = ext2_htree_append_block(vp, buf2, cnp, blksize); free(buf1, M_TEMP); free(buf2, M_TEMP); return (error); out: if (bp != NULL) brelse(bp); out1: free(buf1, M_TEMP); free(buf2, M_TEMP); return (error); }
static errcode_t copy_dir_entries(ext2_filsys fs, struct fill_dir_struct *fd, struct out_dir *outdir) { errcode_t retval; char *block_start; struct hash_entry *ent; struct ext2_dir_entry *dirent; int i, rec_len, left; ext2_dirhash_t prev_hash; int offset; 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; left = fs->blocksize; for (i=0; i < fd->num_array; i++) { ent = fd->harray + i; if (ent->dir->inode == 0) continue; rec_len = EXT2_DIR_REC_LEN(ent->dir->name_len & 0xFF); if (rec_len > left) { if (left) dirent->rec_len += left; if ((retval = get_next_block(fs, outdir, &block_start))) return retval; offset = 0; } left = fs->blocksize - 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; dirent->name_len = ent->dir->name_len; dirent->rec_len = rec_len; memcpy(dirent->name, ent->dir->name, dirent->name_len & 0xFF); offset += rec_len; left -= rec_len; if (left < 12) { dirent->rec_len += left; offset += left; left = 0; } prev_hash = ent->hash; } if (left) dirent->rec_len += left; return 0; }
static int fill_dir_block(ext2_filsys fs, blk_t *block_nr, e2_blkcnt_t blockcnt, blk_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; 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 (HOLE_BLKADDR(*block_nr)) { memset(dir, 0, fs->blocksize); dirent = (struct ext2_dir_entry *) dir; dirent->rec_len = fs->blocksize; } else { fd->err = ext2fs_read_dir_block(fs, *block_nr, dir); if (fd->err) return BLOCK_ABORT; } /* While the directory block is "hot", index it. */ dir_offset = 0; while (dir_offset < fs->blocksize) { dirent = (struct ext2_dir_entry *) (dir + dir_offset); if (((dir_offset + dirent->rec_len) > fs->blocksize) || (dirent->rec_len < 8) || ((dirent->rec_len % 4) != 0) || (((dirent->name_len & 0xFF)+8) > dirent->rec_len)) { fd->err = EXT2_ET_DIR_CORRUPTED; return BLOCK_ABORT; } dir_offset += dirent->rec_len; if (dirent->inode == 0) continue; if (!fd->compress && ((dirent->name_len&0xFF) == 1) && (dirent->name[0] == '.')) continue; if (!fd->compress && ((dirent->name_len&0xFF) == 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(dirent->name_len & 0xFF); if (fd->compress) ent->hash = ent->minor_hash = 0; else { fd->err = ext2fs_dirhash(fs->super->s_def_hash_version, dirent->name, dirent->name_len & 0xFF, fs->super->s_hash_seed, &ent->hash, &ent->minor_hash); if (fd->err) return BLOCK_ABORT; } } return 0; }
static int ext2_readdir (struct inode * inode, struct file * filp, struct dirent * dirent, int count) { unsigned long offset, blk; int i, num, stored, dlen; struct buffer_head * bh, * tmp, * bha[16]; struct ext2_dir_entry * de; struct super_block * sb; int err, version; if (!inode || !S_ISDIR(inode->i_mode)) return -EBADF; sb = inode->i_sb; stored = 0; bh = NULL; offset = filp->f_pos & (sb->s_blocksize - 1); while (count > 0 && !stored && filp->f_pos < inode->i_size) { blk = (filp->f_pos) >> EXT2_BLOCK_SIZE_BITS(sb); bh = ext2_bread (inode, blk, 0, &err); if (!bh) { filp->f_pos += sb->s_blocksize - offset; continue; } /* * Do the readahead */ if (!offset) { for (i = 16 >> (EXT2_BLOCK_SIZE_BITS(sb) - 9), num = 0; i > 0; i--) { tmp = ext2_getblk (inode, ++blk, 0, &err); if (tmp && !tmp->b_uptodate && !tmp->b_lock) bha[num++] = tmp; else brelse (tmp); } if (num) { ll_rw_block (READA, num, bha); for (i = 0; i < num; i++) brelse (bha[i]); } } revalidate: /* If the dir block has changed since the last call to * readdir(2), then we might be pointing to an invalid * dirent right now. Scan from the start of the block * to make sure. */ if (filp->f_version != inode->i_version) { for (i = 0; i < sb->s_blocksize && i < offset; ) { de = (struct ext2_dir_entry *) (bh->b_data + i); /* It's too expensive to do a full * dirent test each time round this * loop, but we do have to test at * least that it is non-zero. A * failure will be detected in the * dirent test below. */ if (de->rec_len < EXT2_DIR_REC_LEN(1)) break; i += de->rec_len; } offset = i; filp->f_pos = (filp->f_pos & ~(sb->s_blocksize - 1)) | offset; filp->f_version = inode->i_version; } while (count > 0 && filp->f_pos < inode->i_size && offset < sb->s_blocksize) { de = (struct ext2_dir_entry *) (bh->b_data + offset); if (!ext2_check_dir_entry ("ext2_readdir", inode, de, bh, offset)) { /* On error, skip the f_pos to the next block. */ filp->f_pos = (filp->f_pos & (sb->s_blocksize - 1)) + sb->s_blocksize; brelse (bh); return stored; } if (de->inode) { dlen = ROUND_UP(NAME_OFFSET(dirent) + de->name_len + 1); /* Old libc libraries always use a count of 1. */ if (count == 1 && !stored) count = dlen; if (count < dlen) { count = 0; break; } /* We might block in the next section * if the data destination is * currently swapped out. So, use a * version stamp to detect whether or * not the directory has been modified * during the copy operation. */ version = inode->i_version; i = de->name_len; memcpy_tofs (dirent->d_name, de->name, i); put_fs_long (de->inode, &dirent->d_ino); put_fs_byte (0, dirent->d_name + i); put_fs_word (i, &dirent->d_reclen); put_fs_long (dlen, &dirent->d_off); if (version != inode->i_version) goto revalidate; dcache_add(inode, de->name, de->name_len, de->inode); stored += dlen; count -= dlen; ((char *) dirent) += dlen; } offset += de->rec_len; filp->f_pos += de->rec_len; } offset = 0; brelse (bh); } if (!IS_RDONLY(inode)) { inode->i_atime = CURRENT_TIME; inode->i_dirt = 1; } return stored; }
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; }
bool ext2_add_entry( PEXT2_FILESYS Ext2Sys, ULONG parent, ULONG inode, int filetype, char *name ) { PEXT2_DIR_ENTRY2 dir = NULL, newdir = NULL; EXT2_INODE parent_inode; ULONG dwRet; char *buf; int rec_len; bool bRet = false; rec_len = EXT2_DIR_REC_LEN(strlen(name)); if (!ext2_load_inode(Ext2Sys, parent, &parent_inode)) { return false; } buf = (char *)RtlAllocateHeap(RtlGetProcessHeap(), 0, parent_inode.i_size); if (!ext2_read_inode(Ext2Sys, parent, 0, buf, parent_inode.i_size, &dwRet)) { return false; } dir = (PEXT2_DIR_ENTRY2) buf; while ((char *)dir < buf + parent_inode.i_size) { if ((dir->inode == 0 && dir->rec_len >= rec_len) || (dir->rec_len >= dir->name_len + rec_len) ) { if (dir->inode) { newdir = (PEXT2_DIR_ENTRY2) ((PUCHAR)dir + EXT2_DIR_REC_LEN(dir->name_len)); newdir->rec_len = dir->rec_len - EXT2_DIR_REC_LEN(dir->name_len); dir->rec_len = EXT2_DIR_REC_LEN(dir->name_len); dir = newdir; } dir->file_type = filetype; dir->inode = inode; dir->name_len = strlen(name); memcpy(dir->name, name, strlen(name)); bRet = true; break; } dir = (PEXT2_DIR_ENTRY2) (dir->rec_len + (PUCHAR) dir); } if (bRet) return ext2_write_inode(Ext2Sys, parent, 0, buf, parent_inode.i_size, &dwRet); return bRet; }
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, dot, dotdot; 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; problem_t 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; size_t inline_data_size = 0; int filetype = 0; 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); } if (EXT2_HAS_INCOMPAT_FEATURE(fs->super, EXT2_FEATURE_INCOMPAT_FILETYPE)) filetype = EXT2_FT_DIR << 8; /* * 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 (EXT2_HAS_INCOMPAT_FEATURE(fs->super, EXT4_FEATURE_INCOMPAT_INLINE_DATA)) { errcode_t ec; ec = ext2fs_inline_data_size(fs, ino, &inline_data_size); if (ec && ec != EXT2_ET_NO_INLINE_DATA) return DIRENT_ABORT; } if (db->blk == 0 && !inline_data_size) { 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")); if (inline_data_size) cd->pctx.errcode = ext2fs_inline_data_get(fs, ino, 0, buf, 0); else 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) && (ext2fs_dirent_name_len(dirent) == 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 && !inline_data_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 { dgrp_t group; ext2_ino_t first_unused_inode; unsigned int name_len; problem = 0; if (!inline_data_size || dot_state > 1) { 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) || ((ext2fs_dirent_name_len(dirent) + 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; } } else { if (dot_state == 0) { memset(&dot, 0, sizeof(dot)); dirent = ˙ dirent->inode = ino; dirent->rec_len = EXT2_DIR_REC_LEN(1); dirent->name_len = 1 | filetype; dirent->name[0] = '.'; } else if (dot_state == 1) { memset(&dotdot, 0, sizeof(dotdot)); dirent = &dotdot; dirent->inode = ((struct ext2_dir_entry *)buf)->inode; dirent->rec_len = EXT2_DIR_REC_LEN(2); dirent->name_len = 2 | filetype; dirent->name[0] = '.'; dirent->name[1] = '.'; } else { fatal_error(ctx, _("Can not continue.")); } cd->pctx.dirent = dirent; cd->pctx.num = offset; } 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. */ name_len = ext2fs_dirent_name_len(dirent); 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) && (name_len == 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) && (name_len == 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) && (name_len == 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, ext2fs_dirent_name_len(dirent), 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); if (!inline_data_size || dot_state > 1) { offset += rec_len; } else { if (dot_state == 1) offset = 4; } dot_state++; } while (is_last_entry(fs, inline_data_size, offset, 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 (inline_data_size) { if (offset != inline_data_size) { cd->pctx.num = rec_len + offset - inline_data_size; if (fix_problem(ctx, PR_2_FINAL_RECLEN, &cd->pctx)) { dirent->rec_len = cd->pctx.num; dir_modified++; } } } else { 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; if (inline_data_size) { cd->pctx.errcode = ext2fs_inline_data_set(fs, ino, 0, buf, inline_data_size); } else 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; }
/* * Write a directory entry after a call to namei, using the parameters * that it left in the directory inode. The argument ip is the inode which * the new directory entry will refer to. Dvp is a pointer to the directory * to be written, which was left locked by namei. Remaining parameters * (dp->i_offset, dp->i_count) indicate how the space for the new * entry is to be obtained. */ int ext2_direnter(struct inode *ip, struct vnode *dvp, struct componentname *cnp) { struct ext2_dir_entry_2 *ep, *nep; struct inode *dp; struct buf *bp; struct ext2_dir_entry_2 newdir; struct iovec aiov; struct uio auio; u_int dsize; int error, loc, newentrysize, spacefree; char *dirbuf; int DIRBLKSIZ = ip->i_e2fs->s_blocksize; dp = VTOI(dvp); newdir.inode = ip->i_number; newdir.name_len = cnp->cn_namelen; if (EXT2_HAS_INCOMPAT_FEATURE(ip->i_e2fs->s_es, EXT2_FEATURE_INCOMPAT_FILETYPE)) newdir.file_type = DTTOFT(IFTODT(ip->i_mode)); else newdir.file_type = EXT2_FT_UNKNOWN; bcopy(cnp->cn_nameptr, newdir.name, (unsigned)cnp->cn_namelen + 1); newentrysize = EXT2_DIR_REC_LEN(newdir.name_len); if (dp->i_count == 0) { /* * If dp->i_count is 0, then namei could find no * space in the directory. Here, dp->i_offset will * be on a directory block boundary and we will write the * new entry into a fresh block. */ if (dp->i_offset & (DIRBLKSIZ - 1)) panic("ext2_direnter: newblk"); auio.uio_offset = dp->i_offset; newdir.rec_len = DIRBLKSIZ; auio.uio_resid = newentrysize; aiov.iov_len = newentrysize; aiov.iov_base = (caddr_t)&newdir; auio.uio_iov = &aiov; auio.uio_iovcnt = 1; auio.uio_rw = UIO_WRITE; auio.uio_segflg = UIO_SYSSPACE; auio.uio_td = NULL; error = VOP_WRITE(dvp, &auio, IO_SYNC, cnp->cn_cred); if (DIRBLKSIZ > VFSTOEXT2(dvp->v_mount)->um_mountp->mnt_stat.f_bsize) /* XXX should grow with balloc() */ panic("ext2_direnter: frag size"); else if (!error) { dp->i_size = roundup(dp->i_size, DIRBLKSIZ); dp->i_flag |= IN_CHANGE; } return (error); } /* * If dp->i_count is non-zero, then namei found space * for the new entry in the range dp->i_offset to * dp->i_offset + dp->i_count in the directory. * To use this space, we may have to compact the entries located * there, by copying them together towards the beginning of the * block, leaving the free space in one usable chunk at the end. */ /* * Increase size of directory if entry eats into new space. * This should never push the size past a new multiple of * DIRBLKSIZE. * * N.B. - THIS IS AN ARTIFACT OF 4.2 AND SHOULD NEVER HAPPEN. */ if (dp->i_offset + dp->i_count > dp->i_size) dp->i_size = dp->i_offset + dp->i_count; /* * Get the block containing the space for the new directory entry. */ if ((error = EXT2_BLKATOFF(dvp, (off_t)dp->i_offset, &dirbuf, &bp)) != 0) return (error); /* * Find space for the new entry. In the simple case, the entry at * offset base will have the space. If it does not, then namei * arranged that compacting the region dp->i_offset to * dp->i_offset + dp->i_count would yield the * space. */ ep = (struct ext2_dir_entry_2 *)dirbuf; dsize = EXT2_DIR_REC_LEN(ep->name_len); spacefree = ep->rec_len - dsize; for (loc = ep->rec_len; loc < dp->i_count; ) { nep = (struct ext2_dir_entry_2 *)(dirbuf + loc); if (ep->inode) { /* trim the existing slot */ ep->rec_len = dsize; ep = (struct ext2_dir_entry_2 *)((char *)ep + dsize); } else { /* overwrite; nothing there; header is ours */ spacefree += dsize; } dsize = EXT2_DIR_REC_LEN(nep->name_len); spacefree += nep->rec_len - dsize; loc += nep->rec_len; bcopy((caddr_t)nep, (caddr_t)ep, dsize); } /* * Update the pointer fields in the previous entry (if any), * copy in the new entry, and write out the block. */ if (ep->inode == 0) { if (spacefree + dsize < newentrysize) panic("ext2_direnter: compact1"); newdir.rec_len = spacefree + dsize; } else { if (spacefree < newentrysize) panic("ext2_direnter: compact2"); newdir.rec_len = spacefree; ep->rec_len = dsize; ep = (struct ext2_dir_entry_2 *)((char *)ep + dsize); } bcopy((caddr_t)&newdir, (caddr_t)ep, (u_int)newentrysize); error = bwrite(bp); dp->i_flag |= IN_CHANGE | IN_UPDATE; if (!error && dp->i_endoff && dp->i_endoff < dp->i_size) error = EXT2_TRUNCATE(dvp, (off_t)dp->i_endoff, IO_SYNC, cnp->cn_cred); return (error); }
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; }
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 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 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; int i, left; ext2_dirhash_t prev_hash; int offset, slack; 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; } 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; left = fs->blocksize; slack = fd->compress ? 12 : (fs->blocksize * 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(ent->dir->name_len & 0xFF); if (rec_len > left) { if (left) { left += prev_rec_len; retval = ext2fs_set_rec_len(fs, left, dirent); if (retval) return retval; } if ((retval = get_next_block(fs, outdir, &block_start))) return retval; offset = 0; } left = fs->blocksize - 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; dirent->name_len = ent->dir->name_len; retval = ext2fs_set_rec_len(fs, rec_len, dirent); if (retval) return retval; prev_rec_len = rec_len; memcpy(dirent->name, ent->dir->name, dirent->name_len & 0xFF); 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); return retval; }
/* * 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; }
/* * Convert a component of a pathname into a pointer to a locked inode. * This is a very central and rather complicated routine. * If the file system is not maintained in a strict tree hierarchy, * this can result in a deadlock situation (see comments in code below). * * The cnp->cn_nameiop argument is LOOKUP, CREATE, RENAME, or DELETE depending * on whether the name is to be looked up, created, renamed, or deleted. * When CREATE, RENAME, or DELETE is specified, information usable in * creating, renaming, or deleting a directory entry may be calculated. * If flag has LOCKPARENT or'ed into it and the target of the pathname * exists, lookup returns both the target and its parent directory locked. * When creating or renaming and LOCKPARENT is specified, the target may * not be ".". When deleting and LOCKPARENT is specified, the target may * be "."., but the caller must check to ensure it does an vrele and vput * instead of two vputs. * * Overall outline of ufs_lookup: * * search for name in directory, to found or notfound * notfound: * if creating, return locked directory, leaving info on available slots * else return error * found: * if at end of path and deleting, return information to allow delete * if at end of path and rewriting (RENAME and LOCKPARENT), lock target * inode and return info to allow rewrite * if not at end, add name to cache; if at end and neither creating * nor deleting, add name to cache * * ext2_lookup(struct vnode *a_dvp, struct vnode **a_vpp, * struct componentname *a_cnp) */ int ext2_lookup(struct vop_old_lookup_args *ap) { struct vnode *vdp; /* vnode for directory being searched */ struct inode *dp; /* inode for directory being searched */ struct buf *bp; /* a buffer of directory entries */ struct ext2_dir_entry_2 *ep; /* the current directory entry */ int entryoffsetinblock; /* offset of ep in bp's buffer */ enum {NONE, COMPACT, FOUND} slotstatus; doff_t slotoffset; /* offset of area with free space */ int slotsize; /* size of area at slotoffset */ int slotfreespace; /* amount of space free in slot */ int slotneeded; /* size of the entry we're seeking */ int numdirpasses; /* strategy for directory search */ doff_t endsearch; /* offset to end directory search */ doff_t prevoff; /* prev entry dp->i_offset */ struct vnode *pdp; /* saved dp during symlink work */ struct vnode *tdp; /* returned by VFS_VGET */ doff_t enduseful; /* pointer past last used dir slot */ u_long bmask; /* block offset mask */ int lockparent; /* 1 => lockparent flag is set */ int wantparent; /* 1 => wantparent or lockparent flag */ int namlen, error; struct vnode **vpp = ap->a_vpp; struct componentname *cnp = ap->a_cnp; struct ucred *cred = cnp->cn_cred; int flags = cnp->cn_flags; int nameiop = cnp->cn_nameiop; int DIRBLKSIZ = VTOI(ap->a_dvp)->i_e2fs->s_blocksize; bp = NULL; slotoffset = -1; *vpp = NULL; vdp = ap->a_dvp; dp = VTOI(vdp); lockparent = flags & CNP_LOCKPARENT; wantparent = flags & (CNP_LOCKPARENT|CNP_WANTPARENT); /* * We now have a segment name to search for, and a directory to search. */ /* * Suppress search for slots unless creating * file and at end of pathname, in which case * we watch for a place to put the new file in * case it doesn't already exist. */ slotstatus = FOUND; slotfreespace = slotsize = slotneeded = 0; if (nameiop == NAMEI_CREATE || nameiop == NAMEI_RENAME) { slotstatus = NONE; slotneeded = EXT2_DIR_REC_LEN(cnp->cn_namelen); /* was slotneeded = (sizeof(struct direct) - MAXNAMLEN + cnp->cn_namelen + 3) &~ 3; */ } /* * If there is cached information on a previous search of * this directory, pick up where we last left off. * We cache only lookups as these are the most common * and have the greatest payoff. Caching CREATE has little * benefit as it usually must search the entire directory * to determine that the entry does not exist. Caching the * location of the last DELETE or RENAME has not reduced * profiling time and hence has been removed in the interest * of simplicity. */ bmask = VFSTOEXT2(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1; if (nameiop != NAMEI_LOOKUP || dp->i_diroff == 0 || dp->i_diroff > dp->i_size) { entryoffsetinblock = 0; dp->i_offset = 0; numdirpasses = 1; } else { dp->i_offset = dp->i_diroff; if ((entryoffsetinblock = dp->i_offset & bmask) && (error = EXT2_BLKATOFF(vdp, (off_t)dp->i_offset, NULL, &bp))) return (error); numdirpasses = 2; } prevoff = dp->i_offset; endsearch = roundup(dp->i_size, DIRBLKSIZ); enduseful = 0; searchloop: while (dp->i_offset < endsearch) { /* * If necessary, get the next directory block. */ if ((dp->i_offset & bmask) == 0) { if (bp != NULL) brelse(bp); if ((error = EXT2_BLKATOFF(vdp, (off_t)dp->i_offset, NULL, &bp)) != 0) return (error); entryoffsetinblock = 0; } /* * If still looking for a slot, and at a DIRBLKSIZE * boundary, have to start looking for free space again. */ if (slotstatus == NONE && (entryoffsetinblock & (DIRBLKSIZ - 1)) == 0) { slotoffset = -1; slotfreespace = 0; } /* * Get pointer to next entry. * Full validation checks are slow, so we only check * enough to insure forward progress through the * directory. Complete checks can be run by patching * "dirchk" to be true. */ ep = (struct ext2_dir_entry_2 *) ((char *)bp->b_data + entryoffsetinblock); if (ep->rec_len == 0 || (dirchk && ext2_dirbadentry(vdp, ep, entryoffsetinblock))) { int i; ext2_dirbad(dp, dp->i_offset, "mangled entry"); i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)); dp->i_offset += i; entryoffsetinblock += i; continue; } /* * If an appropriate sized slot has not yet been found, * check to see if one is available. Also accumulate space * in the current block so that we can determine if * compaction is viable. */ if (slotstatus != FOUND) { int size = ep->rec_len; if (ep->inode != 0) size -= EXT2_DIR_REC_LEN(ep->name_len); if (size > 0) { if (size >= slotneeded) { slotstatus = FOUND; slotoffset = dp->i_offset; slotsize = ep->rec_len; } else if (slotstatus == NONE) { slotfreespace += size; if (slotoffset == -1) slotoffset = dp->i_offset; if (slotfreespace >= slotneeded) { slotstatus = COMPACT; slotsize = dp->i_offset + ep->rec_len - slotoffset; } } } } /* * Check for a name match. */ if (ep->inode) { namlen = ep->name_len; if (namlen == cnp->cn_namelen && !bcmp(cnp->cn_nameptr, ep->name, (unsigned)namlen)) { /* * Save directory entry's inode number and * reclen in ndp->ni_ufs area, and release * directory buffer. */ dp->i_ino = ep->inode; dp->i_reclen = ep->rec_len; goto found; } } prevoff = dp->i_offset; dp->i_offset += ep->rec_len; entryoffsetinblock += ep->rec_len; if (ep->inode) enduseful = dp->i_offset; } /* notfound: */ /* * If we started in the middle of the directory and failed * to find our target, we must check the beginning as well. */ if (numdirpasses == 2) { numdirpasses--; dp->i_offset = 0; endsearch = dp->i_diroff; goto searchloop; } if (bp != NULL) brelse(bp); /* * If creating, and at end of pathname and current * directory has not been removed, then can consider * allowing file to be created. */ if ((nameiop == NAMEI_CREATE || nameiop == NAMEI_RENAME) && dp->i_nlink != 0) { /* * Access for write is interpreted as allowing * creation of files in the directory. */ if ((error = VOP_EACCESS(vdp, VWRITE, cred)) != 0) return (error); /* * Return an indication of where the new directory * entry should be put. If we didn't find a slot, * then set dp->i_count to 0 indicating * that the new slot belongs at the end of the * directory. If we found a slot, then the new entry * can be put in the range from dp->i_offset to * dp->i_offset + dp->i_count. */ if (slotstatus == NONE) { dp->i_offset = roundup(dp->i_size, DIRBLKSIZ); dp->i_count = 0; enduseful = dp->i_offset; } else { dp->i_offset = slotoffset; dp->i_count = slotsize; if (enduseful < slotoffset + slotsize) enduseful = slotoffset + slotsize; } dp->i_endoff = roundup(enduseful, DIRBLKSIZ); dp->i_flag |= IN_CHANGE | IN_UPDATE; /* * We return with the directory locked, so that * the parameters we set up above will still be * valid if we actually decide to do a direnter(). * We return ni_vp == NULL to indicate that the entry * does not currently exist; we leave a pointer to * the (locked) directory inode in ndp->ni_dvp. * The pathname buffer is saved so that the name * can be obtained later. * * NB - if the directory is unlocked, then this * information cannot be used. */ if (!lockparent) vn_unlock(vdp); return (EJUSTRETURN); } return (ENOENT); found: /* * Check that directory length properly reflects presence * of this entry. */ if (entryoffsetinblock + EXT2_DIR_REC_LEN(ep->name_len) > dp->i_size) { ext2_dirbad(dp, dp->i_offset, "i_size too small"); dp->i_size = entryoffsetinblock+EXT2_DIR_REC_LEN(ep->name_len); dp->i_flag |= IN_CHANGE | IN_UPDATE; } brelse(bp); /* * Found component in pathname. * If the final component of path name, save information * in the cache as to where the entry was found. */ if (nameiop == NAMEI_LOOKUP) dp->i_diroff = dp->i_offset &~ (DIRBLKSIZ - 1); /* * If deleting, and at end of pathname, return * parameters which can be used to remove file. * If the wantparent flag isn't set, we return only * the directory (in ndp->ni_dvp), otherwise we go * on and lock the inode, being careful with ".". */ if (nameiop == NAMEI_DELETE) { /* * Write access to directory required to delete files. */ if ((error = VOP_EACCESS(vdp, VWRITE, cred)) != 0) return (error); /* * Return pointer to current entry in dp->i_offset, * and distance past previous entry (if there * is a previous entry in this block) in dp->i_count. * Save directory inode pointer in ndp->ni_dvp for dirremove(). */ if ((dp->i_offset & (DIRBLKSIZ - 1)) == 0) dp->i_count = 0; else dp->i_count = dp->i_offset - prevoff; if (dp->i_number == dp->i_ino) { vref(vdp); *vpp = vdp; return (0); } if ((error = VFS_VGET(vdp->v_mount, NULL, dp->i_ino, &tdp)) != 0) return (error); /* * If directory is "sticky", then user must own * the directory, or the file in it, else she * may not delete it (unless she's root). This * implements append-only directories. */ if ((dp->i_mode & ISVTX) && cred->cr_uid != 0 && cred->cr_uid != dp->i_uid && VTOI(tdp)->i_uid != cred->cr_uid) { vput(tdp); return (EPERM); } *vpp = tdp; if (!lockparent) vn_unlock(vdp); return (0); } /* * If rewriting (RENAME), return the inode and the * information required to rewrite the present directory * Must get inode of directory entry to verify it's a * regular file, or empty directory. */ if (nameiop == NAMEI_RENAME && wantparent) { if ((error = VOP_EACCESS(vdp, VWRITE, cred)) != 0) return (error); /* * Careful about locking second inode. * This can only occur if the target is ".". */ if (dp->i_number == dp->i_ino) return (EISDIR); if ((error = VFS_VGET(vdp->v_mount, NULL, dp->i_ino, &tdp)) != 0) return (error); *vpp = tdp; if (!lockparent) vn_unlock(vdp); return (0); } /* * Step through the translation in the name. We do not `vput' the * directory because we may need it again if a symbolic link * is relative to the current directory. Instead we save it * unlocked as "pdp". We must get the target inode before unlocking * the directory to insure that the inode will not be removed * before we get it. We prevent deadlock by always fetching * inodes from the root, moving down the directory tree. Thus * when following backward pointers ".." we must unlock the * parent directory before getting the requested directory. * There is a potential race condition here if both the current * and parent directories are removed before the VFS_VGET for the * inode associated with ".." returns. We hope that this occurs * infrequently since we cannot avoid this race condition without * implementing a sophisticated deadlock detection algorithm. * Note also that this simple deadlock detection scheme will not * work if the file system has any hard links other than ".." * that point backwards in the directory structure. */ pdp = vdp; if (flags & CNP_ISDOTDOT) { vn_unlock(pdp); /* race to get the inode */ if ((error = VFS_VGET(vdp->v_mount, NULL, dp->i_ino, &tdp)) != 0) { vn_lock(pdp, LK_EXCLUSIVE | LK_RETRY); return (error); } if (lockparent && (error = vn_lock(pdp, LK_EXCLUSIVE))) { vput(tdp); return (error); } *vpp = tdp; } else if (dp->i_number == dp->i_ino) { vref(vdp); /* we want ourself, ie "." */ *vpp = vdp; } else { if ((error = VFS_VGET(vdp->v_mount, NULL, dp->i_ino, &tdp)) != 0) return (error); if (!lockparent) vn_unlock(pdp); *vpp = tdp; } return (0); }