aboutsummaryrefslogtreecommitdiff
path: root/fsck/dir.c
diff options
context:
space:
mode:
authorJaegeuk Kim <jaegeuk@kernel.org>2015-12-09 16:18:44 -0800
committerJaegeuk Kim <jaegeuk@kernel.org>2016-04-18 21:11:26 -0400
commit603f8f9d3628e8b81fea301e5f543a9f0b0d902d (patch)
tree903a3a88d22de833be6c852559f74712f079df7d /fsck/dir.c
parentd3be08825e9f5ebe8e9bf9da9a9b77cefd9b525d (diff)
downloadf2fs-tools-603f8f9d3628e8b81fea301e5f543a9f0b0d902d.tar.gz
sload.f2fs: support loading files into partition directly
This patch implements loading files into the existing partition. For example, # sload.f2fs -f ./ /dev/sdb1 Then, all the directories and files will be loaded into /dev/sdb1. By default, newly files should have inline_data and inline_xattr, if possible. Signed-off-by: Hou Pengyang <houpengyang@huawei.com> Signed-off-by: Liu Shuoran <liushuoran@huawei.com> Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org>
Diffstat (limited to 'fsck/dir.c')
-rw-r--r--fsck/dir.c584
1 files changed, 584 insertions, 0 deletions
diff --git a/fsck/dir.c b/fsck/dir.c
new file mode 100644
index 0000000..18b79bd
--- /dev/null
+++ b/fsck/dir.c
@@ -0,0 +1,584 @@
+/**
+ * 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"
+
+static unsigned int dir_buckets(unsigned int level)
+{
+ if (level < MAX_DIR_HASH_DEPTH / 2)
+ return 1 << level;
+ else
+ return MAX_DIR_BUCKETS;
+}
+
+static unsigned int bucket_blocks(unsigned int level)
+{
+ if (level < MAX_DIR_HASH_DEPTH / 2)
+ return 2;
+ else
+ return 4;
+}
+
+static unsigned long dir_block_index(unsigned int level,
+ int dir_level, unsigned int idx)
+{
+ unsigned long i;
+ unsigned long bidx = 0;
+
+ for (i = 0; i < level; i++)
+ bidx += dir_buckets(i + dir_level) * bucket_blocks(i);
+ bidx += idx * bucket_blocks(level);
+ return bidx;
+}
+
+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;
+
+}
+
+static void make_dentry_ptr(struct f2fs_dentry_ptr *d, void *src, int type)
+{
+ if (type == 1) {
+ struct f2fs_dentry_block *t = (struct f2fs_dentry_block *)src;
+ d->max = NR_DENTRY_IN_BLOCK;
+ d->bitmap = t->dentry_bitmap;
+ d->dentry = t->dentry;
+ d->filename = t->filename;
+ } else {
+ struct f2fs_inline_dentry *t = (struct f2fs_inline_dentry *)src;
+ d->max = NR_INLINE_DENTRY;
+ d->bitmap = t->dentry_bitmap;
+ d->dentry = t->dentry;
+ d->filename = t->filename;
+ }
+}
+
+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 < 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, 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 = {0};
+ void *dentry_blk;
+ int max_slots = 214;
+ nid_t ino = dir->footer.ino;
+ f2fs_hash_t namehash;
+ int ret = 0;
+
+ namehash = f2fs_dentry_hash(de->name, de->len);
+
+ nbucket = dir_buckets(level);
+ nblock = bucket_blocks(level);
+
+ bidx = dir_block_index(level, 0, le32_to_cpu(namehash) % nbucket);
+ end_block = bidx + nblock;
+
+ dentry_blk = calloc(BLOCK_SZ, 1);
+ ASSERT(dentry_blk);
+
+ 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 = dir->i.i_current_depth;
+ for (level = 0; level < max_depth; level ++) {
+ if (find_in_level(sbi, dir, level, de))
+ return 1;
+ }
+ return 0;
+}
+
+static void f2fs_update_dentry(nid_t ino, umode_t mode,
+ 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 = 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);
+ set_de_type(de, mode);
+ 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.
+ */
+static int f2fs_add_link(struct f2fs_sb_info *sbi, struct f2fs_node *parent,
+ struct f2fs_node *child, block_t p_blkaddr)
+{
+ int level = 0, current_depth, bit_pos;
+ int nbucket, nblock, bidx, block;
+ const unsigned char *name = child->i.i_name;
+ int name_len = le32_to_cpu(child->i.i_namelen);
+ 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 = {0};
+ nid_t pino = le32_to_cpu(parent->footer.ino);
+ nid_t ino = le32_to_cpu(child->footer.ino);
+ umode_t mode = le16_to_cpu(child->i.i_mode);
+ int ret;
+
+ if (parent == NULL || child == 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);
+ nblock = bucket_blocks(level);
+ bidx = dir_block_index(level, 0, le32_to_cpu(dentry_hash) % nbucket);
+
+ 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, (void *)dentry_blk, 1);
+ f2fs_update_dentry(ino, mode, &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 (S_ISDIR(mode)) {
+ u32 links = le32_to_cpu(parent->i.i_links);
+ parent->i.i_links = cpu_to_le32(links + 1);
+ dn.idirty = 1;
+ }
+
+ if ((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;
+ 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[0] = 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;
+ int ret;
+
+ get_node_info(sbi, ino, &ni);
+
+ /* store into inline_data */
+ if (symlen + 1 <= MAX_INLINE_DATA) {
+ inode->i.i_inline |= F2FS_INLINE_DATA;
+ inode->i.i_inline |= F2FS_DATA_EXIST;
+ memcpy(&inode->i.i_addr[1], 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[0] = 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)
+ 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;
+ node_blk->i.i_current_depth = cpu_to_le32(1);
+ 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;
+
+ 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);
+}
+
+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;
+ 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);
+
+ 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, ni.blk_addr);
+ 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\" type=%x, ino=%x / %x into \"%s\"\n",
+ de->full_path, de->file_type,
+ de->ino, de->pino, de->path);
+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;
+}