mirror of
https://github.com/BoredDevNL/BoredOS.git
synced 2026-05-15 10:48:38 +00:00
816 lines
25 KiB
C
816 lines
25 KiB
C
// Copyright (c) 2023-2026 Chris (boreddevnl)
|
|
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
|
|
// This header needs to maintain in any file it is present in, as per the GPL license terms.
|
|
#include "vfs.h"
|
|
#include "memory_manager.h"
|
|
#include "spinlock.h"
|
|
#include <stddef.h>
|
|
#include "disk.h"
|
|
#include "process.h"
|
|
|
|
|
|
static vfs_mount_t mounts[VFS_MAX_MOUNTS];
|
|
static int mount_count = 0;
|
|
static vfs_file_t open_files[VFS_MAX_OPEN_FILES];
|
|
static spinlock_t vfs_lock = SPINLOCK_INIT;
|
|
|
|
extern void serial_write(const char *str);
|
|
extern void serial_write_num(uint64_t num);
|
|
|
|
static int vfs_strlen(const char *s) {
|
|
int n = 0;
|
|
while (s[n]) n++;
|
|
return n;
|
|
}
|
|
|
|
static void vfs_strcpy(char *d, const char *s) {
|
|
while ((*d++ = *s++));
|
|
}
|
|
|
|
static int vfs_strcmp(const char *a, const char *b) {
|
|
while (*a && *a == *b) { a++; b++; }
|
|
return (unsigned char)*a - (unsigned char)*b;
|
|
}
|
|
|
|
static int vfs_strncmp(const char *a, const char *b, int n) {
|
|
for (int i = 0; i < n; i++) {
|
|
if (a[i] != b[i]) return (unsigned char)a[i] - (unsigned char)b[i];
|
|
if (!a[i]) return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static bool vfs_starts_with(const char *str, const char *prefix) {
|
|
while (*prefix) {
|
|
if (*str++ != *prefix++) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool vfs_path_is_parent(const char *parent, const char *child) {
|
|
int plen = vfs_strlen(parent);
|
|
if (vfs_strncmp(parent, child, plen) != 0) return false;
|
|
if (child[plen] == '\0') return true;
|
|
if (child[plen] == '/') return true;
|
|
if (plen == 1 && parent[0] == '/') return true;
|
|
return false;
|
|
}
|
|
|
|
void vfs_normalize_path(const char *cwd, const char *path, char *normalized) {
|
|
char parts[32][64]; // Reduced size to save stack, 64 is enough for most names
|
|
int depth = 0;
|
|
int i = 0;
|
|
|
|
// Handle relative path by starting with CWD
|
|
if (path[0] != '/' && cwd) {
|
|
int ci = 0;
|
|
if (cwd[0] == '/') ci = 1;
|
|
while (cwd[ci]) {
|
|
if (cwd[ci] == '/') { ci++; continue; }
|
|
int j = 0;
|
|
while (cwd[ci] && cwd[ci] != '/' && j < 63) {
|
|
parts[depth][j++] = cwd[ci++];
|
|
}
|
|
parts[depth][j] = 0;
|
|
if (j > 0) depth++;
|
|
if (depth >= 32) break;
|
|
if (cwd[ci] == '/') ci++;
|
|
}
|
|
}
|
|
|
|
if (path[0] == '/') i = 1;
|
|
|
|
while (path[i]) {
|
|
if (path[i] == '/') { i++; continue; }
|
|
|
|
int j = 0;
|
|
while (path[i] && path[i] != '/' && j < 63) {
|
|
parts[depth][j++] = path[i++];
|
|
}
|
|
parts[depth][j] = 0;
|
|
|
|
if (parts[depth][0] == '.' && parts[depth][1] == 0) {
|
|
// "." skip
|
|
} else if (parts[depth][0] == '.' && parts[depth][1] == '.' && parts[depth][2] == 0) {
|
|
// ".." pop
|
|
if (depth > 0) depth--;
|
|
} else {
|
|
if (j > 0) {
|
|
depth++;
|
|
if (depth >= 32) break;
|
|
}
|
|
}
|
|
|
|
if (path[i] == '/') i++;
|
|
}
|
|
|
|
normalized[0] = '/';
|
|
int pos = 1;
|
|
for (int k = 0; k < depth; k++) {
|
|
int l = 0;
|
|
while (parts[k][l] && pos < VFS_MAX_PATH - 2) {
|
|
normalized[pos++] = parts[k][l++];
|
|
}
|
|
if (k < depth - 1) normalized[pos++] = '/';
|
|
}
|
|
normalized[pos] = 0;
|
|
|
|
if (pos == 1 && normalized[0] == '/') {
|
|
normalized[1] = 0;
|
|
}
|
|
}
|
|
|
|
static vfs_mount_t* vfs_resolve_mount(const char *path, const char **rel_path_out) {
|
|
vfs_mount_t *best = NULL;
|
|
int best_len = -1;
|
|
|
|
for (int i = 0; i < mount_count; i++) {
|
|
if (!mounts[i].active) continue;
|
|
|
|
int mlen = mounts[i].path_len;
|
|
|
|
if (mlen == 1 && mounts[i].path[0] == '/') {
|
|
if (best_len < 1) {
|
|
best = &mounts[i];
|
|
best_len = 1;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (vfs_strncmp(path, mounts[i].path, mlen) == 0) {
|
|
if (path[mlen] == '/' || path[mlen] == '\0') {
|
|
if (mlen > best_len) {
|
|
best = &mounts[i];
|
|
best_len = mlen;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (best && rel_path_out) {
|
|
const char *rel = path + best_len;
|
|
while (*rel == '/') rel++;
|
|
*rel_path_out = rel;
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
static vfs_file_t* vfs_alloc_file(void) {
|
|
for (int i = 0; i < VFS_MAX_OPEN_FILES; i++) {
|
|
if (!open_files[i].valid) {
|
|
open_files[i].valid = true;
|
|
open_files[i].fs_handle = NULL;
|
|
open_files[i].mount = NULL;
|
|
open_files[i].position = 0;
|
|
open_files[i].is_device = false;
|
|
return &open_files[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void vfs_free_file(vfs_file_t *f) {
|
|
if (f) {
|
|
f->valid = false;
|
|
f->fs_handle = NULL;
|
|
f->mount = NULL;
|
|
f->position = 0;
|
|
f->is_device = false;
|
|
}
|
|
}
|
|
|
|
void vfs_init(void) {
|
|
for (int i = 0; i < VFS_MAX_MOUNTS; i++) {
|
|
mounts[i].active = false;
|
|
}
|
|
for (int i = 0; i < VFS_MAX_OPEN_FILES; i++) {
|
|
open_files[i].valid = false;
|
|
}
|
|
mount_count = 0;
|
|
|
|
serial_write("[VFS] Virtual File System initialized\n");
|
|
}
|
|
|
|
// ===============
|
|
// Mount / Unmount
|
|
// ===============
|
|
|
|
bool vfs_mount(const char *mount_path, const char *device, const char *fs_type,
|
|
vfs_fs_ops_t *ops, void *fs_private) {
|
|
uint64_t flags = spinlock_acquire_irqsave(&vfs_lock);
|
|
|
|
if (mount_count >= VFS_MAX_MOUNTS) {
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
serial_write("[VFS] ERROR: Mount table full\n");
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < mount_count; i++) {
|
|
if (mounts[i].active && vfs_strcmp(mounts[i].path, mount_path) == 0) {
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
serial_write("[VFS] ERROR: Mount point already in use: ");
|
|
serial_write(mount_path);
|
|
serial_write("\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
vfs_mount_t *m = &mounts[mount_count];
|
|
vfs_strcpy(m->path, mount_path);
|
|
m->path_len = vfs_strlen(mount_path);
|
|
m->ops = ops;
|
|
m->fs_private = fs_private;
|
|
vfs_strcpy(m->device, device ? device : "none");
|
|
vfs_strcpy(m->fs_type, fs_type ? fs_type : "unknown");
|
|
m->active = true;
|
|
mount_count++;
|
|
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
|
|
serial_write("[VFS] Mounted ");
|
|
serial_write(fs_type);
|
|
serial_write(" (");
|
|
serial_write(device ? device : "none");
|
|
serial_write(") at ");
|
|
serial_write(mount_path);
|
|
serial_write("\n");
|
|
|
|
return true;
|
|
}
|
|
|
|
bool vfs_umount(const char *mount_path) {
|
|
uint64_t flags = spinlock_acquire_irqsave(&vfs_lock);
|
|
|
|
for (int i = 0; i < mount_count; i++) {
|
|
if (mounts[i].active && vfs_strcmp(mounts[i].path, mount_path) == 0) {
|
|
for (int j = 0; j < VFS_MAX_OPEN_FILES; j++) {
|
|
if (open_files[j].valid && open_files[j].mount == &mounts[i]) {
|
|
if (mounts[i].ops->close) {
|
|
mounts[i].ops->close(mounts[i].fs_private, open_files[j].fs_handle);
|
|
}
|
|
vfs_free_file(&open_files[j]);
|
|
}
|
|
}
|
|
|
|
serial_write("[VFS] Unmounted ");
|
|
serial_write(mounts[i].path);
|
|
serial_write("\n");
|
|
|
|
mounts[i].active = false;
|
|
|
|
// Compact array
|
|
for (int k = i; k < mount_count - 1; k++) {
|
|
mounts[k] = mounts[k + 1];
|
|
}
|
|
mount_count--;
|
|
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
return false;
|
|
}
|
|
|
|
// ==============
|
|
// File Operations
|
|
// ==============
|
|
|
|
vfs_file_t* vfs_open(const char *path, const char *mode) {
|
|
if (!path || !mode) return NULL;
|
|
|
|
char normalized[VFS_MAX_PATH];
|
|
process_t *proc = process_get_current();
|
|
vfs_normalize_path(proc ? proc->cwd : "/", path, normalized);
|
|
|
|
uint64_t flags = spinlock_acquire_irqsave(&vfs_lock);
|
|
|
|
const char *rel_path = NULL;
|
|
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
|
|
|
// Fallback for block devices (/dev/sda etc)
|
|
if (vfs_starts_with(normalized, "/dev/")) {
|
|
const char *devname = normalized + 5;
|
|
Disk *d = disk_get_by_name(devname);
|
|
if (d && (!mount || mount->path_len == 1)) {
|
|
vfs_file_t *vf = vfs_alloc_file();
|
|
if (vf) {
|
|
vf->mount = &mounts[0];
|
|
vf->fs_handle = (void*)d;
|
|
vf->is_device = true;
|
|
vf->position = 0;
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
return vf;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!mount || !mount->ops->open) {
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
return NULL;
|
|
}
|
|
|
|
if (!rel_path || rel_path[0] == '\0') {
|
|
rel_path = "/";
|
|
}
|
|
|
|
vfs_file_t *vf = vfs_alloc_file();
|
|
if (!vf) {
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
serial_write("[VFS] ERROR: No free file handles\n");
|
|
return NULL;
|
|
}
|
|
|
|
vf->mount = mount;
|
|
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
|
|
void *fs_handle = mount->ops->open(mount->fs_private, rel_path, mode);
|
|
if (!fs_handle) {
|
|
flags = spinlock_acquire_irqsave(&vfs_lock);
|
|
vfs_free_file(vf);
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
return NULL;
|
|
}
|
|
|
|
vf->fs_handle = fs_handle;
|
|
return vf;
|
|
}
|
|
|
|
void vfs_close(vfs_file_t *file) {
|
|
if (!file || !file->valid) return;
|
|
|
|
vfs_mount_t *mount = file->mount;
|
|
if (mount && mount->ops->close) {
|
|
mount->ops->close(mount->fs_private, file->fs_handle);
|
|
}
|
|
|
|
uint64_t flags = spinlock_acquire_irqsave(&vfs_lock);
|
|
vfs_free_file(file);
|
|
spinlock_release_irqrestore(&vfs_lock, flags);
|
|
}
|
|
|
|
int vfs_read(vfs_file_t *file, void *buf, int size) {
|
|
if (!file || !file->valid || !file->mount) return -1;
|
|
|
|
if (file->is_device) {
|
|
Disk *d = (Disk*)file->fs_handle;
|
|
if (!d) return -1;
|
|
|
|
uint32_t total_read = 0;
|
|
uint32_t sector = (uint32_t)(file->position / 512);
|
|
uint32_t offset = (uint32_t)(file->position % 512);
|
|
uint8_t sector_buf[512];
|
|
|
|
while (total_read < (uint32_t)size) {
|
|
if (sector >= d->total_sectors) break;
|
|
if (d->read_sector(d, sector, sector_buf) != 0) break;
|
|
|
|
uint32_t to_copy = 512 - offset;
|
|
if (to_copy > (uint32_t)size - total_read) to_copy = (uint32_t)size - total_read;
|
|
|
|
extern void mem_memcpy(void *dest, const void *src, size_t len);
|
|
mem_memcpy((uint8_t*)buf + total_read, sector_buf + offset, to_copy);
|
|
|
|
total_read += to_copy;
|
|
file->position += to_copy;
|
|
sector++;
|
|
offset = 0;
|
|
}
|
|
return (int)total_read;
|
|
}
|
|
|
|
if (!file->mount->ops->read) return -1;
|
|
int ret = file->mount->ops->read(file->mount->fs_private, file->fs_handle, buf, size);
|
|
if (ret > 0) file->position += ret;
|
|
return ret;
|
|
}
|
|
|
|
int vfs_write(vfs_file_t *file, const void *buf, int size) {
|
|
if (!file || !file->valid || !file->mount) return -1;
|
|
if (!file->mount->ops->write) return -1;
|
|
|
|
return file->mount->ops->write(file->mount->fs_private, file->fs_handle, buf, size);
|
|
}
|
|
|
|
int vfs_seek(vfs_file_t *file, int offset, int whence) {
|
|
if (!file || !file->valid || !file->mount) return -1;
|
|
|
|
if (file->is_device) {
|
|
Disk *d = (Disk*)file->fs_handle;
|
|
if (!d) return -1;
|
|
uint64_t new_pos = file->position;
|
|
if (whence == 0) new_pos = (uint64_t)offset; // SET
|
|
else if (whence == 1) new_pos += (uint64_t)offset; // CUR
|
|
else if (whence == 2) new_pos = (uint64_t)(d->total_sectors * 512 + offset); // END
|
|
|
|
if (new_pos > (uint64_t)d->total_sectors * 512) new_pos = (uint64_t)d->total_sectors * 512;
|
|
file->position = new_pos;
|
|
return 0;
|
|
}
|
|
|
|
if (!file->mount->ops->seek) return -1;
|
|
int ret = file->mount->ops->seek(file->mount->fs_private, file->fs_handle, offset, whence);
|
|
if (ret == 0) {
|
|
// Sync position back from driver if possible
|
|
if (file->mount->ops->get_position) {
|
|
file->position = file->mount->ops->get_position(file->fs_handle);
|
|
} else {
|
|
// Manual sync if driver doesn't support get_position but seek succeeded
|
|
if (whence == 0) file->position = offset;
|
|
else if (whence == 1) file->position += offset;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
uint32_t vfs_file_position(vfs_file_t *file) {
|
|
if (!file || !file->valid || !file->mount) return 0;
|
|
if (file->is_device) return (uint32_t)file->position;
|
|
if (!file->mount->ops->get_position) return 0;
|
|
return file->mount->ops->get_position(file->fs_handle);
|
|
}
|
|
|
|
uint32_t vfs_file_size(vfs_file_t *file) {
|
|
if (!file || !file->valid || !file->mount) return 0;
|
|
if (file->is_device) {
|
|
Disk *d = (Disk*)file->fs_handle;
|
|
return d ? d->total_sectors * 512 : 0;
|
|
}
|
|
if (!file->mount->ops->get_size) return 0;
|
|
return file->mount->ops->get_size(file->fs_handle);
|
|
}
|
|
|
|
|
|
|
|
int vfs_list_directory(const char *path, vfs_dirent_t *entries, int max) {
|
|
if (!path || !entries) return -1;
|
|
|
|
|
|
char normalized[VFS_MAX_PATH];
|
|
vfs_normalize_path("/", path, normalized);
|
|
|
|
const char *rel_path = NULL;
|
|
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
|
|
|
int count = 0;
|
|
if (mount && mount->ops->readdir) {
|
|
if (!rel_path || rel_path[0] == '\0') rel_path = "/";
|
|
count = mount->ops->readdir(mount->fs_private, rel_path, entries, max);
|
|
if (count < 0) count = 0;
|
|
}
|
|
|
|
uint64_t v_flags = spinlock_acquire_irqsave(&vfs_lock);
|
|
for (int i = 0; i < mount_count; i++) {
|
|
if (!mounts[i].active) continue;
|
|
if (vfs_strcmp(mounts[i].path, normalized) == 0) continue;
|
|
|
|
if (vfs_path_is_parent(normalized, mounts[i].path)) {
|
|
const char *sub = mounts[i].path + vfs_strlen(normalized);
|
|
if (*sub == '/') sub++;
|
|
|
|
if (*sub != '\0') {
|
|
char comp[VFS_MAX_NAME];
|
|
int j = 0;
|
|
while (sub[j] && sub[j] != '/' && j < VFS_MAX_NAME - 1) {
|
|
comp[j] = sub[j];
|
|
j++;
|
|
}
|
|
comp[j] = 0;
|
|
|
|
bool found = false;
|
|
for (int k = 0; k < count; k++) {
|
|
if (vfs_strcmp(entries[k].name, comp) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found && count < max) {
|
|
vfs_strcpy(entries[count].name, comp);
|
|
entries[count].is_directory = 1;
|
|
entries[count].size = 0;
|
|
entries[count].start_cluster = 0;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
spinlock_release_irqrestore(&vfs_lock, v_flags);
|
|
|
|
// Special case: Ensure "dev", "sys", "proc" are visible in "/"
|
|
if (vfs_strcmp(normalized, "/") == 0) {
|
|
const char *virtual_dirs[] = {"dev", "sys", "proc"};
|
|
for (int v = 0; v < 3; v++) {
|
|
bool found = false;
|
|
for (int i = 0; i < count; i++) {
|
|
if (vfs_strcmp(entries[i].name, virtual_dirs[v]) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found && count < max) {
|
|
vfs_strcpy(entries[count].name, virtual_dirs[v]);
|
|
entries[count].is_directory = 1;
|
|
entries[count].size = 0;
|
|
entries[count].start_cluster = 0;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Special case: /dev listing for block devices
|
|
if (vfs_strcmp(normalized, "/dev") == 0) {
|
|
int dcount = disk_get_count();
|
|
for (int i = 0; i < dcount && count < max; i++) {
|
|
Disk *d = disk_get_by_index(i);
|
|
if (d) {
|
|
bool found = false;
|
|
for (int k = 0; k < count; k++) {
|
|
if (vfs_strcmp(entries[k].name, d->devname) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
vfs_strcpy(entries[count].name, d->devname);
|
|
entries[count].size = d->total_sectors * 512;
|
|
entries[count].is_directory = 0;
|
|
entries[count].start_cluster = 0;
|
|
entries[count].write_date = 0;
|
|
entries[count].write_time = 0;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
bool vfs_mkdir(const char *path) {
|
|
if (!path) return false;
|
|
|
|
char normalized[VFS_MAX_PATH];
|
|
vfs_normalize_path("/", path, normalized);
|
|
|
|
const char *rel_path = NULL;
|
|
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
|
|
|
if (vfs_starts_with(normalized, "/dev/")) {
|
|
if (!mount || !rel_path || rel_path[0] == '\0') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!mount || !mount->ops->mkdir) return false;
|
|
return mount->ops->mkdir(mount->fs_private, rel_path);
|
|
}
|
|
|
|
bool vfs_rmdir(const char *path) {
|
|
if (!path) return false;
|
|
|
|
char normalized[VFS_MAX_PATH];
|
|
vfs_normalize_path("/", path, normalized);
|
|
|
|
if (normalized[0] == '/' && normalized[1] == '\0') return false;
|
|
if (vfs_strcmp(normalized, "/dev") == 0) return false;
|
|
|
|
const char *rel_path = NULL;
|
|
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
|
|
|
if (vfs_starts_with(normalized, "/dev/")) {
|
|
if (!mount || !rel_path || rel_path[0] == '\0') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!mount || !mount->ops->rmdir) return false;
|
|
return mount->ops->rmdir(mount->fs_private, rel_path);
|
|
}
|
|
|
|
bool vfs_delete(const char *path) {
|
|
if (!path) return false;
|
|
|
|
char normalized[VFS_MAX_PATH];
|
|
vfs_normalize_path("/", path, normalized);
|
|
|
|
if (normalized[0] == '/' && normalized[1] == '\0') return false;
|
|
if (vfs_strcmp(normalized, "/dev") == 0) return false;
|
|
|
|
const char *rel_path = NULL;
|
|
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
|
|
|
if (vfs_starts_with(normalized, "/dev/")) {
|
|
if (!mount || !rel_path || rel_path[0] == '\0') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!mount || !mount->ops->unlink) return false;
|
|
return mount->ops->unlink(mount->fs_private, rel_path);
|
|
}
|
|
|
|
bool vfs_rename(const char *old_path, const char *new_path) {
|
|
if (!old_path || !new_path) return false;
|
|
|
|
char norm_old[VFS_MAX_PATH], norm_new[VFS_MAX_PATH];
|
|
vfs_normalize_path("/", old_path, norm_old);
|
|
vfs_normalize_path("/", new_path, norm_new);
|
|
|
|
const char *rel_old = NULL, *rel_new = NULL;
|
|
vfs_mount_t *mount_old = vfs_resolve_mount(norm_old, &rel_old);
|
|
vfs_mount_t *mount_new = vfs_resolve_mount(norm_new, &rel_new);
|
|
|
|
if (!mount_old || mount_old != mount_new) return false;
|
|
if (!mount_old->ops->rename) return false;
|
|
|
|
if (!rel_old || rel_old[0] == '\0') return false;
|
|
if (!rel_new || rel_new[0] == '\0') return false;
|
|
|
|
return mount_old->ops->rename(mount_old->fs_private, rel_old, rel_new);
|
|
}
|
|
|
|
bool vfs_exists(const char *path) {
|
|
if (!path) return false;
|
|
|
|
char normalized[VFS_MAX_PATH];
|
|
vfs_normalize_path("/", path, normalized);
|
|
|
|
if (normalized[0] == '/' && normalized[1] == '\0') return true;
|
|
|
|
uint64_t flags_vfs = spinlock_acquire_irqsave(&vfs_lock);
|
|
for (int i = 0; i < mount_count; i++) {
|
|
if (mounts[i].active && vfs_starts_with(mounts[i].path, normalized)) {
|
|
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
|
return true;
|
|
}
|
|
}
|
|
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
|
|
|
if (vfs_strcmp(normalized, "/dev") == 0 ||
|
|
vfs_strcmp(normalized, "/sys") == 0 ||
|
|
vfs_strcmp(normalized, "/proc") == 0) return true;
|
|
|
|
if (vfs_starts_with(normalized, "/dev/")) {
|
|
const char *dev = normalized + 5;
|
|
if (disk_get_by_name(dev)) return true;
|
|
}
|
|
|
|
const char *rel_path = NULL;
|
|
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
|
if (!mount || !mount->ops->exists) return false;
|
|
|
|
if (!rel_path || rel_path[0] == '\0') return true;
|
|
|
|
return mount->ops->exists(mount->fs_private, rel_path);
|
|
}
|
|
|
|
bool vfs_is_directory(const char *path) {
|
|
if (!path) return false;
|
|
|
|
char normalized[VFS_MAX_PATH];
|
|
vfs_normalize_path("/", path, normalized);
|
|
|
|
if (normalized[0] == '/' && normalized[1] == '\0') return true;
|
|
|
|
uint64_t flags_vfs = spinlock_acquire_irqsave(&vfs_lock);
|
|
for (int i = 0; i < mount_count; i++) {
|
|
if (mounts[i].active && vfs_path_is_parent(normalized, mounts[i].path)) {
|
|
if (vfs_strcmp(mounts[i].path, normalized) == 0) {
|
|
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
|
return true;
|
|
}
|
|
// If normalized is a parent of a mount, it's a virtual directory
|
|
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
|
return true;
|
|
}
|
|
}
|
|
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
|
|
|
if (vfs_strcmp(normalized, "/dev") == 0 ||
|
|
vfs_strcmp(normalized, "/sys") == 0 ||
|
|
vfs_strcmp(normalized, "/proc") == 0) return true;
|
|
|
|
if (vfs_starts_with(normalized, "/dev/")) {
|
|
const char *dev = normalized + 5;
|
|
Disk *d = disk_get_by_name(dev);
|
|
if (d) return false;
|
|
}
|
|
|
|
const char *rel_path = NULL;
|
|
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
|
if (!mount) return false;
|
|
|
|
if (!rel_path || rel_path[0] == '\0') return true;
|
|
|
|
if (!mount->ops->is_dir) return false;
|
|
return mount->ops->is_dir(mount->fs_private, rel_path);
|
|
}
|
|
|
|
int vfs_get_info(const char *path, vfs_dirent_t *info) {
|
|
if (!path || !info) return -1;
|
|
|
|
char normalized[VFS_MAX_PATH];
|
|
vfs_normalize_path("/", path, normalized);
|
|
|
|
if (normalized[0] == '/' && normalized[1] == '\0') {
|
|
vfs_strcpy(info->name, "/");
|
|
info->size = 0;
|
|
info->is_directory = 1;
|
|
info->start_cluster = 0;
|
|
info->write_date = 0;
|
|
info->write_time = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (vfs_strcmp(normalized, "/dev") == 0 ||
|
|
vfs_strcmp(normalized, "/sys") == 0 ||
|
|
vfs_strcmp(normalized, "/proc") == 0) {
|
|
const char *name = normalized + 1;
|
|
vfs_strcpy(info->name, name);
|
|
info->size = 0;
|
|
info->is_directory = 1;
|
|
info->start_cluster = 0;
|
|
info->write_date = 0;
|
|
info->write_time = 0;
|
|
return 0;
|
|
}
|
|
|
|
uint64_t flags_vfs = spinlock_acquire_irqsave(&vfs_lock);
|
|
for (int i = 0; i < mount_count; i++) {
|
|
if (mounts[i].active && vfs_path_is_parent(normalized, mounts[i].path)) {
|
|
if (vfs_strcmp(mounts[i].path, normalized) != 0) {
|
|
const char *p = normalized + vfs_strlen(normalized);
|
|
while (p > normalized && *(p-1) != '/') p--;
|
|
vfs_strcpy(info->name, p);
|
|
info->size = 0;
|
|
info->is_directory = 1;
|
|
info->start_cluster = 0;
|
|
info->write_date = 0;
|
|
info->write_time = 0;
|
|
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
spinlock_release_irqrestore(&vfs_lock, flags_vfs);
|
|
|
|
// Device check
|
|
if (vfs_starts_with(normalized, "/dev/")) {
|
|
const char *dev = normalized + 5;
|
|
Disk *d = disk_get_by_name(dev);
|
|
if (d) {
|
|
vfs_strcpy(info->name, d->devname);
|
|
info->size = d->total_sectors * 512;
|
|
info->is_directory = 0;
|
|
info->start_cluster = 0;
|
|
info->write_date = 0;
|
|
info->write_time = 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const char *rel_path = NULL;
|
|
vfs_mount_t *mount = vfs_resolve_mount(normalized, &rel_path);
|
|
if (!mount || !mount->ops->get_info) return -1;
|
|
|
|
if (!rel_path || rel_path[0] == '\0') {
|
|
// Info about mount root
|
|
vfs_strcpy(info->name, mount->device);
|
|
info->size = 0;
|
|
info->is_directory = 1;
|
|
info->start_cluster = 0;
|
|
info->write_date = 0;
|
|
info->write_time = 0;
|
|
return 0;
|
|
}
|
|
|
|
return mount->ops->get_info(mount->fs_private, rel_path, info);
|
|
}
|
|
|
|
int vfs_get_mount_count(void) {
|
|
return mount_count;
|
|
}
|
|
|
|
vfs_mount_t* vfs_get_mount(int index) {
|
|
if (index < 0 || index >= mount_count) return NULL;
|
|
if (!mounts[index].active) return NULL;
|
|
return &mounts[index];
|
|
}
|
|
|
|
void vfs_automount_partition(const char *devname) {
|
|
char mount_path[64] = "/mnt/";
|
|
int i = 5;
|
|
const char *d = devname;
|
|
while (*d && i < 62) mount_path[i++] = *d++;
|
|
mount_path[i] = 0;
|
|
|
|
serial_write("[VFS] Auto-mount requested for ");
|
|
serial_write(devname);
|
|
serial_write(" at ");
|
|
serial_write(mount_path);
|
|
serial_write("\n");
|
|
}
|