Compare commits
2 Commits
71e2ae482a
...
0c5aa72fd3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c5aa72fd3 | ||
|
|
3d5fb4c267 |
@@ -19,15 +19,26 @@ add_subdirectory(src)
|
|||||||
file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/release)
|
file(MAKE_DIRECTORY ${CMAKE_SOURCE_DIR}/release)
|
||||||
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub)
|
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/isodir/boot/grub)
|
||||||
|
|
||||||
# Create grub.cfg for ISO
|
# Generate CPIO initial ramdisk from apps directory.
|
||||||
file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot2 /boot/kernel.bin }")
|
# All files in apps/ are packed into a newc-format CPIO archive.
|
||||||
|
set(INITRD_FILE ${CMAKE_BINARY_DIR}/isodir/boot/initrd.cpio)
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${INITRD_FILE}
|
||||||
|
COMMAND ${CMAKE_SOURCE_DIR}/scripts/gen_initrd.sh ${CMAKE_SOURCE_DIR}/apps ${INITRD_FILE}
|
||||||
|
DEPENDS ${CMAKE_SOURCE_DIR}/apps
|
||||||
|
COMMENT "Generating CPIO initial ramdisk"
|
||||||
|
)
|
||||||
|
add_custom_target(initrd DEPENDS ${INITRD_FILE})
|
||||||
|
|
||||||
|
# Create grub.cfg for ISO - includes module2 for the initrd
|
||||||
|
file(WRITE ${CMAKE_BINARY_DIR}/isodir/boot/grub/grub.cfg "set timeout=0\nset default=0\nsearch --set=root --file /boot/kernel.bin\nmenuentry \"ClaudeOS\" { multiboot2 /boot/kernel.bin\n module2 /boot/initrd.cpio }")
|
||||||
|
|
||||||
|
|
||||||
# ISO Generation
|
# ISO Generation
|
||||||
add_custom_target(iso ALL
|
add_custom_target(iso ALL
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kernel ${CMAKE_BINARY_DIR}/isodir/boot/kernel.bin
|
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kernel ${CMAKE_BINARY_DIR}/isodir/boot/kernel.bin
|
||||||
COMMAND grub-mkrescue -o ${CMAKE_SOURCE_DIR}/release/claude-os.iso ${CMAKE_BINARY_DIR}/isodir
|
COMMAND grub-mkrescue -o ${CMAKE_SOURCE_DIR}/release/claude-os.iso ${CMAKE_BINARY_DIR}/isodir
|
||||||
DEPENDS kernel
|
DEPENDS kernel initrd
|
||||||
COMMENT "Generating bootable ISO image"
|
COMMENT "Generating bootable ISO image"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ Once a task is completed, it should be checked off.
|
|||||||
- [x] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
- [x] Create an initial driver architecture, allowing different drivers included in the kernel to test whether they should load or not.
|
||||||
- [x] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
|
- [x] Create a VGA driver. On startup, some memory statistics should be displayed, as well as boot progress.
|
||||||
- [x] Create subsystem for loading new processes in Ring 3.
|
- [x] Create subsystem for loading new processes in Ring 3.
|
||||||
- [ ] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
|
- [x] Update the build script to generate a ramdisk containing any applications to run. This initial ramdisk is in CPIO format.
|
||||||
- [ ] Write a VFS subsystem.
|
- [x] Write a VFS subsystem.
|
||||||
- [ ] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
|
- [x] Write a VFS driver that provides the contents of the CPIO initial ramdisk to the VFS layer.
|
||||||
- [ ] Create a `hello-world` app. It should print `Hello, World` to its own stdout. The kernel should route this to Qemu and to the VGA dispaly. Ensure this work.
|
- [ ] Create a `hello-world` app. It should print `Hello, World` to its own stdout. The kernel should route this to Qemu and to the VGA dispaly. Ensure this work.
|
||||||
- [ ] Implement the fork system call.
|
- [ ] Implement the fork system call.
|
||||||
- [ ] Implement environment variables. Apps should be able to modify this, and it should be copied to new forks of an app.
|
- [ ] Implement environment variables. Apps should be able to modify this, and it should be copied to new forks of an app.
|
||||||
|
|||||||
1
apps/README
Normal file
1
apps/README
Normal file
@@ -0,0 +1 @@
|
|||||||
|
This is the ClaudeOS initial ramdisk.
|
||||||
81
docs/cpio.md
Normal file
81
docs/cpio.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# CPIO Initial Ramdisk
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The initial ramdisk (initrd) provides files to the kernel at boot time before
|
||||||
|
any filesystem drivers are available. It is a CPIO archive in SVR4/newc format,
|
||||||
|
loaded by GRUB as a Multiboot2 module.
|
||||||
|
|
||||||
|
## Build Process
|
||||||
|
|
||||||
|
During the build, the script `scripts/gen_initrd.sh` packs all files from the
|
||||||
|
`apps/` directory into a CPIO archive:
|
||||||
|
|
||||||
|
```
|
||||||
|
apps/
|
||||||
|
├── README (placeholder)
|
||||||
|
├── hello-world (future: flat binary)
|
||||||
|
└── sh (future: shell binary)
|
||||||
|
```
|
||||||
|
|
||||||
|
The archive is placed at `build/isodir/boot/initrd.cpio` and included in the
|
||||||
|
ISO image. GRUB loads it as a module via:
|
||||||
|
|
||||||
|
```
|
||||||
|
module2 /boot/initrd.cpio
|
||||||
|
```
|
||||||
|
|
||||||
|
## CPIO Format
|
||||||
|
|
||||||
|
The newc (SVR4) CPIO format uses 110-byte headers with hex ASCII fields:
|
||||||
|
|
||||||
|
```
|
||||||
|
Offset Size Field
|
||||||
|
0 6 Magic ("070701")
|
||||||
|
6 8 Inode
|
||||||
|
14 8 Mode
|
||||||
|
22 8 UID
|
||||||
|
30 8 GID
|
||||||
|
38 8 Nlink
|
||||||
|
46 8 Mtime
|
||||||
|
54 8 Filesize
|
||||||
|
62 8 Devmajor
|
||||||
|
70 8 Devminor
|
||||||
|
78 8 Rdevmajor
|
||||||
|
86 8 Rdevminor
|
||||||
|
94 8 Namesize
|
||||||
|
102 8 Check
|
||||||
|
```
|
||||||
|
|
||||||
|
After the header: filename (namesize bytes, padded to 4-byte boundary),
|
||||||
|
then file data (filesize bytes, padded to 4-byte boundary). The archive
|
||||||
|
ends with a `TRAILER!!!` entry.
|
||||||
|
|
||||||
|
## Kernel Interface
|
||||||
|
|
||||||
|
The kernel finds the initrd by scanning Multiboot2 boot information for
|
||||||
|
`MULTIBOOT_TAG_TYPE_MODULE` (type 3). The module's physical memory range
|
||||||
|
is identity-mapped, so it can be read directly.
|
||||||
|
|
||||||
|
```c
|
||||||
|
#include "cpio.h"
|
||||||
|
|
||||||
|
// Find a file
|
||||||
|
cpio_entry_t entry;
|
||||||
|
if (cpio_find("hello-world", &entry) == 0) {
|
||||||
|
// entry.data = pointer to file contents
|
||||||
|
// entry.datasize = file size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate all files
|
||||||
|
uint32_t offset = 0;
|
||||||
|
while (cpio_next(&offset, &entry) == 0) {
|
||||||
|
// entry.name, entry.data, entry.datasize
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `scripts/gen_initrd.sh` — Build script to generate CPIO archive
|
||||||
|
- `src/cpio.h` / `src/cpio.c` — CPIO parser (find, iterate, count)
|
||||||
|
- `CMakeLists.txt` — `initrd` target and `module2` in grub.cfg
|
||||||
78
docs/vfs.md
Normal file
78
docs/vfs.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# Virtual Filesystem (VFS)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The VFS provides a unified interface for file and directory operations across
|
||||||
|
different filesystem implementations. Filesystem drivers register ops structs
|
||||||
|
and are mounted at specific paths. Path resolution finds the longest-matching
|
||||||
|
mount point and delegates to that filesystem.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
User/Kernel Code
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
vfs_open("/initrd/hello-world", 0)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
VFS: find_mount("/initrd/hello-world")
|
||||||
|
│ → mount "/initrd", rel_path = "hello-world"
|
||||||
|
▼
|
||||||
|
resolve_path → initrd_finddir("hello-world")
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
vfs_read(fd, buf, size)
|
||||||
|
│ → initrd_read(node, offset, size, buf)
|
||||||
|
▼
|
||||||
|
Returns file data from CPIO archive
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mount Points
|
||||||
|
|
||||||
|
Filesystems are mounted at absolute paths. The VFS supports up to 16
|
||||||
|
simultaneous mounts. Path resolution uses longest-prefix matching:
|
||||||
|
|
||||||
|
```
|
||||||
|
Mount: "/initrd" → handles /initrd/*
|
||||||
|
Mount: "/sys" → handles /sys/*
|
||||||
|
Mount: "/dev" → handles /dev/*
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Operations
|
||||||
|
|
||||||
|
| Function | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `vfs_open(path, flags)` | Open a file, returns fd |
|
||||||
|
| `vfs_close(fd)` | Close a file descriptor |
|
||||||
|
| `vfs_read(fd, buf, size)` | Read bytes, advances offset |
|
||||||
|
| `vfs_write(fd, buf, size)` | Write bytes, advances offset |
|
||||||
|
| `vfs_seek(fd, offset, whence)` | Seek within file |
|
||||||
|
| `vfs_readdir(path, idx, out)` | Read directory entry |
|
||||||
|
| `vfs_stat(path, out)` | Get file info |
|
||||||
|
|
||||||
|
## Filesystem Driver Interface
|
||||||
|
|
||||||
|
Each filesystem provides a `vfs_fs_ops_t` struct:
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct vfs_fs_ops {
|
||||||
|
int (*open)(vfs_node_t *node, uint32_t flags);
|
||||||
|
void (*close)(vfs_node_t *node);
|
||||||
|
int32_t (*read)(vfs_node_t *node, uint32_t offset, uint32_t size, void *buf);
|
||||||
|
int32_t (*write)(vfs_node_t *node, uint32_t offset, uint32_t size, const void *buf);
|
||||||
|
int (*readdir)(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out);
|
||||||
|
int (*finddir)(vfs_node_t *dir, const char *name, vfs_node_t *out);
|
||||||
|
} vfs_fs_ops_t;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Initrd Filesystem Driver
|
||||||
|
|
||||||
|
The initrd filesystem (`initrd_fs.c`) provides read-only access to the CPIO
|
||||||
|
ramdisk. It is automatically mounted at `/initrd` during boot. Files are
|
||||||
|
accessed via zero-copy reads directly from the CPIO archive in memory.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `src/vfs.h` / `src/vfs.c` — VFS core: mount table, fd table, path resolution
|
||||||
|
- `src/initrd_fs.h` / `src/initrd_fs.c` — CPIO ramdisk VFS driver
|
||||||
14
scripts/gen_initrd.sh
Executable file
14
scripts/gen_initrd.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Generate CPIO initial ramdisk from apps directory
|
||||||
|
# Usage: gen_initrd.sh <apps_dir> <output_file>
|
||||||
|
set -e
|
||||||
|
|
||||||
|
APPS_DIR="$1"
|
||||||
|
OUTPUT="$2"
|
||||||
|
|
||||||
|
# Ensure output directory exists
|
||||||
|
mkdir -p "$(dirname "$OUTPUT")"
|
||||||
|
|
||||||
|
cd "$APPS_DIR"
|
||||||
|
find . -not -name '.' | cpio -o -H newc > "$OUTPUT" 2>/dev/null
|
||||||
|
echo "Generated initrd: $(wc -c < "$OUTPUT") bytes"
|
||||||
@@ -16,6 +16,9 @@ add_executable(kernel
|
|||||||
tss.c
|
tss.c
|
||||||
process.c
|
process.c
|
||||||
syscall.c
|
syscall.c
|
||||||
|
cpio.c
|
||||||
|
vfs.c
|
||||||
|
initrd_fs.c
|
||||||
interrupts.S
|
interrupts.S
|
||||||
kernel.c
|
kernel.c
|
||||||
)
|
)
|
||||||
|
|||||||
179
src/cpio.c
Normal file
179
src/cpio.c
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
/**
|
||||||
|
* @file cpio.c
|
||||||
|
* @brief CPIO newc archive parser implementation.
|
||||||
|
*
|
||||||
|
* Parses CPIO archives in the SVR4/newc format. The archive is expected
|
||||||
|
* to be loaded into memory by GRUB as a Multiboot2 module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "cpio.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Debug print helpers defined in kernel.c */
|
||||||
|
extern void offset_print(const char *str);
|
||||||
|
extern void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
/** Pointer to the CPIO archive in memory. */
|
||||||
|
static const uint8_t *archive = NULL;
|
||||||
|
|
||||||
|
/** Size of the archive (0 if unknown). */
|
||||||
|
static uint32_t archive_len = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse an N-character hexadecimal ASCII string to uint32_t.
|
||||||
|
*
|
||||||
|
* @param s Pointer to hex string.
|
||||||
|
* @param n Number of characters to parse.
|
||||||
|
* @return Parsed value.
|
||||||
|
*/
|
||||||
|
static uint32_t parse_hex(const char *s, int n) {
|
||||||
|
uint32_t val = 0;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
char c = s[i];
|
||||||
|
uint32_t digit;
|
||||||
|
if (c >= '0' && c <= '9') {
|
||||||
|
digit = (uint32_t)(c - '0');
|
||||||
|
} else if (c >= 'a' && c <= 'f') {
|
||||||
|
digit = (uint32_t)(c - 'a' + 10);
|
||||||
|
} else if (c >= 'A' && c <= 'F') {
|
||||||
|
digit = (uint32_t)(c - 'A' + 10);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
val = (val << 4) | digit;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Round up to 4-byte boundary.
|
||||||
|
*/
|
||||||
|
static inline uint32_t align4(uint32_t v) {
|
||||||
|
return (v + 3) & ~3u;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a CPIO entry at the given offset.
|
||||||
|
*
|
||||||
|
* @param offset Byte offset into the archive.
|
||||||
|
* @param entry Output entry information.
|
||||||
|
* @return Offset of the next entry, or 0 on error/end.
|
||||||
|
*/
|
||||||
|
static uint32_t parse_entry(uint32_t offset, cpio_entry_t *entry) {
|
||||||
|
if (!archive) return 0;
|
||||||
|
|
||||||
|
const cpio_newc_header_t *hdr = (const cpio_newc_header_t *)(archive + offset);
|
||||||
|
|
||||||
|
/* Verify magic */
|
||||||
|
if (memcmp(hdr->magic, CPIO_MAGIC, 6) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t namesize = parse_hex(hdr->namesize, 8);
|
||||||
|
uint32_t filesize = parse_hex(hdr->filesize, 8);
|
||||||
|
uint32_t mode = parse_hex(hdr->mode, 8);
|
||||||
|
|
||||||
|
/* Filename starts right after the header */
|
||||||
|
const char *name = (const char *)(archive + offset + CPIO_HEADER_SIZE);
|
||||||
|
|
||||||
|
/* Data starts after header + name, aligned to 4 bytes */
|
||||||
|
uint32_t data_offset = align4(offset + CPIO_HEADER_SIZE + namesize);
|
||||||
|
const void *data = archive + data_offset;
|
||||||
|
|
||||||
|
/* Next entry starts after data, aligned to 4 bytes */
|
||||||
|
uint32_t next_offset = align4(data_offset + filesize);
|
||||||
|
|
||||||
|
entry->name = name;
|
||||||
|
entry->namesize = namesize;
|
||||||
|
entry->data = data;
|
||||||
|
entry->datasize = filesize;
|
||||||
|
entry->mode = mode;
|
||||||
|
|
||||||
|
return next_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cpio_init(const void *archive_start, uint32_t archive_size) {
|
||||||
|
archive = (const uint8_t *)archive_start;
|
||||||
|
archive_len = archive_size;
|
||||||
|
|
||||||
|
offset_print(" CPIO: archive at ");
|
||||||
|
print_hex((uint32_t)archive_start);
|
||||||
|
offset_print(" CPIO: size = ");
|
||||||
|
print_hex(archive_size);
|
||||||
|
|
||||||
|
/* Count and list entries */
|
||||||
|
uint32_t count = 0;
|
||||||
|
uint32_t off = 0;
|
||||||
|
cpio_entry_t entry;
|
||||||
|
while (1) {
|
||||||
|
uint32_t next = parse_entry(off, &entry);
|
||||||
|
if (next == 0) break;
|
||||||
|
if (strcmp(entry.name, CPIO_TRAILER) == 0) break;
|
||||||
|
|
||||||
|
offset_print(" CPIO: [");
|
||||||
|
offset_print(entry.name);
|
||||||
|
offset_print("] size=");
|
||||||
|
print_hex(entry.datasize);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
off = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" CPIO: ");
|
||||||
|
print_hex(count);
|
||||||
|
offset_print(" CPIO: files found\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int cpio_find(const char *name, cpio_entry_t *entry) {
|
||||||
|
if (!archive) return -1;
|
||||||
|
|
||||||
|
uint32_t off = 0;
|
||||||
|
while (1) {
|
||||||
|
uint32_t next = parse_entry(off, entry);
|
||||||
|
if (next == 0) return -1;
|
||||||
|
if (strcmp(entry->name, CPIO_TRAILER) == 0) return -1;
|
||||||
|
|
||||||
|
/* Match by name. CPIO entries often have "./" prefix, try both. */
|
||||||
|
if (strcmp(entry->name, name) == 0) return 0;
|
||||||
|
|
||||||
|
/* Try matching without "./" prefix */
|
||||||
|
if (entry->name[0] == '.' && entry->name[1] == '/' &&
|
||||||
|
strcmp(entry->name + 2, name) == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try matching with "./" prefix */
|
||||||
|
if (name[0] != '.' && entry->namesize > 2) {
|
||||||
|
/* Already handled above */
|
||||||
|
}
|
||||||
|
|
||||||
|
off = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int cpio_next(uint32_t *offset, cpio_entry_t *entry) {
|
||||||
|
if (!archive) return -1;
|
||||||
|
|
||||||
|
uint32_t next = parse_entry(*offset, entry);
|
||||||
|
if (next == 0) return -1;
|
||||||
|
if (strcmp(entry->name, CPIO_TRAILER) == 0) return -1;
|
||||||
|
|
||||||
|
*offset = next;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cpio_count(void) {
|
||||||
|
if (!archive) return 0;
|
||||||
|
|
||||||
|
uint32_t count = 0;
|
||||||
|
uint32_t off = 0;
|
||||||
|
cpio_entry_t entry;
|
||||||
|
while (1) {
|
||||||
|
uint32_t next = parse_entry(off, &entry);
|
||||||
|
if (next == 0) break;
|
||||||
|
if (strcmp(entry.name, CPIO_TRAILER) == 0) break;
|
||||||
|
count++;
|
||||||
|
off = next;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
92
src/cpio.h
Normal file
92
src/cpio.h
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/**
|
||||||
|
* @file cpio.h
|
||||||
|
* @brief CPIO newc archive parser.
|
||||||
|
*
|
||||||
|
* Parses CPIO archives in the SVR4/newc format (magic "070701").
|
||||||
|
* Used to read files from the initial ramdisk loaded by GRUB.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CPIO_H
|
||||||
|
#define CPIO_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CPIO newc header (110 bytes).
|
||||||
|
* All fields are 8-character hexadecimal ASCII strings.
|
||||||
|
*/
|
||||||
|
typedef struct cpio_newc_header {
|
||||||
|
char magic[6]; /**< Must be "070701". */
|
||||||
|
char ino[8];
|
||||||
|
char mode[8];
|
||||||
|
char uid[8];
|
||||||
|
char gid[8];
|
||||||
|
char nlink[8];
|
||||||
|
char mtime[8];
|
||||||
|
char filesize[8];
|
||||||
|
char devmajor[8];
|
||||||
|
char devminor[8];
|
||||||
|
char rdevmajor[8];
|
||||||
|
char rdevminor[8];
|
||||||
|
char namesize[8];
|
||||||
|
char check[8];
|
||||||
|
} cpio_newc_header_t;
|
||||||
|
|
||||||
|
/** Size of the CPIO newc header in bytes. */
|
||||||
|
#define CPIO_HEADER_SIZE 110
|
||||||
|
|
||||||
|
/** CPIO newc magic string. */
|
||||||
|
#define CPIO_MAGIC "070701"
|
||||||
|
|
||||||
|
/** Trailer entry name that marks end of archive. */
|
||||||
|
#define CPIO_TRAILER "TRAILER!!!"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CPIO file entry (result of iteration or lookup).
|
||||||
|
*/
|
||||||
|
typedef struct cpio_entry {
|
||||||
|
const char *name; /**< Filename (pointer into archive). */
|
||||||
|
uint32_t namesize; /**< Length of filename including NUL. */
|
||||||
|
const void *data; /**< Pointer to file data within archive. */
|
||||||
|
uint32_t datasize; /**< Size of file data in bytes. */
|
||||||
|
uint32_t mode; /**< File mode/permissions. */
|
||||||
|
} cpio_entry_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the CPIO parser with the archive location.
|
||||||
|
*
|
||||||
|
* @param archive_start Pointer to the start of the CPIO archive in memory.
|
||||||
|
* @param archive_size Size of the archive in bytes (0 if unknown).
|
||||||
|
*/
|
||||||
|
void cpio_init(const void *archive_start, uint32_t archive_size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a file in the CPIO archive by name.
|
||||||
|
*
|
||||||
|
* @param name Filename to search for (without leading "./").
|
||||||
|
* @param entry Output: filled with file information if found.
|
||||||
|
* @return 0 on success, -1 if not found.
|
||||||
|
*/
|
||||||
|
int cpio_find(const char *name, cpio_entry_t *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate through all entries in the CPIO archive.
|
||||||
|
*
|
||||||
|
* Call with *offset = 0 to start. Returns 0 on success, -1 when no
|
||||||
|
* more entries exist or the TRAILER is reached.
|
||||||
|
*
|
||||||
|
* @param offset In/out: current position in the archive.
|
||||||
|
* @param entry Output: filled with the next entry's information.
|
||||||
|
* @return 0 on success, -1 at end of archive.
|
||||||
|
*/
|
||||||
|
int cpio_next(uint32_t *offset, cpio_entry_t *entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of files in the CPIO archive (excluding TRAILER).
|
||||||
|
*
|
||||||
|
* @return Number of files.
|
||||||
|
*/
|
||||||
|
uint32_t cpio_count(void);
|
||||||
|
|
||||||
|
#endif /* CPIO_H */
|
||||||
123
src/initrd_fs.c
Normal file
123
src/initrd_fs.c
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* @file initrd_fs.c
|
||||||
|
* @brief CPIO initial ramdisk VFS driver implementation.
|
||||||
|
*
|
||||||
|
* Provides a read-only VFS interface to the CPIO archive loaded at boot.
|
||||||
|
* Files are accessed directly from the archive memory (zero-copy reads).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "initrd_fs.h"
|
||||||
|
#include "vfs.h"
|
||||||
|
#include "cpio.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Debug print helpers defined in kernel.c */
|
||||||
|
extern void offset_print(const char *str);
|
||||||
|
extern void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from a file in the initrd.
|
||||||
|
* Data is read directly from the CPIO archive (memory-mapped).
|
||||||
|
*/
|
||||||
|
static int32_t initrd_read(vfs_node_t *node, uint32_t offset,
|
||||||
|
uint32_t size, void *buf) {
|
||||||
|
if (!node || !node->fs_data || !buf) return -1;
|
||||||
|
|
||||||
|
/* fs_data points to the file's data within the CPIO archive */
|
||||||
|
const uint8_t *data = (const uint8_t *)node->fs_data;
|
||||||
|
uint32_t file_size = node->size;
|
||||||
|
|
||||||
|
if (offset >= file_size) return 0;
|
||||||
|
|
||||||
|
uint32_t remaining = file_size - offset;
|
||||||
|
if (size > remaining) size = remaining;
|
||||||
|
|
||||||
|
memcpy(buf, data + offset, size);
|
||||||
|
return (int32_t)size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a directory entry from the initrd root.
|
||||||
|
* The initrd is a flat archive — all files are at the root level.
|
||||||
|
*/
|
||||||
|
static int initrd_readdir(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out) {
|
||||||
|
(void)dir;
|
||||||
|
|
||||||
|
uint32_t off = 0;
|
||||||
|
uint32_t current = 0;
|
||||||
|
cpio_entry_t entry;
|
||||||
|
|
||||||
|
while (cpio_next(&off, &entry) == 0) {
|
||||||
|
/* Skip the "." directory entry if present */
|
||||||
|
if (entry.name[0] == '.' && entry.name[1] == '\0') continue;
|
||||||
|
|
||||||
|
/* Strip "./" prefix */
|
||||||
|
const char *name = entry.name;
|
||||||
|
if (name[0] == '.' && name[1] == '/') name += 2;
|
||||||
|
|
||||||
|
/* Skip empty names */
|
||||||
|
if (*name == '\0') continue;
|
||||||
|
|
||||||
|
if (current == idx) {
|
||||||
|
memset(out, 0, sizeof(vfs_dirent_t));
|
||||||
|
strncpy(out->name, name, VFS_MAX_NAME - 1);
|
||||||
|
out->inode = current;
|
||||||
|
out->type = VFS_FILE;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
current++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1; /* No more entries */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a file by name within the initrd.
|
||||||
|
*/
|
||||||
|
static int initrd_finddir(vfs_node_t *dir, const char *name, vfs_node_t *out) {
|
||||||
|
(void)dir;
|
||||||
|
|
||||||
|
cpio_entry_t entry;
|
||||||
|
if (cpio_find(name, &entry) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(out, 0, sizeof(vfs_node_t));
|
||||||
|
/* Strip "./" prefix for the node name */
|
||||||
|
const char *display_name = entry.name;
|
||||||
|
if (display_name[0] == '.' && display_name[1] == '/') {
|
||||||
|
display_name += 2;
|
||||||
|
}
|
||||||
|
strncpy(out->name, display_name, VFS_MAX_NAME - 1);
|
||||||
|
out->type = VFS_FILE;
|
||||||
|
out->size = entry.datasize;
|
||||||
|
out->mode = entry.mode;
|
||||||
|
out->fs_data = (void *)entry.data; /* Direct pointer into CPIO archive */
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Filesystem operations for the initrd. */
|
||||||
|
static vfs_fs_ops_t initrd_ops = {
|
||||||
|
.open = NULL, /* No special open needed */
|
||||||
|
.close = NULL, /* No special close needed */
|
||||||
|
.read = initrd_read,
|
||||||
|
.write = NULL, /* Read-only */
|
||||||
|
.readdir = initrd_readdir,
|
||||||
|
.finddir = initrd_finddir,
|
||||||
|
};
|
||||||
|
|
||||||
|
int init_initrd_fs(void) {
|
||||||
|
if (cpio_count() == 0) {
|
||||||
|
offset_print(" INITRD_FS: no files in ramdisk\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = vfs_mount("/initrd", &initrd_ops, NULL);
|
||||||
|
if (ret != 0) {
|
||||||
|
offset_print(" INITRD_FS: failed to mount\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_print(" INITRD_FS: mounted at /initrd\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
20
src/initrd_fs.h
Normal file
20
src/initrd_fs.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @file initrd_fs.h
|
||||||
|
* @brief CPIO initial ramdisk VFS driver.
|
||||||
|
*
|
||||||
|
* Provides a read-only filesystem backed by the CPIO initial ramdisk.
|
||||||
|
* Mounted at "/initrd" to expose the contents of the ramdisk via the VFS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef INITRD_FS_H
|
||||||
|
#define INITRD_FS_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the initrd filesystem driver and mount it at "/initrd".
|
||||||
|
* Must be called after init_vfs() and cpio_init().
|
||||||
|
*
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int init_initrd_fs(void);
|
||||||
|
|
||||||
|
#endif /* INITRD_FS_H */
|
||||||
52
src/kernel.c
52
src/kernel.c
@@ -13,6 +13,9 @@
|
|||||||
#include "tss.h"
|
#include "tss.h"
|
||||||
#include "syscall.h"
|
#include "syscall.h"
|
||||||
#include "process.h"
|
#include "process.h"
|
||||||
|
#include "cpio.h"
|
||||||
|
#include "vfs.h"
|
||||||
|
#include "initrd_fs.h"
|
||||||
|
|
||||||
void offset_print(const char *str)
|
void offset_print(const char *str)
|
||||||
{
|
{
|
||||||
@@ -54,6 +57,26 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
init_pmm(addr);
|
init_pmm(addr);
|
||||||
offset_print("PMM initialized\n");
|
offset_print("PMM initialized\n");
|
||||||
|
|
||||||
|
/* Scan Multiboot2 tags for the initrd module */
|
||||||
|
uint32_t initrd_start = 0, initrd_end = 0;
|
||||||
|
{
|
||||||
|
struct multiboot_tag *tag;
|
||||||
|
for (tag = (struct multiboot_tag *)(addr + 8);
|
||||||
|
tag->type != MULTIBOOT_TAG_TYPE_END;
|
||||||
|
tag = (struct multiboot_tag *)((uint8_t *)tag + ((tag->size + 7) & ~7u))) {
|
||||||
|
if (tag->type == MULTIBOOT_TAG_TYPE_MODULE) {
|
||||||
|
struct multiboot_tag_module *mod = (struct multiboot_tag_module *)tag;
|
||||||
|
initrd_start = mod->mod_start;
|
||||||
|
initrd_end = mod->mod_end;
|
||||||
|
offset_print("Initrd module at ");
|
||||||
|
print_hex(initrd_start);
|
||||||
|
offset_print(" to ");
|
||||||
|
print_hex(initrd_end);
|
||||||
|
break; /* Use first module */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init_paging();
|
init_paging();
|
||||||
offset_print("Paging initialized\n");
|
offset_print("Paging initialized\n");
|
||||||
|
|
||||||
@@ -72,6 +95,35 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
init_kmalloc();
|
init_kmalloc();
|
||||||
offset_print("Memory allocator initialized\n");
|
offset_print("Memory allocator initialized\n");
|
||||||
|
|
||||||
|
/* Initialize CPIO ramdisk if module was loaded */
|
||||||
|
if (initrd_start != 0) {
|
||||||
|
cpio_init((const void *)initrd_start, initrd_end - initrd_start);
|
||||||
|
offset_print("CPIO ramdisk initialized\n");
|
||||||
|
} else {
|
||||||
|
offset_print("No initrd module found\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
init_vfs();
|
||||||
|
offset_print("VFS initialized\n");
|
||||||
|
|
||||||
|
if (initrd_start != 0) {
|
||||||
|
init_initrd_fs();
|
||||||
|
offset_print("Initrd filesystem mounted\n");
|
||||||
|
|
||||||
|
/* Test VFS: read a file from the initrd */
|
||||||
|
int fd = vfs_open("/initrd/README", 0);
|
||||||
|
if (fd >= 0) {
|
||||||
|
char buf[64];
|
||||||
|
int32_t n = vfs_read(fd, buf, sizeof(buf) - 1);
|
||||||
|
if (n > 0) {
|
||||||
|
buf[n] = '\0';
|
||||||
|
offset_print("VFS read /initrd/README: ");
|
||||||
|
offset_print(buf);
|
||||||
|
}
|
||||||
|
vfs_close(fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init_tss();
|
init_tss();
|
||||||
offset_print("TSS initialized\n");
|
offset_print("TSS initialized\n");
|
||||||
|
|
||||||
|
|||||||
295
src/vfs.c
Normal file
295
src/vfs.c
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
/**
|
||||||
|
* @file vfs.c
|
||||||
|
* @brief Virtual Filesystem (VFS) subsystem implementation.
|
||||||
|
*
|
||||||
|
* Manages mount points and routes file operations to the appropriate
|
||||||
|
* filesystem driver. Path resolution walks the mount table to find the
|
||||||
|
* longest-matching mount point, then delegates to that fs's operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "vfs.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Debug print helpers defined in kernel.c */
|
||||||
|
extern void offset_print(const char *str);
|
||||||
|
extern void print_hex(uint32_t val);
|
||||||
|
|
||||||
|
/** Mount table. */
|
||||||
|
static vfs_mount_t mounts[VFS_MAX_MOUNTS];
|
||||||
|
|
||||||
|
/** Open file descriptor table. */
|
||||||
|
static vfs_fd_t fd_table[VFS_MAX_OPEN_FILES];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the mount point that best matches a given path.
|
||||||
|
* Returns the index of the longest matching mount, or -1 if none.
|
||||||
|
*
|
||||||
|
* @param path The absolute path to resolve.
|
||||||
|
* @param rel_path Output: pointer within `path` past the mount prefix.
|
||||||
|
* @return Mount index, or -1.
|
||||||
|
*/
|
||||||
|
static int find_mount(const char *path, const char **rel_path) {
|
||||||
|
int best = -1;
|
||||||
|
uint32_t best_len = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < VFS_MAX_MOUNTS; i++) {
|
||||||
|
if (!mounts[i].active) continue;
|
||||||
|
|
||||||
|
uint32_t mlen = strlen(mounts[i].path);
|
||||||
|
|
||||||
|
/* Check if path starts with this mount point */
|
||||||
|
if (strncmp(path, mounts[i].path, mlen) != 0) continue;
|
||||||
|
|
||||||
|
/* Must match at a directory boundary */
|
||||||
|
if (mlen > 1 && path[mlen] != '\0' && path[mlen] != '/') continue;
|
||||||
|
|
||||||
|
if (mlen > best_len) {
|
||||||
|
best = i;
|
||||||
|
best_len = mlen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best >= 0 && rel_path) {
|
||||||
|
const char *r = path + best_len;
|
||||||
|
/* Skip leading slash in relative path */
|
||||||
|
while (*r == '/') r++;
|
||||||
|
*rel_path = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a path to a VFS node by finding the appropriate mount
|
||||||
|
* and asking the filesystem driver.
|
||||||
|
*
|
||||||
|
* @param path Full absolute path.
|
||||||
|
* @param out Output node.
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
static int resolve_path(const char *path, vfs_node_t *out) {
|
||||||
|
const char *rel_path = NULL;
|
||||||
|
int mount_idx = find_mount(path, &rel_path);
|
||||||
|
|
||||||
|
if (mount_idx < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfs_mount_t *mnt = &mounts[mount_idx];
|
||||||
|
|
||||||
|
/* If rel_path is empty, we're looking at the mount root */
|
||||||
|
if (*rel_path == '\0') {
|
||||||
|
/* Return a directory node for the mount root */
|
||||||
|
memset(out, 0, sizeof(vfs_node_t));
|
||||||
|
strncpy(out->name, mnt->path, VFS_MAX_NAME - 1);
|
||||||
|
out->type = VFS_DIRECTORY;
|
||||||
|
out->ops = mnt->ops;
|
||||||
|
out->fs_data = mnt->fs_data;
|
||||||
|
out->mount_idx = mount_idx;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Walk path components through the filesystem */
|
||||||
|
vfs_node_t current;
|
||||||
|
memset(¤t, 0, sizeof(vfs_node_t));
|
||||||
|
current.type = VFS_DIRECTORY;
|
||||||
|
current.ops = mnt->ops;
|
||||||
|
current.fs_data = mnt->fs_data;
|
||||||
|
current.mount_idx = mount_idx;
|
||||||
|
|
||||||
|
/* Parse path components */
|
||||||
|
char component[VFS_MAX_NAME];
|
||||||
|
const char *p = rel_path;
|
||||||
|
|
||||||
|
while (*p) {
|
||||||
|
/* Skip slashes */
|
||||||
|
while (*p == '/') p++;
|
||||||
|
if (*p == '\0') break;
|
||||||
|
|
||||||
|
/* Extract component */
|
||||||
|
const char *end = p;
|
||||||
|
while (*end && *end != '/') end++;
|
||||||
|
|
||||||
|
uint32_t clen = (uint32_t)(end - p);
|
||||||
|
if (clen >= VFS_MAX_NAME) clen = VFS_MAX_NAME - 1;
|
||||||
|
memcpy(component, p, clen);
|
||||||
|
component[clen] = '\0';
|
||||||
|
|
||||||
|
/* Look up this component in the current directory */
|
||||||
|
if (!current.ops || !current.ops->finddir) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfs_node_t child;
|
||||||
|
if (current.ops->finddir(¤t, component, &child) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
child.ops = mnt->ops;
|
||||||
|
/* Preserve fs_data set by finddir; only use mount fs_data if not set */
|
||||||
|
if (!child.fs_data) {
|
||||||
|
child.fs_data = mnt->fs_data;
|
||||||
|
}
|
||||||
|
child.mount_idx = mount_idx;
|
||||||
|
current = child;
|
||||||
|
p = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = current;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a free file descriptor slot.
|
||||||
|
* @return Index (>= 0), or -1 if all slots are used.
|
||||||
|
*/
|
||||||
|
static int alloc_fd(void) {
|
||||||
|
/* Skip fds 0,1,2 (stdin, stdout, stderr) for user processes */
|
||||||
|
for (int i = 3; i < VFS_MAX_OPEN_FILES; i++) {
|
||||||
|
if (!fd_table[i].active) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_vfs(void) {
|
||||||
|
memset(mounts, 0, sizeof(mounts));
|
||||||
|
memset(fd_table, 0, sizeof(fd_table));
|
||||||
|
offset_print(" VFS: initialized\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int vfs_mount(const char *path, vfs_fs_ops_t *ops, void *fs_data) {
|
||||||
|
/* Find a free mount slot */
|
||||||
|
int slot = -1;
|
||||||
|
for (int i = 0; i < VFS_MAX_MOUNTS; i++) {
|
||||||
|
if (!mounts[i].active) {
|
||||||
|
slot = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot < 0) {
|
||||||
|
offset_print(" VFS: no free mount slots\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(mounts[slot].path, path, VFS_MAX_PATH - 1);
|
||||||
|
mounts[slot].ops = ops;
|
||||||
|
mounts[slot].fs_data = fs_data;
|
||||||
|
mounts[slot].active = 1;
|
||||||
|
|
||||||
|
offset_print(" VFS: mounted at ");
|
||||||
|
offset_print(path);
|
||||||
|
offset_print("\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vfs_open(const char *path, uint32_t flags) {
|
||||||
|
vfs_node_t node;
|
||||||
|
if (resolve_path(path, &node) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd = alloc_fd();
|
||||||
|
if (fd < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Call filesystem's open if available */
|
||||||
|
if (node.ops && node.ops->open) {
|
||||||
|
if (node.ops->open(&node, flags) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_table[fd].node = node;
|
||||||
|
fd_table[fd].offset = 0;
|
||||||
|
fd_table[fd].flags = flags;
|
||||||
|
fd_table[fd].active = 1;
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_close(int fd) {
|
||||||
|
if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return;
|
||||||
|
if (!fd_table[fd].active) return;
|
||||||
|
|
||||||
|
vfs_fd_t *f = &fd_table[fd];
|
||||||
|
if (f->node.ops && f->node.ops->close) {
|
||||||
|
f->node.ops->close(&f->node);
|
||||||
|
}
|
||||||
|
|
||||||
|
f->active = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t vfs_read(int fd, void *buf, uint32_t size) {
|
||||||
|
if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return -1;
|
||||||
|
if (!fd_table[fd].active) return -1;
|
||||||
|
|
||||||
|
vfs_fd_t *f = &fd_table[fd];
|
||||||
|
if (!f->node.ops || !f->node.ops->read) return -1;
|
||||||
|
|
||||||
|
int32_t bytes = f->node.ops->read(&f->node, f->offset, size, buf);
|
||||||
|
if (bytes > 0) {
|
||||||
|
f->offset += (uint32_t)bytes;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t vfs_write(int fd, const void *buf, uint32_t size) {
|
||||||
|
if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return -1;
|
||||||
|
if (!fd_table[fd].active) return -1;
|
||||||
|
|
||||||
|
vfs_fd_t *f = &fd_table[fd];
|
||||||
|
if (!f->node.ops || !f->node.ops->write) return -1;
|
||||||
|
|
||||||
|
int32_t bytes = f->node.ops->write(&f->node, f->offset, size, buf);
|
||||||
|
if (bytes > 0) {
|
||||||
|
f->offset += (uint32_t)bytes;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t vfs_seek(int fd, int32_t offset, int whence) {
|
||||||
|
if (fd < 0 || fd >= VFS_MAX_OPEN_FILES) return -1;
|
||||||
|
if (!fd_table[fd].active) return -1;
|
||||||
|
|
||||||
|
vfs_fd_t *f = &fd_table[fd];
|
||||||
|
int32_t new_offset;
|
||||||
|
|
||||||
|
switch (whence) {
|
||||||
|
case VFS_SEEK_SET:
|
||||||
|
new_offset = offset;
|
||||||
|
break;
|
||||||
|
case VFS_SEEK_CUR:
|
||||||
|
new_offset = (int32_t)f->offset + offset;
|
||||||
|
break;
|
||||||
|
case VFS_SEEK_END:
|
||||||
|
new_offset = (int32_t)f->node.size + offset;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_offset < 0) return -1;
|
||||||
|
f->offset = (uint32_t)new_offset;
|
||||||
|
return new_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vfs_readdir(const char *path, uint32_t idx, vfs_dirent_t *out) {
|
||||||
|
vfs_node_t node;
|
||||||
|
if (resolve_path(path, &node) != 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type != VFS_DIRECTORY) return -1;
|
||||||
|
if (!node.ops || !node.ops->readdir) return -1;
|
||||||
|
|
||||||
|
return node.ops->readdir(&node, idx, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
int vfs_stat(const char *path, vfs_node_t *out) {
|
||||||
|
return resolve_path(path, out);
|
||||||
|
}
|
||||||
197
src/vfs.h
Normal file
197
src/vfs.h
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* @file vfs.h
|
||||||
|
* @brief Virtual Filesystem (VFS) subsystem.
|
||||||
|
*
|
||||||
|
* Provides a unified interface for file and directory operations across
|
||||||
|
* different filesystem implementations. Filesystem drivers register
|
||||||
|
* themselves and can be mounted at specific paths.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef VFS_H
|
||||||
|
#define VFS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/** Maximum number of open files across all processes. */
|
||||||
|
#define VFS_MAX_OPEN_FILES 256
|
||||||
|
|
||||||
|
/** Maximum number of mounted filesystems. */
|
||||||
|
#define VFS_MAX_MOUNTS 16
|
||||||
|
|
||||||
|
/** Maximum path length. */
|
||||||
|
#define VFS_MAX_PATH 256
|
||||||
|
|
||||||
|
/** Maximum filename length. */
|
||||||
|
#define VFS_MAX_NAME 128
|
||||||
|
|
||||||
|
/** File types. */
|
||||||
|
#define VFS_FILE 0x01
|
||||||
|
#define VFS_DIRECTORY 0x02
|
||||||
|
#define VFS_CHARDEV 0x03
|
||||||
|
#define VFS_BLOCKDEV 0x04
|
||||||
|
#define VFS_SYMLINK 0x06
|
||||||
|
|
||||||
|
/** Seek origins. */
|
||||||
|
#define VFS_SEEK_SET 0
|
||||||
|
#define VFS_SEEK_CUR 1
|
||||||
|
#define VFS_SEEK_END 2
|
||||||
|
|
||||||
|
/** Forward declarations. */
|
||||||
|
struct vfs_node;
|
||||||
|
struct vfs_dirent;
|
||||||
|
struct vfs_fs_ops;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory entry, returned by readdir.
|
||||||
|
*/
|
||||||
|
typedef struct vfs_dirent {
|
||||||
|
char name[VFS_MAX_NAME]; /**< Entry name. */
|
||||||
|
uint32_t inode; /**< Inode number (fs-specific). */
|
||||||
|
uint8_t type; /**< VFS_FILE, VFS_DIRECTORY, etc. */
|
||||||
|
} vfs_dirent_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VFS node representing a file or directory.
|
||||||
|
*/
|
||||||
|
typedef struct vfs_node {
|
||||||
|
char name[VFS_MAX_NAME]; /**< Node name. */
|
||||||
|
uint8_t type; /**< VFS_FILE, VFS_DIRECTORY, etc. */
|
||||||
|
uint32_t size; /**< File size in bytes. */
|
||||||
|
uint32_t inode; /**< Inode number (fs-specific). */
|
||||||
|
uint32_t mode; /**< Permissions/mode. */
|
||||||
|
|
||||||
|
/** Filesystem-specific operations. */
|
||||||
|
struct vfs_fs_ops *ops;
|
||||||
|
|
||||||
|
/** Opaque pointer for the filesystem driver. */
|
||||||
|
void *fs_data;
|
||||||
|
|
||||||
|
/** Mount index (which mount this node belongs to). */
|
||||||
|
int mount_idx;
|
||||||
|
} vfs_node_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filesystem operations provided by each filesystem driver.
|
||||||
|
*/
|
||||||
|
typedef struct vfs_fs_ops {
|
||||||
|
/** Open a file. Returns 0 on success. */
|
||||||
|
int (*open)(vfs_node_t *node, uint32_t flags);
|
||||||
|
|
||||||
|
/** Close a file. */
|
||||||
|
void (*close)(vfs_node_t *node);
|
||||||
|
|
||||||
|
/** Read up to `size` bytes at `offset`. Returns bytes read, or -1. */
|
||||||
|
int32_t (*read)(vfs_node_t *node, uint32_t offset, uint32_t size, void *buf);
|
||||||
|
|
||||||
|
/** Write up to `size` bytes at `offset`. Returns bytes written, or -1. */
|
||||||
|
int32_t (*write)(vfs_node_t *node, uint32_t offset, uint32_t size, const void *buf);
|
||||||
|
|
||||||
|
/** Read directory entry at index `idx`. Returns 0 on success, -1 at end. */
|
||||||
|
int (*readdir)(vfs_node_t *dir, uint32_t idx, vfs_dirent_t *out);
|
||||||
|
|
||||||
|
/** Look up a child by name within a directory. Returns 0 on success. */
|
||||||
|
int (*finddir)(vfs_node_t *dir, const char *name, vfs_node_t *out);
|
||||||
|
} vfs_fs_ops_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount point entry.
|
||||||
|
*/
|
||||||
|
typedef struct vfs_mount {
|
||||||
|
char path[VFS_MAX_PATH]; /**< Mount path (e.g., "/initrd"). */
|
||||||
|
vfs_fs_ops_t *ops; /**< Filesystem operations. */
|
||||||
|
void *fs_data; /**< Filesystem-specific data. */
|
||||||
|
int active; /**< 1 if mounted, 0 if free. */
|
||||||
|
} vfs_mount_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open file descriptor.
|
||||||
|
*/
|
||||||
|
typedef struct vfs_fd {
|
||||||
|
vfs_node_t node; /**< The file node. */
|
||||||
|
uint32_t offset; /**< Current read/write offset. */
|
||||||
|
uint32_t flags; /**< Open flags. */
|
||||||
|
int active; /**< 1 if in use, 0 if free. */
|
||||||
|
} vfs_fd_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the VFS subsystem.
|
||||||
|
*/
|
||||||
|
void init_vfs(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount a filesystem at the given path.
|
||||||
|
*
|
||||||
|
* @param path Mount point path (e.g., "/initrd").
|
||||||
|
* @param ops Filesystem operations.
|
||||||
|
* @param fs_data Filesystem-specific data pointer.
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int vfs_mount(const char *path, vfs_fs_ops_t *ops, void *fs_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a file by path.
|
||||||
|
*
|
||||||
|
* @param path Absolute path to the file.
|
||||||
|
* @param flags Open flags (currently unused).
|
||||||
|
* @return File descriptor (>= 0), or -1 on failure.
|
||||||
|
*/
|
||||||
|
int vfs_open(const char *path, uint32_t flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close an open file descriptor.
|
||||||
|
*
|
||||||
|
* @param fd File descriptor.
|
||||||
|
*/
|
||||||
|
void vfs_close(int fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read from an open file.
|
||||||
|
*
|
||||||
|
* @param fd File descriptor.
|
||||||
|
* @param buf Buffer to read into.
|
||||||
|
* @param size Maximum bytes to read.
|
||||||
|
* @return Bytes read, or -1 on error.
|
||||||
|
*/
|
||||||
|
int32_t vfs_read(int fd, void *buf, uint32_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write to an open file.
|
||||||
|
*
|
||||||
|
* @param fd File descriptor.
|
||||||
|
* @param buf Buffer to write from.
|
||||||
|
* @param size Bytes to write.
|
||||||
|
* @return Bytes written, or -1 on error.
|
||||||
|
*/
|
||||||
|
int32_t vfs_write(int fd, const void *buf, uint32_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seek within an open file.
|
||||||
|
*
|
||||||
|
* @param fd File descriptor.
|
||||||
|
* @param offset Offset to seek to.
|
||||||
|
* @param whence VFS_SEEK_SET, VFS_SEEK_CUR, or VFS_SEEK_END.
|
||||||
|
* @return New offset, or -1 on error.
|
||||||
|
*/
|
||||||
|
int32_t vfs_seek(int fd, int32_t offset, int whence);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a directory entry.
|
||||||
|
*
|
||||||
|
* @param path Path to the directory.
|
||||||
|
* @param idx Entry index (0-based).
|
||||||
|
* @param out Output directory entry.
|
||||||
|
* @return 0 on success, -1 at end or on error.
|
||||||
|
*/
|
||||||
|
int vfs_readdir(const char *path, uint32_t idx, vfs_dirent_t *out);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stat a file (get its node info).
|
||||||
|
*
|
||||||
|
* @param path Path to the file.
|
||||||
|
* @param out Output node.
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int vfs_stat(const char *path, vfs_node_t *out);
|
||||||
|
|
||||||
|
#endif /* VFS_H */
|
||||||
Reference in New Issue
Block a user