Files
Mark 44a3dc21b4 ext2: add (kind of) unlink() for ext2
"kind of" == directories aren't resized and overall
space handling is crappy
2020-08-09 01:53:22 +03:00

164 lines
5.1 KiB
C

#include "fs/ext2/alloc.h"
#include "fs/ext2/block.h"
#include "fs/ext2/dir.h"
#include "fs/ext2/ext2.h"
#include "fs/fs.h"
#include "fs/node.h"
#include "sys/assert.h"
#include "sys/debug.h"
#include "sys/string.h"
#include "user/errno.h"
int ext2_dir_insert_inode(struct fs *ext2,
struct ext2_inode *at_inode,
uint32_t at_ino,
const char *name,
uint32_t ino,
enum vnode_type type) {
// Align name
// Not zero-terminated
struct ext2_data *data = ext2->fs_private;
size_t size_needed = ((strlen(name) + 3) & ~3) + sizeof(struct ext2_dirent);
size_t block_count = at_inode->size_lower / data->block_size;
struct ext2_dirent *new = NULL;
int inode_changed = 0;
uint32_t index;
char buf[data->block_size];
// Try to fit inside already-existing blocks
for (size_t i = 0; i < block_count; ++i) {
// Find out how much space is free in this dir
_assert(ext2_read_inode_block(ext2, at_inode, buf, i) == 0);
size_t offset = 0;
while (offset < data->block_size) {
struct ext2_dirent *ent = (struct ext2_dirent *) &buf[offset];
// Minimum size needed for this entry
size_t size_needed_prev = sizeof(struct ext2_dirent);
if (ent->ino) {
// A real inode dirent
size_needed_prev += ((ent->name_length_low + 3) & ~3);
}
_assert(ent->ent_size >= size_needed_prev);
size_t spare_space = ent->ent_size - size_needed_prev;
if (spare_space >= size_needed) {
// Cut off the spare space from previous entry
ent->ent_size = size_needed_prev;
// This will be the new entry
new = (struct ext2_dirent *) &buf[offset + ent->ent_size];
new->ent_size = spare_space;
index = i;
break;
}
offset += ent->ent_size;
}
if (new) {
break;
}
}
if (!new) {
// Grow the directory
_assert(ext2_file_resize(ext2, at_ino, at_inode, at_inode->size_lower + data->block_size) == 0);
inode_changed = 1;
memset(buf, 0, data->block_size);
// New entry takes the whole block
new = (struct ext2_dirent *) buf;
new->ent_size = data->block_size;
index = block_count;
}
new->ino = ino;
new->name_length_low = strlen(name);
strncpy(new->name, name, new->name_length_low);
// TODO
if (data->sb.required_features & EXT2_REQ_ENT_TYPE) {
switch (type) {
case VN_REG:
new->type_indicator = 1;
break;
case VN_DIR:
new->type_indicator = 2;
break;
default:
panic("Unsupported vnode type: %02x\n", type);
}
} else {
new->name_length_high = 0;
}
// Write back
_assert(ext2_write_inode_block(ext2, at_inode, buf, index) == 0);
if (inode_changed) {
_assert(ext2_write_inode(ext2, at_inode, at_ino) == 0);
}
return 0;
}
int ext2_dir_del_inode(struct fs *ext2, struct ext2_inode *at_inode, uint32_t at_ino, struct vnode *vn) {
size_t offset;
struct ext2_data *data = ext2->fs_private;
size_t block_count = at_inode->size_lower / data->block_size;
char buf[data->block_size];
struct ext2_dirent *prev, *next;
for (size_t i = 0; i < block_count; ++i) {
offset = 0;
prev = NULL;
_assert(ext2_read_inode_block(ext2, at_inode, buf, i) == 0);
while (offset < data->block_size) {
struct ext2_dirent *ent = (struct ext2_dirent *) &buf[offset];
if (ent->ino == vn->ino) {
_assert(!strncmp(ent->name, vn->name, ent->name_length_low));
ent->ino = 0;
// TODO: Free directory blocks when the last block entry
// is removed
// TODO: would be better to just "lshift" all the following
// entries to reduce fragmentation
if (ent->ent_size == data->block_size) {
kinfo("TODO: free directory blocks\n");
return ext2_write_inode_block(ext2, at_inode, buf, i);
}
if (prev) {
prev->ent_size += ent->ent_size;
return ext2_write_inode_block(ext2, at_inode, buf, i);
} else {
_assert(!offset);
_assert(ent->ent_size < data->block_size);
next = (struct ext2_dirent *) &buf[offset + ent->ent_size];
size_t old = ent->ent_size;
size_t tot = ent->ent_size + next->ent_size;
size_t len = next->ent_size;
memmove(ent, next, len);
ent->ent_size = tot;
return ext2_write_inode_block(ext2, at_inode, buf, i);
}
}
prev = ent;
offset += ent->ent_size;
}
}
return -ENOENT;
}