fs: ext2: implement fs_truncate
Introduce fs_truncate function. Signed-off-by: Franciszek Zdobylak <fzdobylak@antmicro.com>
This commit is contained in:
parent
a87cc6c0b6
commit
685682ff49
|
@ -25,6 +25,8 @@ static int get_level_offsets(struct ext2_data *fs, uint32_t block, uint32_t offs
|
|||
static inline uint32_t get_ngroups(struct ext2_data *fs);
|
||||
|
||||
#define MAX_OFFSETS_SIZE 4
|
||||
/* Array of zeros to be used in inode block calculation */
|
||||
static const uint32_t zero_offsets[MAX_OFFSETS_SIZE];
|
||||
|
||||
/**
|
||||
* @brief Fetch block group and inode table of given inode.
|
||||
|
@ -163,6 +165,99 @@ int ext2_fetch_inode_block(struct ext2_inode *inode, uint32_t block)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool all_zero(const uint32_t *offsets, int lvl)
|
||||
{
|
||||
for (int i = 0; i < lvl; ++i) {
|
||||
if (offsets[i] > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief delete blocks from one described with offsets array
|
||||
*
|
||||
* NOTE: To use this function safely drop all fetched inode blocks
|
||||
*
|
||||
* @retval >=0 Number of removed blocks (only the blocks with actual inode data)
|
||||
* @retval <0 Error
|
||||
*/
|
||||
static int64_t delete_blocks(struct ext2_data *fs, uint32_t block_num, int lvl,
|
||||
const uint32_t *offsets)
|
||||
{
|
||||
if (block_num == 0) {
|
||||
return 0;
|
||||
}
|
||||
__ASSERT(lvl >= 0 && lvl < MAX_OFFSETS_SIZE,
|
||||
"Expected 0 <= lvl < %d (got: lvl=%d)", lvl, MAX_OFFSETS_SIZE);
|
||||
|
||||
int ret;
|
||||
int64_t removed = 0, rem;
|
||||
uint32_t *list, start_blk;
|
||||
struct ext2_block *list_block = NULL;
|
||||
bool remove_current = false;
|
||||
|
||||
if (lvl == 0) {
|
||||
/* If we got here we will remove this block
|
||||
* and it is also a block with actual inode data, hence we count it.
|
||||
*/
|
||||
remove_current = true;
|
||||
removed++;
|
||||
} else {
|
||||
list_block = ext2_get_block(fs, block_num);
|
||||
|
||||
if (list_block == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
list = (uint32_t *)list_block->data;
|
||||
|
||||
if (all_zero(offsets, lvl)) {
|
||||
/* We remove all blocks that are referenced by current block and current
|
||||
* block isn't needed anymore.
|
||||
*/
|
||||
remove_current = true;
|
||||
start_blk = 0;
|
||||
} else {
|
||||
/* We don't remove all blocks referenced by current block. */
|
||||
start_blk = offsets[0] + 1;
|
||||
rem = delete_blocks(fs, list[offsets[0]], lvl - 1, &offsets[1]);
|
||||
if (rem < 0) {
|
||||
removed = rem;
|
||||
goto out;
|
||||
}
|
||||
removed += rem;
|
||||
}
|
||||
|
||||
/* Iterate over blocks that will be entirely deleted */
|
||||
for (uint32_t i = start_blk; i < fs->block_size / EXT2_BLOCK_NUM_SIZE; ++i) {
|
||||
if (list[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
rem = delete_blocks(fs, list[i], lvl - 1, zero_offsets);
|
||||
if (rem < 0) {
|
||||
removed = rem;
|
||||
goto out;
|
||||
}
|
||||
removed += rem;
|
||||
}
|
||||
}
|
||||
|
||||
if (remove_current) {
|
||||
LOG_DBG("free block %d (lvl %d)", block_num, lvl);
|
||||
|
||||
ret = ext2_free_block(fs, block_num);
|
||||
if (ret < 0) {
|
||||
removed = ret;
|
||||
}
|
||||
}
|
||||
out:
|
||||
ext2_drop_block(fs, list_block);
|
||||
|
||||
/* On error removed will contain negative error code */
|
||||
return removed;
|
||||
}
|
||||
|
||||
static int get_level_offsets(struct ext2_data *fs, uint32_t block, uint32_t offsets[4])
|
||||
{
|
||||
const uint32_t B = fs->block_size / EXT2_BLOCK_NUM_SIZE;
|
||||
|
@ -211,6 +306,54 @@ static int get_level_offsets(struct ext2_data *fs, uint32_t block, uint32_t offs
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int block0_level(uint32_t block)
|
||||
{
|
||||
if (block >= EXT2_INODE_BLOCK_1LVL) {
|
||||
return block - EXT2_INODE_BLOCK_1LVL + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int inode_remove_blocks(struct ext2_inode *inode, uint32_t first)
|
||||
{
|
||||
uint32_t start;
|
||||
int max_lvl;
|
||||
int64_t removed;
|
||||
uint32_t offsets[4];
|
||||
|
||||
max_lvl = get_level_offsets(inode->i_fs, first, offsets);
|
||||
|
||||
if (all_zero(offsets, max_lvl)) {
|
||||
/* We remove also the first block because all blocks referenced from it will be
|
||||
* deleted.
|
||||
*/
|
||||
start = offsets[0];
|
||||
} else {
|
||||
/* There will be some blocks referenced from first affected block hence we can't
|
||||
* remove it.
|
||||
*/
|
||||
start = offsets[0] + 1;
|
||||
removed = delete_blocks(inode->i_fs, inode->i_block[offsets[0]],
|
||||
block0_level(offsets[0]), &offsets[1]);
|
||||
if (removed < 0) {
|
||||
return removed;
|
||||
}
|
||||
|
||||
inode->i_blocks -= removed * (inode->i_fs->block_size / 512);
|
||||
}
|
||||
|
||||
for (uint32_t i = start; i < EXT2_INODE_BLOCKS; i++) {
|
||||
removed = delete_blocks(inode->i_fs, inode->i_block[i], block0_level(i),
|
||||
zero_offsets);
|
||||
if (removed < 0) {
|
||||
return removed;
|
||||
}
|
||||
inode->i_blocks -= removed * (inode->i_fs->block_size / 512);
|
||||
inode->i_block[i] = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline uint32_t get_ngroups(struct ext2_data *fs)
|
||||
{
|
||||
uint32_t ngroups =
|
||||
|
@ -546,3 +689,41 @@ int32_t ext2_alloc_inode(struct ext2_data *fs)
|
|||
|
||||
return total;
|
||||
}
|
||||
|
||||
int ext2_free_block(struct ext2_data *fs, uint32_t block)
|
||||
{
|
||||
LOG_DBG("Free block %d", block);
|
||||
|
||||
/* Block bitmaps tracks blocks starting from block number 1.
|
||||
* (Block 0 is ommited.)
|
||||
*/
|
||||
block -= 1;
|
||||
|
||||
int rc;
|
||||
uint32_t group = block / EXT2_DATA_SBLOCK(fs)->s_blocks_per_group;
|
||||
uint32_t off = block % EXT2_DATA_SBLOCK(fs)->s_blocks_per_group;
|
||||
|
||||
rc = ext2_fetch_block_group(fs, group);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ext2_fetch_bg_bbitmap(fs->bgroup);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ext2_bitmap_unset(BGROUP_BLOCK_BITMAP(fs->bgroup), off, fs->block_size);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
current_disk_bgroup(fs->bgroup)->bg_free_blocks_count += 1;
|
||||
EXT2_DATA_SBLOCK(fs)->s_free_blocks_count += 1;
|
||||
|
||||
fs->sblock->flags |= EXT2_BLOCK_DIRTY;
|
||||
fs->bgroup->block->flags |= EXT2_BLOCK_DIRTY;
|
||||
fs->bgroup->block_bitmap->flags |= EXT2_BLOCK_DIRTY;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -159,4 +159,14 @@ int64_t ext2_alloc_block(struct ext2_data *fs);
|
|||
*/
|
||||
int32_t ext2_alloc_inode(struct ext2_data *fs);
|
||||
|
||||
/**
|
||||
* @brief Free the block
|
||||
*
|
||||
* @param fs File system data
|
||||
*
|
||||
* @retval 0 on success
|
||||
* @retval <0 error
|
||||
*/
|
||||
int ext2_free_block(struct ext2_data *fs, uint32_t block);
|
||||
|
||||
#endif /* __EXT2_DISKOPS_H__ */
|
||||
|
|
|
@ -631,6 +631,84 @@ ssize_t ext2_inode_write(struct ext2_inode *inode, const void *buf, uint32_t off
|
|||
return written;
|
||||
}
|
||||
|
||||
int ext2_inode_trunc(struct ext2_inode *inode, off_t length)
|
||||
{
|
||||
if (length > UINT32_MAX) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
int rc = 0;
|
||||
uint32_t new_size = (uint32_t)length;
|
||||
uint32_t old_size = inode->i_size;
|
||||
|
||||
LOG_DBG("Resizing inode from %d to %d", old_size, new_size);
|
||||
|
||||
if (old_size == new_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t block_size = inode->i_fs->block_size;
|
||||
uint32_t new_block = new_size / block_size;
|
||||
|
||||
if (new_size > old_size) {
|
||||
if (old_size % block_size != 0) {
|
||||
/* file ends inside some block */
|
||||
|
||||
LOG_DBG("Has to insert zeros to the end of block");
|
||||
|
||||
/* insert zeros to the end of last block */
|
||||
uint32_t old_block = old_size / block_size;
|
||||
uint32_t start_off = old_size % block_size;
|
||||
uint32_t to_write = MIN(new_size - old_size, block_size);
|
||||
|
||||
rc = ext2_fetch_inode_block(inode, old_block);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
memset(inode_current_block_mem(inode) + start_off, 0, to_write);
|
||||
rc = ext2_commit_inode_block(inode);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
/* There is no need to zero rest of blocks because they will be automatically
|
||||
* treated as zero filled.
|
||||
*/
|
||||
|
||||
} else {
|
||||
/* remove unused blocks */
|
||||
uint32_t start_blk = new_block + 1;
|
||||
|
||||
if (new_size % block_size == 0) {
|
||||
/* new size is block aligned hence we can remove previous block too
|
||||
* because nothing should be stored on it
|
||||
*
|
||||
* e.g. size = 3 blks new_block = 3
|
||||
* but then we can remove block 3 because it is unused
|
||||
*
|
||||
* end of file
|
||||
* v
|
||||
* |-- blk 0 --|-- blk 1 --|-- blk 2 --|-- blk 3 --|-- blk 4 --|-- blk 5 --|
|
||||
*
|
||||
*/
|
||||
start_blk = new_block;
|
||||
}
|
||||
|
||||
/* Remove blocks starting with start_blk. */
|
||||
rc = inode_remove_blocks(inode, start_blk);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
inode->i_size = new_size;
|
||||
|
||||
rc = ext2_commit_inode(inode);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int ext2_get_direntry(struct ext2_dir *dir, struct fs_dirent *ent)
|
||||
{
|
||||
if (dir->d_off >= dir->d_inode->i_size) {
|
||||
|
|
|
@ -186,6 +186,18 @@ ssize_t ext2_inode_read(struct ext2_inode *inode, void *buf, uint32_t offset,
|
|||
ssize_t ext2_inode_write(struct ext2_inode *inode, const void *buf,
|
||||
uint32_t offset, size_t nbytes);
|
||||
|
||||
/**
|
||||
* @brief Truncate the inode
|
||||
*
|
||||
* @param inode Inode
|
||||
* @param size New size for inode
|
||||
*
|
||||
* @retval 0 on success
|
||||
* @retval -ENOTSUP when requested size is too big
|
||||
* @retval <0 other error
|
||||
*/
|
||||
int ext2_inode_trunc(struct ext2_inode *inode, off_t size);
|
||||
|
||||
/* Directory operations */
|
||||
|
||||
/**
|
||||
|
@ -264,4 +276,11 @@ int ext2_inode_drop(struct ext2_inode *inode);
|
|||
/* Drop blocks fetched in inode structure. */
|
||||
void ext2_inode_drop_blocks(struct ext2_inode *inode);
|
||||
|
||||
/**
|
||||
* @ Remove all blocks starting with some block
|
||||
*
|
||||
* @param first First block to remove
|
||||
*/
|
||||
int inode_remove_blocks(struct ext2_inode *inode, uint32_t first);
|
||||
|
||||
#endif /* __EXT2_IMPL_H__ */
|
||||
|
|
|
@ -202,6 +202,22 @@ static off_t ext2_tell(struct fs_file_t *filp)
|
|||
return f->f_off;
|
||||
}
|
||||
|
||||
static int ext2_truncate(struct fs_file_t *filp, off_t length)
|
||||
{
|
||||
struct ext2_file *f = filp->filep;
|
||||
|
||||
if ((f->f_flags & FS_O_WRITE) == 0) {
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
int rc = ext2_inode_trunc(f->f_inode, length);
|
||||
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Directory operations */
|
||||
|
||||
static int ext2_mkdir(struct fs_mount_t *mountp, const char *name)
|
||||
|
@ -456,6 +472,7 @@ static const struct fs_file_system_t ext2_fs = {
|
|||
.write = ext2_write,
|
||||
.lseek = ext2_lseek,
|
||||
.tell = ext2_tell,
|
||||
.truncate = ext2_truncate,
|
||||
.mkdir = ext2_mkdir,
|
||||
.mount = ext2_mount,
|
||||
.unmount = ext2_unmount,
|
||||
|
|
Loading…
Reference in a new issue