diff --git a/etc/make/conf.mk b/etc/make/conf.mk index e8f119d..d60a189 100644 --- a/etc/make/conf.mk +++ b/etc/make/conf.mk @@ -47,4 +47,15 @@ OBJS+=$(O)/sys/debug.o \ $(O)/sys/panic.o \ $(O)/sys/errno.o \ $(O)/sys/blk.o \ - $(O)/sys/dev.o + $(O)/sys/dev.o \ + $(O)/sys/tty.o \ + $(O)/sys/chr.o \ + $(O)/sys/vfs/fs_class.o \ + $(O)/sys/vfs/node.o \ + $(O)/sys/vfs/vfs.o \ + $(O)/sys/vfs/ext2/ext2alloc.o \ + $(O)/sys/vfs/ext2/ext2blk.o \ + $(O)/sys/vfs/ext2/ext2.o \ + $(O)/sys/vfs/ext2/ext2dir.o \ + $(O)/sys/vfs/ext2/ext2vnop.o +DIRS+=$(O)/sys/vfs/ext2 diff --git a/include/sys/chr.h b/include/sys/chr.h new file mode 100644 index 0000000..9a75bc7 --- /dev/null +++ b/include/sys/chr.h @@ -0,0 +1,14 @@ +#pragma once +#include "sys/types.h" + +struct chrdev { + char name[64]; + void *dev_data; + + // TODO: maybe something like flush() + ssize_t (*write) (struct chrdev *chr, const void *buf, size_t pos, size_t lim); + ssize_t (*read) (struct chrdev *chr, void *buf, size_t pos, size_t lim); +}; + +ssize_t chr_write(struct chrdev *chr, const void *buf, size_t pos, size_t lim); +ssize_t chr_read(struct chrdev *chr, void *buf, size_t pos, size_t lim); diff --git a/include/sys/fs/dirent.h b/include/sys/fs/dirent.h new file mode 100644 index 0000000..7709b8e --- /dev/null +++ b/include/sys/fs/dirent.h @@ -0,0 +1,10 @@ +#pragma once +#include + +struct dirent { + uint32_t d_ino; + uint32_t d_off; + uint16_t d_reclen; + uint8_t d_type; + char d_name[]; +}; diff --git a/include/sys/fs/ext2.h b/include/sys/fs/ext2.h new file mode 100644 index 0000000..4908fbd --- /dev/null +++ b/include/sys/fs/ext2.h @@ -0,0 +1,147 @@ +#pragma once +#include +#include "fs.h" + +#define EXT2_MAGIC ((uint16_t) 0xEF53) + +#define EXT2_SBSIZ 1024 +#define EXT2_SBOFF 1024 + +#define EXT2_ROOTINO 2 + +#define EXT2_GOOD ((uint16_t) 1) +#define EXT2_BAD ((uint16_t) 2) + +#define EXT2_EACT_IGN ((uint16_t) 1) +#define EXT2_EACT_REM ((uint16_t) 2) +#define EXT2_EACT_PAN ((uint16_t) 3) + +#define EXT2_TYPE_REG ((uint16_t) 0x8000) +#define EXT2_TYPE_DIR ((uint16_t) 0x4000) +#define EXT2_TYPE_LNK ((uint16_t) 0xA000) + +struct ext2_sb { + uint32_t inode_count; + uint32_t block_count; + uint32_t su_reserved; + uint32_t free_block_count; + uint32_t free_inode_count; + uint32_t sb_block_number; + uint32_t block_size_log; + uint32_t frag_size_log; + uint32_t block_group_size_blocks; + uint32_t block_group_size_frags; + uint32_t block_group_size_inodes; + uint32_t last_mount_time; + uint32_t last_mtime; + uint16_t mount_count_since_fsck; + uint16_t mount_max_before_fsck; + uint16_t magic; + uint16_t fs_state; + uint16_t error_action; + uint16_t version_minor; + uint32_t last_fsck_time; + uint32_t os_id; + uint32_t version_major; + uint16_t su_uid; + uint16_t su_gid; +} __attribute__((packed)); + +struct ext2_extsb { + struct ext2_sb sb; + uint32_t first_non_reserved; + uint16_t inode_struct_size; + uint16_t backup_group_number; + uint32_t optional_features; + uint32_t required_features; + uint32_t ro_required_features; + char fsid[16]; + char volname[16]; + char last_mount_path[64]; + uint32_t compression; + uint8_t prealloc_file_block_number; + uint8_t prealloc_dir_block_number; + uint16_t __un0; + char journal_id[16]; + uint32_t journal_inode; + uint32_t journal_dev; + uint32_t orphan_inode_head; + + // driver-specific info + uint32_t block_size; + uint32_t block_group_count; + uint32_t block_group_descriptor_table_block; + uint32_t block_group_descriptor_table_size_blocks; + struct ext2_grp_desc *block_group_descriptor_table; +} __attribute__((packed)); + +struct ext2_grp_desc { + uint32_t block_usage_bitmap_block; + uint32_t inode_usage_bitmap_block; + uint32_t inode_table_block; + uint16_t free_blocks; + uint16_t free_inodes; + uint16_t dir_count; + char __pad[14]; +} __attribute__((packed)); + +struct ext2_inode { + uint16_t type_perm; + uint16_t uid; + uint32_t size_lower; + uint32_t atime; + uint32_t ctime; + uint32_t mtime; + uint32_t dtime; + uint16_t gid; + uint16_t hard_link_count; + uint32_t disk_sector_count; + uint32_t flags; + uint32_t os_value_1; + uint32_t direct_blocks[12]; + uint32_t l1_indirect_block; + uint32_t l2_indirect_block; + uint32_t l3_indirect_block; + uint32_t gen_number; + uint32_t acl; + union { + uint32_t dir_acl; + uint32_t size_upper; + }; + uint32_t frag_block_addr; + char os_value_2[12]; +} __attribute__((packed)); + +struct ext2_dirent { + uint32_t ino; + uint16_t len; + uint8_t name_len; + uint8_t type_ind; + char name[]; +} __attribute__((packed)); + +void ext2_class_init(void); +enum vnode_type ext2_inode_type(struct ext2_inode *i); + +// Implemented in ext2blk.c +int ext2_write_superblock(fs_t *ext2); +int ext2_read_block(fs_t *ext2, uint32_t block_no, void *buf); +int ext2_write_block(fs_t *ext2, uint32_t block_no, const void *buf); +int ext2_read_inode_block(fs_t *ext2, struct ext2_inode *inode, uint32_t index, void *buf); +int ext2_write_inode_block(fs_t *ext2, struct ext2_inode *inode, uint32_t index, const void *buf); +int ext2_read_inode(fs_t *ext2, struct ext2_inode *inode, uint32_t ino); +int ext2_write_inode(fs_t *ext2, const struct ext2_inode *inode, uint32_t ino); + +// Implemented in ext2alloc.c +int ext2_alloc_block(fs_t *ext2, uint32_t *block_no); +int ext2_free_block(fs_t *ext2, uint32_t block_no); +int ext2_inode_alloc_block(fs_t *ext2, struct ext2_inode *inode, uint32_t ino, uint32_t index); +int ext2_free_inode_block(fs_t *ext2, struct ext2_inode *inode, uint32_t ino, uint32_t index); +int ext2_free_inode(fs_t *ext2, uint32_t ino); +int ext2_alloc_inode(fs_t *ext2, uint32_t *ino); + +// Implemented in ext2dir.c +int ext2_dir_add_inode(fs_t *ext2, vnode_t *dir, const char *name, uint32_t ino); +int ext2_dir_remove_inode(fs_t *ext2, vnode_t *dir, const char *name, uint32_t ino); + +extern struct vnode_operations ext2_vnode_ops; diff --git a/include/sys/fs/fcntl.h b/include/sys/fs/fcntl.h new file mode 100644 index 0000000..7c4ba1b --- /dev/null +++ b/include/sys/fs/fcntl.h @@ -0,0 +1,31 @@ +#pragma once + +#define R_OK 4 +#define W_OK 2 +#define X_OK 1 +#define F_OK 0 + + +// O_EXEC is a special one for opening a node for +// execution +#define O_EXEC (1 << 2) + +#define O_ACCMODE 00000003 +#define O_RDONLY 00000000 +#define O_WRONLY 00000001 +#define O_RDWR 00000002 +#define O_CREAT 00000100 +// #define O_EXCL 00000200 +// #define O_NOCTTY 00000400 +#define O_TRUNC 00001000 +#define O_APPEND 00002000 +// #define O_NONBLOCK 00004000 +// #define O_DSYNC 00010000 +// #define FASYNC 00020000 +// #define O_DIRECT 00040000 +// #define O_LARGEFILE 00100000 +#define O_DIRECTORY 00200000 +// #define O_NOFOLLOW 00400000 +// #define O_NOATIME 01000000 +// #define O_CLOEXEC 02000000 + diff --git a/include/sys/fs/fs.h b/include/sys/fs/fs.h new file mode 100644 index 0000000..21ea879 --- /dev/null +++ b/include/sys/fs/fs.h @@ -0,0 +1,47 @@ +#pragma once +#include "sys/fs/node.h" +#include "sys/blk.h" + +// Means that the filesystem has a weird way of storing hierarchical +// nodes and employs a custom mapping algorithm. This ensures that +// at mount time the whole node tree for the filesystem is mapped +// to the VFS tree and the nodes do not get deallocated until the +// FS is unmounted. +// Note: when this flag is on, get_root is not used, as the filesystem +// produces the VFS tree all by itself. +#define FS_NODE_MAPPER (1 << 0) + +struct statvfs; +struct vfs_node; + +struct fs_class { + char name[256]; + + int opt; + + vnode_t *(*get_root) (fs_t *fs); + int (*mount) (fs_t *fs, const char *opt); + int (*umount) (fs_t *fs); + int (*statvfs) (fs_t *fs, struct statvfs *st); + int (*mapper) (fs_t *fs, struct vfs_node **root); +}; + +// The actual filesystem instance, +// one per each mount +struct fs { + // Mount details here + vnode_t *mnt_at; + + // Block device on which the filesystem resides + struct blkdev *blk; + + // Private data structure per-mount + void *fs_private; + + struct fs_class *cls; +}; + +struct fs *fs_create(struct fs_class *cls, struct blkdev *blk, vnode_t *mnt_at); +void fs_release(struct fs *fs); +struct fs_class *fs_class_by_name(const char *name); +int fs_class_register(struct fs_class *cls); diff --git a/include/sys/fs/hash.h b/include/sys/fs/hash.h new file mode 100644 index 0000000..38c5379 --- /dev/null +++ b/include/sys/fs/hash.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +typedef struct hash_entry { + uint64_t key; + void *value; + struct hash_entry *next; +} hash_entry_t; + +typedef struct { + int (*keycmp) (uint64_t, uint64_t); + uint64_t (*keyhsh) (uint64_t); + uint64_t (*keydup) (uint64_t); + void (*keyfree) (uint64_t); + void (*valfree) (void *); + + size_t bucket_count; + size_t item_count; + hash_entry_t **buckets; +} hash_t; + + +void hash_init(hash_t *h, int nb); +void hash_walk(hash_t *h, int (*wlkfn) (hash_entry_t *)); +void hash_clear(hash_t *h); + +void hash_put(hash_t *h, uint64_t key, void *value); +int hash_del(hash_t *h, uint64_t key); +int hash_get(hash_t *h, uint64_t key, void **value); + +// Helper functions +int hash_u64_keycmp(uint64_t v0, uint64_t v1); +uint64_t hash_u64_keyhsh(uint64_t v0); diff --git a/include/sys/fs/list.h b/include/sys/fs/list.h new file mode 100644 index 0000000..1effb64 --- /dev/null +++ b/include/sys/fs/list.h @@ -0,0 +1,7 @@ +#pragma once + +typedef struct list_simple { + void *car; + struct list_simple *cdr; +} list_simple_t; + diff --git a/include/sys/fs/node.h b/include/sys/fs/node.h new file mode 100644 index 0000000..8e4430d --- /dev/null +++ b/include/sys/fs/node.h @@ -0,0 +1,80 @@ +/** vim: ft=c.doxygen + * @file node.h + * @brief vnode + */ +#pragma once +#include +#include "sys/types.h" +#include "dirent.h" +#include "stat.h" + +struct ofile; +struct vfs_ioctx; +typedef struct vnode vnode_t; +typedef struct fs fs_t; + +enum vnode_type { + VN_REG, + VN_DIR, + VN_BLK, + VN_CHR, + VN_LNK +}; + +/** + * @brief Set of functions implemented by filesystem driver + */ +struct vnode_operations { + // File tree traversal, node instance operations + int (*find) (vnode_t *node, const char *path, vnode_t **res); + void (*destroy) (vnode_t *node); + + // Symlink + int (*readlink) (vnode_t *node, char *path); + int (*symlink) (vnode_t *at, struct vfs_ioctx *ctx, const char *name, const char *dst); + + // File entry operations + int (*access) (vnode_t *node, uid_t *uid, gid_t *gid, mode_t *mode); + int (*creat) (vnode_t *node, struct vfs_ioctx *ctx, const char *name, mode_t mode, int opt, vnode_t **res); + int (*mkdir) (vnode_t *at, const char *name, mode_t mode); + int (*unlink) (vnode_t *at, vnode_t *vn, const char *name); + int (*stat) (vnode_t *node, struct stat *st); + int (*chmod) (vnode_t *node, mode_t mode); + int (*chown) (vnode_t *node, uid_t uid, gid_t gid); + + // Directory access + int (*opendir) (vnode_t *node, int opt); + int (*readdir) (struct ofile *fd); + + // File access + int (*open) (vnode_t *node, int opt); + void (*close) (struct ofile *fd); + ssize_t (*read) (struct ofile *fd, void *buf, size_t count); + ssize_t (*write) (struct ofile *fd, const void *buf, size_t count); + int (*truncate) (struct ofile *fd, size_t length); +}; + +struct vnode { + enum vnode_type type; + + uint32_t refcount; + + fs_t *fs; + // Private filesystem-specific data (like inode struct) + void *fs_data; + // Private filesystem-specific number (like inode number) + uint32_t fs_number; + + /* + * (struct blkdev *) if type == VN_BLK + * (struct chrdev *) if type == VN_CHR + */ + void *dev; + void *tree_node; + + struct vnode_operations *op; +}; + +void vnode_ref(vnode_t *vn); +void vnode_unref(vnode_t *vn); +void vnode_free(vnode_t *vn); diff --git a/include/sys/fs/ofile.h b/include/sys/fs/ofile.h new file mode 100644 index 0000000..f601895 --- /dev/null +++ b/include/sys/fs/ofile.h @@ -0,0 +1,11 @@ +#pragma once +#include "node.h" +#include + +struct ofile { + int flags; + vnode_t *vnode; + size_t pos; + // Dirent buffer + char dirent_buf[512]; +}; diff --git a/include/sys/fs/stat.h b/include/sys/fs/stat.h new file mode 100644 index 0000000..f6f7702 --- /dev/null +++ b/include/sys/fs/stat.h @@ -0,0 +1,59 @@ +/** vi: ft=c.doxygen : + * @file stat.h + * @brief File status structure + */ +#pragma once +#include +#include "sys/types.h" + +#define S_IFMT 0170000 +#define S_IFSOCK 0140000 +#define S_IFLNK 0120000 +#define S_IFREG 0100000 +#define S_IFBLK 0060000 +#define S_IFDIR 0040000 +#define S_IFCHR 0020000 +#define S_IFIFO 0010000 + +#define S_ISSOCK(m) ((m & S_IFMT) == S_IFSOCK) +#define S_ISLNK(m) ((m & S_IFMT) == S_IFLNK) +#define S_ISREG(m) ((m & S_IFMT) == S_IFREG) +#define S_ISBLK(m) ((m & S_IFMT) == S_IFBLK) +#define S_ISDIR(m) ((m & S_IFMT) == S_IFDIR) +#define S_ISCHR(m) ((m & S_IFMT) == S_IFCHR) +#define S_ISFIFO(m) ((m & S_IFMT) == S_IFIFO) + +#define S_ISUID 04000 +#define S_ISGID 02000 +#define S_ISVTX 01000 + +#define S_IRUSR 00400 +#define S_IWUSR 00200 +#define S_IXUSR 00100 +#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) + +#define S_IRGRP 00040 +#define S_IWGRP 00020 +#define S_IXGRP 00010 +#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) + +#define S_IROTH 00004 +#define S_IWOTH 00002 +#define S_IXOTH 00001 +#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) + +struct stat { + uint32_t st_dev; + uint32_t st_ino; + mode_t st_mode; + uint32_t st_nlink; + uid_t st_uid; + gid_t st_gid; + uint32_t st_rdev; + uint32_t st_size; + uint32_t st_blksize; + uint32_t st_blocks; + uint32_t st_atime; + uint32_t st_mtime; + uint32_t st_ctime; +}; diff --git a/include/sys/fs/statvfs.h b/include/sys/fs/statvfs.h new file mode 100644 index 0000000..5e9fdf2 --- /dev/null +++ b/include/sys/fs/statvfs.h @@ -0,0 +1,22 @@ +/** vi: ft=c.doxygen : + * @file statvfs.h + * @brief Filesystem status information + */ +#pragma once +#include + +typedef uint64_t fsblkcnt_t; + +struct statvfs { + uint64_t f_bsize; + uint64_t f_frsize; + fsblkcnt_t f_blocks; + fsblkcnt_t f_bfree; + fsblkcnt_t f_bavail; + fsblkcnt_t f_files; + fsblkcnt_t f_ffree; + fsblkcnt_t f_favail; + uint64_t f_fsid; + uint64_t f_flag; + uint64_t f_namemax; +}; diff --git a/include/sys/fs/tar.h b/include/sys/fs/tar.h new file mode 100644 index 0000000..a5fccb7 --- /dev/null +++ b/include/sys/fs/tar.h @@ -0,0 +1,4 @@ +#pragma once +#include "sys/fs/fs.h" + +void tarfs_init(void); diff --git a/include/sys/fs/tree.h b/include/sys/fs/tree.h new file mode 100644 index 0000000..4a1f808 --- /dev/null +++ b/include/sys/fs/tree.h @@ -0,0 +1,8 @@ +#pragma once +#include "list.h" + +typedef struct tree { + void *value; + struct tree *parent; + list_simple_t *children; +} tree_t; diff --git a/include/sys/fs/vfs.h b/include/sys/fs/vfs.h new file mode 100644 index 0000000..b561cf8 --- /dev/null +++ b/include/sys/fs/vfs.h @@ -0,0 +1,150 @@ +/** vi: ft=c.doxygen : + * @file vfs.h + * @brief Virtual filesystem (VFS) core functions + */ +#pragma once +#include "tree.h" +#include "node.h" +#include "ofile.h" +#include "stat.h" +#include "statvfs.h" + +/** + * @brief Internal VFS tree node. + * As the actual vnodes are not associated + * with any location in the filesystem tree, + * we use this struct to keep things in order. + */ +struct vfs_node { + char name[256]; + // Current vnode unless mnt, otherwise the root node of + // the mounted filesystem + vnode_t *vnode; + // Real vnode if mountpoint + vnode_t *real_vnode; + // Is 1 when node is a mountpoint + int ismount; + // Parent ref + struct vfs_node *parent; + // Linked list of children + struct vfs_node *child; + struct vfs_node *cdr; +}; + +/** + * @brief Process-specific I/O context details + * @note This will be moved to other headers once + * I integrate the VFS project into my kernel. + */ +struct vfs_ioctx { + // Process' current working directory + vnode_t *cwd_vnode; + uid_t uid; + gid_t gid; +}; + +/** + * @brief Set process' current working directory + * @param ctx I/O context + * @param cwd New CWD + * @return 0 on success, negative values on error + */ +int vfs_setcwd(struct vfs_ioctx *ctx, const char *cwd); +/** + * @brief Set process' working directory relative to the + * current one. + * @param ctx I/O context + * @param cwd_rel Relative/absolute path + * @return 0 on success, negative values on error + */ +int vfs_chdir(struct vfs_ioctx *ctx, const char *cwd_rel); + + +/** + * @brief Initialize the VFS + */ +void vfs_init(void); +/** + * @brief Dump the internal filesystem representation + */ +void vfs_dump_tree(void); +/** + * @brief Get a string representation of a path + * inside a filesystem tree. + * @param path The result path + * @param vn vnode + */ +void vfs_vnode_path(char *path, vnode_t *vn); + +// Tree node ops +/** + * @brief Free internal VFS node struct + * @param n Tree node + */ +void vfs_node_free(struct vfs_node *n); +/** + * @brief Allocate a new node and associate it with + * a vnode + * @param name Name to be given to the tree node + * @param vn The vnode to bind it to + * @return Non-NULL (struct vfs_node *) on success, NULL otherwise + */ +struct vfs_node *vfs_node_create(const char *name, vnode_t *vn); + +/** + * @brief Create a mountpoint in a filesystem tree so that + * a "source" directory actually refers to the root + * node of a "target" filesystem. + * @param ctx I/O context + * @param at Path to create mountpoint at + * @param blk Block device on which a filesystem shall be mounted + * @param fs_name Filesystem name + * @param fs_opt Filesystem mounting options + * @return 0 on success, + * -EACCES if the caller context's UID is not zero, + * -EBUSY if trying to create a mountpoint at a + * directory which is not empty, + * -ENOENT if the directory does not exist + */ +int vfs_mount(struct vfs_ioctx *ctx, const char *at, void *blk, const char *fs_name, const char *fs_opt); +/** + * @brief Destroy a mountpoint in a filesystem tree + * @param ctx I/O context + * @param target Path to unmount + * @return 0 on success, + * -EBUSY if there are any used filesystem objects inside + * the mountpoint, + * -EACCES if the caller context's UID is not zero, + * -ENOENT if the directory does not exist + */ +int vfs_umount(struct vfs_ioctx *ctx, const char *target); + +/** + * @brief Read symlink's destination path into a buffer + * @param ctx I/O context + * @param path + */ +int vfs_readlink(struct vfs_ioctx *ctx, const char *path, char *buf); +int vfs_readlinkat(struct vfs_ioctx *ctx, vnode_t *at, const char *path, char *buf); +int vfs_symlink(struct vfs_ioctx *ctx, const char *target, const char *linkpath); + +// File ops +int vfs_truncate(struct vfs_ioctx *ctx, struct ofile *fd, size_t length); +int vfs_creat(struct vfs_ioctx *ctx, struct ofile *fd, const char *path, int mode, int opt); +int vfs_open(struct vfs_ioctx *ctx, struct ofile *fd, const char *path, int mode, int opt); +int vfs_open_node(struct vfs_ioctx *ctx, struct ofile *fd, vnode_t *vn, int opt); +void vfs_close(struct vfs_ioctx *ctx, struct ofile *fd); +ssize_t vfs_read(struct vfs_ioctx *ctx, struct ofile *fd, void *buf, size_t count); +ssize_t vfs_write(struct vfs_ioctx *ctx, struct ofile *fd, const void *buf, size_t count); +int vfs_unlink(struct vfs_ioctx *ctx, const char *path); + +int vfs_stat(struct vfs_ioctx *ctx, const char *path, struct stat *st); +int vfs_statat(struct vfs_ioctx *ctx, vnode_t *at, const char *path, struct stat *st); +int vfs_chmod(struct vfs_ioctx *ctx, const char *path, mode_t mode); +int vfs_chown(struct vfs_ioctx *ctx, const char *path, uid_t uid, gid_t gid); +int vfs_access(struct vfs_ioctx *ctx, const char *path, int mode); +// Directroy ops +int vfs_mkdir(struct vfs_ioctx *ctx, const char *path, mode_t mode); +struct dirent *vfs_readdir(struct vfs_ioctx *ctx, struct ofile *fd); + +int vfs_statvfs(struct vfs_ioctx *ctx, const char *path, struct statvfs *st); diff --git a/include/sys/string.h b/include/sys/string.h index b091dfd..e0775eb 100644 --- a/include/sys/string.h +++ b/include/sys/string.h @@ -5,6 +5,7 @@ #pragma once #include "sys/types.h" +#define MAX(x, y) ((x) > (y) ? (x) : (y)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) void *memcpy(void *restrict dst, const void *restrict src, size_t cnt); diff --git a/include/sys/tty.h b/include/sys/tty.h new file mode 100644 index 0000000..41ff432 --- /dev/null +++ b/include/sys/tty.h @@ -0,0 +1,7 @@ +#pragma once +#include "sys/fs/node.h" + +extern vnode_t *tty0; + +void tty_buffer_write(int n, char c); +void tty_init(void); diff --git a/sys/amd64/hw/ide/ahci.c b/sys/amd64/hw/ide/ahci.c index 0bb6df2..8434889 100644 --- a/sys/amd64/hw/ide/ahci.c +++ b/sys/amd64/hw/ide/ahci.c @@ -46,7 +46,7 @@ static ssize_t ahci_blk_read(struct blkdev *blk, void *buf, size_t off, size_t c // TODO: error? ahci_sata_read((struct ahci_port_registers *) blk->dev_data, buf, nsect, lba); - return 0; + return count; } static int ahci_add_port_dev(struct ahci_port_registers *port) { diff --git a/sys/amd64/kernel.c b/sys/amd64/kernel.c index 76a2608..7590a7b 100644 --- a/sys/amd64/kernel.c +++ b/sys/amd64/kernel.c @@ -13,6 +13,7 @@ #include "sys/amd64/hw/ps2.h" #include "sys/panic.h" #include "sys/assert.h" +#include "sys/fs/vfs.h" static multiboot_info_t *multiboot_info; @@ -37,6 +38,8 @@ void kernel_main(struct amd64_loader_data *data) { amd64_apic_init(); pci_init(); + vfs_init(); + #if defined(AMD64_SMP) amd64_smp_init(); #endif diff --git a/sys/chr.c b/sys/chr.c new file mode 100644 index 0000000..8498875 --- /dev/null +++ b/sys/chr.c @@ -0,0 +1,22 @@ +#include "sys/chr.h" +#include "sys/errno.h" + +ssize_t chr_write(struct chrdev *chr, const void *buf, size_t off, size_t count) { + if (!chr) { + return -ENODEV; + } + if (!chr->write) { + return -EROFS; + } + return chr->write(chr, buf, off, count); +} + +ssize_t chr_read(struct chrdev *chr, void *buf, size_t off, size_t count) { + if (!chr) { + return -ENODEV; + } + if (!chr->read) { + return -EINVAL; + } + return chr->read(chr, buf, off, count); +} diff --git a/sys/tty.c b/sys/tty.c new file mode 100644 index 0000000..d9ce240 --- /dev/null +++ b/sys/tty.c @@ -0,0 +1,108 @@ +#include "sys/tty.h" +#include "sys/chr.h" +#include "sys/errno.h" +#include "sys/debug.h" +#include "sys/panic.h" +#include "sys/string.h" +#include "sys/mm.h" + +#define DEV_TTY(n) (n ## ULL) +#define DEV_DATA_TTY(n) ((void *) n ## ULL) +#define DEV_DATA_GETTTY(d) ((uint64_t) (d)->dev_data) + +static ssize_t tty_write(struct chrdev *tty, const void *buf, size_t pos, size_t lim); +static ssize_t tty_read(struct chrdev *tty, void *buf, size_t pos, size_t lim); + +static struct chrdev _dev_tty0 = { + .name = "tty0", + .dev_data = DEV_DATA_TTY(0), + .write = tty_write, + .read = tty_read +}; + +static vnode_t _tty0 = { + .type = VN_CHR, + .dev = &_dev_tty0 +}; +vnode_t *tty0 = &_tty0; + +// TODO: this seems very ugly +static char *_tty0_reading_now = NULL; +static size_t _tty0_left = 0; + +// This function receives keystrokes from keyboard drivers +void tty_buffer_write(int tty_no, char c) { + asm volatile ("cli"); + if (tty_no != 0) { + panic("Not implemented\n"); + } + + if (!_tty0_left) { + // Ignore keystrokes sent while we're not reading + // This is bad and should be reimplemented by + // storing key data inside a ring buffer + return; + } + + // No safety + *_tty0_reading_now++ = c; + --_tty0_left; +} + +void tty_init(void) { +} + +// TODO: multiple ttys +static ssize_t tty_write(struct chrdev *tty, const void *buf, size_t pos, size_t lim) { + uint64_t tty_no = DEV_DATA_GETTTY(tty); + + if (tty_no != DEV_TTY(0)) { + return -EINVAL; + } + + // TODO: buffer sanity checks + for (size_t i = 0; i < lim; ++i) { + debugc(DEBUG_INFO, ((const char *) buf)[i]); + } + return lim; +} + +static ssize_t tty_read(struct chrdev *tty, void *buf, size_t pos, size_t lim) { + uint64_t tty_no = DEV_DATA_GETTTY(tty); + char ibuf[16]; + + if (tty_no != DEV_TTY(0)) { + return -EINVAL; + } + + if (lim == 0) { + return 0; + } + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + size_t read = 0; + + while (lim > 0) { + // XXX: This is very ugly + // Better just send the task to some kind of "busy" + // state and then wait + size_t read_now = MIN(16, lim); + _tty0_left = read_now; + _tty0_reading_now = ibuf; + while (1) { + asm volatile ("cli"); + if (_tty0_left == 0) { + break; + } + asm volatile ("sti; hlt"); + } + // Read 16 byte block + lim -= read_now; + // TODO: memcpy_kernel_to_user + memcpy((void *) ((uintptr_t) buf + read), ibuf, read_now); + read += read_now; + } + + + return 16; +} diff --git a/sys/vfs/ext2/ext2.c b/sys/vfs/ext2/ext2.c new file mode 100644 index 0000000..e949d7f --- /dev/null +++ b/sys/vfs/ext2/ext2.c @@ -0,0 +1,152 @@ +#include "sys/fs/ext2.h" +#include "sys/fs/vfs.h" +#include "sys/debug.h" +#include "sys/panic.h" +#include "sys/errno.h" +#include "sys/heap.h" + +enum vnode_type ext2_inode_type(struct ext2_inode *i) { + uint16_t v = i->type_perm & 0xF000; + switch (v) { + case EXT2_TYPE_DIR: + return VN_DIR; + case EXT2_TYPE_REG: + return VN_REG; + case EXT2_TYPE_LNK: + return VN_LNK; + default: + // fprintf(stderr, "Unknown file type: %x\n", v); + // abort(); + panic("Unknown file type: %x\n", v); + return 0; + } +} + +static int ext2_fs_mount(fs_t *fs, const char *opt) { + int res; + kdebug("ext2_fs_mount()\n"); + struct ext2_extsb *sb; + // ext2's private data is its superblock structure + sb = (struct ext2_extsb *) kmalloc(EXT2_SBSIZ); + fs->fs_private = sb; + + // Read the superblock from blkdev + if ((res = blk_read(fs->blk, sb, EXT2_SBOFF, EXT2_SBSIZ)) != EXT2_SBSIZ) { + kfree(fs->fs_private); + + kerror("ext2: superblock read failed\n"); + return -EINVAL; + } + + // Check if superblock is ext2 + if (sb->sb.magic != EXT2_MAGIC) { + kfree(sb); + + kdebug("ext2: magic mismatch\n"); + return -EINVAL; + } + + // Check if we have an extended ext2 sb + if (sb->sb.version_major == 0) { + // Initialize params which are missing in non-extended sbs + sb->inode_struct_size = 128; + sb->first_non_reserved = 11; + } + sb->block_size = 1024 << sb->sb.block_size_log; + + // Load block group descriptor table + // Get descriptor table size + uint32_t block_group_descriptor_table_length = sb->sb.block_count / sb->sb.block_group_size_blocks; + if (block_group_descriptor_table_length * sb->sb.block_group_size_blocks < sb->sb.block_count) { + ++block_group_descriptor_table_length; + } + sb->block_group_count = block_group_descriptor_table_length; + + uint32_t block_group_descriptor_table_size_blocks = 32 * block_group_descriptor_table_length / + sb->block_size + 1; + + uint32_t block_group_descriptor_table_block = 2; + if (sb->block_size > 1024) { + block_group_descriptor_table_block = 1; + } + sb->block_group_descriptor_table_block = block_group_descriptor_table_block; + sb->block_group_descriptor_table_size_blocks = block_group_descriptor_table_size_blocks; + + // Load all block group descriptors into memory + kdebug("Allocating %u bytes for BGDT\n", sb->block_group_descriptor_table_size_blocks * sb->block_size); + sb->block_group_descriptor_table = (struct ext2_grp_desc *) kmalloc(sb->block_group_descriptor_table_size_blocks * sb->block_size); + + for (size_t i = 0; i < sb->block_group_descriptor_table_size_blocks; ++i) { + ext2_read_block(fs, i + sb->block_group_descriptor_table_block, + (void *) (((uintptr_t) sb->block_group_descriptor_table) + i * sb->block_size)); + } + + return 0; +} + +static int ext2_fs_umount(fs_t *fs) { + struct ext2_extsb *sb = (struct ext2_extsb *) fs->fs_private; + // Free block group descriptor table + kfree(sb->block_group_descriptor_table); + // Free superblock + kfree(sb); + return 0; +} + +static vnode_t *ext2_fs_get_root(fs_t *fs) { + struct ext2_extsb *sb = fs->fs_private; + kdebug("ext2_fs_get_root()\n"); + + struct ext2_inode *inode = (struct ext2_inode *) kmalloc(sb->inode_struct_size); + // Read root inode (2) + if (ext2_read_inode(fs, inode, EXT2_ROOTINO) != 0) { + kfree(inode); + return NULL; + } + + vnode_t *res = (vnode_t *) kmalloc(sizeof(vnode_t)); + + res->fs = fs; + res->fs_data = inode; + res->fs_number = EXT2_ROOTINO; + res->op = &ext2_vnode_ops; + res->type = ext2_inode_type(inode); + + return res; +} + +static int ext2_fs_statvfs(fs_t *fs, struct statvfs *st) { + struct ext2_extsb *sb = fs->fs_private; + + st->f_blocks = sb->sb.block_count; + st->f_bfree = sb->sb.free_block_count; + st->f_bavail = sb->sb.block_count - sb->sb.su_reserved; + + st->f_files = sb->sb.inode_count; + st->f_ffree = sb->sb.free_inode_count; + st->f_favail = sb->sb.inode_count - sb->first_non_reserved + 1; + + st->f_bsize = sb->block_size; + st->f_frsize = sb->block_size; + + // XXX: put something here + st->f_fsid = 0; + st->f_flag = 0; + st->f_namemax = 256; + + return 0; +} + + +static struct fs_class ext2_class = { + .name = "ext2", + .get_root = ext2_fs_get_root, + .mount = ext2_fs_mount, + .umount = ext2_fs_umount, + .statvfs = ext2_fs_statvfs +}; + +void ext2_class_init(void) { + fs_class_register(&ext2_class); +} + diff --git a/sys/vfs/ext2/ext2alloc.c b/sys/vfs/ext2/ext2alloc.c new file mode 100644 index 0000000..3780b13 --- /dev/null +++ b/sys/vfs/ext2/ext2alloc.c @@ -0,0 +1,319 @@ +// ext2fs block/inode alloc/free +#include "sys/fs/ext2.h" +#include "sys/string.h" +#include "sys/assert.h" +#include "sys/debug.h" +#include "sys/errno.h" + +// #include +// #include +// #include +// #include + +int ext2_alloc_block(fs_t *ext2, uint32_t *block_no) { + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + char block_buffer[sb->block_size]; + uint32_t res_block_no = 0; + uint32_t res_group_no = 0; + uint32_t res_block_no_in_group = 0; + int found = 0; + int res; + + for (size_t i = 0; i < sb->block_group_count; ++i) { + if (sb->block_group_descriptor_table[i].free_blocks > 0) { + // Found a free block here + kdebug("Allocating a block in group #%zu\n", i); + + if ((res = ext2_read_block(ext2, + sb->block_group_descriptor_table[i].block_usage_bitmap_block, + block_buffer)) < 0) { + return res; + } + + for (size_t j = 0; j < sb->block_size / sizeof(uint64_t); ++j) { + uint64_t qw = ((uint64_t *) block_buffer)[j]; + if (qw != (uint64_t) -1) { + for (size_t k = 0; k < 64; ++k) { + if (!(qw & (1 << k))) { + res_block_no_in_group = k + j * 64; + res_group_no = i; + // XXX: had to increment the resulting block_no + // because for some reason linux's ext2 + // impl hasn't marked #531 as used in one + // case, but it was actually a L1-indirect + // block. So I just had to make it allocate + // #532 instead as a workaround (though + // block numbering should start with 0) + res_block_no = res_block_no_in_group + i * sb->sb.block_group_size_blocks + 1; + found = 1; + break; + } + } + + if (found) { + break; + } + } + } + if (found) { + break; + } + } + } + + if (!found) { + return -ENOSPC; + } + + // Write block usage bitmap + ((uint64_t *) block_buffer)[res_block_no_in_group / 64] |= (1 << (res_block_no_in_group % 64)); + if ((res = ext2_write_block(ext2, + sb->block_group_descriptor_table[res_group_no].block_usage_bitmap_block, + block_buffer)) < 0) { + return res; + } + + // Update BGDT + --sb->block_group_descriptor_table[res_group_no].free_blocks; + for (size_t i = 0; i < sb->block_group_descriptor_table_size_blocks; ++i) { + void *blk_ptr = (void *) (((uintptr_t) sb->block_group_descriptor_table) + i * sb->block_size); + + if ((res = ext2_write_block(ext2, sb->block_group_descriptor_table_block + i, blk_ptr)) < 0) { + return res; + } + } + + // Update global block count and flush superblock + --sb->sb.free_block_count; + if ((res = ext2_write_superblock(ext2)) < 0) { + return res; + } + + *block_no = res_block_no; + kdebug("Allocated block #%u\n", res_block_no); + return 0; +} + +int ext2_free_block(fs_t *ext2, uint32_t block_no) { + _assert(block_no); + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + char block_buffer[sb->block_size]; + int res; + + uint32_t block_group_no = (block_no - 1) / sb->sb.block_group_size_blocks; + uint32_t block_no_in_group = (block_no - 1) % sb->sb.block_group_size_blocks; + + // Read block ussge bitmap block + if ((res = ext2_read_block(ext2, + sb->block_group_descriptor_table[block_group_no].block_usage_bitmap_block, + block_buffer)) < 0) { + return res; + } + + // Update the bitmap + _assert(((uint64_t *) block_buffer)[block_no_in_group / 64] & (1 << (block_no_in_group % 64))); + ((uint64_t *) block_buffer)[block_no_in_group / 64] &= ~(1 << (block_no_in_group % 64)); + + if ((res = ext2_write_block(ext2, + sb->block_group_descriptor_table[block_group_no].block_usage_bitmap_block, + block_buffer)) < 0) { + return res; + } + + // Update BGDT + ++sb->block_group_descriptor_table[block_group_no].free_blocks; + for (size_t i = 0; i < sb->block_group_descriptor_table_size_blocks; ++i) { + void *blk_ptr = (void *) (((uintptr_t) sb->block_group_descriptor_table) + i * sb->block_size); + + if ((res = ext2_write_block(ext2, sb->block_group_descriptor_table_block + i, blk_ptr)) < 0) { + return res; + } + } + + // Update global block count + ++sb->sb.free_block_count; + if ((res = ext2_write_superblock(ext2)) < 0) { + return res; + } + + kdebug("Freed block #%u\n", block_no); + + return 0; +} + +int ext2_inode_alloc_block(fs_t *ext2, struct ext2_inode *inode, uint32_t ino, uint32_t index) { + if (index >= 12) { + panic("Not implemented\n"); + } + + int res; + uint32_t block_no; + + // Allocate the block itself + if ((res = ext2_alloc_block(ext2, &block_no)) < 0) { + return res; + } + + // Write direct block list entry + inode->direct_blocks[index] = block_no; + + // Flush changes to the device + return ext2_write_inode(ext2, inode, ino); +} + +int ext2_free_inode_block(fs_t *ext2, struct ext2_inode *inode, uint32_t ino, uint32_t index) { + if (index >= 12) { + panic("Not implemented\n"); + } + // All sanity checks regarding whether the block is present + // at all are left to the caller + + int res; + uint32_t block_no; + + // Get block number + block_no = inode->direct_blocks[index]; + + // Free the block + if ((res = ext2_free_block(ext2, block_no)) < 0) { + return res; + } + + // Write updated inode + inode->direct_blocks[index] = 0; + return ext2_write_inode(ext2, inode, ino); +} + +int ext2_free_inode(fs_t *ext2, uint32_t ino) { + _assert(ino); + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + char block_buffer[sb->block_size]; + uint32_t ino_block_group_number = (ino - 1) / sb->sb.block_group_size_inodes; + uint32_t ino_inode_index_in_group = (ino - 1) % sb->sb.block_group_size_inodes; + int res; + + // Read inode usage block + if ((res = ext2_read_block(ext2, + sb->block_group_descriptor_table[ino_block_group_number].inode_usage_bitmap_block, + block_buffer)) < 0) { + return res; + } + + // Remove usage bit + _assert(((uint64_t *) block_buffer)[ino_inode_index_in_group / 64] & (1 << (ino_inode_index_in_group % 64))); + ((uint64_t *) block_buffer)[ino_inode_index_in_group / 64] &= ~(1 << (ino_inode_index_in_group % 64)); + + // Write modified bitmap back + if ((res = ext2_write_block(ext2, + sb->block_group_descriptor_table[ino_block_group_number].inode_usage_bitmap_block, + block_buffer)) < 0) { + return res; + } + + // Increment free inode count in BGDT entry and write it back + // TODO: this code is repetitive and maybe should be moved to + // ext2_bgdt_inode_inc/_dec() + ++sb->block_group_descriptor_table[ino_block_group_number].free_inodes; + for (size_t i = 0; i < sb->block_group_descriptor_table_size_blocks; ++i) { + void *blk_ptr = (void *) (((uintptr_t) sb->block_group_descriptor_table) + i * sb->block_size); + + if ((res = ext2_write_block(ext2, sb->block_group_descriptor_table_block + i, blk_ptr)) < 0) { + return res; + } + } + + // Update global inode count + ++sb->sb.free_inode_count; + if ((res = ext2_write_superblock(ext2)) < 0) { + return res; + } + + kdebug("Freed inode #%u\n", ino); + return 0; +} + +int ext2_alloc_inode(fs_t *ext2, uint32_t *ino) { + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + char block_buffer[sb->block_size]; + uint32_t res_ino = 0; + uint32_t res_group_no = 0; + uint32_t res_ino_number_in_group = 0; + int res; + + // Look through BGDT to find any block groups with free inodes + for (size_t i = 0; i < sb->block_group_count; ++i) { + if (sb->block_group_descriptor_table[i].free_inodes > 0) { + // Found a block group with free inodes + kdebug("Allocating an inode inside block group #%zu\n", i); + + // Read inode usage bitmap + if ((res = ext2_read_block(ext2, + sb->block_group_descriptor_table[i].inode_usage_bitmap_block, + block_buffer)) < 0) { + return res; + } + + // Find a free bit + // Think this should be fine on amd64 + for (size_t j = 0; j < sb->block_size / sizeof(uint64_t); ++j) { + // Get bitmap qword + uint64_t qw = ((uint64_t *) block_buffer)[j]; + // If not all bits are set in this qword, find exactly which one + if (qw != ((uint64_t) -1)) { + for (size_t k = 0; k < 64; ++k) { + if (!(qw & (1 << k))) { + res_ino_number_in_group = k + j * 64; + res_group_no = i; + res_ino = res_ino_number_in_group + i * sb->sb.block_group_size_inodes + 1; + break; + } + } + + if (res_ino) { + break; + } + } + + if (res_ino) { + break; + } + } + } + + if (res_ino) { + break; + } + } + if (res_ino == 0) { + return -ENOSPC; + } + + // Write updated bitmap + ((uint64_t *) block_buffer)[res_ino_number_in_group / 64] |= (1 << (res_ino_number_in_group % 64)); + if ((res = ext2_write_block(ext2, + sb->block_group_descriptor_table[res_group_no].inode_usage_bitmap_block, + block_buffer)) < 0) { + return res; + } + + // Write updated BGDT + --sb->block_group_descriptor_table[res_group_no].free_inodes; + for (size_t i = 0; i < sb->block_group_descriptor_table_size_blocks; ++i) { + void *blk_ptr = (void *) (((uintptr_t) sb->block_group_descriptor_table) + i * sb->block_size); + + if ((res = ext2_write_block(ext2, sb->block_group_descriptor_table_block + i, blk_ptr)) < 0) { + return res; + } + } + + // Update global inode count and flush superblock + --sb->sb.free_inode_count; + if ((res = ext2_write_superblock(ext2)) < 0) { + return res; + } + + *ino = res_ino; + + return 0; +} + diff --git a/sys/vfs/ext2/ext2blk.c b/sys/vfs/ext2/ext2blk.c new file mode 100644 index 0000000..daa2cd9 --- /dev/null +++ b/sys/vfs/ext2/ext2blk.c @@ -0,0 +1,138 @@ +#include "sys/fs/ext2.h" +#include "sys/string.h" +#include "sys/assert.h" +#include "sys/errno.h" +#include "sys/debug.h" + +#define ext2_super(e) ((struct ext2_extsb *) (e)->fs_private) + +int ext2_write_superblock(fs_t *ext2) { + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + return blk_write(ext2->blk, sb, EXT2_SBOFF, EXT2_SBSIZ); +} + +int ext2_read_block(fs_t *ext2, uint32_t block_no, void *buf) { + if (!block_no) { + return -1; + } + //printf("ext2_read_block %u\n", block_no); + int res = blk_read(ext2->blk, buf, block_no * ext2_super(ext2)->block_size, ext2_super(ext2)->block_size); + + if (res < 0) { + //fprintf(stderr, "ext2: Failed to read %uth block\n", block_no); + kerror("ext2: Failed to read %uth block\n", block_no); + } + + return res; +} + +int ext2_write_block(fs_t *ext2, uint32_t block_no, const void *buf) { + if (!block_no) { + return -1; + } + + int res = blk_write(ext2->blk, buf, block_no * ext2_super(ext2)->block_size, ext2_super(ext2)->block_size); + + if (res < 0) { + //fprintf(stderr, "ext2: Failed to write %uth block\n", block_no); + kerror("ext2: Failed to write %uth block\n", block_no); + } + + return res; +} + +int ext2_write_inode_block(fs_t *ext2, struct ext2_inode *inode, uint32_t index, const void *buf) { + if (index < 12) { + uint32_t block_number = inode->direct_blocks[index]; + return ext2_write_block(ext2, block_number, buf); + } else { + // TODO + //abort(); + panic("Not implemented\n"); + } +} + +int ext2_read_inode_block(fs_t *ext2, struct ext2_inode *inode, uint32_t index, void *buf) { + if (index < 12) { + // Use direct ptrs + uint32_t block_number = inode->direct_blocks[index]; + return ext2_read_block(ext2, block_number, buf); + } else { + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + // Use buf as indirection block buffer (I think we're allowed to do so) + + if (index < 12 + (sb->block_size / 4)) { + // Single indirection + if (ext2_read_block(ext2, inode->l1_indirect_block, buf) < 0) { + return -EIO; + } + + uint32_t block_number = ((uint32_t *) buf)[index - 12]; + return ext2_read_block(ext2, block_number, buf); + } else { + // Not implemented yet + return -EIO; + } + } +} + +int ext2_read_inode(fs_t *ext2, struct ext2_inode *inode, uint32_t ino) { + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + //printf("ext2_read_inode %d\n", ino); + char inode_block_buffer[sb->block_size]; + + uint32_t ino_block_group_number = (ino - 1) / sb->sb.block_group_size_inodes; + //printf("inode block group number = %d\n", ino_block_group_number); + uint32_t ino_inode_table_block = sb->block_group_descriptor_table[ino_block_group_number].inode_table_block; + //printf("inode table is at block %d\n", ino_inode_table_block); + uint32_t ino_inode_index_in_group = (ino - 1) % sb->sb.block_group_size_inodes; + //printf("inode entry index in the group = %d\n", ino_inode_index_in_group); + uint32_t ino_inode_block_in_group = (ino_inode_index_in_group * sb->inode_struct_size) / sb->block_size; + //printf("inode entry offset is %d blocks\n", ino_inode_block_in_group); + uint32_t ino_inode_block_number = ino_inode_block_in_group + ino_inode_table_block; + //printf("inode block number is %uth block\n", ino_inode_block_number); + + //struct ext2_inode *root_inode_block_inode_table = (struct ext2_inode *) root_inode_block_buf; + if (ext2_read_block(ext2, ino_inode_block_number, inode_block_buffer) < 0) { + kerror("ext2: failed to load inode#%d block\n", ino); + return -1; + } + + uint32_t ino_entry_in_block = (ino_inode_index_in_group * sb->inode_struct_size) % sb->block_size; + memcpy(inode, &inode_block_buffer[ino_entry_in_block], sb->inode_struct_size); + + return 0; +} + +int ext2_write_inode(fs_t *ext2, const struct ext2_inode *inode, uint32_t ino) { + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + //printf("ext2_read_inode %d\n", ino); + char inode_block_buffer[sb->block_size]; + int res; + + uint32_t ino_block_group_number = (ino - 1) / sb->sb.block_group_size_inodes; + //printf("inode block group number = %d\n", ino_block_group_number); + uint32_t ino_inode_table_block = sb->block_group_descriptor_table[ino_block_group_number].inode_table_block; + //printf("inode table is at block %d\n", ino_inode_table_block); + uint32_t ino_inode_index_in_group = (ino - 1) % sb->sb.block_group_size_inodes; + //printf("inode entry index in the group = %d\n", ino_inode_index_in_group); + uint32_t ino_inode_block_in_group = (ino_inode_index_in_group * sb->inode_struct_size) / sb->block_size; + //printf("inode entry offset is %d blocks\n", ino_inode_block_in_group); + uint32_t ino_inode_block_number = ino_inode_block_in_group + ino_inode_table_block; + //printf("inode block number is %uth block\n", ino_inode_block_number); + + // Need to read the block to modify it + if ((res = ext2_read_block(ext2, ino_inode_block_number, inode_block_buffer)) < 0) { + return res; + } + + uint32_t ino_entry_in_block = (ino_inode_index_in_group * sb->inode_struct_size) % sb->block_size; + memcpy(&inode_block_buffer[ino_entry_in_block], inode, sb->inode_struct_size); + + // Write the block back + if ((res = ext2_write_block(ext2, ino_inode_block_number, inode_block_buffer)) < 0) { + return res; + } + + return 0; +} diff --git a/sys/vfs/ext2/ext2dir.c b/sys/vfs/ext2/ext2dir.c new file mode 100644 index 0000000..20bfdc5 --- /dev/null +++ b/sys/vfs/ext2/ext2dir.c @@ -0,0 +1,177 @@ +// ext2fs directory content operations +#include "sys/fs/ext2.h" +#include "sys/string.h" +#include "sys/assert.h" +#include "sys/debug.h" +#include "sys/errno.h" + +// #include +// #include +// #include +// #include +// #include + +// Add an inode to directory +int ext2_dir_add_inode(fs_t *ext2, vnode_t *dir, const char *name, uint32_t ino) { + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + char block_buffer[sb->block_size]; + struct ext2_inode *dir_inode = dir->fs_data; + struct ext2_dirent *current_dirent, *result_dirent; + int res; + + size_t req_free = strlen(name) + sizeof(struct ext2_dirent); + // Align up 4 bytes + req_free = (req_free + 3) & ~3; + + // Try reading parent dirent blocks to see if any has + // some space to fit our file + size_t dir_size_blocks = (dir_inode->size_lower + sb->block_size - 1) / sb->block_size; + for (size_t i = 0; i < dir_size_blocks; ++i) { + current_dirent = NULL; + result_dirent = NULL; + size_t off = 0; + + // Read directory content block + if ((res = ext2_read_inode_block(ext2, dir_inode, i, block_buffer)) < 0) { + return res; + } + + // Check if any of the entries can be split to fit our entry + while (off < sb->block_size) { + current_dirent = (struct ext2_dirent *) &block_buffer[off]; + if (current_dirent->ino == 0) { + kwarn("ext2: found dirent with ino = 0\n"); + } + + // Check how much space we need to still store the entry + size_t real_len = current_dirent->name_len + sizeof(struct dirent); + real_len = (real_len + 3) & ~3; + + // And check how much is left to fit our entry + if (real_len < current_dirent->len /* Sanity? */ && + current_dirent->len - real_len >= req_free) { + // Yay, can fit our dirent in there + + // Sanity check that we're aligned properly + _assert(((off + real_len) & 3) == 0); + result_dirent = (struct ext2_dirent *) &block_buffer[off + real_len]; + result_dirent->len = current_dirent->len - real_len; + result_dirent->name_len = strlen(name); + result_dirent->type_ind = 0; + result_dirent->ino = ino; + strncpy(result_dirent->name, name, result_dirent->name_len); + current_dirent->len = real_len; + + if ((res = ext2_write_inode_block(ext2, dir_inode, i, block_buffer)) < 0) { + return res; + } + + return 0; + } + + off += current_dirent->len; + } + } + + dir_inode->size_lower += sb->block_size; + if ((res = ext2_inode_alloc_block(ext2, dir_inode, dir->fs_number, dir_size_blocks)) < 0) { + dir_inode->size_lower -= sb->block_size; + return res; + } + + memset(block_buffer, 0, sb->block_size); + current_dirent = (struct ext2_dirent *) block_buffer; + current_dirent->ino = ino; + current_dirent->len = sb->block_size; + current_dirent->name_len = strlen(name); + current_dirent->type_ind = 0; + strncpy(current_dirent->name, name, current_dirent->name_len); + + return ext2_write_inode_block(ext2, dir_inode, dir_size_blocks, block_buffer); +} + +// Not only free the block itself, but also remove it from index list +static int ext2_free_block_index(fs_t *ext2, struct ext2_inode *inode, uint32_t index, uint32_t ino, size_t sz) { + if (index >= 12) { + // TODO: Implement this + panic("Not implemented\n"); + } + + int res; + uint32_t block_no = inode->direct_blocks[index]; + + if ((res = ext2_free_block(ext2, block_no)) < 0) { + return res; + } + + // Shift direct indexed blocks + for (uint32_t i = index; i < 11; ++i) { + inode->direct_blocks[i] = inode->direct_blocks[i + 1]; + } + // TODO: inode->direct_blocks[11] becomes the first block of indirect block + inode->direct_blocks[11] = 0; + + inode->size_lower -= sz; + + return ext2_write_inode(ext2, inode, ino); +} + +int ext2_dir_remove_inode(fs_t *ext2, vnode_t *dir, const char *name, uint32_t ino) { + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + char block_buffer[sb->block_size]; + struct ext2_inode *dir_inode = dir->fs_data; + struct ext2_dirent *current_dirent, *prev_dirent; + int res; + + size_t dir_size_blocks = (dir_inode->size_lower + sb->block_size - 1) / sb->block_size; + + for (size_t i = 0; i < dir_size_blocks; ++i) { + if ((res = ext2_read_inode_block(ext2, dir_inode, i, block_buffer)) < 0) { + return res; + } + + size_t off = 0; + current_dirent = NULL; + prev_dirent = NULL; + + while (off < sb->block_size) { + prev_dirent = current_dirent; + current_dirent = (struct ext2_dirent *) &block_buffer[off]; + + if (current_dirent->ino == 0) { + kwarn("ext2: found dirent with ino = 0\n"); + } + + if (current_dirent->name_len == strlen(name) && + !strncmp(current_dirent->name, name, current_dirent->name_len)) { + // Found matching dirent + // Sanity + _assert(current_dirent->ino == ino); + + if (current_dirent->len + off >= sb->block_size) { + // It's the last node in the list + if (!prev_dirent) { + return ext2_free_block_index(ext2, dir_inode, i, dir->fs_number, sb->block_size); + } + + // Resize the previous node + prev_dirent->len += current_dirent->len; + return ext2_write_inode_block(ext2, dir_inode, i, block_buffer); + } else { + // It's not the last one - relocate the next entry + uint32_t len = current_dirent->len; + struct ext2_dirent *next_dirent = (struct ext2_dirent *) &block_buffer[off + len]; + memmove(current_dirent, next_dirent, next_dirent->len); + next_dirent = current_dirent; + next_dirent->len += len; + _assert(((off + len) & 3) == 0); + return ext2_write_inode_block(ext2, dir_inode, i, block_buffer); + } + } + + off += current_dirent->len; + } + } + + return -EIO; +} diff --git a/sys/vfs/ext2/ext2vnop.c b/sys/vfs/ext2/ext2vnop.c new file mode 100644 index 0000000..09e3850 --- /dev/null +++ b/sys/vfs/ext2/ext2vnop.c @@ -0,0 +1,750 @@ +// ext2fs vnode operations +#include "sys/fs/ext2.h" +#include "sys/fs/node.h" +#include "sys/fs/ofile.h" +#include "sys/fs/fcntl.h" +#include "sys/fs/vfs.h" +#include "sys/string.h" +#include "sys/debug.h" +#include "sys/assert.h" +#include "sys/panic.h" +#include "sys/errno.h" +#include "sys/heap.h" + +// #include +// #include +// #include +// #include +// #include +// #include + +// Forward declaration of ext2 vnode functions +static int ext2_vnode_find(vnode_t *vn, const char *name, vnode_t **resvn); +static int ext2_vnode_creat(vnode_t *at, struct vfs_ioctx *ctx, const char *name, mode_t mode, int opt, vnode_t **resvn); +static int ext2_vnode_mkdir(vnode_t *at, const char *name, mode_t mode); +static int ext2_vnode_open(vnode_t *vn, int opt); +static int ext2_vnode_opendir(vnode_t *vn, int opt); +static ssize_t ext2_vnode_read(struct ofile *fd, void *buf, size_t count); +static ssize_t ext2_vnode_write(struct ofile *fd, const void *buf, size_t count); +static int ext2_vnode_truncate(struct ofile *fd, size_t length); +static int ext2_vnode_readdir(struct ofile *fd); +static void ext2_vnode_destroy(vnode_t *vn); +static int ext2_vnode_stat(vnode_t *vn, struct stat *st); +static int ext2_vnode_chmod(vnode_t *vn, mode_t mode); +static int ext2_vnode_chown(vnode_t *vn, uid_t uid, gid_t gid); +static int ext2_vnode_unlink(vnode_t *at, vnode_t *vn, const char *name); +static int ext2_vnode_access(vnode_t *vn, uid_t *uid, gid_t *gid, mode_t *mode); +static int ext2_vnode_readlink(vnode_t *vn, char *dst); +static int ext2_vnode_symlink(vnode_t *at, struct vfs_ioctx *ctx, const char *name, const char *dst); + +struct vnode_operations ext2_vnode_ops = { + .find = ext2_vnode_find, + .creat = ext2_vnode_creat, + .mkdir = ext2_vnode_mkdir, + .destroy = ext2_vnode_destroy, + + .readlink = ext2_vnode_readlink, + .symlink = ext2_vnode_symlink, + + .chmod = ext2_vnode_chmod, + .chown = ext2_vnode_chown, + .stat = ext2_vnode_stat, + .unlink = ext2_vnode_unlink, + .access = ext2_vnode_access, + + .opendir = ext2_vnode_opendir, + .readdir = ext2_vnode_readdir, + + .open = ext2_vnode_open, + .read = ext2_vnode_read, + .write = ext2_vnode_write, + .truncate = ext2_vnode_truncate, +}; + +//// vnode function implementation + +static int ext2_vnode_find(vnode_t *vn, const char *name, vnode_t **res) { + fs_t *ext2 = vn->fs; + struct ext2_extsb *sb = vn->fs->fs_private; + struct ext2_inode *inode = vn->fs_data; + + char buffer[sb->block_size]; + struct ext2_dirent *dirent = NULL; + + size_t block_count = (inode->size_lower + (sb->block_size - 1)) / sb->block_size; + // char ent_name[256]; + size_t index = 0; + + while (index < block_count) { + // Read directory contents block + if (ext2_read_inode_block(ext2, inode, index, buffer) < 0) { + return -EIO; + } + + size_t offset = 0; + while (1) { + dirent = (struct ext2_dirent *) &buffer[offset]; + if (!dirent->len) { + break; + } + if (dirent->ino) { + if (strlen(name) == dirent->name_len && !strncmp(dirent->name, name, dirent->name_len)) { + // Found the entry + vnode_t *out = (vnode_t *) kmalloc(sizeof(vnode_t)); + out->op = &ext2_vnode_ops; + out->fs = ext2; + + struct ext2_inode *result_inode = (struct ext2_inode *) kmalloc(sb->inode_struct_size); + if (ext2_read_inode(ext2, result_inode, dirent->ino) != 0) { + return -EIO; + } + + out->fs_data = result_inode; + out->fs_number = dirent->ino; + out->type = ext2_inode_type(result_inode); + + *res = out; + //printf("Lookup %s in ino %d = %d\n", name, vn->fs_number, out->fs_number); + return 0; + } + } + offset += dirent->len; + if (offset >= sb->block_size) { + break; + } + } + + ++index; + } + + return -ENOENT; +} + +static int ext2_vnode_opendir(vnode_t *vn, int opt) { + _assert(vn->type == VN_DIR); + return 0; +} + +static int ext2_vnode_open(vnode_t *vn, int opt) { + _assert(vn->type == VN_REG); + return 0; +} + +static int ext2_vnode_mkdir(vnode_t *at, const char *name, mode_t mode) { + fs_t *ext2 = at->fs; + _assert(at->type == VN_DIR); + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + char block_buffer[sb->block_size]; + + uint32_t new_ino, new_block_no; + int res; + + // Allocate a new inode for the directory + if ((res = ext2_alloc_inode(ext2, &new_ino)) != 0) { + kerror("ext2: Failed to allocate an inode\n"); + return res; + } + + // Allocate a block for "." and ".." entries + if ((res = ext2_alloc_block(ext2, &new_block_no)) < 0) { + kerror("ext2: Failed to allocate a block\n"); + return res; + } + + struct ext2_inode *ent_inode = (struct ext2_inode *) kmalloc(sb->inode_struct_size); + + // Now create an entry in parents dirent list + if ((res = ext2_dir_add_inode(ext2, at, name, new_ino)) < 0) { + return res; + } + + // Fill the inode + ent_inode->flags = 0; + ent_inode->dir_acl = 0; + ent_inode->frag_block_addr = 0; + ent_inode->gen_number = 0; + ent_inode->hard_link_count = 1; + ent_inode->acl = 0; + ent_inode->os_value_1 = 0; + memset(ent_inode->os_value_2, 0, sizeof(ent_inode->os_value_2)); + // TODO: time support in kernel + ent_inode->atime = 0; + ent_inode->mtime = 0; + ent_inode->ctime = 0; + ent_inode->dtime = 0; + + memset(ent_inode->direct_blocks, 0, sizeof(ent_inode->direct_blocks)); + ent_inode->direct_blocks[0] = new_block_no; + ent_inode->l1_indirect_block = 0; + ent_inode->l2_indirect_block = 0; + ent_inode->l3_indirect_block = 0; + + ent_inode->type_perm = (mode & 0x1FF) | EXT2_TYPE_DIR; + // TODO: obtain these from process context in kernel + ent_inode->uid = 0; + ent_inode->gid = 0; + ent_inode->disk_sector_count = 0; + ent_inode->size_lower = sb->block_size; + + memset(block_buffer, 0, sb->block_size); + // "." + struct ext2_dirent *dirent = (struct ext2_dirent *) block_buffer; + dirent->ino = new_ino; + dirent->name_len = 1; + dirent->len = (sizeof(struct ext2_dirent) + 4) & ~3; + dirent->name[0] = '.'; + dirent->type_ind = 0; + + // ".." + dirent = (struct ext2_dirent *) &block_buffer[dirent->len]; + dirent->ino = at->fs_number; + dirent->name_len = 2; + dirent->len = sb->block_size - ((sizeof(struct ext2_dirent) + 4) & ~3); + dirent->name[0] = '.'; + dirent->name[1] = '.'; + dirent->type_ind = 0; + + // Write directory's first block + if ((res = ext2_write_block(ext2, new_block_no, block_buffer)) < 0) { + return res; + } + + // Write directory inode + if ((res = ext2_write_inode(ext2, ent_inode, new_ino)) < 0) { + return res; + } + + return 0; +} + +static int ext2_vnode_creat(vnode_t *at, struct vfs_ioctx *ctx, const char *name, mode_t mode, int opt, vnode_t **resvn) { + fs_t *ext2 = at->fs; + _assert(at->type == VN_DIR); + _assert(/* Don't support making directories like this */ !(mode & O_DIRECTORY)); + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + + uint32_t new_ino; + int res; + + // Allocate new inode number + if ((res = ext2_alloc_inode(ext2, &new_ino)) != 0) { + kerror("Failed to allocate inode\n"); + return res; + } + + kdebug("Allocated inode %d\n", new_ino); + + // Create an inode struct in memory + struct ext2_inode *ent_inode = (struct ext2_inode *) kmalloc(sb->inode_struct_size); + + // Now create an entry in parents dirent list + if ((res = ext2_dir_add_inode(ext2, at, name, new_ino)) < 0) { + return res; + } + + // Fill the inode + ent_inode->flags = 0; + ent_inode->dir_acl = 0; + ent_inode->frag_block_addr = 0; + ent_inode->gen_number = 0; + ent_inode->hard_link_count = 1; + ent_inode->acl = 0; + ent_inode->os_value_1 = 0; + memset(ent_inode->os_value_2, 0, sizeof(ent_inode->os_value_2)); + // TODO: time support in kernel + ent_inode->atime = 0; + ent_inode->mtime = 0; + ent_inode->ctime = 0; + ent_inode->dtime = 0; + + memset(ent_inode->direct_blocks, 0, sizeof(ent_inode->direct_blocks)); + ent_inode->l1_indirect_block = 0; + ent_inode->l2_indirect_block = 0; + ent_inode->l3_indirect_block = 0; + + ent_inode->uid = ctx->uid; + ent_inode->gid = ctx->gid; + // TODO: only regular files can be created this way now + ent_inode->type_perm = (mode & 0x1FF) | (EXT2_TYPE_REG); + ent_inode->disk_sector_count = 0; + ent_inode->size_lower = 0; + + // Write the inode + if ((res = ext2_write_inode(ext2, ent_inode, new_ino)) < 0) { + return res; + } + + // Create the resulting vnode + vnode_t *vn = (vnode_t *) kmalloc(sizeof(vnode_t)); + vn->fs = ext2; + vn->fs_data = ent_inode; + vn->fs_number = new_ino; + vn->op = &ext2_vnode_ops; + vn->type = VN_REG; + + *resvn = vn; + + return 0; +} + +static ssize_t ext2_vnode_read(struct ofile *fd, void *buf, size_t count) { + vnode_t *vn = fd->vnode; + struct ext2_inode *inode = (struct ext2_inode *) vn->fs_data; + struct ext2_extsb *sb = vn->fs->fs_private; + + size_t nread = MIN(inode->size_lower - fd->pos, count); + + if (nread == 0) { + return -1; + } + + size_t block_number = fd->pos / sb->block_size; + size_t nblocks = (nread + sb->block_size - 1) / sb->block_size; + char block_buffer[sb->block_size]; + + for (size_t i = 0; i < nblocks; ++i) { + if (ext2_read_inode_block(vn->fs, inode, i + block_number, block_buffer) < 0) { + kerror("Failed to read inode %d block #%zu\n", vn->fs_number, i + block_number); + return -EIO; + } + if (i == 0) { + size_t ncpy = MIN(sb->block_size - fd->pos % sb->block_size, nread); + memcpy(buf, block_buffer + fd->pos % sb->block_size, ncpy); + } else { + size_t ncpy = MIN(sb->block_size, nread - sb->block_size * i); + memcpy((void *) (((uintptr_t) buf) + sb->block_size * i), block_buffer, ncpy); + } + } + + return nread; +} + +static ssize_t ext2_vnode_write(struct ofile *fd, const void *buf, size_t count) { + vnode_t *vn = fd->vnode; + _assert(vn); + struct ext2_inode *inode = (struct ext2_inode *) vn->fs_data; + fs_t *ext2 = vn->fs; + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + char block_buffer[sb->block_size]; + int res; + + if (fd->pos > inode->size_lower) { + // This shouldn't be possible, yeah? + return -ESPIPE; + } + + // How many bytes can we write into the blocks already allocated + size_t size_blocks = (inode->size_lower + sb->block_size - 1) / sb->block_size; + size_t can_write = size_blocks * sb->block_size - inode->size_lower; + size_t current_block = fd->pos / sb->block_size; + size_t written = 0; + size_t remaining = count; + + if (can_write) { + size_t can_write_blocks = (can_write + sb->block_size - 1) / sb->block_size; + + for (size_t i = 0; i < can_write_blocks; ++i) { + size_t block_index = current_block + i; + size_t pos_in_block = fd->pos % sb->block_size; + size_t need_write = MIN(remaining, sb->block_size - pos_in_block); + + kdebug("Write %zuB to block %zu offset %zu\n", need_write, block_index, pos_in_block); + if (need_write == sb->block_size) { + // Can write block without reading it + // TODO: implement this + panic("Not implemented\n"); + } else { + // Read the block to change its contents + // and write it back again + if ((res = ext2_read_inode_block(ext2, inode, block_index, block_buffer)) < 0) { + break; + } + + memcpy(block_buffer + pos_in_block, (void *) (((uintptr_t) buf) + written), need_write); + + if ((res = ext2_write_inode_block(ext2, inode, block_index, block_buffer)) < 0) { + break; + } + } + + written += need_write; + fd->pos += need_write; + remaining -= need_write; + } + + inode->size_lower = MAX(fd->pos, inode->size_lower); + current_block += can_write_blocks; + } + + if (remaining) { + // Need to allocate additional blocks + size_t need_blocks = (remaining + sb->block_size - 1) / sb->block_size; + + for (size_t i = 0; i < need_blocks; ++i) { + size_t block_index = current_block + i; + size_t need_write = MIN(remaining, sb->block_size); + + // Update the size here so it gets written when the block is allocated + // and inode struct is flushed + inode->size_lower += need_write; + // Allocate a block for the index + if ((res = ext2_inode_alloc_block(ext2, inode, vn->fs_number, block_index)) < 0) { + kerror("Could not allocate a block for writing\n"); + break; + } + + + if (need_write == sb->block_size) { + // TODO: implement this + panic("Not implemented\n"); + } else { + // Writing the last block + memcpy(block_buffer, (void *) (((uintptr_t) buf) + written), need_write); + + if ((res = ext2_write_inode_block(ext2, inode, block_index, block_buffer)) < 0) { + break; + } + } + + written += need_write; + fd->pos += need_write; + remaining -= need_write; + } + } else { + if (written) { + // Flush inode struct to disk - size has changed + ext2_write_inode(ext2, inode, vn->fs_number); + } + } + + return written; +} + +static int ext2_vnode_truncate(struct ofile *fd, size_t length) { + vnode_t *vn = fd->vnode; + fs_t *ext2 = vn->fs; + struct ext2_inode *inode = (struct ext2_inode *) vn->fs_data; + struct ext2_extsb *sb = vn->fs->fs_private; + int res; + + if (length == inode->size_lower) { + // Already good + return 0; + } + + size_t was_blocks = (inode->size_lower + sb->block_size - 1) / sb->block_size; + size_t now_blocks = (length + sb->block_size - 1) / sb->block_size; + ssize_t delta_blocks = now_blocks - was_blocks; + + if (delta_blocks < 0) { + // Free truncated blocks + // XXX: reverse the loop + for (size_t i = now_blocks; i < was_blocks; ++i) { + // Modify inode right here because ext2_free_inode_block will + // flush these changes to disk so we don't have to write it + // twice + inode->size_lower -= sb->block_size; + if ((res = ext2_free_inode_block(ext2, inode, vn->fs_number, i)) < 0) { + // Put the block size back, couldn't free it + inode->size_lower += sb->block_size; + return res; + } + } + + // All the blocks were successfully freed, can set proper file length + if (inode->size_lower != length) { + // If requested size is not block-aligned, we need to write inode + // struct to disk once again + inode->size_lower = length; + + if ((res = ext2_write_inode(ext2, inode, vn->fs_number)) < 0) { + return res; + } + } + + return 0; + } else { + kerror("Not implemented: upwards truncation (ext2)\n"); + return -EINVAL; + } +} + +// TODO: replace this with getdents +static int ext2_vnode_readdir(struct ofile *fd) { + vnode_t *vn = fd->vnode; + struct ext2_inode *inode = (struct ext2_inode *) vn->fs_data; + struct ext2_extsb *sb = vn->fs->fs_private; + + if (fd->pos >= inode->size_lower) { + return -1; + } + + size_t block_number = fd->pos / sb->block_size; + char block_buffer[sb->block_size]; + + if (ext2_read_inode_block(vn->fs, inode, block_number, block_buffer) < 0) { + return -EIO; + } + + size_t block_offset = fd->pos % sb->block_size; + struct ext2_dirent *ext2dir = (struct ext2_dirent *) &block_buffer[block_offset]; + + if (ext2dir->len == 0) { + // If entry size is zero, guess we're finished - align the fd->pos up to block size + fd->pos = (fd->pos + sb->block_size - 1) / sb->block_size; + return -1; + } + + struct dirent *vfsdir = (struct dirent *) fd->dirent_buf; + + vfsdir->d_ino = ext2dir->ino; + strncpy(vfsdir->d_name, ext2dir->name, ext2dir->name_len); + vfsdir->d_name[ext2dir->name_len] = 0; + vfsdir->d_reclen = ext2dir->len; + vfsdir->d_type = ext2dir->type_ind; + // Not implemented, I guess + vfsdir->d_off = 0; + + fd->pos += ext2dir->len; + + return 0; +} + +static void ext2_vnode_destroy(vnode_t *vn) { + // Release inode struct + kfree(vn->fs_data); +} + +static int ext2_vnode_stat(vnode_t *vn, struct stat *st) { + _assert(vn && vn->fs); + struct ext2_inode *inode = (struct ext2_inode *) vn->fs_data; + _assert(inode); + struct ext2_extsb *sb = (struct ext2_extsb *) vn->fs->fs_private; + _assert(sb); + + st->st_atime = inode->atime; + st->st_ctime = inode->ctime; + st->st_mtime = inode->mtime; + st->st_dev = 0; // Not implemented + st->st_rdev = 0; // Not implemented + st->st_gid = inode->gid; + st->st_uid = inode->uid; + st->st_mode = inode->type_perm; + st->st_size = inode->size_lower; + st->st_blocks = (inode->size_lower + sb->block_size - 1) / sb->block_size; + st->st_blksize = sb->block_size; + st->st_nlink = 0; + st->st_ino = vn->fs_number; + + return 0; +} + +static int ext2_vnode_chmod(vnode_t *vn, mode_t mode) { + _assert(vn && vn->fs && vn->fs_data); + struct ext2_inode *inode = (struct ext2_inode *) vn->fs_data; + + // Update only access mode + inode->type_perm &= ~0x1FF; + inode->type_perm |= mode & 0x1FF; + + // Write the inode back + return ext2_write_inode(vn->fs, inode, vn->fs_number); +} + +static int ext2_vnode_chown(vnode_t *vn, uid_t uid, gid_t gid) { + _assert(vn && vn->fs && vn->fs_data); + struct ext2_inode *inode = (struct ext2_inode *) vn->fs_data; + + inode->gid = gid; + inode->uid = uid; + + // Write the inode back + return ext2_write_inode(vn->fs, inode, vn->fs_number); +} + +static int ext2_vnode_unlink(vnode_t *at, vnode_t *vn, const char *name) { + struct ext2_inode *inode = vn->fs_data; + struct ext2_inode *at_inode = at->fs_data; + fs_t *ext2 = vn->fs; + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + uint32_t ino = vn->fs_number; + int res; + + if (vn->type == VN_DIR) { + // Check if the directory we're unlinking has any entries besides + // . and .. + // Can tell this just by looking at the first block + if (inode->size_lower > sb->block_size) { + // Directory size is more than one block - totally + // has something inside + return -EISDIR; + } + char block_buffer[sb->block_size]; + size_t off = 0; + + if ((res = ext2_read_inode_block(ext2, inode, 0, block_buffer)) < 0) { + return res; + } + + while (off < sb->block_size) { + struct ext2_dirent *ent = (struct ext2_dirent *) &block_buffer[off]; + if (!ent->ino) { + break; + } + if (ent->name_len == 1 && ent->name[0] == '.') { + off += ent->len; + continue; + } + if (ent->name_len == 2 && ent->name[1] == '.' && ent->name[0] == '.') { + off += ent->len; + continue; + } + + return -EISDIR; + } + } + + // Free blocks used by the inode - truncate the file to zero + size_t nblocks = (inode->size_lower + sb->block_size - 1) / sb->block_size; + + inode->size_lower = nblocks * sb->block_size; + for (ssize_t i = nblocks - 1; i >= 0; --i) { + inode->size_lower -= sb->block_size; + if ((res = ext2_free_inode_block(ext2, inode, ino, i)) < 0) { + return res; + } + } + + // inode->size_lower is now 0 + _assert(inode->size_lower == 0); + + // Free the inode itself + if ((res = ext2_free_inode(ext2, ino)) < 0) { + return res; + } + + // Now remove the entry from directory + if ((res = ext2_dir_remove_inode(ext2, at, name, ino)) < 0) { + return res; + } + + return 0; +} + +static int ext2_vnode_access(vnode_t *vn, uid_t *uid, gid_t *gid, mode_t *mode) { + _assert(vn && vn->fs_data); + struct ext2_inode *inode = vn->fs_data; + + *uid = inode->uid; + *gid = inode->gid; + *mode = inode->type_perm & 0x1FF; + + return 0; +} + +static int ext2_vnode_readlink(vnode_t *vn, char *dst) { + _assert(vn && vn->fs_data); + struct ext2_inode *inode = vn->fs_data; + fs_t *ext2 = vn->fs; + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + + if (inode->size_lower >= 60) { + char block_buffer[sb->block_size]; + int res; + + if ((res = ext2_read_inode_block(ext2, inode, 0, block_buffer)) < 0) { + return res; + } + + strncpy(dst, block_buffer, inode->size_lower); + dst[inode->size_lower] = 0; + } else { + const char *src = (const char *) inode->direct_blocks; + strncpy(dst, src, inode->size_lower); + dst[inode->size_lower] = 0; + } + + return 0; +} + +static int ext2_vnode_symlink(vnode_t *at, struct vfs_ioctx *ctx, const char *name, const char *dst) { + _assert(at && at->fs && at->fs_data); + struct ext2_inode *inode = at->fs_data; + fs_t *ext2 = at->fs; + struct ext2_extsb *sb = (struct ext2_extsb *) ext2->fs_private; + + uint32_t new_ino; + int res; + + // Allocate new inode number + if ((res = ext2_alloc_inode(ext2, &new_ino)) != 0) { + kerror("Failed to allocate inode\n"); + return res; + } + + kdebug("Allocated inode %d\n", new_ino); + + // Create an inode struct in memory + struct ext2_inode *ent_inode = (struct ext2_inode *) kmalloc(sb->inode_struct_size); + + // Now create an entry in parents dirent list + if ((res = ext2_dir_add_inode(ext2, at, name, new_ino)) < 0) { + return res; + } + + // Fill the inode + ent_inode->flags = 0; + ent_inode->dir_acl = 0; + ent_inode->frag_block_addr = 0; + ent_inode->gen_number = 0; + ent_inode->hard_link_count = 1; + ent_inode->acl = 0; + ent_inode->os_value_1 = 0; + memset(ent_inode->os_value_2, 0, sizeof(ent_inode->os_value_2)); + // TODO: time support in kernel + ent_inode->atime = 0; + ent_inode->mtime = 0; + ent_inode->ctime = 0; + ent_inode->dtime = 0; + + ent_inode->size_lower = strlen(dst); + + if (ent_inode->size_lower <= 60) { + char *dst_str = (char *) ent_inode->direct_blocks; + memset(dst_str, 0, 60); + + strncpy(dst_str, dst, ent_inode->size_lower); + } else { + char block_buffer[sb->block_size]; + uint32_t block_no; + + if ((res = ext2_alloc_block(ext2, &block_no)) < 0) { + return res; + } + + memset(block_buffer, 0, sb->block_size); + strncpy(block_buffer, dst, sb->block_size); + + if ((res = ext2_write_block(ext2, block_no, block_buffer)) < 0) { + return res; + } + + memset(ent_inode->direct_blocks, 0, sizeof(ent_inode->direct_blocks)); + ent_inode->l1_indirect_block = 0; + ent_inode->l2_indirect_block = 0; + ent_inode->l3_indirect_block = 0; + + ent_inode->direct_blocks[0] = block_no; + } + + ent_inode->uid = ctx->uid; + ent_inode->gid = ctx->gid; + ent_inode->type_perm = 0777 | EXT2_TYPE_LNK; + + // Write the inode + if ((res = ext2_write_inode(ext2, ent_inode, new_ino)) < 0) { + return res; + } + + return 0; +} diff --git a/sys/vfs/fs_class.c b/sys/vfs/fs_class.c new file mode 100644 index 0000000..d804bf5 --- /dev/null +++ b/sys/vfs/fs_class.c @@ -0,0 +1,49 @@ +#include "sys/string.h" +#include "sys/fs/fs.h" +#include "sys/errno.h" + +// #include +// #include +// #include + +static struct fs_class *fses[10] = { NULL }; +static struct fs mounts[10]; + +struct fs *fs_create(struct fs_class *cls, struct blkdev *blk, vnode_t *at) { + for (size_t i = 0; i < 10; ++i) { + if (mounts[i].cls == NULL) { + mounts[i].cls = cls; + mounts[i].blk= blk; + mounts[i].mnt_at = at; + return &mounts[i]; + } + } + return NULL; +} + +struct fs_class *fs_class_by_name(const char *name) { + for (size_t i = 0; i < 10; ++i) { + if (!fses[i]) { + break; + } + if (!strcmp(name, fses[i]->name)) { + return fses[i]; + } + } + return NULL; +} + +int fs_class_register(struct fs_class *cls) { + if (fs_class_by_name(cls->name)) { + return -EEXIST; + } + + for (size_t i = 0; i < 10; ++i) { + if (!fses[i]) { + fses[i] = cls; + return 0; + } + } + + return -ENOMEM; +} diff --git a/sys/vfs/node.c b/sys/vfs/node.c new file mode 100644 index 0000000..84a2029 --- /dev/null +++ b/sys/vfs/node.c @@ -0,0 +1,122 @@ +#include "sys/fs/node.h" +#include "sys/fs/vfs.h" +#include "sys/fs/fs.h" + +#include "sys/debug.h" +#include "sys/assert.h" +#include "sys/heap.h" +#include "sys/string.h" + +static void vfs_node_remove(struct vfs_node *node) { + // If node->parent == NULL, we've called this + // function on [root], which should not be possible + if (!node || !node->parent) { + return; + } + + struct vfs_node *parent = node->parent; + + if (parent->child == node) { + parent->child = node->cdr; + } else { + for (struct vfs_node *it = parent->child; it; it = it->cdr) { + if (it->cdr == node) { + it->cdr = node->cdr; + break; + } + } + + // TODO: handle case when for some reason node is absent in + // parent's children list + } + + // Decrement refcount for parent, unless it's [root] + if (parent->parent && !parent->child && !parent->vnode->refcount) { + vnode_free(parent->vnode); + } + + // Just a wrapper around free() + vfs_node_free(node); +} + +void vnode_free(vnode_t *vn) { + _assert(vn && vn->op); + _assert(!vn->refcount); + struct vfs_node *node = vn->tree_node; + struct vfs_node *link_node = NULL; + + if (node->ismount) { + return; + } + + if (vn->op->destroy) { + // This will free/release underlying fs_data + vn->op->destroy(vn); + } + + if (node) { + vfs_node_remove(node); + } + + memset(vn, 0, sizeof(vnode_t)); + kfree(vn); +} + +void vnode_ref(vnode_t *vn) { +// char buf[1024]; +// vfs_vnode_path(buf, vn); +// printf("++refcount for %s\n", buf); + _assert(vn); + _assert(vn->fs); + _assert(vn->fs->cls); + + // FS_NODE_MAPPER means persistent vnodes + if (vn->fs->cls->opt & FS_NODE_MAPPER) { + return; + } + + struct vfs_node *node = (struct vfs_node *) vn->tree_node; + if (node && !node->parent) { + // Don't change refcounter for root nodes + return; + } + + ++vn->refcount; +} + +void vnode_unref(vnode_t *vn) { + _assert(vn); + _assert(vn->fs); + _assert(vn->fs->cls); + + // FS_NODE_MAPPER means persistent vnodes + if (vn->fs->cls->opt & FS_NODE_MAPPER) { + return; + } + + // TODO: don't free root nodes + char buf[1024]; + vfs_vnode_path(buf, vn); + struct vfs_node *node = (struct vfs_node *) vn->tree_node; + if (!node->parent) { + return; + } + + if (vn->refcount > 0) { + --vn->refcount; + + if (vn->refcount == 0) { + if (node->child) { + return; + } + + kdebug("free %s\n", buf); + // Free vnode + vnode_free(vn); + } + } else { + _assert(node->child); + + kdebug("--refcount with 0 but child\n"); + } +} diff --git a/sys/vfs/vfs.c b/sys/vfs/vfs.c new file mode 100644 index 0000000..f7bdcdb --- /dev/null +++ b/sys/vfs/vfs.c @@ -0,0 +1,1491 @@ +#include "sys/fs/vfs.h" +#include "sys/fs/fcntl.h" +#include "sys/assert.h" +#include "sys/string.h" +#include "sys/debug.h" +#include "sys/fs/fs.h" +#include "sys/errno.h" +#include "sys/panic.h" +#include "sys/heap.h" +#include "sys/chr.h" + +// #include +// #include +// #include <_assert.h> +// #include +// #include +// +// #include + +static struct vfs_node vfs_root_node; + +static int vfs_find(vnode_t *cwd_vnode, const char *path, vnode_t **res_vnode); +static int vfs_access_internal(struct vfs_ioctx *ctx, int desm, mode_t mode, uid_t uid, gid_t gid); +static int vfs_vnode_access(struct vfs_ioctx *ctx, vnode_t *vn, int mode); + +static int vfs_setcwd_rel(struct vfs_ioctx *ctx, vnode_t *at, const char *path) { + // cwd is absolute path + vnode_t *new_cwd; + int res; + if ((res = vfs_find(at, path, &new_cwd)) != 0) { + return res; + } + + vnode_ref(new_cwd); + if (new_cwd->type != VN_DIR) { + vnode_unref(new_cwd); + return -ENOTDIR; + } + + if ((res = vfs_vnode_access(ctx, new_cwd, X_OK)) < 0) { + vnode_unref(new_cwd); + return res; + } + + if (ctx->cwd_vnode) { + vnode_unref(ctx->cwd_vnode); + } + ctx->cwd_vnode = new_cwd; + + return 0; +} + +int vfs_setcwd(struct vfs_ioctx *ctx, const char *cwd) { + return vfs_setcwd_rel(ctx, NULL, cwd); +} + +int vfs_chdir(struct vfs_ioctx *ctx, const char *cwd_rel) { + return vfs_setcwd_rel(ctx, ctx->cwd_vnode, cwd_rel); +} + +static int vfs_open_access_mask(int oflags) { + if (oflags & O_EXEC) { + return X_OK; + } + + switch (oflags & O_ACCMODE) { + case O_WRONLY: + return W_OK; + case O_RDONLY: + return R_OK; + case O_RDWR: + return R_OK | W_OK; + default: + panic("Unknown access mode\n"); + } +} + +static int vfs_access_internal(struct vfs_ioctx *ctx, int desm, mode_t mode, uid_t uid, gid_t gid) { + if (ctx->uid == 0) { + if (desm & X_OK) { + // Check if anyone at all can execute this + if (!(mode & (S_IXOTH | S_IXGRP | S_IXUSR))) { + return -EACCES; + } + } + + return 0; + } + + if (uid == ctx->uid) { + if ((desm & R_OK) && !(mode & S_IRUSR)) { + return -EACCES; + } + if ((desm & W_OK) && !(mode & S_IWUSR)) { + return -EACCES; + } + if ((desm & X_OK) && !(mode & S_IXUSR)) { + return -EACCES; + } + } else if (gid == ctx->gid) { + if ((desm & R_OK) && !(mode & S_IRGRP)) { + return -EACCES; + } + if ((desm & W_OK) && !(mode & S_IWGRP)) { + return -EACCES; + } + if ((desm & X_OK) && !(mode & S_IXGRP)) { + return -EACCES; + } + } else { + if ((desm & R_OK) && !(mode & S_IROTH)) { + return -EACCES; + } + if ((desm & W_OK) && !(mode & S_IWOTH)) { + return -EACCES; + } + if ((desm & X_OK) && !(mode & S_IXOTH)) { + return -EACCES; + } + } + + return 0; +} + +static int vfs_vnode_access(struct vfs_ioctx *ctx, vnode_t *vn, int mode) { + mode_t vn_mode; + uid_t vn_uid; + gid_t vn_gid; + int res; + + // Filesystem does not have permissions + if (!vn->op || !vn->op->access) { + return 0; + } + + if ((res = vn->op->access(vn, &vn_uid, &vn_gid, &vn_mode)) < 0) { + return res; + } + + return vfs_access_internal(ctx, mode, vn_mode, vn_uid, vn_gid); +} + +void vfs_init(void) { + // Setup root node + strcpy(vfs_root_node.name, "[root]"); + vfs_root_node.vnode = NULL; + vfs_root_node.real_vnode = NULL; + vfs_root_node.parent = NULL; + vfs_root_node.cdr = NULL; + vfs_root_node.child = NULL; +} + +static const char *vfs_path_element(char *dst, const char *src) { + const char *sep = strchr(src, '/'); + if (!sep) { + strcpy(dst, src); + return NULL; + } else { + strncpy(dst, src, sep - src); + dst[sep - src] = 0; + while (*sep == '/') { + ++sep; + } + if (!*sep) { + return NULL; + } + return sep; + } +} + +void vfs_node_free(struct vfs_node *node) { + _assert(node && node->vnode); + _assert(node->vnode->refcount == 0); + kfree(node); +} + +struct vfs_node *vfs_node_create(const char *name, vnode_t *vn) { + _assert(vn); + struct vfs_node *node = (struct vfs_node *) kmalloc(sizeof(struct vfs_node)); + vn->refcount = 0; + vn->tree_node = node; + node->vnode = vn; + strcpy(node->name, name); + node->ismount = 0; + node->real_vnode = NULL; + node->parent = NULL; + node->child = NULL; + node->cdr = NULL; + return node; +} + +/** + * @brief The same as vfs_find, but more internal to the VFS - it operates + * on VFS path tree instead of vnodes (as they have no hierarchy defined) + * + * XXX: ".." will leave you with dangling nodes in the tree + */ +static int vfs_find_tree(struct vfs_node *root_node, const char *path, struct vfs_node **res_node) { + if (!path || !*path) { + // The path refers to the node itself + *res_node = root_node; + return 0; + } + + // Assuming the path is normalized + char path_element[256]; + const char *child_path = vfs_path_element(path_element, path); + + // TODO: this should also be handled by path canonicalizer + while (!strcmp(path_element, ".")) { + if (!child_path) { + // The node we're looking for is this node + *res_node = root_node; + return 0; + } + child_path = vfs_path_element(path_element, child_path); + } + if (!strcmp(path_element, "..")) { + if (root_node->parent) { + return vfs_find_tree(root_node->parent, child_path, res_node); + } else { + while (!strcmp(path_element, "..")) { + if (!child_path) { + *res_node = root_node; + return 0; + } + child_path = vfs_path_element(path_element, child_path); + } + } + } + + vnode_t *root_vnode = root_node->vnode; + _assert(root_vnode); + + // This shouldn't happen + _assert(root_vnode->type != VN_LNK); + + int res; + + // 1. Make sure we're either looking inside a directory + if (root_vnode->type == VN_DIR) { + // 3.1. It's a directory, try looking up path element inside + // the path tree + for (struct vfs_node *it = root_node->child; it; it = it->cdr) { + if (!strcmp(it->name, path_element)) { + // Found matching path element + if (!child_path) { + // We're at terminal path element - which means we've + // found what we're looking for + //vnode_ref(root_vnode); + *res_node = it; + return 0; + } else { + //printf("Entering vfs_node %s\n", it->name); + // Continue searching deeper + if ((res = vfs_find_tree(it, child_path, res_node)) != 0) { + // Nothing found + return res; + } + + // Found something + return 0; + } + } + } + + // 3.2. Nothing found in path tree - request the fs to find + // the vnode for the path element given + vnode_t *child_vnode = NULL; + struct vfs_node *child_node = NULL; + _assert(root_vnode->op && root_vnode->op->find); + + //printf("Calling op->find on %s\n", root_node->name); + if ((res = root_vnode->op->find(root_vnode, path_element, &child_vnode)) != 0) { + // fs didn't find anything - no such file or directory exists + return res; + } + + // We've found a link and there's still some path to traverse + if (child_vnode->type == VN_LNK && child_path) { + char linkbuf[1024]; + struct vfs_node *link_node; + _assert(child_vnode->op && child_vnode->op->readlink); + child_node = vfs_node_create(path_element, child_vnode); + + if ((res = child_vnode->op->readlink(child_vnode, linkbuf)) < 0) { + return res; + } + + if ((res = vfs_find_tree(root_node->parent ? + root_node->parent : + &vfs_root_node, linkbuf, &link_node)) < 0) { + return res; + } + + if ((res = vfs_find_tree(link_node, child_path, res_node)) < 0) { + return res; + } + + return 0; + } + + // 3.3. Found some vnode, attach it to the VFS tree + child_node = vfs_node_create(path_element, child_vnode); + + // Prepend it to parent's child list + child_node->parent = root_node; + child_node->cdr = root_node->child; + root_node->child = child_node; + + if (!child_path) { + // We're at terminal path element - return the node + *res_node = child_node; + return 0; + } else { + //printf("Entering vfs_node %s\n", child_node->name); + if ((res = vfs_find_tree(child_node, child_path, res_node)) != 0) { + // Nothing found in the child + return res; + } + + // Found what we're looking for + // Now have not only a child reference, but also someone uses the + // node down the tree, so increment the refcounter + return 0; + } + } else { + // Not a directory/mountpoint - cannot contain anything + return -ENOENT; + } +} + +static int vfs_find_at(vnode_t *root_vnode, const char *path, vnode_t **res_vnode) { + // The input path should be without leading slash and relative to root_vnode + struct vfs_node *res_node = NULL; + int res; + + if (!root_vnode) { + // Root node contains no vnode - which means there's no root + // at all + if (!vfs_root_node.vnode) { + return -ENOENT; + } + + res = vfs_find_tree(&vfs_root_node, path, &res_node); + } else { + _assert(root_vnode->tree_node); + + res = vfs_find_tree((struct vfs_node *) root_vnode->tree_node, path, &res_node); + } + + if (res != 0) { + return res; + } + + _assert(res_node); + *res_vnode = res_node->vnode; + return 0; +} + +static int vfs_find(vnode_t *cwd_vnode, const char *path, vnode_t **res_vnode) { + if (*path != '/') { + return vfs_find_at(cwd_vnode, path, res_vnode); + } else { + // Use root as search base + while (*path == '/') { + ++path; + } + + return vfs_find_at(NULL, path, res_vnode); + } +} + +static void vfs_dump_node(struct vfs_node *node, int o) { + // TODO: node dumpinh +} + +void vfs_dump_tree(void) { + if (!vfs_root_node.vnode) { + return; + } + vfs_dump_node(&vfs_root_node, 0); +} + +void vfs_vnode_path(char *path, vnode_t *vn) { + // TODO: path printing + path[0] = 0; +} + +static int vfs_mount_internal(struct vfs_node *at, void *blkdev, const char *fs_name, const char *opt) { + struct fs_class *fs_class; + + if ((fs_class = fs_class_by_name(fs_name)) == NULL) { + return -EINVAL; + } + + // Create a new fs instance/mount + struct fs *fs; + + if ((fs = fs_create(fs_class, blkdev, NULL)) == NULL) { + return -EINVAL; + } + + if (!at) { + at = &vfs_root_node; + } + + vnode_t *old_vnode = at->vnode; + vnode_t *fs_root; + int res; + + if ((fs_class->mount != NULL) && (fs_class->mount(fs, opt) != 0)) { + return -1; + } + + if (at->child) { + // Target directory already has child nodes loaded in memory, return "busy" + // TODO: destroy fs + return -EBUSY; + } + + if (at->ismount) { + // TODO: report error and destroy fs + panic("Trying to mount a filesystem at a destination which already is a mount\n"); + } + + + if (fs->cls->opt & FS_NODE_MAPPER) { + _assert(fs_class->mapper); + // Request the driver to map VFS tree for us + struct vfs_node *fs_root_node; + + if ((res = fs_class->mapper(fs, &fs_root_node)) < 0) { + panic("Node mapper function failed\n"); + } + + _assert(fs_root_node); + _assert(fs_root_node->vnode); + fs_root = fs_root_node->vnode; + + // Reparent vnode to the actual mountpoint + fs_root->tree_node = at; + + // Reparent fs_root_node children to the actual mountpoint + for (struct vfs_node *child = fs_root_node->child; child; child = child->cdr) { + child->parent = at; + child->cdr = at->child; + at->child = child; + } + + // Root node can be freed + kfree(fs_root_node); + } else { + _assert(fs_class->get_root); + // Try to get root + if ((fs_root = fs_class->get_root(fs)) == NULL) { + // TODO: report error and destroy fs + panic("Failed to get root node of the filesystem\n"); + } + } + + // If it's a root mount, set root vnode + kdebug("Mounting new fs on %s\n", at->name); + at->vnode = fs_root; + at->real_vnode = old_vnode; + at->ismount = 1; + fs_root->tree_node = at; + + return 0; +} + +int vfs_mount(struct vfs_ioctx *ctx, const char *target, void *blkdev, const char *fs_name, const char *opt) { + if (ctx->uid != 0) { + return -EACCES; + } + + struct vfs_node *mount_at; + vnode_t *vnode_mount_at; + int res; + + if (!vfs_root_node.vnode) { + // Root does not yet exist, check if we're mounting root: + if (!strcmp(target, "/")) { + return vfs_mount_internal(NULL, blkdev, fs_name, opt); + } + + // Otherwise we cannot perform mounting + return -ENOENT; + } + + // Lookup the tree node we're mounting at + if ((res = vfs_find(ctx->cwd_vnode, target, &vnode_mount_at)) != 0) { + return res; + } + + // Get tree node + mount_at = vnode_mount_at->tree_node; + _assert(mount_at); + + return vfs_mount_internal(mount_at, blkdev, fs_name, opt); +} + +int vfs_umount(struct vfs_ioctx *ctx, const char *target) { + if (ctx->uid != 0) { + return -EACCES; + } + if (!vfs_root_node.vnode) { + // No root, don't even bother umounting anything + return -ENOENT; + } + + // Lookup target vnode's tree_node + _assert(target); + vnode_t *at_vnode; + struct vfs_node *at; + int res; + + if ((res = vfs_find(ctx->cwd_vnode, target, &at_vnode)) != 0) { + return res; + } + + at = at_vnode->tree_node; + _assert(at); + + if (!at->ismount) { + // Not mounted + return -EINVAL; + } + + if (at->child) { + // There're some used vnodes down the tree + return -EBUSY; + } + + at->vnode = at->real_vnode; + at->ismount = 0; + + if (at_vnode == ctx->cwd_vnode) { + // Umounting the cwd + ctx->cwd_vnode = NULL; + } + at_vnode->refcount = 0; + vnode_free(at_vnode); + + return 0; +} + +static void vfs_path_parent(char *dst, const char *path) { + // The function expects normalized paths without . and .. + // Possible inputs: + // "/" -> "/" + // "/dir/x/y" -> "/dir/x" + + const char *slash = strrchr(path, '/'); + if (!slash) { + dst[0] = 0; + return; + } + + strncpy(dst, path, slash - path); + dst[slash - path] = 0; +} + +static const char *vfs_path_basename(const char *path) { + const char *slash = strrchr(path, '/'); + if (!slash) { + return path; + } + + return slash + 1; +} + +static int vfs_creat_internal(struct vfs_ioctx *ctx, vnode_t *at, const char *name, int mode, int opt, vnode_t **resvn) { + // Create a file without opening it + _assert(at && at->op && at->tree_node); + int res; + + if (!at->op->creat) { + return -EROFS; + } + + if ((res = at->op->creat(at, ctx, name, mode, opt, resvn)) != 0) { + return res; + } + + struct vfs_node *parent_node = at->tree_node; + struct vfs_node *child_node = vfs_node_create(name, *resvn); + + // Prepend it to parent's child list + child_node->parent = parent_node; + child_node->cdr = parent_node->child; + parent_node->child = child_node; + + return 0; +} + +int vfs_creat(struct vfs_ioctx *ctx, struct ofile *of, const char *path, int mode, int opt) { + vnode_t *parent_vnode = NULL; + vnode_t *vnode = NULL; + int res; + + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) == 0) { + vnode_ref(vnode); + if ((res = vfs_open_node(ctx, of, vnode, opt & ~O_CREAT)) < 0) { + vnode_unref(vnode); + } + return res; + } + + if (*path == '/') { + // Get parent vnode + char parent_path[1024]; + vfs_path_parent(parent_path, path); + + if ((res = vfs_find(NULL, parent_path, &parent_vnode)) != 0) { + kerror("Parent does not exist: %s\n", parent_path); + // Parent doesn't exist, too - error + return res; + } + } else { + char parent_path[1024]; + vfs_path_parent(parent_path, path); + + if (!*parent_path) { + parent_path[0] = '.'; + parent_path[1] = 0; + } + + // Find parent + if ((res = vfs_find(ctx->cwd_vnode, parent_path, &parent_vnode)) != 0) { + kerror("Parent does not exist: %s\n", parent_path); + return res; + } + } + + vnode_ref(parent_vnode); + + if (parent_vnode->type == VN_LNK) { + _assert(parent_vnode->op); + _assert(parent_vnode->op->readlink); + char lnk[1024]; + vnode_t *vn_lnk; + struct vfs_node *vn_node = (struct vfs_node *) parent_vnode->tree_node; + struct vfs_node *vn_lnk_node; + _assert(vn_node); + + if ((res = parent_vnode->op->readlink(parent_vnode, lnk)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + if ((res = vfs_find_tree(vn_node->parent, lnk, &vn_lnk_node)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + vn_lnk = vn_lnk_node->vnode; + vnode_ref(vn_lnk); + vnode_unref(parent_vnode); + + parent_vnode = vn_lnk; + } + + if (parent_vnode->type != VN_DIR) { + vnode_unref(parent_vnode); + // Parent is not a directory + return -ENOTDIR; + } + + if (vfs_vnode_access(ctx, parent_vnode, W_OK) < 0) { + vnode_unref(parent_vnode); + return -EACCES; + } + + kdebug("Path: %s\n", path); + path = vfs_path_basename(path); + + if (!path) { + return -EINVAL; + } + + if ((res = vfs_creat_internal(ctx, parent_vnode, path, mode, opt & ~O_CREAT, &vnode)) != 0) { + vnode_unref(parent_vnode); + // Could not create entry + return res; + } + + vnode_ref(vnode); + vnode_unref(parent_vnode); + + if (!of) { + vnode_unref(vnode); + // Need opening the file, but no descriptor provided + return -EINVAL; + } + + if ((res = vfs_open_node(ctx, of, vnode, opt & ~O_CREAT)) < 0) { + vnode_unref(vnode); + } + + return res; +} + +int vfs_open(struct vfs_ioctx *ctx, struct ofile *of, const char *path, int mode, int opt) { + _assert(of); + // Try to find the file + int res; + vnode_t *vnode = NULL; + + // TODO: normalize path + + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) != 0) { + if (!(opt & O_CREAT)) { + return -ENOENT; + } + + return vfs_creat(ctx, of, path, mode, opt); + } + + vnode_ref(vnode); + + // Resolve symlink to open the resource it's pointing to + if (vnode->type == VN_LNK) { + _assert(vnode->op); + _assert(vnode->op->readlink); + char lnk[1024]; + vnode_t *vn_lnk; + struct vfs_node *vn_node = (struct vfs_node *) vnode->tree_node; + struct vfs_node *vn_lnk_node; + _assert(vn_node); + + if ((res = vnode->op->readlink(vnode, lnk)) < 0) { + vnode_unref(vnode); + return res; + } + + if ((res = vfs_find_tree(vn_node->parent, lnk, &vn_lnk_node)) < 0) { + vnode_unref(vnode); + return res; + } + + vn_lnk = vn_lnk_node->vnode; + vnode_ref(vn_lnk); + vnode_unref(vnode); + + vnode = vn_lnk; + } + + if ((res = vfs_open_node(ctx, of, vnode, opt & ~O_CREAT)) < 0) { + vnode_unref(vnode); + return res; + } + + // Leave refcount + 1'd, ioctx is using the node + return 0; +} + +int vfs_open_node(struct vfs_ioctx *ctx, struct ofile *of, vnode_t *vn, int opt) { + // TODO: O_APPEND + _assert(vn && vn->op && of); + int res; + + if (vfs_vnode_access(ctx, vn, vfs_open_access_mask(opt)) < 0) { + return -EACCES; + } + + if (opt & O_DIRECTORY) { + _assert((opt & O_ACCMODE) == O_RDONLY); + // How does one truncate a directory? + _assert(!(opt & O_TRUNC)); + _assert(!(opt & O_CREAT)); + + if (vn->type != VN_DIR) { + return -ENOTDIR; + } + + // opendir + if (vn->op->opendir) { + if ((res = vn->op->opendir(vn, opt)) != 0) { + return res; + } + } else { + return -EINVAL; + } + + of->flags = opt; + of->vnode = vn; + of->pos = 0; + + return res; + } + + // Check flag sanity + // Can't have O_CREAT here + if (opt & O_CREAT) { + return -EINVAL; + } + // Can't be both (RD|WR) and EX + if (opt & O_EXEC) { + if (opt & O_ACCMODE) { + return -EACCES; + } + } + + if (vn->type == VN_DIR) { + return -EISDIR; + } + if (vn->type != VN_REG) { + panic("Not implemented\n"); + } + + of->vnode = vn; + of->flags = opt; + of->pos = 0; + + if (opt & O_APPEND) { + // TODO: rewrite open() to accept struct ofile * + // instead of vnode so that open() function of the + // vnode can properly set of->pos + //fprintf(stderr, "O_APPEND not yet implemented\n"); + kerror("O_APPEND not yet implemented\n"); + return -EINVAL; + } + + // Check if file has to be truncated before opening it + if (opt & O_TRUNC) { + if (!vn->op->truncate) { + return -EINVAL; + } + + if ((res = vn->op->truncate(of, 0)) != 0) { + return res; + } + } + + if (vn->op->open) { + if ((res = vn->op->open(vn, opt)) != 0) { + return res; + } + } + + return 0; +} + +void vfs_close(struct vfs_ioctx *ctx, struct ofile *of) { + _assert(of); + vnode_t *vn = of->vnode; + _assert(vn && vn->op); + + if (vn->op->close) { + vn->op->close(of); + } + + vnode_unref(of->vnode); +} + +int vfs_statat(struct vfs_ioctx *ctx, vnode_t *at, const char *path, struct stat *st) { + _assert(at && path && st); + int res; + vnode_t *vnode; + + if ((res = vfs_find(at, path, &vnode)) != 0) { + return res; + } + + vnode_ref(vnode); + + if (!vnode->op || !vnode->op->stat) { + res = -EINVAL; + } else { + res = vnode->op->stat(vnode, st); + } + + vnode_unref(vnode); + return res; +} + +int vfs_stat(struct vfs_ioctx *ctx, const char *path, struct stat *st) { + _assert(path && st); + vnode_t *vnode; + int res; + + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) != 0) { + return res; + } + + vnode_ref(vnode); + + if (!vnode->op || !vnode->op->stat) { + res = -EINVAL; + } else { + res = vnode->op->stat(vnode, st); + } + + vnode_unref(vnode); + return res; +} + +ssize_t vfs_read(struct vfs_ioctx *ctx, struct ofile *fd, void *buf, size_t count) { + _assert(fd); + vnode_t *vn = fd->vnode; + _assert(vn); + + switch (vn->type) { + case VN_REG: + _assert(vn->op); + // XXX: should these be checked on every read? + if (vfs_vnode_access(ctx, vn, R_OK) < 0) { + return -EACCES; + } + + if (fd->flags & O_DIRECTORY) { + return -EISDIR; + } + if ((fd->flags & O_ACCMODE) == O_WRONLY) { + return -EINVAL; + } + if (vn->op->read == NULL) { + return -EINVAL; + } + + ssize_t nr = vn->op->read(fd, buf, count); + + if (nr > 0) { + fd->pos += nr; + } + + return nr; + + // We don't need filesystem at all to read from devices + case VN_BLK: + _assert(vn->dev); + return blk_read((struct blkdev *) vn->dev, buf, fd->pos, count); + case VN_CHR: + _assert(vn->dev); + return chr_read((struct chrdev *) vn->dev, buf, fd->pos, count); + + default: + panic("Not supported\n"); + } +} + +ssize_t vfs_write(struct vfs_ioctx *ctx, struct ofile *fd, const void *buf, size_t count) { + _assert(fd); + vnode_t *vn = fd->vnode; + _assert(vn); + + switch (vn->type) { + case VN_REG: + _assert(vn->op); + + // XXX: should these be checked on every write? + if (vfs_vnode_access(ctx, vn, W_OK) < 0) { + return -EACCES; + } + + if (fd->flags & O_DIRECTORY) { + return -EISDIR; + } + if ((fd->flags & O_ACCMODE) == O_RDONLY) { + return -EINVAL; + } + if (vn->op->write == NULL) { + return -EINVAL; + } + + return vn->op->write(fd, buf, count); + + // We don't need filesystem at all to write to devices + case VN_BLK: + _assert(vn->dev); + return blk_write((struct blkdev *) vn->dev, buf, fd->pos, count); + case VN_CHR: + _assert(vn->dev); + return chr_write((struct chrdev *) vn->dev, buf, fd->pos, count); + + default: + panic("Not supported\n"); + } +} + +int vfs_truncate(struct vfs_ioctx *ctx, struct ofile *of, size_t length) { + _assert(of); + if ((of->flags & O_ACCMODE) == O_RDONLY) { + return -EINVAL; + } + if ((of->flags & O_DIRECTORY)) { + return -EINVAL; + } + vnode_t *vn = of->vnode; + _assert(vn && vn->op); + // XXX: should these be checked on every write? + if (vfs_vnode_access(ctx, vn, W_OK) < 0) { + return -EACCES; + } + + if (!vn->op->truncate) { + return -EINVAL; + } + + return vn->op->truncate(of, length); +} + +// XXX: Linux seems to differentiate between +// unlink() and rmdir(). I think just +// passing a flag whether sys_rmdir or +// sys_unlink was called. +int vfs_unlink(struct vfs_ioctx *ctx, const char *path) { + // XXX: validate this with removing mounted roots + _assert(path); + // Find the vnode to unlink + int res; + vnode_t *parent_vnode, *vnode; + + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) < 0) { + return res; + } + + _assert(vnode && vnode->op); + vnode_ref(vnode); + + // Get node parent + struct vfs_node *node = vnode->tree_node; + _assert(node); + + if (!node->parent) { + // Trying to unlink root node? + vnode_unref(vnode); + return -EACCES; + } + + if (vnode->refcount != 0) { + // Trying to unlink the file someone is using. + // Good solution would be to (TODO) defer the + // actual unlinking and perform it once no one + // is using it or notify writers/reader that the + // file is slated for removal. I think just adding + // a flag to vnode like "deleted" should suffice. + // For now, just check if vfs_ctx is trying to shoot + // its' leg off by unlinking the CWD + if (vnode == ctx->cwd_vnode) { + return -EINVAL; + } + } + + parent_vnode = node->parent->vnode; + vnode_ref(parent_vnode); + + // Can only remove a child in a directory + if (parent_vnode->type != VN_DIR) { + vnode_unref(vnode); + vnode_unref(parent_vnode); + return res; + } + + if ((res = vfs_vnode_access(ctx, parent_vnode, W_OK)) < 0) { + vnode_unref(vnode); + vnode_unref(parent_vnode); + return res; + } + + if (parent_vnode->op->unlink) { + // TODO: handle + // unlink("path/to/node/./.") + path = vfs_path_basename(path); + _assert(path); + + if ((res = parent_vnode->op->unlink(parent_vnode, vnode, path)) < 0) { + vnode_unref(vnode); + vnode_unref(parent_vnode); + return res; + } + + vnode_unref(vnode); + vnode_unref(parent_vnode); + return 0; + } else { + //fprintf(stderr, "File system does not implement unlink()\n"); + kerror("Filesystem does not implement unlink()\n"); + // File node does not support unlinking + vnode_unref(vnode); + vnode_unref(parent_vnode); + return -EINVAL; + } +} + +int vfs_chmod(struct vfs_ioctx *ctx, const char *path, mode_t mode) { + _assert(path); + vnode_t *vnode; + int res; + + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) < 0) { + return res; + } + + vnode_ref(vnode); + _assert(vnode && vnode->op); + + if (vnode->op->access) { + mode_t vn_mode; + uid_t vn_uid; + gid_t vn_gid; + + if ((res = vnode->op->access(vnode, &vn_uid, &vn_gid, &vn_mode)) < 0) { + return res; + } + + // To chmod, the uid of the user has to match + // the node's one + if ((vn_uid != ctx->uid) && (ctx->uid != 0)) { + return -EACCES; + } + } + + if (!vnode->op->chmod) { + return -EINVAL; + } + + res = vnode->op->chmod(vnode, mode); + + vnode_unref(vnode); + return res; +} + +int vfs_chown(struct vfs_ioctx *ctx, const char *path, uid_t uid, gid_t gid) { + _assert(path); + vnode_t *vnode; + int res; + + if (ctx->uid != 0) { + // For now, only root can change ownership of the nodes + return -EACCES; + } + + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) < 0) { + return res; + } + + vnode_ref(vnode); + _assert(vnode && vnode->op); + + if (!vnode->op->chown) { + return -EINVAL; + } + + res = vnode->op->chown(vnode, uid, gid); + + vnode_unref(vnode); + return res; +} + +// TODO: change signature so it can return errno +struct dirent *vfs_readdir(struct vfs_ioctx *ctx, struct ofile *fd) { + _assert(fd); + if (!(fd->flags & O_DIRECTORY)) { + return NULL; + } + vnode_t *vn = fd->vnode; + _assert(vn && vn->op); + + if (vfs_vnode_access(ctx, vn, R_OK) < 0) { + return NULL; + } + + if (!vn->op->readdir) { + return NULL; + } + + if (vn->op->readdir(fd) == 0) { + return (struct dirent *) fd->dirent_buf; + } + + return NULL; +} + +int vfs_mkdir(struct vfs_ioctx *ctx, const char *path, mode_t mode) { + vnode_t *parent_vnode = NULL; + vnode_t *vnode = NULL; + int res; + + // Check if a directory with such name already exists + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) == 0) { + vnode_ref(vnode); + vnode_unref(vnode); + return -EEXIST; + } + + // Just copypasted this from creat() + if (*path == '/') { + // Get parent vnode + char parent_path[1024]; + vfs_path_parent(parent_path, path); + + if ((res = vfs_find(NULL, parent_path, &parent_vnode)) != 0) { + kerror("Parent does not exist: %s\n", parent_path); + // Parent doesn't exist, too - error + return res; + } + } else { + char parent_path[1024]; + vfs_path_parent(parent_path, path); + + if (!*parent_path) { + parent_path[0] = '.'; + parent_path[1] = 0; + } + + // Find parent + if ((res = vfs_find(ctx->cwd_vnode, parent_path, &parent_vnode)) != 0) { + kerror("Parent does not exist: %s\n", parent_path); + return res; + } + } + + vnode_ref(parent_vnode); + + if (parent_vnode->type == VN_LNK) { + _assert(parent_vnode->op); + _assert(parent_vnode->op->readlink); + char lnk[1024]; + vnode_t *vn_lnk; + struct vfs_node *vn_node = (struct vfs_node *) parent_vnode->tree_node; + struct vfs_node *vn_lnk_node; + _assert(vn_node); + + if ((res = parent_vnode->op->readlink(parent_vnode, lnk)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + if ((res = vfs_find_tree(vn_node->parent, lnk, &vn_lnk_node)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + vn_lnk = vn_lnk_node->vnode; + vnode_ref(vn_lnk); + vnode_unref(parent_vnode); + + parent_vnode = vn_lnk; + } + + if (parent_vnode->type != VN_DIR) { + // Parent is not a directory + vnode_unref(parent_vnode); + return -ENOTDIR; + } + + // Need write permission + if ((res = vfs_vnode_access(ctx, parent_vnode, W_OK)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + kdebug("Path: %s\n", path); + path = vfs_path_basename(path); + + if (!path) { + vnode_unref(parent_vnode); + return -EINVAL; + } + + if (!parent_vnode->op || !parent_vnode->op->mkdir) { + vnode_unref(parent_vnode); + return -EINVAL; + } + + if ((res = parent_vnode->op->mkdir(parent_vnode, path, mode)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + vnode_unref(parent_vnode); + return res; +} + +int vfs_access(struct vfs_ioctx *ctx, const char *path, int mode) { + _assert(path); + vnode_t *vnode; + int res; + + mode_t vn_mode; + uid_t vn_uid; + gid_t vn_gid; + + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) != 0) { + return res; + } + + vnode_ref(vnode); + if (mode == F_OK) { + vnode_unref(vnode); + return 0; + } + + _assert(vnode->op); + + if (!vnode->op->access) { + vnode_unref(vnode); + // Filesystem has no permissions? + return -EINVAL; + } + + if ((res = vnode->op->access(vnode, &vn_uid, &vn_gid, &vn_mode)) < 0) { + vnode_unref(vnode); + return res; + } + + vnode_unref(vnode); + + return vfs_access_internal(ctx, mode, vn_mode, vn_uid, vn_gid); +} + +int vfs_statvfs(struct vfs_ioctx *ctx, const char *path, struct statvfs *st) { + _assert(ctx && path && st); + vnode_t *vnode; + int res; + fs_t *fs; + + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) < 0) { + return res; + } + + vnode_ref(vnode); + + if (!(fs = vnode->fs)) { + vnode_unref(vnode); + return -EINVAL; + } + + vnode_unref(vnode); + + if (!fs->cls || !fs->cls->statvfs) { + return -EINVAL; + } + + return fs->cls->statvfs(fs, st); +} + +int vfs_readlinkat(struct vfs_ioctx *ctx, vnode_t *at, const char *path, char *buf) { + int res; + vnode_t *vnode; + + if ((res = vfs_find(at, path, &vnode)) < 0) { + return res; + } + + vnode_ref(vnode); + + if (!vnode->op || !vnode->op->readlink) { + vnode_unref(vnode); + return -EINVAL; + } + if (vnode->type != VN_LNK) { + vnode_unref(vnode); + return -EINVAL; + } + + res = vnode->op->readlink(vnode, buf); + vnode_unref(vnode); + return res; +} + +int vfs_readlink(struct vfs_ioctx *ctx, const char *path, char *buf) { + int res; + vnode_t *vnode; + + if ((res = vfs_find(ctx->cwd_vnode, path, &vnode)) < 0) { + return res; + } + + vnode_ref(vnode); + + if (!vnode->op || !vnode->op->readlink) { + vnode_unref(vnode); + return -EINVAL; + } + if (vnode->type != VN_LNK) { + vnode_unref(vnode); + return -EINVAL; + } + + res = vnode->op->readlink(vnode, buf); + vnode_unref(vnode); + return res; +} + +int vfs_symlink(struct vfs_ioctx *ctx, const char *target, const char *linkpath) { + vnode_t *parent_vnode = NULL; + vnode_t *vnode = NULL; + int res; + + // Check if a directory with such name already exists + if ((res = vfs_find(ctx->cwd_vnode, linkpath, &vnode)) == 0) { + vnode_ref(vnode); + vnode_unref(vnode); + return -EEXIST; + } + + // Just copypasted this from creat() + if (*linkpath == '/') { + // Get parent vnode + char parent_path[1024]; + vfs_path_parent(parent_path, linkpath); + + if ((res = vfs_find(NULL, parent_path, &parent_vnode)) != 0) { + kerror("Parent does not exist: %s\n", parent_path); + // Parent doesn't exist, too - error + return res; + } + } else { + char parent_path[1024]; + vfs_path_parent(parent_path, linkpath); + + if (!*parent_path) { + parent_path[0] = '.'; + parent_path[1] = 0; + } + + // Find parent + if ((res = vfs_find(ctx->cwd_vnode, parent_path, &parent_vnode)) != 0) { + kerror("Parent does not exist: %s\n", parent_path); + return res; + } + } + + vnode_ref(parent_vnode); + + if (parent_vnode->type == VN_LNK) { + _assert(parent_vnode->op); + _assert(parent_vnode->op->readlink); + char lnk[1024]; + vnode_t *vn_lnk; + struct vfs_node *vn_node = (struct vfs_node *) parent_vnode->tree_node; + struct vfs_node *vn_lnk_node; + _assert(vn_node); + + if ((res = parent_vnode->op->readlink(parent_vnode, lnk)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + if ((res = vfs_find_tree(vn_node->parent, lnk, &vn_lnk_node)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + vn_lnk = vn_lnk_node->vnode; + vnode_ref(vn_lnk); + vnode_unref(parent_vnode); + + parent_vnode = vn_lnk; + } + + if (parent_vnode->type != VN_DIR) { + // Parent is not a directory + vnode_unref(parent_vnode); + return -ENOTDIR; + } + + // Need write permission + if ((res = vfs_vnode_access(ctx, parent_vnode, W_OK)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + kdebug("Path: %s\n", linkpath); + linkpath = vfs_path_basename(linkpath); + + if (!linkpath) { + vnode_unref(parent_vnode); + return -EINVAL; + } + + if (!parent_vnode->op || !parent_vnode->op->symlink) { + vnode_unref(parent_vnode); + return -EINVAL; + } + + // if ((res = parent_vnode->op->mkdir(parent_vnode, linkpath, mode)) < 0) { + // vnode_unref(parent_vnode); + // return res; + // } + if ((res = parent_vnode->op->symlink(parent_vnode, ctx, linkpath, target)) < 0) { + vnode_unref(parent_vnode); + return res; + } + + vnode_unref(parent_vnode); + return res; +}