Backport ext2
This commit is contained in:
@@ -43,6 +43,7 @@ OBJS+=$(O)/sys/debug.o \
|
||||
$(O)/sys/char/tty.o \
|
||||
$(O)/sys/char/chr.o \
|
||||
$(O)/sys/block/pseudo.o \
|
||||
$(O)/sys/block/part_gpt.o \
|
||||
$(O)/sys/block/ram.o \
|
||||
$(O)/sys/block/blk.o \
|
||||
$(O)/sys/dev.o \
|
||||
@@ -53,6 +54,9 @@ OBJS+=$(O)/sys/debug.o \
|
||||
$(O)/sys/fs/node.o \
|
||||
$(O)/sys/fs/tar.o \
|
||||
$(O)/sys/fs/sysfs.o \
|
||||
$(O)/sys/fs/ext2/block.o \
|
||||
$(O)/sys/fs/ext2/ext2.o \
|
||||
$(O)/sys/fs/ext2/node.o \
|
||||
$(O)/sys/time.o \
|
||||
$(O)/sys/sys_file.o \
|
||||
$(O)/sys/sys_sys.o \
|
||||
@@ -65,6 +69,7 @@ OBJS+=$(O)/sys/driver/pci/pci.o \
|
||||
$(O)/sys/driver/ata/ahci.o
|
||||
|
||||
DIRS+=$(O)/sys/fs \
|
||||
$(O)/sys/fs/ext2 \
|
||||
$(O)/sys/char \
|
||||
$(O)/sys/block \
|
||||
$(O)/sys/driver/pci \
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "sys/types.h"
|
||||
|
||||
struct blkdev;
|
||||
struct vnode;
|
||||
|
||||
int blk_enumerate_gpt(struct blkdev *dev, struct vnode *of, uint8_t *head);
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "sys/types.h"
|
||||
|
||||
struct ext2_inode;
|
||||
struct fs;
|
||||
|
||||
int ext2_read_superblock(struct fs *ext2);
|
||||
int ext2_write_superblock(struct fs *ext2);
|
||||
|
||||
int ext2_read_block(struct fs *ext2, void *blk, uint32_t no);
|
||||
|
||||
int ext2_read_inode_block(struct fs *ext2, struct ext2_inode *inode, void *block, uint32_t index);
|
||||
|
||||
int ext2_read_inode(struct fs *ext2, struct ext2_inode *dst, uint32_t ino);
|
||||
// NOTE: this function automatically updates access and modification time
|
||||
// on the inode written
|
||||
int ext2_write_inode(struct fs *ext2, struct ext2_inode *node, uint32_t ino);
|
||||
@@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
#include "sys/types.h"
|
||||
|
||||
#define EXT2_FLAG_RO (1 << 0)
|
||||
|
||||
#define EXT2_STATE_CLEAN 1
|
||||
#define EXT2_STATE_DIRTY 2
|
||||
|
||||
#define EXT2_ERROR_IGNORE 1
|
||||
#define EXT2_ERROR_READONLY 2
|
||||
#define EXT2_ERROR_PANIC 3
|
||||
|
||||
#define EXT2_REQ_COMP (1 << 0)
|
||||
#define EXT2_REQ_ENT_TYPE (1 << 1)
|
||||
#define EXT2_REQ_REPLAY_JOURNAL (1 << 2)
|
||||
#define EXT2_REQ_JOURNAL_DEV (1 << 3)
|
||||
|
||||
#define EXT2_RO_SPARSE_SB (1 << 0)
|
||||
#define EXT2_RO_64BIT_SIZE (1 << 1)
|
||||
#define EXT2_RO_DIR_BTREE (1 << 2)
|
||||
|
||||
#define EXT2_SIGNATURE 0xEF53
|
||||
|
||||
#define EXT2_IFIFO 0x1000
|
||||
#define EXT2_IFCHR 0x2000
|
||||
#define EXT2_IFDIR 0x4000
|
||||
#define EXT2_IFBLK 0x6000
|
||||
#define EXT2_IFREG 0x8000
|
||||
#define EXT2_IFLNK 0xA000
|
||||
#define EXT2_IFSOCK 0xC000
|
||||
|
||||
struct vnode;
|
||||
|
||||
struct ext2_superblock {
|
||||
// Base superblock
|
||||
uint32_t inode_count;
|
||||
uint32_t block_count;
|
||||
uint32_t reserved_blocks;
|
||||
uint32_t free_blocks;
|
||||
uint32_t free_inodes;
|
||||
uint32_t superblock_no;
|
||||
uint32_t log2_block_size;
|
||||
uint32_t log2_frag_size;
|
||||
uint32_t block_group_blocks;
|
||||
uint32_t block_group_frags;
|
||||
uint32_t block_group_inodes;
|
||||
uint32_t mount_time;
|
||||
uint32_t write_time;
|
||||
uint16_t mount_count;
|
||||
uint16_t fsck_mount_count;
|
||||
uint16_t ext2_signature;
|
||||
uint16_t state;
|
||||
uint16_t error_behavior;
|
||||
uint16_t version_minor;
|
||||
uint32_t fsck_time;
|
||||
uint32_t fsck_interval;
|
||||
uint32_t os_id;
|
||||
uint32_t version_major;
|
||||
uint16_t reserved_uid;
|
||||
uint16_t reserved_gid;
|
||||
// Extended superblock
|
||||
uint32_t first_inode; // == 11 in < 1.0
|
||||
uint16_t inode_size; // == 128 in < 1.0
|
||||
uint16_t superblock_block_group; // For backup
|
||||
uint32_t optional_features; // Don't matter
|
||||
uint32_t required_features; // Cannot read without these
|
||||
uint32_t write_required_features; // Can read without these
|
||||
char filesystem_id[16];
|
||||
char volume_name[16];
|
||||
char last_mount_path[64];
|
||||
uint32_t compression;
|
||||
uint8_t prealloc_file_blocks;
|
||||
uint8_t prealloc_dir_blocks;
|
||||
uint16_t __res0;
|
||||
char journal_id[16];
|
||||
uint32_t journal_inode;
|
||||
uint32_t journal_device;
|
||||
uint32_t orphan_inode_list_head;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ext2_blkgrp_desc {
|
||||
uint32_t block_bitmap_no;
|
||||
uint32_t inode_bitmap_no;
|
||||
uint32_t inode_table_no;
|
||||
uint16_t free_blocks;
|
||||
uint16_t free_inodes;
|
||||
uint16_t directory_count;
|
||||
char __pad[14];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ext2_inode {
|
||||
uint16_t mode;
|
||||
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_links;
|
||||
uint32_t sector_count;
|
||||
uint32_t flags;
|
||||
uint32_t os_val1;
|
||||
uint32_t direct_blocks[12];
|
||||
uint32_t indirect_block_l1;
|
||||
uint32_t indirect_block_l2;
|
||||
uint32_t indirect_block_l3;
|
||||
uint32_t generation;
|
||||
uint32_t facl;
|
||||
union {
|
||||
uint32_t size_upper;
|
||||
uint32_t dir_acl;
|
||||
};
|
||||
uint32_t frag_block_no;
|
||||
uint32_t os_val2;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ext2_dirent {
|
||||
uint32_t ino;
|
||||
uint16_t ent_size;
|
||||
uint8_t name_length_low;
|
||||
union {
|
||||
uint8_t type_indicator;
|
||||
uint8_t name_length_high;
|
||||
};
|
||||
char name[0];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ext2_data {
|
||||
union {
|
||||
struct ext2_superblock sb;
|
||||
char __data[1024];
|
||||
};
|
||||
struct ext2_blkgrp_desc *bgdt;
|
||||
|
||||
struct vnode *root;
|
||||
struct ext2_inode *root_inode;
|
||||
|
||||
uint32_t flags;
|
||||
uint32_t block_size;
|
||||
uint32_t inode_size;
|
||||
uint32_t inodes_per_block;
|
||||
uint32_t blkgrp_inode_blocks;
|
||||
uint32_t bgdt_entry_count;
|
||||
uint32_t bgdt_size_blocks;
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "sys/types.h"
|
||||
|
||||
struct vnode_operations;
|
||||
struct ext2_inode;
|
||||
struct vnode;
|
||||
|
||||
extern struct vnode_operations g_ext2_vnode_ops;
|
||||
|
||||
void ext2_inode_to_vnode(struct vnode *vnode, struct ext2_inode *inode, uint32_t ino);
|
||||
+73
-72
@@ -1,12 +1,14 @@
|
||||
//#include "sys/blk/part_gpt.h"
|
||||
#include "sys/block/part_gpt.h"
|
||||
#include "sys/user/errno.h"
|
||||
#include "sys/block/blk.h"
|
||||
#include "sys/fs/node.h"
|
||||
#include "sys/assert.h"
|
||||
#include "sys/fs/vfs.h"
|
||||
#include "sys/string.h"
|
||||
#include "sys/fs/fs.h"
|
||||
#include "sys/debug.h"
|
||||
#include "sys/heap.h"
|
||||
#include "sys/dev.h"
|
||||
|
||||
struct blk_part {
|
||||
struct blkdev *device;
|
||||
@@ -65,77 +67,76 @@ int blk_mount_auto(struct vnode *at, struct blkdev *blk, const char *opt) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
//static ssize_t blk_part_write(struct blkdev *dev, const void *buf, size_t off, size_t count) {
|
||||
// struct blk_part *part = (struct blk_part *) dev->dev_data;
|
||||
// size_t part_size_bytes = part->size * 512;
|
||||
// size_t part_off_bytes = part->lba_start * 512;
|
||||
//
|
||||
// if (off >= part_size_bytes) {
|
||||
// return -1;
|
||||
// }
|
||||
//
|
||||
// size_t l = MIN(part_size_bytes - off, count);
|
||||
//
|
||||
// return blk_write(part->device, buf, off + part_off_bytes, l);
|
||||
//}
|
||||
//
|
||||
//static ssize_t blk_part_read(struct blkdev *dev, void *buf, size_t off, size_t count) {
|
||||
// struct blk_part *part = (struct blk_part *) dev->dev_data;
|
||||
// size_t part_size_bytes = part->size * 512;
|
||||
// size_t part_off_bytes = part->lba_start * 512;
|
||||
//
|
||||
// if (off >= part_size_bytes) {
|
||||
// return -1;
|
||||
// }
|
||||
//
|
||||
// size_t l = MIN(part_size_bytes - off, count);
|
||||
//
|
||||
// return blk_read(part->device, buf, off + part_off_bytes, l);
|
||||
//}
|
||||
//
|
||||
//int blk_add_part(struct vnode *of, int n, uint64_t lba, uint64_t size) {
|
||||
// // Allocate both a device and partition details
|
||||
// char *buf = kmalloc(sizeof(struct blk_part) + sizeof(struct blkdev));
|
||||
// _assert(buf);
|
||||
// struct blk_part *part = (struct blk_part *) (buf + sizeof(struct blkdev));
|
||||
// struct blkdev *dev = (struct blkdev *) buf;
|
||||
//
|
||||
// dev->dev_data = part;
|
||||
// dev->read = blk_part_read;
|
||||
// dev->write = blk_part_write;
|
||||
// dev->flags = 0;
|
||||
//
|
||||
// part->device = of->dev;
|
||||
// part->lba_start = lba;
|
||||
// part->size = size;
|
||||
//
|
||||
// char name[16];
|
||||
// size_t len = strlen(of->name);
|
||||
// _assert(len < sizeof(name) - 2);
|
||||
// strncpy(name, of->name, len);
|
||||
// name[len] = '1' + n;
|
||||
// name[len + 1] = 0;
|
||||
//
|
||||
// dev_add(DEV_CLASS_BLOCK, DEV_BLOCK_PART, dev, name);
|
||||
//
|
||||
// return 0;
|
||||
//}
|
||||
static ssize_t blk_part_write(struct blkdev *dev, const void *buf, size_t off, size_t count) {
|
||||
struct blk_part *part = (struct blk_part *) dev->dev_data;
|
||||
size_t part_size_bytes = part->size * 512;
|
||||
size_t part_off_bytes = part->lba_start * 512;
|
||||
|
||||
if (off >= part_size_bytes) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t l = MIN(part_size_bytes - off, count);
|
||||
|
||||
return blk_write(part->device, buf, off + part_off_bytes, l);
|
||||
}
|
||||
|
||||
static ssize_t blk_part_read(struct blkdev *dev, void *buf, size_t off, size_t count) {
|
||||
struct blk_part *part = (struct blk_part *) dev->dev_data;
|
||||
size_t part_size_bytes = part->size * 512;
|
||||
size_t part_off_bytes = part->lba_start * 512;
|
||||
|
||||
if (off >= part_size_bytes) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t l = MIN(part_size_bytes - off, count);
|
||||
|
||||
return blk_read(part->device, buf, off + part_off_bytes, l);
|
||||
}
|
||||
|
||||
int blk_add_part(struct vnode *of, int n, uint64_t lba, uint64_t size) {
|
||||
// Allocate both a device and partition details
|
||||
char *buf = kmalloc(sizeof(struct blk_part) + sizeof(struct blkdev));
|
||||
_assert(buf);
|
||||
struct blk_part *part = (struct blk_part *) (buf + sizeof(struct blkdev));
|
||||
struct blkdev *dev = (struct blkdev *) buf;
|
||||
|
||||
dev->dev_data = part;
|
||||
dev->read = blk_part_read;
|
||||
dev->write = blk_part_write;
|
||||
dev->flags = 0;
|
||||
|
||||
part->device = of->dev;
|
||||
part->lba_start = lba;
|
||||
part->size = size;
|
||||
|
||||
char name[16];
|
||||
size_t len = strlen(of->name);
|
||||
_assert(len < sizeof(name) - 2);
|
||||
strncpy(name, of->name, len);
|
||||
name[len] = '1' + n;
|
||||
name[len + 1] = 0;
|
||||
|
||||
dev_add(DEV_CLASS_BLOCK, DEV_BLOCK_PART, dev, name);
|
||||
|
||||
int blk_enumerate_partitions(struct blkdev *blk, struct vnode *node) {
|
||||
// // TODO: better differentiation for CD/DVD-drives than just assuming every
|
||||
// // drive with block size of 2048 is one
|
||||
// ssize_t res;
|
||||
// uint8_t buf[1024];
|
||||
//
|
||||
// if (blk->block_size == 512) {
|
||||
// if ((res = blk_read(blk, buf, 0, 1024)) < 0) {
|
||||
// return res;
|
||||
// }
|
||||
//
|
||||
// if (!strncmp((const char *) (buf + 512), "EFI PART", 8)) {
|
||||
// return blk_enumerate_gpt(blk, node, buf);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blk_enumerate_partitions(struct blkdev *blk, struct vnode *node) {
|
||||
// TODO: better differentiation for CD/DVD-drives than just assuming every
|
||||
// drive with block size of 2048 is one
|
||||
ssize_t res;
|
||||
uint8_t buf[1024];
|
||||
|
||||
if (blk->block_size == 512) {
|
||||
if ((res = blk_read(blk, buf, 0, 1024)) < 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (!strncmp((const char *) (buf + 512), "EFI PART", 8)) {
|
||||
return blk_enumerate_gpt(blk, node, buf);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#include "sys/user/errno.h"
|
||||
#include "sys/block/blk.h"
|
||||
#include "sys/fs/node.h"
|
||||
#include "sys/string.h"
|
||||
#include "sys/assert.h"
|
||||
#include "sys/debug.h"
|
||||
|
||||
// Align to some nice boundary (it's 36)
|
||||
#define GUID_STRLEN 40
|
||||
|
||||
const char GUID_EMPTY[16] = {0};
|
||||
const struct {
|
||||
const char *name;
|
||||
const char *guid;
|
||||
} guiddb[] = {
|
||||
// A bunch of known GUID so I can pretty-print stuff
|
||||
{ "EFI System Partition", "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" },
|
||||
{ "Linux Filesystem", "0FC63DAF-8483-4772-8E79-3D69D8477DE4" },
|
||||
{ "Microsoft Basic Data", "EBD0A0A2-B9E5-4433-87C0-68B6B72699C7" },
|
||||
};
|
||||
|
||||
struct gpt_header {
|
||||
char signature[8];
|
||||
uint32_t revision;
|
||||
uint32_t header_size;
|
||||
uint32_t header_crc32;
|
||||
uint32_t __res0;
|
||||
uint64_t my_lba;
|
||||
uint64_t alt_lba;
|
||||
uint64_t first_lba;
|
||||
uint64_t last_lba;
|
||||
char disk_guid[16];
|
||||
uint64_t partition_entry_lba;
|
||||
uint32_t partition_count;
|
||||
uint32_t partition_entry_size;
|
||||
uint32_t partition_array_crc32;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct gpt_part_entry {
|
||||
char type_guid[16];
|
||||
char part_guid[16];
|
||||
uint64_t start_lba;
|
||||
uint64_t end_lba;
|
||||
uint64_t attr;
|
||||
char part_name[72];
|
||||
};
|
||||
|
||||
static void strguid(char *str, const char *guid) {
|
||||
static const char *const hexc = "0123456789ABCDEF";
|
||||
// TODO: snprintf
|
||||
int off = 0;
|
||||
for (int i = 3; i >= 0; --i) {
|
||||
str[off++] = hexc[(guid[i] >> 4) & 0xF];
|
||||
str[off++] = hexc[guid[i] & 0xF];
|
||||
}
|
||||
str[off++] = '-';
|
||||
for (int i = 1; i >= 0; --i) {
|
||||
str[off++] = hexc[(guid[i + 4] >> 4) & 0xF];
|
||||
str[off++] = hexc[guid[i + 4] & 0xF];
|
||||
}
|
||||
str[off++] = '-';
|
||||
for (int i = 1; i >= 0; --i) {
|
||||
str[off++] = hexc[(guid[i + 6] >> 4) & 0xF];
|
||||
str[off++] = hexc[guid[i + 6] & 0xF];
|
||||
}
|
||||
str[off++] = '-';
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
str[off++] = hexc[(guid[i + 8] >> 4) & 0xF];
|
||||
str[off++] = hexc[guid[i + 8] & 0xF];
|
||||
}
|
||||
str[off++] = '-';
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
str[off++] = hexc[(guid[i + 10] >> 4) & 0xF];
|
||||
str[off++] = hexc[guid[i + 10] & 0xF];
|
||||
}
|
||||
str[off] = 0;
|
||||
}
|
||||
|
||||
static void gpt_describe_entry(struct vnode *of, size_t i, struct gpt_part_entry *ent) {
|
||||
char type_guid_str[GUID_STRLEN];
|
||||
char part_guid_str[GUID_STRLEN];
|
||||
kdebug("%s partition %d:\n", of->name, i);
|
||||
strguid(type_guid_str, ent->type_guid);
|
||||
strguid(part_guid_str, ent->part_guid);
|
||||
kdebug("Type: %s\n", type_guid_str);
|
||||
for (size_t j = 0; j < sizeof(guiddb) / sizeof(guiddb[0]); ++j) {
|
||||
if (!strncmp(guiddb[j].guid, type_guid_str, GUID_STRLEN)) {
|
||||
kdebug(" %s\n", guiddb[j].name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
kdebug("Part: %s\n", part_guid_str);
|
||||
}
|
||||
|
||||
int blk_enumerate_gpt(struct blkdev *dev, struct vnode *of, uint8_t *head) {
|
||||
// TODO: support other devices
|
||||
_assert(dev->block_size == 512);
|
||||
// LBA1 - GPT header
|
||||
struct gpt_header *hdr = (struct gpt_header *) &head[512];
|
||||
// Read a single block for partition table entries
|
||||
char buf[512];
|
||||
|
||||
_assert(blk_read(dev, buf, hdr->partition_entry_lba * 512, 512) >= 0);
|
||||
|
||||
size_t offset = 0;
|
||||
size_t part_n = 0;
|
||||
int res;
|
||||
|
||||
for (size_t i = 0; i < hdr->partition_count; ++i) {
|
||||
struct gpt_part_entry *ent = (struct gpt_part_entry *) &buf[offset];
|
||||
|
||||
if (memcmp(ent->type_guid, GUID_EMPTY, 16)) {
|
||||
// Maybe some known filesystem here, ignore partition type guid
|
||||
if ((res = blk_add_part(of, i, ent->start_lba, ent->end_lba - ent->start_lba)) != 0) {
|
||||
kerror("Failed to add partition: %s\n", kstrerror(res));
|
||||
} else {
|
||||
gpt_describe_entry(of, i, ent);
|
||||
}
|
||||
}
|
||||
|
||||
offset += hdr->partition_entry_size;
|
||||
if (offset >= 512) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "sys/fs/ext2/block.h"
|
||||
#include "sys/fs/ext2/ext2.h"
|
||||
#include "sys/user/errno.h"
|
||||
#include "sys/block/blk.h"
|
||||
#include "sys/assert.h"
|
||||
#include "sys/string.h"
|
||||
#include "sys/fs/fs.h"
|
||||
#include "sys/debug.h"
|
||||
|
||||
int ext2_read_superblock(struct fs *fs) {
|
||||
struct ext2_data *data = fs->fs_private;
|
||||
_assert(data);
|
||||
int res = blk_read(fs->blk, data->__data, 1024, 1024);
|
||||
if (res == 1024) {
|
||||
return 0;
|
||||
}
|
||||
kerror("Superblock read failed\n");
|
||||
return res;
|
||||
}
|
||||
|
||||
int ext2_read_block(struct fs *fs, void *block, uint32_t no) {
|
||||
struct ext2_data *data = fs->fs_private;
|
||||
_assert(data);
|
||||
kdebug("Read block %u: %p\n", no, no * data->block_size);
|
||||
int res = blk_read(fs->blk, block, no * data->block_size, data->block_size);
|
||||
if (res == 1024) {
|
||||
return 0;
|
||||
}
|
||||
kerror("Failed to read block %u: %s\n", no, kstrerror(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
int ext2_write_block(struct fs *fs, const void *block, uint32_t no) {
|
||||
struct ext2_data *data = fs->fs_private;
|
||||
_assert(data);
|
||||
kdebug("Write block %u: %p\n", no, no * data->block_size);
|
||||
int res = blk_write(fs->blk, block, no * data->block_size, data->block_size);
|
||||
if (res == 1024) {
|
||||
return 0;
|
||||
}
|
||||
kerror("Failed to write block %u: %s\n", no, kstrerror(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
int ext2_read_inode(struct fs *fs, struct ext2_inode *inode, uint32_t ino) {
|
||||
struct ext2_data *data = fs->fs_private;
|
||||
_assert(data);
|
||||
char block[data->block_size];
|
||||
if (ino < 1 || ino >= data->sb.inode_count) {
|
||||
panic("Invalid inode number: %u\n", ino);
|
||||
}
|
||||
int res;
|
||||
|
||||
--ino;
|
||||
uint32_t ino_group = ino / data->sb.block_group_inodes;
|
||||
uint32_t ino_in_group = ino % data->sb.block_group_inodes;
|
||||
uint32_t ino_block = data->bgdt[ino_group].inode_table_no + ino_in_group / data->inodes_per_block;
|
||||
uint32_t offset_in_block = (ino_in_group % data->inodes_per_block) * data->inode_size;
|
||||
_assert(offset_in_block < data->block_size);
|
||||
|
||||
if ((res = ext2_read_block(fs, block, ino_block)) != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
memcpy(inode, offset_in_block + block, data->inode_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ext2_write_inode(struct fs *fs, struct ext2_inode *inode, uint32_t ino) {
|
||||
// Automatically update inode times
|
||||
inode->mtime = time();
|
||||
inode->atime = inode->mtime;
|
||||
|
||||
struct ext2_data *data = fs->fs_private;
|
||||
_assert(data);
|
||||
char block[data->block_size];
|
||||
if (ino < 1 || ino >= data->sb.inode_count) {
|
||||
panic("Invalid inode number: %u\n", ino);
|
||||
}
|
||||
int res;
|
||||
|
||||
--ino;
|
||||
uint32_t ino_group = ino / data->sb.block_group_inodes;
|
||||
uint32_t ino_in_group = ino % data->sb.block_group_inodes;
|
||||
uint32_t ino_block = data->bgdt[ino_group].inode_table_no + ino_in_group / data->inodes_per_block;
|
||||
uint32_t offset_in_block = (ino_in_group % data->inodes_per_block) * data->inode_size;
|
||||
_assert(offset_in_block < data->block_size);
|
||||
|
||||
if ((res = ext2_read_block(fs, block, ino_block)) != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
memcpy(offset_in_block + block, inode, data->inode_size);
|
||||
|
||||
return ext2_write_block(fs, block, ino_block);
|
||||
}
|
||||
|
||||
int ext2_read_inode_block(struct fs *fs, struct ext2_inode *inode, void *buf, uint32_t index) {
|
||||
if (index < 12) {
|
||||
return ext2_read_block(fs, buf, inode->direct_blocks[index]);
|
||||
} else {
|
||||
panic("Not implemented\n");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
#include "sys/fs/ext2/block.h"
|
||||
#include "sys/fs/ext2/node.h"
|
||||
#include "sys/fs/ext2/ext2.h"
|
||||
#include "sys/user/errno.h"
|
||||
#include "sys/fs/node.h"
|
||||
#include "sys/string.h"
|
||||
#include "sys/assert.h"
|
||||
#include "sys/debug.h"
|
||||
#include "sys/fs/fs.h"
|
||||
#include "sys/heap.h"
|
||||
#include "sys/attr.h"
|
||||
|
||||
static int ext2_init(struct fs *ext2, const char *opt);
|
||||
static struct vnode *ext2_get_root(struct fs *ext2);
|
||||
|
||||
////
|
||||
|
||||
static struct fs_class g_ext2 = {
|
||||
.name = "ext2",
|
||||
.opt = 0,
|
||||
.init = ext2_init,
|
||||
.get_root = ext2_get_root
|
||||
};
|
||||
|
||||
////
|
||||
|
||||
static int ext2_init(struct fs *ext2, const char *opt) {
|
||||
_assert(ext2->blk);
|
||||
|
||||
struct ext2_data *data = kmalloc(sizeof(struct ext2_data));
|
||||
_assert(data);
|
||||
ext2->fs_private = data;
|
||||
|
||||
int res;
|
||||
|
||||
// Read superblock
|
||||
if ((res = ext2_read_superblock(ext2)) != 0) {
|
||||
kfree(data);
|
||||
ext2->fs_private = NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Check the signature
|
||||
if (data->sb.ext2_signature != EXT2_SIGNATURE) {
|
||||
kerror("Invalid ext2 signature\n");
|
||||
kfree(data);
|
||||
ext2->fs_private = NULL;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// TODO: perform some kind of fsck?
|
||||
kinfo("Volume ID: \"%s\"\n", data->sb.volume_name);
|
||||
kinfo("Filesystem version: %d.%d\n", data->sb.version_major, data->sb.version_minor);
|
||||
|
||||
// Calculate filesystem parameters
|
||||
data->block_size = 1024 << data->sb.log2_block_size;
|
||||
|
||||
if (data->sb.version_major != 0) {
|
||||
data->inode_size = data->sb.inode_size;
|
||||
} else {
|
||||
data->inode_size = 128;
|
||||
}
|
||||
|
||||
data->inodes_per_block = data->block_size / data->inode_size;
|
||||
data->blkgrp_inode_blocks = data->sb.block_group_inodes / data->inodes_per_block;
|
||||
|
||||
kinfo("Block size: %u, inode size: %u\n", data->block_size, data->inode_size);
|
||||
|
||||
data->bgdt_entry_count = (data->sb.block_count + data->sb.block_group_blocks - 1) / data->sb.block_group_blocks;
|
||||
uint32_t bgdt_size = data->bgdt_entry_count * sizeof(struct ext2_blkgrp_desc);
|
||||
data->bgdt_size_blocks = (bgdt_size + data->block_size - 1) / data->block_size;
|
||||
|
||||
kinfo("Block groups: %u\n", data->bgdt_entry_count);
|
||||
kinfo("BGDT occupies %u block(s)\n", data->bgdt_size_blocks);
|
||||
|
||||
data->bgdt = kmalloc(data->bgdt_size_blocks * data->block_size);
|
||||
_assert(data->bgdt);
|
||||
|
||||
// BGDT starts with block 2
|
||||
for (uint32_t i = 0; i < data->bgdt_size_blocks; ++i) {
|
||||
if ((res = ext2_read_block(ext2, ((void *) data->bgdt) + i * data->block_size, i + 2)) != 0) {
|
||||
kfree(data->bgdt);
|
||||
kfree(data);
|
||||
ext2->fs_private = NULL;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
data->root_inode = kmalloc(data->inode_size);
|
||||
_assert(data->root_inode);
|
||||
data->root = vnode_create(VN_DIR, NULL);
|
||||
_assert(data->root);
|
||||
|
||||
// Read root inode
|
||||
if ((res = ext2_read_inode(ext2, data->root_inode, 2)) != 0) {
|
||||
kfree(data->root_inode);
|
||||
kfree(data->root);
|
||||
kfree(data->bgdt);
|
||||
kfree(data);
|
||||
return res;
|
||||
}
|
||||
|
||||
data->root->fs = ext2;
|
||||
ext2_inode_to_vnode(data->root, data->root_inode, 2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct vnode *ext2_get_root(struct fs *ext2) {
|
||||
struct ext2_data *data = ext2->fs_private;
|
||||
_assert(data);
|
||||
_assert(data->root);
|
||||
|
||||
return data->root;
|
||||
}
|
||||
|
||||
static void __init ext2_class_init(void) {
|
||||
fs_class_register(&g_ext2);
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
#include "sys/fs/ext2/block.h"
|
||||
#include "sys/fs/ext2/node.h"
|
||||
#include "sys/fs/ext2/ext2.h"
|
||||
#include "sys/user/fcntl.h"
|
||||
#include "sys/user/errno.h"
|
||||
#include "sys/fs/ofile.h"
|
||||
#include "sys/fs/node.h"
|
||||
#include "sys/string.h"
|
||||
#include "sys/assert.h"
|
||||
#include "sys/fs/fs.h"
|
||||
#include "sys/panic.h"
|
||||
#include "sys/debug.h"
|
||||
#include "sys/heap.h"
|
||||
|
||||
static int ext2_vnode_find(struct vnode *at, const char *name, struct vnode **res);
|
||||
static ssize_t ext2_vnode_read(struct ofile *fd, void *buf, size_t count);
|
||||
static int ext2_vnode_open(struct ofile *fd, int opt);
|
||||
static int ext2_vnode_opendir(struct ofile *fd);
|
||||
static int ext2_vnode_chmod(struct vnode *vn, mode_t new_mode);
|
||||
static int ext2_vnode_chown(struct vnode *node, uid_t new_uid, gid_t new_gid);
|
||||
static ssize_t ext2_vnode_readdir(struct ofile *fd, struct dirent *ent);
|
||||
static int ext2_vnode_stat(struct vnode *at, struct stat *st);
|
||||
|
||||
////
|
||||
|
||||
struct vnode_operations g_ext2_vnode_ops = {
|
||||
.find = ext2_vnode_find,
|
||||
|
||||
.opendir = ext2_vnode_opendir,
|
||||
.readdir = ext2_vnode_readdir,
|
||||
|
||||
.stat = ext2_vnode_stat,
|
||||
.chmod = ext2_vnode_chmod,
|
||||
.chown = ext2_vnode_chown,
|
||||
|
||||
.open = ext2_vnode_open,
|
||||
.read = ext2_vnode_read,
|
||||
};
|
||||
|
||||
////
|
||||
|
||||
static int ext2_vnode_find(struct vnode *at, const char *name, struct vnode **result) {
|
||||
_assert(at && at->type == VN_DIR);
|
||||
_assert(name);
|
||||
_assert(result);
|
||||
struct ext2_inode *at_inode = at->fs_data;
|
||||
_assert(at_inode);
|
||||
struct fs *ext2 = at->fs;
|
||||
_assert(ext2);
|
||||
struct ext2_data *data = ext2->fs_private;
|
||||
_assert(data);
|
||||
|
||||
char block_buffer[data->block_size];
|
||||
uint32_t block_offset;
|
||||
uint32_t dir_blocks = at_inode->size_lower / data->block_size;
|
||||
size_t name_length = strlen(name);
|
||||
int res;
|
||||
|
||||
// Because
|
||||
_assert(name_length < 255);
|
||||
|
||||
for (uint32_t block_index = 0; block_index < dir_blocks; ++block_index) {
|
||||
block_offset = 0;
|
||||
|
||||
if ((res = ext2_read_inode_block(ext2, at_inode, block_buffer, block_index)) != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
while (block_offset < data->block_size) {
|
||||
struct ext2_dirent *dirent = (struct ext2_dirent *) (block_buffer + block_offset);
|
||||
if (dirent->ino && (dirent->name_length_low == name_length)) {
|
||||
if (!strncmp(dirent->name, name, name_length)) {
|
||||
// Found the dirent
|
||||
struct ext2_inode *res_inode = kmalloc(data->inode_size);
|
||||
_assert(res_inode);
|
||||
struct vnode *node = vnode_create(VN_DIR, name);
|
||||
|
||||
if ((res = ext2_read_inode(ext2, res_inode, dirent->ino)) != 0) {
|
||||
kfree(res_inode);
|
||||
return res;
|
||||
}
|
||||
|
||||
node->fs = ext2;
|
||||
ext2_inode_to_vnode(node, res_inode, dirent->ino);
|
||||
|
||||
*result = node;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
block_offset += dirent->ent_size;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int ext2_vnode_open(struct ofile *fd, int opt) {
|
||||
if ((opt & O_APPEND) && (opt & O_ACCMODE) == O_RDONLY) {
|
||||
// Impossible, I guess
|
||||
return -EINVAL;
|
||||
}
|
||||
if ((opt & O_ACCMODE) != O_RDONLY) {
|
||||
// Not implemented
|
||||
return -EROFS;
|
||||
}
|
||||
_assert(!(opt & O_DIRECTORY));
|
||||
|
||||
fd->pos = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t ext2_vnode_read(struct ofile *fd, void *buf, size_t count) {
|
||||
_assert(fd);
|
||||
_assert(buf);
|
||||
struct vnode *node = fd->vnode;
|
||||
_assert(node);
|
||||
struct ext2_inode *inode = node->fs_data;
|
||||
_assert(inode);
|
||||
struct fs *ext2 = node->fs;
|
||||
_assert(ext2);
|
||||
struct ext2_data *data = ext2->fs_private;
|
||||
_assert(data);
|
||||
|
||||
// TODO
|
||||
_assert(!inode->size_upper);
|
||||
|
||||
if (fd->pos >= inode->size_lower) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char block_buffer[data->block_size];
|
||||
size_t rem = MIN(count, inode->size_lower - fd->pos);
|
||||
size_t bread = 0;
|
||||
int res;
|
||||
|
||||
while (rem) {
|
||||
size_t block_offset = fd->pos % data->block_size;
|
||||
size_t can_read = MIN(rem, data->block_size - block_offset);
|
||||
uint32_t block_index = fd->pos / data->block_size;
|
||||
|
||||
if ((res = ext2_read_inode_block(ext2, inode, block_buffer, block_index)) != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
memcpy(buf, block_buffer + block_offset, can_read);
|
||||
|
||||
buf += can_read;
|
||||
fd->pos += can_read;
|
||||
bread += can_read;
|
||||
rem -= can_read;
|
||||
}
|
||||
|
||||
return bread;
|
||||
}
|
||||
|
||||
static int ext2_vnode_opendir(struct ofile *fd) {
|
||||
_assert(fd && fd->vnode);
|
||||
_assert(fd->vnode->type == VN_DIR);
|
||||
fd->pos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t ext2_vnode_readdir(struct ofile *fd, struct dirent *ent) {
|
||||
_assert(fd);
|
||||
_assert(ent);
|
||||
struct vnode *node = fd->vnode;
|
||||
_assert(node);
|
||||
struct ext2_inode *inode = node->fs_data;
|
||||
_assert(inode);
|
||||
struct fs *ext2 = node->fs;
|
||||
_assert(ext2);
|
||||
struct ext2_data *data = ext2->fs_private;
|
||||
_assert(data);
|
||||
|
||||
if (fd->pos >= inode->size_lower) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
char block_buffer[data->block_size];
|
||||
uint32_t block_index = fd->pos / data->block_size;
|
||||
struct ext2_dirent *dirent = (struct ext2_dirent *) (block_buffer + fd->pos % data->block_size);
|
||||
int res;
|
||||
|
||||
if ((res = ext2_read_inode_block(ext2, inode, block_buffer, block_index)) != 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
if (!dirent->ino) {
|
||||
fd->pos += dirent->ent_size;
|
||||
|
||||
if (fd->pos / data->block_size != block_index) {
|
||||
// Dirent reclen cannot point beyond block size
|
||||
_assert(!(fd->pos % data->block_size));
|
||||
// Requires reading a next block for dirent
|
||||
return ext2_vnode_readdir(fd, ent);
|
||||
}
|
||||
|
||||
// Don't think the scenario of two empty dirents following each other is likely
|
||||
dirent = (struct ext2_dirent *) (block_buffer + fd->pos % data->block_size);
|
||||
_assert(dirent->ino);
|
||||
}
|
||||
|
||||
uint32_t name_length = dirent->name_length_low;
|
||||
ent->d_type = DT_UNKNOWN;
|
||||
if (data->sb.required_features & EXT2_REQ_ENT_TYPE) {
|
||||
switch (dirent->type_indicator) {
|
||||
case 1:
|
||||
ent->d_type = DT_REG;
|
||||
break;
|
||||
case 2:
|
||||
ent->d_type = DT_DIR;
|
||||
break;
|
||||
case 7:
|
||||
ent->d_type = DT_LNK;
|
||||
break;
|
||||
default:
|
||||
panic("Unsupported dirent type: %u\n", dirent->type_indicator);
|
||||
}
|
||||
} else {
|
||||
name_length += (uint16_t) dirent->name_length_high << 8;
|
||||
}
|
||||
strncpy(ent->d_name, dirent->name, name_length);
|
||||
ent->d_name[name_length] = 0;
|
||||
ent->d_off = fd->pos;
|
||||
ent->d_reclen = dirent->ent_size;
|
||||
|
||||
// Calculate next dirent position
|
||||
fd->pos += dirent->ent_size;
|
||||
|
||||
return ent->d_reclen;
|
||||
}
|
||||
|
||||
static int ext2_vnode_chmod(struct vnode *node, mode_t new_mode) {
|
||||
_assert(node);
|
||||
struct ext2_inode *inode = node->fs_data;
|
||||
_assert(inode);
|
||||
struct fs *ext2 = node->fs;
|
||||
_assert(ext2);
|
||||
|
||||
// Only rewrite inode if something really changed
|
||||
if ((inode->mode & 0xFFF) != (new_mode & 0xFFF)) {
|
||||
// chmod() only updates file mode, don't touch file type
|
||||
inode->mode &= ~0xFFF;
|
||||
inode->mode |= new_mode & 0xFFF;
|
||||
node->mode = new_mode & 0xFFF;
|
||||
|
||||
return ext2_write_inode(ext2, inode, node->ino);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ext2_vnode_chown(struct vnode *node, uid_t new_uid, gid_t new_gid) {
|
||||
_assert(node);
|
||||
struct ext2_inode *inode = node->fs_data;
|
||||
_assert(inode);
|
||||
struct fs *ext2 = node->fs;
|
||||
_assert(ext2);
|
||||
|
||||
if (new_uid != inode->uid || new_gid != inode->gid) {
|
||||
inode->uid = new_uid;
|
||||
inode->gid = new_gid;
|
||||
node->uid = new_uid;
|
||||
node->gid = new_gid;
|
||||
|
||||
return ext2_write_inode(ext2, inode, node->ino);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ext2_vnode_stat(struct vnode *node, struct stat *st) {
|
||||
_assert(node && st);
|
||||
struct ext2_inode *inode = node->fs_data;
|
||||
_assert(inode);
|
||||
struct fs *ext2 = node->fs;
|
||||
_assert(ext2);
|
||||
struct ext2_data *data = ext2->fs_private;
|
||||
_assert(data);
|
||||
|
||||
_assert(inode->uid == node->uid);
|
||||
_assert(inode->gid == node->gid);
|
||||
_assert((inode->mode & 0xFFF) == node->mode);
|
||||
|
||||
st->st_mode = inode->mode;
|
||||
st->st_uid = inode->uid;
|
||||
st->st_gid = inode->gid;
|
||||
|
||||
st->st_size = inode->size_lower;
|
||||
_assert(!inode->size_upper);
|
||||
st->st_blksize = data->block_size;
|
||||
st->st_blocks = (st->st_size + data->block_size - 1) / data->block_size;
|
||||
|
||||
st->st_dev = 0;
|
||||
st->st_rdev = 0;
|
||||
|
||||
st->st_mtime = inode->mtime;
|
||||
st->st_atime = inode->atime;
|
||||
st->st_ctime = inode->ctime;
|
||||
|
||||
st->st_nlink = inode->hard_links;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
// Map inode parameters to vnode fields
|
||||
void ext2_inode_to_vnode(struct vnode *vnode, struct ext2_inode *inode, uint32_t ino) {
|
||||
vnode->ino = ino;
|
||||
vnode->uid = inode->uid;
|
||||
vnode->gid = inode->gid;
|
||||
vnode->mode = inode->mode & 0xFFF;
|
||||
vnode->fs_data = inode;
|
||||
vnode->op = &g_ext2_vnode_ops;
|
||||
|
||||
switch (inode->mode & 0xF000) {
|
||||
case EXT2_IFDIR:
|
||||
vnode->type = VN_DIR;
|
||||
break;
|
||||
case EXT2_IFREG:
|
||||
vnode->type = VN_REG;
|
||||
break;
|
||||
default:
|
||||
panic("Unsupported inode type: %04x\n", inode->mode & 0xF000);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user