Files
grblHAL/vfs.c
T
Terje Io 8c3d44bfca Added helper code for plugins claiming auxiliary ports to make plugin coding easier and behaviour consistent.
Some minor bug fixes such as incorrect error code returned for unknown $-commands and temporary incorrect position reporting on in-flight G92 offset changes.
2025-10-11 18:28:21 +02:00

726 lines
16 KiB
C

/*
vfs.c - An embedded CNC Controller with rs274/ngc (g-code) support
Virtual File System handler
Part of grblHAL
Copyright (c) 2022-2025 Terje Io
grblHAL is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
grblHAL is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with grblHAL. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdlib.h>
#include "hal.h"
#include "vfs.h"
#ifdef ARDUINO_SAM_DUE
#undef feof
#endif
static vfs_mount_t *get_rootfs (void);
static inline vfs_mount_t *path_is_mount_dir (const char *path)
{
size_t plen = strlen(path);
vfs_mount_t *mount = get_rootfs();
if(*path == '/' && plen == 1)
return mount;
size_t mlen;
if((mount = mount->next)) do {
mlen = strlen(mount->path) - 1;
if(mlen == plen && !strncmp(mount->path, path, mlen))
break;
} while((mount = mount->next));
return mount;
}
// NULL file system
static vfs_file_t *fs_open (const char *filename, const char *mode)
{
return NULL;
}
static void fs_close (vfs_file_t *file)
{
}
static size_t fs_read (void *buffer, size_t size, size_t count, vfs_file_t *file)
{
return 0;
}
static size_t fs_write (const void *buffer, size_t size, size_t count, vfs_file_t *file)
{
return 0;
}
static size_t fs_tell (vfs_file_t *file)
{
return 0;
}
static int fs_seek (vfs_file_t *file, size_t offset)
{
return -1;
}
static bool fs_eof (vfs_file_t *file)
{
return true;
}
static int fs_unlink (const char *filename)
{
return -1;
}
static int fs_dirop (const char *path)
{
return -1;
}
static vfs_dir_t *fs_opendir (const char *path)
{
static vfs_dir_t *dir = NULL;
if(dir == NULL)
dir = calloc(sizeof(vfs_dir_t) + 3, 1);
if(dir) {
vfs_mount_t **mount = (vfs_mount_t **)&dir->handle;
*mount = get_rootfs()->next;
}
return !strcmp(path, "/") ? dir : NULL;
}
static char *fs_readdir (vfs_dir_t *dir, vfs_dirent_t *dirent)
{
vfs_mount_t **mount = (vfs_mount_t **)&dir->handle;
vfs_errno = 0;
if(*mount) {
strcpy(dirent->name, (*mount)->path);
*(strchr(dirent->name, '\0') - 1) = '\0';
dirent->st_mode = (*mount)->mode;
dirent->st_mode.directory = true;
while((*mount = (*mount)->next)) {
if(!(*mount)->mode.hidden)
break;
}
} else
*dirent->name = '\0';
return *dirent->name ? dirent->name : NULL;
}
static void fs_closedir (vfs_dir_t *dir)
{
}
static int fs_stat (const char *filename, vfs_stat_t *st)
{
char path[64];
vfs_mount_t *mount;
if((mount = path_is_mount_dir(strcat(strcpy(path, "/"), filename)))) {
if(!(mount->vfs->fstat && (vfs_errno = mount->vfs->fstat("/", st) == 0))) {
vfs_errno = 0;
st->st_size = 1024;
st->st_mode = mount->mode;
st->st_mode.directory = true;
#if ESP_PLATFORM
st->st_mtim = mount->st_mtim;
#else
st->st_mtime = mount->st_mtime;
#endif
}
} else
vfs_errno = -1;
return vfs_errno;
}
static int fs_chdir (const char *path)
{
return !strcmp(path, "/") ? 0 : -1;
}
static char *fs_getcwd (char *buf, size_t size)
{
return "/";
}
static const vfs_t fs_null = {
.fopen = fs_open,
.fclose = fs_close,
.fread = fs_read,
.fwrite = fs_write,
.ftell = fs_tell,
.fseek = fs_seek,
.feof = fs_eof,
.funlink = fs_unlink,
.fmkdir = fs_dirop,
.fchdir = fs_chdir,
.frmdir = fs_dirop,
.fopendir = fs_opendir,
.readdir = fs_readdir,
.fclosedir = fs_closedir,
.fstat = fs_stat,
.fgetcwd = fs_getcwd
};
// End NULL file system
static vfs_mount_t root = {
.path = "/",
.vfs = &fs_null,
.mode = { .directory = true, .read_only = true, .hidden = true },
.next = NULL
};
static vfs_mount_t *cwdmount = &root;
static char cwd[100] = "/";
volatile int vfs_errno = 0;
vfs_events_t vfs = {0};
static vfs_mount_t *get_rootfs (void)
{
return &root;
}
// Strip trailing directory separator, FatFS dont't like it (WinSCP adds it)
char *vfs_fixpath (char *path)
{
char *s = path + strlen(path) - 1;
if(s > path && *s == '/' && s != strchr(path, '/'))
*s = '\0';
return path;
}
static vfs_mount_t *get_mount (const char *path)
{
vfs_errno = 0;
if(*path != '/')
return cwdmount;
vfs_mount_t *mount;
if((mount = path_is_mount_dir(path)) == NULL) {
mount = root.next;
if(mount) do {
if(!strncmp(mount->path, path, strlen(mount->path)))
break;
} while((mount = mount->next));
if(mount == NULL)
mount = &root;
}
return mount;
}
static const char *get_filename (vfs_mount_t *mount, const char *filename)
{
if(*filename == '/') {
size_t len = strlen(mount->path);
return filename + (len == 1 ? 0 : len - 1);
} else
return filename;
}
vfs_file_t *vfs_open (const char *filename, const char *mode)
{
vfs_file_t *file = NULL;
vfs_mount_t *mount = get_mount(filename);
if(mount && (file = mount->vfs->fopen(get_filename(mount, filename), mode))) {
file->fs = mount->vfs;
file->update = !mount->mode.hidden && !!strchr(mode, 'w');
}
return file;
}
void vfs_close (vfs_file_t *file)
{
vfs_errno = 0;
((vfs_t *)(file->fs))->fclose(file);
if(file->update && vfs.on_fs_changed)
vfs.on_fs_changed((vfs_t *)file->fs);
}
size_t vfs_read (void *buffer, size_t size, size_t count, vfs_file_t *file)
{
vfs_errno = 0;
return ((vfs_t *)(file->fs))->fread(buffer, size, count, file);
}
size_t vfs_write (const void *buffer, size_t size, size_t count, vfs_file_t *file)
{
vfs_errno = 0;
return ((vfs_t *)(file->fs))->fwrite(buffer, size, count, file);
}
int vfs_puts (const char *s, vfs_file_t *file)
{
size_t count = strlen(s), ret;
vfs_errno = 0;
if((ret = ((vfs_t *)(file->fs))->fwrite(s, sizeof(char), count, file)) != count)
return -1;
return (int)ret;
}
size_t vfs_tell (vfs_file_t *file)
{
vfs_errno = 0;
return ((vfs_t *)(file->fs))->ftell(file);
}
int vfs_seek (vfs_file_t *file, size_t offset)
{
vfs_errno = 0;
return ((vfs_t *)(file->fs))->fseek(file, offset);
}
bool vfs_eof (vfs_file_t *file)
{
vfs_errno = 0;
return ((vfs_t *)(file->fs))->feof(file);
}
int vfs_rename (const char *from, const char *to)
{
int ret;
vfs_mount_t *mount_from = get_mount(from), *mount_to = get_mount(to);
if((ret = mount_from == mount_to ? mount_from->vfs->frename(get_filename(mount_from, from), get_filename(mount_to, to)) : -1) != -1 &&
vfs.on_fs_changed && !mount_from->mode.hidden) {
vfs.on_fs_changed(mount_from->vfs);
if(mount_from != mount_to && !mount_to->mode.hidden)
vfs.on_fs_changed(mount_to->vfs);
}
return ret;
}
int vfs_unlink (const char *filename)
{
int ret;
vfs_mount_t *mount = get_mount(filename); // TODO: test for dir?
if((ret = mount ? mount->vfs->funlink(get_filename(mount, filename)) : -1) != -1 && vfs.on_fs_changed && !mount->mode.hidden)
vfs.on_fs_changed(mount->vfs);
return ret;
}
int vfs_mkdir (const char *path)
{
int ret;
vfs_mount_t *mount = get_mount(path);
if((ret = mount ? mount->vfs->fmkdir(get_filename(mount, path)) : -1) != -1 && vfs.on_fs_changed && !mount->mode.hidden)
vfs.on_fs_changed(mount->vfs);
return ret;
}
int vfs_rmdir (const char *path)
{
int ret;
vfs_mount_t *mount = get_mount(path);
if((ret = mount ? mount->vfs->frmdir(get_filename(mount, path)) : -1) != -1 && vfs.on_fs_changed && !mount->mode.hidden)
vfs.on_fs_changed(mount->vfs);
return ret;
}
int vfs_chdir (const char *path)
{
int ret;
vfs_errno = 0;
if(*path != '/' && strcmp(cwd, "/")) {
if(strcmp(path, "..")) {
if(strlen(cwd) > 1)
strcat(cwd, "/");
strcat(cwd, path);
} else {
char *s = strrchr(cwd, '/');
if(s)
*s = '\0';
}
} else {
if(*path == '/')
strcpy(cwd, path);
else
strcat(strcpy(cwd, "/"), path);
vfs_fixpath(cwd);
if((cwdmount = get_mount(cwd)) && strchr(cwd + 1, '/') == NULL && cwdmount != &root) {
strcpy(cwd, cwdmount->path);
vfs_fixpath(cwd);
return 0;
}
}
if((ret = cwdmount ? cwdmount->vfs->fchdir(path) : -1) != 0) // + strlen(mount->path));))
vfs_fixpath(cwd);
return ret;
}
vfs_dir_t *vfs_opendir (const char *path)
{
vfs_dir_t *dir = NULL;
vfs_mount_t *mount = get_mount(path), *add_mount;
vfs_mount_ll_entry_t *ml = NULL, *mln;
if(mount && (dir = mount->vfs->fopendir(get_filename(mount, path)))) {
dir->fs = mount->vfs;
dir->mounts = NULL;
if((add_mount = root.next)) do {
if(add_mount != mount && !strncmp(add_mount->path, path, strlen(path))) {
if(!add_mount->mode.hidden && (mln = malloc(sizeof(vfs_mount_ll_entry_t)))) {
mln->mount = add_mount;
mln->next = NULL;
if(dir->mounts == NULL)
dir->mounts = ml = mln;
else {
ml->next = mln;
ml = mln;
}
}
}
} while((add_mount = add_mount->next));
}
return dir;
}
vfs_dirent_t *vfs_readdir (vfs_dir_t *dir)
{
static vfs_dirent_t dirent;
vfs_errno = 0;
((vfs_t *)dir->fs)->readdir(dir, &dirent);
if(*dirent.name == '\0' && dir->mounts) {
char *s;
vfs_mount_ll_entry_t *ml = dir->mounts;
strcpy(dirent.name, ml->mount->path + 1);
if((s = strrchr(dirent.name, '/')))
*s = '\0';
dirent.st_mode = ml->mount->mode;
dirent.st_mode.directory = true;
dir->mounts = dir->mounts->next;
free(ml);
}
return *dirent.name == '\0' ? NULL : &dirent;
}
void vfs_closedir (vfs_dir_t *dir)
{
vfs_errno = 0;
while(dir->mounts) {
vfs_mount_ll_entry_t *ml = dir->mounts;
dir->mounts = dir->mounts->next;
free(ml);
}
((vfs_t *)dir->fs)->fclosedir(dir);
}
char *vfs_getcwd (char *buf, size_t len)
{
char *cwds = cwdmount->vfs->fgetcwd ? cwdmount->vfs->fgetcwd(NULL, len) : cwd;
vfs_errno = 0;
if(buf == NULL)
buf = (char *)malloc(strlen(cwds) + 1);
if(buf)
strcpy(buf, cwds);
return buf ? buf : cwds;
}
int vfs_chmod (const char *filename, vfs_st_mode_t attr, vfs_st_mode_t mask)
{
vfs_mount_t *mount = get_mount(filename);
return mount && mount->vfs->fchmod ? mount->vfs->fchmod(get_filename(mount, filename), attr, mask) : -1;
}
int vfs_stat (const char *filename, vfs_stat_t *st)
{
vfs_mount_t *mount = get_mount(filename);
int ret = mount ? mount->vfs->fstat(get_filename(mount, filename), st) : -1;
if(ret == -1 && strchr(filename, '/') == NULL && !strcmp("/", cwd)) {
strcat(cwd, filename);
mount = get_mount(cwd);
cwd[1] = '\0';
if(mount) {
st->st_size = 0;
st->st_mode.mode = 0;
st->st_mode.directory = true;
#if defined(ESP_PLATFORM)
st->st_mtim = mount->st_mtim;
#else
st->st_mtime = mount->st_mtime;
#endif
ret = 0;
}
}
return ret;
}
int vfs_utime (const char *filename, struct tm *modified)
{
vfs_mount_t *mount = get_mount(filename);
return mount && mount->vfs->futime ? mount->vfs->futime(get_filename(mount, filename), modified) : -1;
}
vfs_free_t *vfs_fgetfree (const char *path)
{
static vfs_free_t free;
vfs_mount_t *mount = get_mount(path);
if(mount && mount->vfs->fgetfree && mount->vfs->fgetfree(&free))
return &free;
return NULL;
}
static bool vfs_get_time (struct tm *time)
{
memset(time, 0, sizeof(struct tm));
// 2025-01-01:00:00:00
time->tm_year = 2025 - 1900;
time->tm_mday = 1;
return true;
}
bool vfs_mount (const char *path, const vfs_t *fs, vfs_st_mode_t mode)
{
vfs_mount_t *mount;
if(!strcmp(path, "/")) {
root.vfs = fs;
root.mode = mode;
} else if((mount = (vfs_mount_t *)calloc(sizeof(vfs_mount_t), 1))) {
struct tm tm;
strcpy(mount->path, path);
if(mount->path[strlen(path) - 1] != '/')
strcat(mount->path, "/");
mount->vfs = fs;
mount->mode = mode;
mount->next = NULL;
if(hal.rtc.get_datetime && hal.rtc.get_datetime(&tm)) {
#ifdef ESP_PLATFORM
mount->st_mtim = mktime(&tm);
#else
mount->st_mtime = mktime(&tm);
#endif
}
vfs_mount_t *lmount = &root;
while(lmount->next)
lmount = lmount->next;
lmount->next = mount;
}
if(hal.rtc.get_datetime == NULL)
hal.rtc.get_datetime = vfs_get_time;
if(fs && vfs.on_mount)
vfs.on_mount(path, fs, mode);
return fs != NULL;
}
bool vfs_unmount (const char *path)
{
// TODO: close open files?
if(!strcmp(path, "/")) {
root.vfs = &fs_null;
root.mode = (vfs_st_mode_t){ .directory = true, .read_only = true, .hidden = true };
} else {
vfs_mount_t *mount = get_mount(path);
if(mount) {
vfs_mount_t *pmount = &root;
while(pmount->next && pmount->next != mount)
pmount = pmount->next;
if(pmount->next == mount)
pmount->next = mount->next;
free(mount);
}
}
if(vfs.on_unmount)
vfs.on_unmount(path);
return true;
}
vfs_drive_t *vfs_get_drive (const char *path)
{
static vfs_drive_t drive;
vfs_mount_t *mount = get_mount(path);
drive.name = mount->vfs->fs_name;
drive.path = (const char *)mount->path;
drive.mode = mount->mode;
drive.removable = mount->vfs->removable;
drive.fs = mount->vfs;
return &drive;
}
vfs_drives_t *vfs_drives_open (void)
{
vfs_drives_t *handle;
vfs_mount_t *mount = &root;
if((handle = malloc(sizeof(vfs_drives_t)))) {
handle->mount = NULL;
do {
if(mount->mode.hidden)
mount = mount->next;
else
handle->mount = mount;
} while(mount && handle->mount == NULL);
if(handle->mount == NULL) {
free(handle);
handle = NULL;
}
}
return handle;
}
vfs_drive_t *vfs_drives_read (vfs_drives_t *handle, bool add_hidden)
{
static vfs_drive_t drive;
bool ok;
if((ok = handle->mount != NULL)) {
drive.name = handle->mount->vfs->fs_name;
drive.path = (const char *)handle->mount->path;
drive.mode = handle->mount->mode;
drive.removable = handle->mount->vfs->removable;
drive.fs = handle->mount->vfs;
handle->mount = handle->mount->next;
if(handle->mount) do {
if((!handle->mount->mode.hidden || add_hidden) && handle->mount->vfs->fs_name)
break;
} while((handle->mount = handle->mount->next));
}
return ok ? &drive : NULL;
}
void vfs_drives_close (vfs_drives_t *handle)
{
free(handle);
}
vfs_free_t *vfs_drive_getfree (vfs_drive_t *drive)
{
static vfs_free_t free;
const vfs_t *fs = drive->fs;
return fs->fgetfree && fs->fgetfree(&free) ? &free : NULL;
}
int vfs_drive_format (vfs_drive_t *drive)
{
const vfs_t *fs = drive->fs;
return (vfs_errno = fs->format ? fs->format() : -1);
}