/** * dir.c * * Many parts of codes are copied from Linux kernel/fs/f2fs. * * Copyright (C) 2015 Huawei Ltd. * Witten by: * Hou Pengyang * Liu Shuoran * Jaegeuk Kim * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include "fsck.h" #include "node.h" static int room_for_filename(const u8 *bitmap, int slots, int max_slots) { int bit_start = 0; int zero_start, zero_end; next: zero_start = find_next_zero_bit_le(bitmap, max_slots, bit_start); if (zero_start >= max_slots) return max_slots; zero_end = find_next_bit_le(bitmap, max_slots, zero_start + 1); if (zero_end - zero_start >= slots) return zero_start; bit_start = zero_end; goto next; } void make_dentry_ptr(struct f2fs_dentry_ptr *d, struct f2fs_node *node_blk, void *src, int type) { if (type == 1) { struct f2fs_dentry_block *t = (struct f2fs_dentry_block *)src; d->max = NR_DENTRY_IN_BLOCK; d->nr_bitmap = SIZE_OF_DENTRY_BITMAP; d->bitmap = t->dentry_bitmap; d->dentry = t->dentry; d->filename = t->filename; } else { int entry_cnt = NR_INLINE_DENTRY(node_blk); int bitmap_size = INLINE_DENTRY_BITMAP_SIZE(node_blk); int reserved_size = INLINE_RESERVED_SIZE(node_blk); d->max = entry_cnt; d->nr_bitmap = bitmap_size; d->bitmap = (u8 *)src; d->dentry = (struct f2fs_dir_entry *) ((char *)src + bitmap_size + reserved_size); d->filename = (__u8 (*)[F2FS_SLOT_LEN])((char *)src + bitmap_size + reserved_size + SIZE_OF_DIR_ENTRY * entry_cnt); } } static struct f2fs_dir_entry *find_target_dentry(const u8 *name, unsigned int len, f2fs_hash_t namehash, int *max_slots, struct f2fs_dentry_ptr *d) { struct f2fs_dir_entry *de; unsigned long bit_pos = 0; int max_len = 0; if (max_slots) *max_slots = 0; while (bit_pos < (unsigned long)d->max) { if (!test_bit_le(bit_pos, d->bitmap)) { bit_pos++; max_len++; continue; } de = &d->dentry[bit_pos]; if (le16_to_cpu(de->name_len) == len && de->hash_code == namehash && !memcmp(d->filename[bit_pos], name, len)) { goto found; } if (max_slots && max_len > *max_slots) *max_slots = max_len; max_len = 0; bit_pos += GET_DENTRY_SLOTS(le16_to_cpu(de->name_len)); } de = NULL; found: if (max_slots && max_len > *max_slots) *max_slots = max_len; return de; } static struct f2fs_dir_entry *find_in_block(void *block, const u8 *name, int len, f2fs_hash_t namehash, int *max_slots) { struct f2fs_dentry_ptr d; make_dentry_ptr(&d, NULL, block, 1); return find_target_dentry(name, len, namehash, max_slots, &d); } static int find_in_level(struct f2fs_sb_info *sbi,struct f2fs_node *dir, unsigned int level, struct dentry *de) { unsigned int nbucket, nblock; unsigned int bidx, end_block; struct f2fs_dir_entry *dentry = NULL; struct dnode_of_data dn; void *dentry_blk; int max_slots = 214; nid_t ino = le32_to_cpu(dir->footer.ino); f2fs_hash_t namehash; unsigned int dir_level = dir->i.i_dir_level; int ret = 0; namehash = f2fs_dentry_hash(de->name, de->len); nbucket = dir_buckets(level, dir_level); nblock = bucket_blocks(level); bidx = dir_block_index(level, dir_level, le32_to_cpu(namehash) % nbucket); end_block = bidx + nblock; dentry_blk = calloc(BLOCK_SZ, 1); ASSERT(dentry_blk); memset(&dn, 0, sizeof(dn)); for (; bidx < end_block; bidx++) { /* Firstly, we should know direct node of target data blk */ if (dn.node_blk && dn.node_blk != dn.inode_blk) free(dn.node_blk); set_new_dnode(&dn, dir, NULL, ino); get_dnode_of_data(sbi, &dn, bidx, LOOKUP_NODE); if (dn.data_blkaddr == NULL_ADDR) continue; ret = dev_read_block(dentry_blk, dn.data_blkaddr); ASSERT(ret >= 0); dentry = find_in_block(dentry_blk, de->name, de->len, namehash, &max_slots); if (dentry) { ret = 1; de->ino = le32_to_cpu(dentry->ino); break; } } if (dn.node_blk && dn.node_blk != dn.inode_blk) free(dn.node_blk); free(dentry_blk); return ret; } static int f2fs_find_entry(struct f2fs_sb_info *sbi, struct f2fs_node *dir, struct dentry *de) { unsigned int max_depth; unsigned int level; max_depth = le32_to_cpu(dir->i.i_current_depth); for (level = 0; level < max_depth; level ++) { if (find_in_level(sbi, dir, level, de)) return 1; } return 0; } /* return ino if file exists, otherwise return 0 */ nid_t f2fs_lookup(struct f2fs_sb_info *sbi, struct f2fs_node *dir, u8 *name, int len) { int err; struct dentry de = { .name = name, .len = len, }; err = f2fs_find_entry(sbi, dir, &de); if (err == 1) return de.ino; else return 0; } static void f2fs_update_dentry(nid_t ino, int file_type, struct f2fs_dentry_ptr *d, const unsigned char *name, int len, f2fs_hash_t name_hash, unsigned int bit_pos) { struct f2fs_dir_entry *de; int slots = GET_DENTRY_SLOTS(len); int i; de = &d->dentry[bit_pos]; de->name_len = cpu_to_le16(len); de->hash_code = name_hash; memcpy(d->filename[bit_pos], name, len); d->filename[bit_pos][len] = 0; de->ino = cpu_to_le32(ino); de->file_type = file_type; for (i = 0; i < slots; i++) test_and_set_bit_le(bit_pos + i, d->bitmap); } /* * f2fs_add_link - Add a new file(dir) to parent dir. */ int f2fs_add_link(struct f2fs_sb_info *sbi, struct f2fs_node *parent, const unsigned char *name, int name_len, nid_t ino, int file_type, block_t p_blkaddr, int inc_link) { int level = 0, current_depth, bit_pos; int nbucket, nblock, bidx, block; int slots = GET_DENTRY_SLOTS(name_len); f2fs_hash_t dentry_hash = f2fs_dentry_hash(name, name_len); struct f2fs_dentry_block *dentry_blk; struct f2fs_dentry_ptr d; struct dnode_of_data dn; nid_t pino = le32_to_cpu(parent->footer.ino); unsigned int dir_level = parent->i.i_dir_level; int ret; if (parent == NULL) return -EINVAL; if (!pino) { ERR_MSG("Wrong parent ino:%d \n", pino); return -EINVAL; } dentry_blk = calloc(BLOCK_SZ, 1); ASSERT(dentry_blk); current_depth = le32_to_cpu(parent->i.i_current_depth); start: if (current_depth == MAX_DIR_HASH_DEPTH) { free(dentry_blk); ERR_MSG("\tError: MAX_DIR_HASH\n"); return -ENOSPC; } /* Need a new dentry block */ if (level == current_depth) ++current_depth; nbucket = dir_buckets(level, dir_level); nblock = bucket_blocks(level); bidx = dir_block_index(level, dir_level, le32_to_cpu(dentry_hash) % nbucket); memset(&dn, 0, sizeof(dn)); for (block = bidx; block <= (bidx + nblock - 1); block++) { /* Firstly, we should know the direct node of target data blk */ if (dn.node_blk && dn.node_blk != dn.inode_blk) free(dn.node_blk); set_new_dnode(&dn, parent, NULL, pino); get_dnode_of_data(sbi, &dn, block, ALLOC_NODE); if (dn.data_blkaddr == NULL_ADDR) { new_data_block(sbi, dentry_blk, &dn, CURSEG_HOT_DATA); } else { ret = dev_read_block(dentry_blk, dn.data_blkaddr); ASSERT(ret >= 0); } bit_pos = room_for_filename(dentry_blk->dentry_bitmap, slots, NR_DENTRY_IN_BLOCK); if (bit_pos < NR_DENTRY_IN_BLOCK) goto add_dentry; } level ++; goto start; add_dentry: make_dentry_ptr(&d, NULL, (void *)dentry_blk, 1); f2fs_update_dentry(ino, file_type, &d, name, name_len, dentry_hash, bit_pos); ret = dev_write_block(dentry_blk, dn.data_blkaddr); ASSERT(ret >= 0); /* * Parent inode needs updating, because its inode info may be changed. * such as i_current_depth and i_blocks. */ if (parent->i.i_current_depth != cpu_to_le32(current_depth)) { parent->i.i_current_depth = cpu_to_le32(current_depth); dn.idirty = 1; } /* Update parent's i_links info*/ if (inc_link && (file_type == F2FS_FT_DIR)){ u32 links = le32_to_cpu(parent->i.i_links); parent->i.i_links = cpu_to_le32(links + 1); dn.idirty = 1; } if ((__u64)((block + 1) * F2FS_BLKSIZE) > le64_to_cpu(parent->i.i_size)) { parent->i.i_size = cpu_to_le64((block + 1) * F2FS_BLKSIZE); dn.idirty = 1; } if (dn.ndirty) { ret = dev_write_block(dn.node_blk, dn.node_blkaddr); ASSERT(ret >= 0); } if (dn.idirty) { ASSERT(parent == dn.inode_blk); ret = dev_write_block(dn.inode_blk, p_blkaddr); ASSERT(ret >= 0); } if (dn.node_blk != dn.inode_blk) free(dn.node_blk); free(dentry_blk); return 0; } static void make_empty_dir(struct f2fs_sb_info *sbi, struct f2fs_node *inode) { struct f2fs_dentry_block *dent_blk; nid_t ino = le32_to_cpu(inode->footer.ino); nid_t pino = le32_to_cpu(inode->i.i_pino); struct f2fs_summary sum; struct node_info ni; block_t blkaddr = NULL_ADDR; int ret; get_node_info(sbi, ino, &ni); dent_blk = calloc(BLOCK_SZ, 1); ASSERT(dent_blk); dent_blk->dentry[0].hash_code = 0; dent_blk->dentry[0].ino = cpu_to_le32(ino); dent_blk->dentry[0].name_len = cpu_to_le16(1); dent_blk->dentry[0].file_type = F2FS_FT_DIR; memcpy(dent_blk->filename[0], ".", 1); dent_blk->dentry[1].hash_code = 0; dent_blk->dentry[1].ino = cpu_to_le32(pino); dent_blk->dentry[1].name_len = cpu_to_le16(2); dent_blk->dentry[1].file_type = F2FS_FT_DIR; memcpy(dent_blk->filename[1], "..", 2); test_and_set_bit_le(0, dent_blk->dentry_bitmap); test_and_set_bit_le(1, dent_blk->dentry_bitmap); set_summary(&sum, ino, 0, ni.version); reserve_new_block(sbi, &blkaddr, &sum, CURSEG_HOT_DATA); ret = dev_write_block(dent_blk, blkaddr); ASSERT(ret >= 0); inode->i.i_addr[get_extra_isize(inode)] = cpu_to_le32(blkaddr); free(dent_blk); } static void page_symlink(struct f2fs_sb_info *sbi, struct f2fs_node *inode, const char *symname, int symlen) { nid_t ino = le32_to_cpu(inode->footer.ino); struct f2fs_summary sum; struct node_info ni; char *data_blk; block_t blkaddr = NULL_ADDR; int ret; get_node_info(sbi, ino, &ni); /* store into inline_data */ if ((unsigned long)(symlen + 1) <= MAX_INLINE_DATA(inode)) { inode->i.i_inline |= F2FS_INLINE_DATA; inode->i.i_inline |= F2FS_DATA_EXIST; memcpy(inline_data_addr(inode), symname, symlen); return; } data_blk = calloc(BLOCK_SZ, 1); ASSERT(data_blk); memcpy(data_blk, symname, symlen); set_summary(&sum, ino, 0, ni.version); reserve_new_block(sbi, &blkaddr, &sum, CURSEG_WARM_DATA); ret = dev_write_block(data_blk, blkaddr); ASSERT(ret >= 0); inode->i.i_addr[get_extra_isize(inode)] = cpu_to_le32(blkaddr); free(data_blk); } static void init_inode_block(struct f2fs_sb_info *sbi, struct f2fs_node *node_blk, struct dentry *de) { struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi); mode_t mode = de->mode; int links = 1; unsigned int size; int blocks = 1; if (de->file_type == F2FS_FT_DIR) { mode |= S_IFDIR; size = 4096; links++; blocks++; } else if (de->file_type == F2FS_FT_REG_FILE) { mode |= S_IFREG; size = 0; } else if (de->file_type == F2FS_FT_SYMLINK) { ASSERT(de->link); mode |= S_IFLNK; size = strlen(de->link); if (size + 1 > MAX_INLINE_DATA(node_blk)) blocks++; } else { ASSERT(0); } node_blk->i.i_mode = cpu_to_le16(mode); node_blk->i.i_advise = 0; node_blk->i.i_uid = cpu_to_le32(de->uid); node_blk->i.i_gid = cpu_to_le32(de->gid); node_blk->i.i_links = cpu_to_le32(links); node_blk->i.i_size = cpu_to_le32(size); node_blk->i.i_blocks = cpu_to_le32(blocks); node_blk->i.i_atime = cpu_to_le64(de->mtime); node_blk->i.i_ctime = cpu_to_le64(de->mtime); node_blk->i.i_mtime = cpu_to_le64(de->mtime); node_blk->i.i_atime_nsec = 0; node_blk->i.i_ctime_nsec = 0; node_blk->i.i_mtime_nsec = 0; node_blk->i.i_generation = 0; if (de->file_type == F2FS_FT_DIR) node_blk->i.i_current_depth = cpu_to_le32(1); else node_blk->i.i_current_depth = cpu_to_le32(0); node_blk->i.i_xattr_nid = 0; node_blk->i.i_flags = 0; node_blk->i.i_inline = F2FS_INLINE_XATTR; node_blk->i.i_pino = cpu_to_le32(de->pino); node_blk->i.i_namelen = cpu_to_le32(de->len); memcpy(node_blk->i.i_name, de->name, de->len); node_blk->i.i_name[de->len] = 0; if (c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) { node_blk->i.i_inline |= F2FS_EXTRA_ATTR; node_blk->i.i_extra_isize = cpu_to_le16(F2FS_TOTAL_EXTRA_ATTR_SIZE); } node_blk->footer.ino = cpu_to_le32(de->ino); node_blk->footer.nid = cpu_to_le32(de->ino); node_blk->footer.flag = 0; node_blk->footer.cp_ver = ckpt->checkpoint_ver; if (S_ISDIR(mode)) make_empty_dir(sbi, node_blk); else if (S_ISLNK(mode)) page_symlink(sbi, node_blk, de->link, size); if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM)) node_blk->i.i_inode_checksum = cpu_to_le32(f2fs_inode_chksum(node_blk)); } int convert_inline_dentry(struct f2fs_sb_info *sbi, struct f2fs_node *node, block_t p_blkaddr) { struct f2fs_inode *inode = &(node->i); unsigned int dir_level = node->i.i_dir_level; nid_t ino = le32_to_cpu(node->footer.ino); char inline_data[MAX_INLINE_DATA(node)]; struct dnode_of_data dn; struct f2fs_dentry_ptr d; unsigned long bit_pos = 0; int ret = 0; if (!(inode->i_inline & F2FS_INLINE_DENTRY)) return 0; memcpy(inline_data, inline_data_addr(node), MAX_INLINE_DATA(node)); memset(inline_data_addr(node), 0, MAX_INLINE_DATA(node)); inode->i_inline &= ~F2FS_INLINE_DENTRY; ret = dev_write_block(node, p_blkaddr); ASSERT(ret >= 0); memset(&dn, 0, sizeof(dn)); if (!dir_level) { struct f2fs_dentry_block *dentry_blk; struct f2fs_dentry_ptr src, dst; dentry_blk = calloc(BLOCK_SZ, 1); ASSERT(dentry_blk); set_new_dnode(&dn, node, NULL, ino); get_dnode_of_data(sbi, &dn, 0, ALLOC_NODE); if (dn.data_blkaddr == NULL_ADDR) new_data_block(sbi, dentry_blk, &dn, CURSEG_HOT_DATA); make_dentry_ptr(&src, node, (void *)inline_data, 2); make_dentry_ptr(&dst, NULL, (void *)dentry_blk, 1); /* copy data from inline dentry block to new dentry block */ memcpy(dst.bitmap, src.bitmap, src.nr_bitmap); memset(dst.bitmap + src.nr_bitmap, 0, dst.nr_bitmap - src.nr_bitmap); memcpy(dst.dentry, src.dentry, SIZE_OF_DIR_ENTRY * src.max); memcpy(dst.filename, src.filename, src.max * F2FS_SLOT_LEN); ret = dev_write_block(dentry_blk, dn.data_blkaddr); ASSERT(ret >= 0); MSG(1, "%s: copy inline entry to block\n", __func__); free(dentry_blk); return ret; } make_empty_dir(sbi, node); make_dentry_ptr(&d, node, (void *)inline_data, 2); while (bit_pos < (unsigned long)d.max) { struct f2fs_dir_entry *de; const unsigned char *filename; int namelen; if (!test_bit_le(bit_pos, d.bitmap)) { bit_pos++; continue; } de = &d.dentry[bit_pos]; if (!de->name_len) { bit_pos++; continue; } filename = d.filename[bit_pos]; namelen = le32_to_cpu(de->name_len); if (is_dot_dotdot(filename, namelen)) { bit_pos += GET_DENTRY_SLOTS(namelen); continue; } ret = f2fs_add_link(sbi, node, filename, namelen, le32_to_cpu(de->ino), de->file_type, p_blkaddr, 0); if (ret) MSG(0, "Convert file \"%s\" ERR=%d\n", filename, ret); else MSG(1, "%s: add inline entry to block\n", __func__); bit_pos += GET_DENTRY_SLOTS(namelen); } return 0; } int f2fs_create(struct f2fs_sb_info *sbi, struct dentry *de) { struct f2fs_node *parent, *child; struct node_info ni; struct f2fs_summary sum; block_t blkaddr = NULL_ADDR; int ret; /* Find if there is a */ get_node_info(sbi, de->pino, &ni); if (ni.blk_addr == NULL_ADDR) { MSG(0, "No parent directory pino=%x\n", de->pino); return -1; } parent = calloc(BLOCK_SZ, 1); ASSERT(parent); ret = dev_read_block(parent, ni.blk_addr); ASSERT(ret >= 0); /* Must convert inline dentry before the following opertions */ ret = convert_inline_dentry(sbi, parent, ni.blk_addr); if (ret) { MSG(0, "Convert inline dentry for pino=%x failed.\n", de->pino); return -1; } ret = f2fs_find_entry(sbi, parent, de); if (ret) { MSG(0, "Skip the existing \"%s\" pino=%x ERR=%d\n", de->name, de->pino, ret); if (de->file_type == F2FS_FT_REG_FILE) de->ino = 0; goto free_parent_dir; } child = calloc(BLOCK_SZ, 1); ASSERT(child); f2fs_alloc_nid(sbi, &de->ino, 1); init_inode_block(sbi, child, de); ret = f2fs_add_link(sbi, parent, child->i.i_name, le32_to_cpu(child->i.i_namelen), le32_to_cpu(child->footer.ino), map_de_type(le16_to_cpu(child->i.i_mode)), ni.blk_addr, 1); if (ret) { MSG(0, "Skip the existing \"%s\" pino=%x ERR=%d\n", de->name, de->pino, ret); goto free_child_dir; } /* write child */ set_summary(&sum, de->ino, 0, ni.version); reserve_new_block(sbi, &blkaddr, &sum, CURSEG_HOT_NODE); /* update nat info */ update_nat_blkaddr(sbi, de->ino, de->ino, blkaddr); ret = dev_write_block(child, blkaddr); ASSERT(ret >= 0); update_free_segments(sbi); MSG(1, "Info: Create %s -> %s\n" " -- ino=%x, type=%x, mode=%x, uid=%x, " "gid=%x, cap=%"PRIx64", size=%lu, pino=%x\n", de->full_path, de->path, de->ino, de->file_type, de->mode, de->uid, de->gid, de->capabilities, de->size, de->pino); free_child_dir: free(child); free_parent_dir: free(parent); return 0; } int f2fs_mkdir(struct f2fs_sb_info *sbi, struct dentry *de) { return f2fs_create(sbi, de); } int f2fs_symlink(struct f2fs_sb_info *sbi, struct dentry *de) { return f2fs_create(sbi, de); } int f2fs_find_path(struct f2fs_sb_info *sbi, char *path, nid_t *ino) { struct f2fs_node *parent; struct node_info ni; struct dentry de; int err = 0; int ret; char *p; if (path[0] != '/') return -ENOENT; *ino = F2FS_ROOT_INO(sbi); parent = calloc(BLOCK_SZ, 1); ASSERT(parent); p = strtok(path, "/"); while (p) { de.name = (const u8 *)p; de.len = strlen(p); get_node_info(sbi, *ino, &ni); if (ni.blk_addr == NULL_ADDR) { err = -ENOENT; goto err; } ret = dev_read_block(parent, ni.blk_addr); ASSERT(ret >= 0); ret = f2fs_find_entry(sbi, parent, &de); if (!ret) { err = -ENOENT; goto err; } *ino = de.ino; p = strtok(NULL, "/"); } err: free(parent); return err; }