Second Extended Filesystem (ext2) and other improvements (#33)
[lunaix-os.git] / lunaix-os / kernel / fs / ext2 / file.c
diff --git a/lunaix-os/kernel/fs/ext2/file.c b/lunaix-os/kernel/fs/ext2/file.c
new file mode 100644 (file)
index 0000000..0fe4d3d
--- /dev/null
@@ -0,0 +1,313 @@
+#include <lunaix/mm/valloc.h>
+#include <lunaix/mm/page.h>
+#include "ext2.h"
+
+#define blkpos(e_sb, fpos) ((fpos) / (e_sb)->block_size)
+#define blkoff(e_sb, fpos) ((fpos) % (e_sb)->block_size)
+
+int
+ext2_open_inode(struct v_inode* inode, struct v_file* file)
+{
+    int errno = 0;
+    struct ext2_file* e_file;
+
+    e_file = valloc(sizeof(*e_file));
+    e_file->b_ino = EXT2_INO(inode);
+    
+    file->data = e_file;
+
+    if (check_directory_node(inode)) {
+        errno = ext2dr_open(inode, file);
+        goto done;
+    }
+
+    // XXX anything for regular file?
+
+done:
+    if (!errno) {
+        return 0;
+    }
+    
+    vfree(e_file);
+    file->data = NULL;
+    return errno;
+}
+
+int
+ext2_close_inode(struct v_file* file)
+{
+    ext2ino_update(file->inode);
+
+    if (check_directory_node(file->inode)) {
+        ext2dr_close(file->inode, file);
+    }
+
+    vfree(file->data);
+    file->data = NULL;
+    return 0;
+}
+
+int
+ext2_sync_inode(struct v_inode* inode)
+{
+    // TODO
+    // a modification to an inode may involves multiple
+    //  blkbuf scattering among different groups.
+    // For now, we just sync everything, until we figure out
+    //  a way to track each dirtied blkbuf w.r.t inode
+    ext2ino_resizing(inode, inode->fsize);
+    blkbuf_syncall(inode->sb->blks, false);
+
+    return 0;
+}
+
+int
+ext2_file_sync(struct v_file* file)
+{
+    return ext2_sync_inode(file->inode);
+}
+
+int
+ext2_seek_inode(struct v_file* file, size_t offset)
+{
+    if (check_directory_node(file->inode)) {
+        return ext2dr_seek(file, offset);
+    }
+
+    // nothing to do, seek on file pos handled by vfs
+    return 0;
+}
+
+int
+ext2_inode_read(struct v_inode *inode, 
+                void *buffer, size_t len, size_t fpos)
+{
+    struct ext2_sbinfo* e_sb;
+    struct ext2_iterator iter;
+    struct ext2b_inode* b_ino;
+    struct ext2_inode* e_ino;
+    unsigned int off;
+    unsigned int end;
+    unsigned int sz = 0, blksz, movsz;
+    
+    e_sb  = EXT2_SB(inode->sb);
+    e_ino = EXT2_INO(inode);
+    b_ino = e_ino->ino;
+    blksz = e_sb->block_size;
+    end = fpos + len;
+
+    ext2db_itbegin(&iter, inode);
+    ext2db_itffw(&iter, fpos / blksz);
+
+    while (fpos < end && ext2db_itnext(&iter)) {
+        off = fpos % blksz;
+        movsz = MIN(end - fpos, blksz - off);
+        
+        memcpy(buffer, offset(iter.data, off), movsz);
+
+        buffer = offset(buffer, movsz);
+        fpos += movsz;
+        sz += movsz;
+    }
+
+    ext2db_itend(&iter);
+    return itstate_sel(&iter, MIN(sz, e_ino->isize));
+}
+
+int
+ext2_inode_read_page(struct v_inode *inode, void *buffer, size_t fpos)
+{
+    struct ext2_sbinfo* e_sb;
+    struct ext2_iterator iter;
+    struct ext2_inode*  e_ino;
+    struct ext2b_inode* b_ino;
+    unsigned int blk_start, n, 
+                 transfer_sz, total_sz = 0;
+
+    assert(!va_offset(fpos));
+
+    e_sb = EXT2_SB(inode->sb);
+    e_ino = EXT2_INO(inode);
+    b_ino = e_ino->ino;
+    
+    blk_start = fpos / e_sb->block_size;
+    n = PAGE_SIZE / e_sb->block_size;
+    transfer_sz = MIN(PAGE_SIZE, e_sb->block_size);
+
+    ext2db_itbegin(&iter, inode);
+    ext2db_itffw(&iter, blk_start);
+
+    while (n-- && ext2db_itnext(&iter)) 
+    {
+        memcpy(buffer, iter.data, transfer_sz);
+        buffer = offset(buffer, transfer_sz);
+        total_sz += transfer_sz;
+    }
+    
+    ext2db_itend(&iter);
+    return itstate_sel(&iter, MIN(total_sz, e_ino->isize));
+}
+
+int
+ext2_inode_write(struct v_inode *inode, 
+                 void *buffer, size_t len, size_t fpos)
+{
+    int errno;
+    unsigned int acc, blk_off, end, size;
+    struct ext2_sbinfo* e_sb;
+    bbuf_t buf;
+
+    e_sb  = EXT2_SB(inode->sb);
+
+    acc = 0;
+    end = fpos + len;
+    while (fpos < end) {
+        errno = ext2db_acquire(inode, blkpos(e_sb, fpos), &buf);
+        if (errno) {
+            return errno;
+        }
+
+        blk_off = blkoff(e_sb, fpos);
+        size = e_sb->block_size - blk_off;
+
+        memcpy(offset(blkbuf_data(buf), blk_off), buffer, size);
+        buffer = offset(buffer, size);
+
+        fsblock_dirty(buf);
+        fsblock_put(buf);
+
+        fpos += size;
+        acc += size;
+    }
+
+    return (int)acc;
+}
+
+int
+ext2_inode_write_page(struct v_inode *inode, void *buffer, size_t fpos)
+{
+    return ext2_inode_write(inode, buffer, PAGE_SIZE, fpos);
+}
+
+#define SYMLNK_INPLACE \
+        sizeof(((struct ext2b_inode*)0)->i_block_arr)
+
+static inline int
+__readlink_symlink(struct v_inode *this, char* path)
+{
+    size_t size;
+    char* link = NULL;
+    int errno;
+    bbuf_t buf;
+    struct ext2_inode* e_ino;
+    
+    e_ino = EXT2_INO(this);
+    size  = e_ino->isize;
+    if (size <= SYMLNK_INPLACE) {
+        link = (char*) e_ino->ino->i_block_arr;
+        strncpy(path, link, size);
+    }
+    else {
+        buf = ext2db_get(this, 0);
+        if (blkbuf_errbuf(buf)) {
+            return EIO;
+        }
+
+        link = blkbuf_data(buf);
+        strncpy(path, link, size);
+
+        fsblock_put(buf);
+    }
+
+    return 0;
+}
+
+int
+ext2_get_symlink(struct v_inode *this, const char **path_out)
+{
+    int errno;
+    size_t size;
+    char* symlink;
+    struct ext2_inode* e_ino;
+    
+    e_ino = EXT2_INO(this);
+    size  = e_ino->isize;
+
+    if (!size) {
+        return ENOENT;
+    }
+
+    if (!e_ino->symlink) {
+        symlink = valloc(size);
+        if ((errno = __readlink_symlink(this, symlink))) {
+            vfree(symlink);
+            return errno;
+        }
+
+        e_ino->symlink = symlink;
+    }
+
+    *path_out = e_ino->symlink;
+
+    return size;
+}
+
+int
+ext2_set_symlink(struct v_inode *this, const char *target)
+{
+    int errno = 0;
+    bbuf_t buf = NULL;
+    char* link;
+    size_t size, new_len;
+    struct ext2_inode* e_ino;
+    
+    e_ino = EXT2_INO(this);
+    size = e_ino->isize;
+    new_len = strlen(target);
+
+    if (new_len > this->sb->blksize) {
+        return ENAMETOOLONG;
+    }
+
+    if (size != new_len) {
+        vfree_safe(e_ino->symlink);
+        e_ino->symlink = valloc(new_len);
+    }
+    
+    link = (char*) e_ino->ino->i_block_arr;
+
+    // if new size is shrinked to inplace range
+    if (size > SYMLNK_INPLACE && new_len <= SYMLNK_INPLACE) 
+    {
+        ext2db_free_pos(this, 0);
+    }
+    
+    // if new size is too big to fit inpalce
+    if (new_len > SYMLNK_INPLACE) {
+
+        // repurpose the i_block array back to normal
+        if (size <= SYMLNK_INPLACE) {
+            memset(link, 0, SYMLNK_INPLACE);
+        }
+
+        errno = ext2db_acquire(this, 0, &buf);
+        if (errno) {
+            goto done;
+        }
+
+        link = blkbuf_data(buf);
+    }
+
+    strncpy(e_ino->symlink, target, new_len);
+    strncpy(link, target, new_len);
+
+    ext2ino_update(this);
+    ext2ino_resizing(this, new_len);
+
+    if (buf) {
+        fsblock_put(buf);
+    }
+
+done:
+    return errno;
+}
\ No newline at end of file