| /** |
| * dir.c |
| * |
| * Many parts of codes are copied from Linux kernel/fs/f2fs. |
| * |
| * Copyright (C) 2015 Huawei Ltd. |
| * Witten by: |
| * Hou Pengyang <houpengyang@huawei.com> |
| * Liu Shuoran <liushuoran@huawei.com> |
| * Jaegeuk Kim <jaegeuk@kernel.org> |
| * |
| * 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" |
| #include <search.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(get_encoding(sbi), IS_CASEFOLDED(&dir->i), |
| 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(get_encoding(sbi), |
| IS_CASEFOLDED(&parent->i), |
| 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 = write_inode(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); |
| ret = reserve_new_block(sbi, &blkaddr, &sum, CURSEG_HOT_DATA, 0); |
| ASSERT(!ret); |
| |
| 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); |
| ret = reserve_new_block(sbi, &blkaddr, &sum, CURSEG_WARM_DATA, 1); |
| ASSERT(!ret); |
| |
| 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 inline int is_extension_exist(const char *s, |
| const char *sub) |
| { |
| unsigned int slen = strlen(s); |
| unsigned int sublen = strlen(sub); |
| int i; |
| |
| /* |
| * filename format of multimedia file should be defined as: |
| * "filename + '.' + extension + (optional: '.' + temp extension)". |
| */ |
| if (slen < sublen + 2) |
| return 0; |
| |
| for (i = 1; i < slen - sublen; i++) { |
| if (s[i] != '.') |
| continue; |
| if (!strncasecmp(s + i + 1, sub, sublen)) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static void set_file_temperature(struct f2fs_sb_info *sbi, |
| struct f2fs_node *node_blk, |
| const unsigned char *name) |
| { |
| __u8 (*extlist)[F2FS_EXTENSION_LEN] = sbi->raw_super->extension_list; |
| int i, cold_count, hot_count; |
| |
| cold_count = le32_to_cpu(sbi->raw_super->extension_count); |
| hot_count = sbi->raw_super->hot_ext_count; |
| |
| for (i = 0; i < cold_count + hot_count; i++) { |
| if (is_extension_exist((const char *)name, |
| (const char *)extlist[i])) |
| break; |
| } |
| |
| if (i == cold_count + hot_count) |
| return; |
| |
| if (i < cold_count) |
| node_blk->i.i_advise |= FADVISE_COLD_BIT; |
| else |
| node_blk->i.i_advise |= FADVISE_HOT_BIT; |
| } |
| |
| 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(calc_extra_isize()); |
| } |
| |
| set_file_temperature(sbi, node_blk, de->name); |
| |
| 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; |
| set_cold_node(node_blk, S_ISDIR(mode)); |
| |
| if (S_ISDIR(mode)) { |
| make_empty_dir(sbi, node_blk); |
| } else if (S_ISLNK(mode)) { |
| page_symlink(sbi, node_blk, de->link, size); |
| |
| free(de->link); |
| de->link = NULL; |
| } |
| |
| 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; |
| } |
| |
| static int cmp_from_devino(const void *a, const void *b) { |
| u64 devino_a = ((struct hardlink_cache_entry*) a)->from_devino; |
| u64 devino_b = ((struct hardlink_cache_entry*) b)->from_devino; |
| |
| return (devino_a > devino_b) - (devino_a < devino_b); |
| } |
| |
| struct hardlink_cache_entry *f2fs_search_hardlink(struct f2fs_sb_info *sbi, |
| struct dentry *de) |
| { |
| struct hardlink_cache_entry *find_hardlink = NULL; |
| struct hardlink_cache_entry *found_hardlink = NULL; |
| void *search_result; |
| |
| /* This might be a hardlink, try to find it in the cache */ |
| find_hardlink = calloc(1, sizeof(struct hardlink_cache_entry)); |
| find_hardlink->from_devino = de->from_devino; |
| |
| search_result = tsearch(find_hardlink, &(sbi->hardlink_cache), |
| cmp_from_devino); |
| ASSERT(search_result != 0); |
| |
| found_hardlink = *(struct hardlink_cache_entry**) search_result; |
| ASSERT(find_hardlink->from_devino == found_hardlink->from_devino); |
| |
| /* If it was already in the cache, free the entry we just created */ |
| if (found_hardlink != find_hardlink) |
| free(find_hardlink); |
| |
| return found_hardlink; |
| } |
| |
| int f2fs_create(struct f2fs_sb_info *sbi, struct dentry *de) |
| { |
| struct f2fs_node *parent, *child; |
| struct hardlink_cache_entry *found_hardlink = NULL; |
| struct node_info ni, hardlink_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; |
| } |
| |
| if (de->from_devino) |
| found_hardlink = f2fs_search_hardlink(sbi, de); |
| |
| 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); |
| |
| if (found_hardlink && found_hardlink->to_ino) { |
| /* |
| * If we found this devino in the cache, we're creating a |
| * hard link. |
| */ |
| get_node_info(sbi, found_hardlink->to_ino, &hardlink_ni); |
| if (hardlink_ni.blk_addr == NULL_ADDR) { |
| MSG(1, "No original inode for hard link to_ino=%x\n", |
| found_hardlink->to_ino); |
| return -1; |
| } |
| |
| /* Use previously-recorded inode */ |
| de->ino = found_hardlink->to_ino; |
| blkaddr = hardlink_ni.blk_addr; |
| MSG(1, "Info: Creating \"%s\" as hard link to inode %d\n", |
| de->path, de->ino); |
| } else { |
| f2fs_alloc_nid(sbi, &de->ino); |
| } |
| |
| 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; |
| } |
| |
| if (found_hardlink) { |
| if (!found_hardlink->to_ino) { |
| MSG(2, "Adding inode %d from %s to hardlink cache\n", |
| de->ino, de->path); |
| found_hardlink->to_ino = de->ino; |
| } else { |
| /* Replace child with original block */ |
| free(child); |
| |
| child = calloc(BLOCK_SZ, 1); |
| ASSERT(child); |
| |
| ret = dev_read_block(child, blkaddr); |
| ASSERT(ret >= 0); |
| |
| /* Increment links and skip to writing block */ |
| child->i.i_links = cpu_to_le32( |
| le32_to_cpu(child->i.i_links) + 1); |
| MSG(2, "Number of links on inode %d is now %d\n", |
| de->ino, le32_to_cpu(child->i.i_links)); |
| goto write_child_dir; |
| } |
| } |
| |
| /* write child */ |
| set_summary(&sum, de->ino, 0, ni.version); |
| ret = reserve_new_block(sbi, &blkaddr, &sum, CURSEG_HOT_NODE, 1); |
| ASSERT(!ret); |
| |
| /* update nat info */ |
| update_nat_blkaddr(sbi, de->ino, de->ino, blkaddr); |
| |
| write_child_dir: |
| 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, link=%u " |
| "blocks=%"PRIx64" pino=%x\n", |
| de->full_path, de->path, |
| de->ino, de->file_type, de->mode, |
| de->uid, de->gid, de->capabilities, de->size, |
| le32_to_cpu(child->i.i_links), |
| le64_to_cpu(child->i.i_blocks), |
| 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; |
| } |