/**
* segment.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 void write_inode(u64 blkaddr, struct f2fs_node *inode)
{
if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
inode->i.i_inode_checksum =
cpu_to_le32(f2fs_inode_chksum(inode));
ASSERT(dev_write_block(inode, blkaddr) >= 0);
}
void reserve_new_block(struct f2fs_sb_info *sbi, block_t *to,
struct f2fs_summary *sum, int type)
{
struct f2fs_fsck *fsck = F2FS_FSCK(sbi);
struct seg_entry *se;
u64 blkaddr, offset;
u64 old_blkaddr = *to;
blkaddr = SM_I(sbi)->main_blkaddr;
if (find_next_free_block(sbi, &blkaddr, 0, type)) {
ERR_MSG("Not enough space to allocate blocks");
ASSERT(0);
}
se = get_seg_entry(sbi, GET_SEGNO(sbi, blkaddr));
offset = OFFSET_IN_SEG(sbi, blkaddr);
se->type = type;
se->valid_blocks++;
f2fs_set_bit(offset, (char *)se->cur_valid_map);
if (c.func == FSCK) {
f2fs_set_main_bitmap(sbi, blkaddr, type);
f2fs_set_sit_bitmap(sbi, blkaddr);
}
if (old_blkaddr == NULL_ADDR) {
sbi->total_valid_block_count++;
if (c.func == FSCK)
fsck->chk.valid_blk_cnt++;
}
se->dirty = 1;
/* read/write SSA */
*to = (block_t)blkaddr;
update_sum_entry(sbi, *to, sum);
}
void new_data_block(struct f2fs_sb_info *sbi, void *block,
struct dnode_of_data *dn, int type)
{
struct f2fs_summary sum;
struct node_info ni;
unsigned int blkaddr = datablock_addr(dn->node_blk, dn->ofs_in_node);
ASSERT(dn->node_blk);
memset(block, 0, BLOCK_SZ);
get_node_info(sbi, dn->nid, &ni);
set_summary(&sum, dn->nid, dn->ofs_in_node, ni.version);
reserve_new_block(sbi, &dn->data_blkaddr, &sum, type);
if (blkaddr == NULL_ADDR)
inc_inode_blocks(dn);
else if (blkaddr == NEW_ADDR)
dn->idirty = 1;
set_data_blkaddr(dn);
}
u64 f2fs_read(struct f2fs_sb_info *sbi, nid_t ino, u8 *buffer,
u64 count, pgoff_t offset)
{
struct dnode_of_data dn;
struct node_info ni;
struct f2fs_node *inode;
char *blk_buffer;
u64 filesize;
u64 off_in_blk;
u64 len_in_blk;
u64 read_count;
u64 remained_blkentries;
block_t blkaddr;
void *index_node = NULL;
memset(&dn, 0, sizeof(dn));
/* Memory allocation for block buffer and inode. */
blk_buffer = calloc(BLOCK_SZ, 2);
ASSERT(blk_buffer);
inode = (struct f2fs_node*)(blk_buffer + BLOCK_SZ);
/* Read inode */
get_node_info(sbi, ino, &ni);
ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
/* Adjust count with file length. */
filesize = le64_to_cpu(inode->i.i_size);
if (offset > filesize)
count = 0;
else if (count + offset > filesize)
count = filesize - offset;
/* Main loop for file blocks */
read_count = remained_blkentries = 0;
while (count > 0) {
if (remained_blkentries == 0) {
set_new_dnode(&dn, inode, NULL, ino);
get_dnode_of_data(sbi, &dn, F2FS_BYTES_TO_BLK(offset),
LOOKUP_NODE);
if (index_node)
free(index_node);
index_node = (dn.node_blk == dn.inode_blk) ?
NULL : dn.node_blk;
remained_blkentries = ADDRS_PER_PAGE(dn.node_blk);
}
ASSERT(remained_blkentries > 0);
blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR)
break;
off_in_blk = offset % BLOCK_SZ;
len_in_blk = BLOCK_SZ - off_in_blk;
if (len_in_blk > count)
len_in_blk = count;
/* Read data from single block. */
if (len_in_blk < BLOCK_SZ) {
ASSERT(dev_read_block(blk_buffer, blkaddr) >= 0);
memcpy(buffer, blk_buffer + off_in_blk, len_in_blk);
} else {
/* Direct read */
ASSERT(dev_read_block(buffer, blkaddr) >= 0);
}
offset += len_in_blk;
count -= len_in_blk;
buffer += len_in_blk;
read_count += len_in_blk;
dn.ofs_in_node++;
remained_blkentries--;
}
if (index_node)
free(index_node);
free(blk_buffer);
return read_count;
}
u64 f2fs_write(struct f2fs_sb_info *sbi, nid_t ino, u8 *buffer,
u64 count, pgoff_t offset)
{
struct dnode_of_data dn;
struct node_info ni;
struct f2fs_node *inode;
char *blk_buffer;
u64 off_in_blk;
u64 len_in_blk;
u64 written_count;
u64 remained_blkentries;
block_t blkaddr;
void* index_node = NULL;
int idirty = 0;
/* Memory allocation for block buffer and inode. */
blk_buffer = calloc(BLOCK_SZ, 2);
ASSERT(blk_buffer);
inode = (struct f2fs_node*)(blk_buffer + BLOCK_SZ);
/* Read inode */
get_node_info(sbi, ino, &ni);
ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
/* Main loop for file blocks */
written_count = remained_blkentries = 0;
while (count > 0) {
if (remained_blkentries == 0) {
set_new_dnode(&dn, inode, NULL, ino);
get_dnode_of_data(sbi, &dn, F2FS_BYTES_TO_BLK(offset),
ALLOC_NODE);
idirty |= dn.idirty;
if (index_node)
free(index_node);
index_node = (dn.node_blk == dn.inode_blk) ?
NULL : dn.node_blk;
remained_blkentries = ADDRS_PER_PAGE(dn.node_blk);
}
ASSERT(remained_blkentries > 0);
blkaddr = datablock_addr(dn.node_blk, dn.ofs_in_node);
if (blkaddr == NULL_ADDR || blkaddr == NEW_ADDR) {
new_data_block(sbi, blk_buffer, &dn, CURSEG_WARM_DATA);
blkaddr = dn.data_blkaddr;
}
off_in_blk = offset % BLOCK_SZ;
len_in_blk = BLOCK_SZ - off_in_blk;
if (len_in_blk > count)
len_in_blk = count;
/* Write data to single block. */
if (len_in_blk < BLOCK_SZ) {
ASSERT(dev_read_block(blk_buffer, blkaddr) >= 0);
memcpy(blk_buffer + off_in_blk, buffer, len_in_blk);
ASSERT(dev_write_block(blk_buffer, blkaddr) >= 0);
} else {
/* Direct write */
ASSERT(dev_write_block(buffer, blkaddr) >= 0);
}
offset += len_in_blk;
count -= len_in_blk;
buffer += len_in_blk;
written_count += len_in_blk;
dn.ofs_in_node++;
if ((--remained_blkentries == 0 || count == 0) && (dn.ndirty))
ASSERT(dev_write_block(dn.node_blk, dn.node_blkaddr) >= 0);
}
if (offset > le64_to_cpu(inode->i.i_size)) {
inode->i.i_size = cpu_to_le64(offset);
idirty = 1;
}
if (idirty) {
ASSERT(inode == dn.inode_blk);
write_inode(ni.blk_addr, inode);
}
if (index_node)
free(index_node);
free(blk_buffer);
return written_count;
}
/* This function updates only inode->i.i_size */
void f2fs_filesize_update(struct f2fs_sb_info *sbi, nid_t ino, u64 filesize)
{
struct node_info ni;
struct f2fs_node *inode;
inode = calloc(BLOCK_SZ, 1);
ASSERT(inode);
get_node_info(sbi, ino, &ni);
ASSERT(dev_read_block(inode, ni.blk_addr) >= 0);
ASSERT(!S_ISDIR(le16_to_cpu(inode->i.i_mode)));
ASSERT(!S_ISLNK(le16_to_cpu(inode->i.i_mode)));
inode->i.i_size = cpu_to_le64(filesize);
write_inode(ni.blk_addr, inode);
free(inode);
}
int f2fs_build_file(struct f2fs_sb_info *sbi, struct dentry *de)
{
int fd, n;
pgoff_t off = 0;
u8 buffer[BLOCK_SZ];
if (de->ino == 0)
return -1;
fd = open(de->full_path, O_RDONLY);
if (fd < 0) {
MSG(0, "Skip: Fail to open %s\n", de->full_path);
return -1;
}
/* inline_data support */
if (de->size <= DEF_MAX_INLINE_DATA) {
struct node_info ni;
struct f2fs_node *node_blk;
int ret;
get_node_info(sbi, de->ino, &ni);
node_blk = calloc(BLOCK_SZ, 1);
ASSERT(node_blk);
ret = dev_read_block(node_blk, ni.blk_addr);
ASSERT(ret >= 0);
node_blk->i.i_inline |= F2FS_INLINE_DATA;
node_blk->i.i_inline |= F2FS_DATA_EXIST;
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);
}
n = read(fd, buffer, BLOCK_SZ);
ASSERT((unsigned long)n == de->size);
memcpy(inline_data_addr(node_blk), buffer, de->size);
node_blk->i.i_size = cpu_to_le64(de->size);
write_inode(ni.blk_addr, node_blk);
free(node_blk);
} else {
while ((n = read(fd, buffer, BLOCK_SZ)) > 0) {
f2fs_write(sbi, de->ino, buffer, n, off);
off += n;
}
}
close(fd);
if (n < 0)
return -1;
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);
return 0;
}