457 lines
9.9 KiB
C
457 lines
9.9 KiB
C
#include <sys/file.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <dprintf.h>
|
|
#include <syslinux/sysappend.h>
|
|
#include "core.h"
|
|
#include "dev.h"
|
|
#include "fs.h"
|
|
#include "cache.h"
|
|
|
|
/* The currently mounted filesystem */
|
|
__export struct fs_info *this_fs = NULL; /* Root filesystem */
|
|
|
|
/* Actual file structures (we don't have malloc yet...) */
|
|
__export struct file files[MAX_OPEN];
|
|
|
|
/* Symlink hard limits */
|
|
#define MAX_SYMLINK_CNT 20
|
|
#define MAX_SYMLINK_BUF 4096
|
|
|
|
/*
|
|
* Get a new inode structure
|
|
*/
|
|
struct inode *alloc_inode(struct fs_info *fs, uint32_t ino, size_t data)
|
|
{
|
|
struct inode *inode = zalloc(sizeof(struct inode) + data);
|
|
if (inode) {
|
|
inode->fs = fs;
|
|
inode->ino = ino;
|
|
inode->refcnt = 1;
|
|
}
|
|
return inode;
|
|
}
|
|
|
|
/*
|
|
* Free a refcounted inode
|
|
*/
|
|
void put_inode(struct inode *inode)
|
|
{
|
|
while (inode) {
|
|
struct inode *dead = inode;
|
|
int refcnt = --(dead->refcnt);
|
|
dprintf("put_inode %p name %s refcnt %u\n", dead, dead->name, refcnt);
|
|
if (refcnt)
|
|
break; /* We still have references */
|
|
inode = dead->parent;
|
|
if (dead->name)
|
|
free((char *)dead->name);
|
|
free(dead);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get an empty file structure
|
|
*/
|
|
static struct file *alloc_file(void)
|
|
{
|
|
int i;
|
|
struct file *file = files;
|
|
|
|
for (i = 0; i < MAX_OPEN; i++) {
|
|
if (!file->fs)
|
|
return file;
|
|
file++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Close and free a file structure
|
|
*/
|
|
static inline void free_file(struct file *file)
|
|
{
|
|
memset(file, 0, sizeof *file);
|
|
}
|
|
|
|
__export void _close_file(struct file *file)
|
|
{
|
|
if (file->fs)
|
|
file->fs->fs_ops->close_file(file);
|
|
free_file(file);
|
|
}
|
|
|
|
/*
|
|
* Find and open the configuration file
|
|
*/
|
|
__export int open_config(void)
|
|
{
|
|
int fd, handle;
|
|
struct file_info *fp;
|
|
|
|
fd = opendev(&__file_dev, NULL, O_RDONLY);
|
|
if (fd < 0)
|
|
return -1;
|
|
|
|
fp = &__file_info[fd];
|
|
|
|
handle = this_fs->fs_ops->open_config(&fp->i.fd);
|
|
if (handle < 0) {
|
|
close(fd);
|
|
errno = ENOENT;
|
|
return -1;
|
|
}
|
|
|
|
fp->i.offset = 0;
|
|
fp->i.nbytes = 0;
|
|
|
|
return fd;
|
|
}
|
|
|
|
__export void mangle_name(char *dst, const char *src)
|
|
{
|
|
this_fs->fs_ops->mangle_name(dst, src);
|
|
}
|
|
|
|
size_t pmapi_read_file(uint16_t *handle, void *buf, size_t sectors)
|
|
{
|
|
bool have_more;
|
|
size_t bytes_read;
|
|
struct file *file;
|
|
|
|
file = handle_to_file(*handle);
|
|
bytes_read = file->fs->fs_ops->getfssec(file, buf, sectors, &have_more);
|
|
|
|
/*
|
|
* If we reach EOF, the filesystem driver will have already closed
|
|
* the underlying file... this really should be cleaner.
|
|
*/
|
|
if (!have_more) {
|
|
_close_file(file);
|
|
*handle = 0;
|
|
}
|
|
|
|
return bytes_read;
|
|
}
|
|
|
|
int searchdir(const char *name, int flags)
|
|
{
|
|
static char root_name[] = "/";
|
|
struct file *file;
|
|
char *path, *inode_name, *next_inode_name;
|
|
struct inode *tmp, *inode = NULL;
|
|
int symlink_count = MAX_SYMLINK_CNT;
|
|
|
|
dprintf("searchdir: %s root: %p cwd: %p\n",
|
|
name, this_fs->root, this_fs->cwd);
|
|
|
|
if (!(file = alloc_file()))
|
|
goto err_no_close;
|
|
file->fs = this_fs;
|
|
|
|
/* if we have ->searchdir method, call it */
|
|
if (file->fs->fs_ops->searchdir) {
|
|
file->fs->fs_ops->searchdir(name, flags, file);
|
|
|
|
if (file->inode)
|
|
return file_to_handle(file);
|
|
else
|
|
goto err;
|
|
}
|
|
|
|
/* else, try the generic-path-lookup method */
|
|
|
|
/* Copy the path */
|
|
path = strdup(name);
|
|
if (!path) {
|
|
dprintf("searchdir: Couldn't copy path\n");
|
|
goto err_path;
|
|
}
|
|
|
|
/* Work with the current directory, by default */
|
|
inode = get_inode(this_fs->cwd);
|
|
if (!inode) {
|
|
dprintf("searchdir: Couldn't use current directory\n");
|
|
goto err_curdir;
|
|
}
|
|
|
|
for (inode_name = path; inode_name; inode_name = next_inode_name) {
|
|
/* Root directory? */
|
|
if (inode_name[0] == '/') {
|
|
next_inode_name = inode_name + 1;
|
|
inode_name = root_name;
|
|
} else {
|
|
/* Find the next inode name */
|
|
next_inode_name = strchr(inode_name + 1, '/');
|
|
if (next_inode_name) {
|
|
/* Terminate the current inode name and point to next */
|
|
*next_inode_name++ = '\0';
|
|
}
|
|
}
|
|
if (next_inode_name) {
|
|
/* Advance beyond redundant slashes */
|
|
while (*next_inode_name == '/')
|
|
next_inode_name++;
|
|
|
|
/* Check if we're at the end */
|
|
if (*next_inode_name == '\0')
|
|
next_inode_name = NULL;
|
|
}
|
|
dprintf("searchdir: inode_name: %s\n", inode_name);
|
|
if (next_inode_name)
|
|
dprintf("searchdir: Remaining: %s\n", next_inode_name);
|
|
|
|
/* Root directory? */
|
|
if (inode_name[0] == '/') {
|
|
/* Release any chain that's already been established */
|
|
put_inode(inode);
|
|
inode = get_inode(this_fs->root);
|
|
continue;
|
|
}
|
|
|
|
/* Current directory? */
|
|
if (!strncmp(inode_name, ".", sizeof "."))
|
|
continue;
|
|
|
|
/* Parent directory? */
|
|
if (!strncmp(inode_name, "..", sizeof "..")) {
|
|
/* If there is no parent, just ignore it */
|
|
if (!inode->parent)
|
|
continue;
|
|
|
|
/* Add a reference to the parent so we can release the child */
|
|
tmp = get_inode(inode->parent);
|
|
|
|
/* Releasing the child will drop the parent back down to 1 */
|
|
put_inode(inode);
|
|
|
|
inode = tmp;
|
|
continue;
|
|
}
|
|
|
|
/* Anything else */
|
|
tmp = inode;
|
|
inode = this_fs->fs_ops->iget(inode_name, inode);
|
|
if (!inode) {
|
|
/* Failure. Release the chain */
|
|
put_inode(tmp);
|
|
break;
|
|
}
|
|
|
|
/* Sanity-check */
|
|
if (inode->parent && inode->parent != tmp) {
|
|
dprintf("searchdir: iget returned a different parent\n");
|
|
put_inode(inode);
|
|
inode = NULL;
|
|
put_inode(tmp);
|
|
break;
|
|
}
|
|
inode->parent = tmp;
|
|
inode->name = strdup(inode_name);
|
|
dprintf("searchdir: path component: %s\n", inode->name);
|
|
|
|
/* Symlink handling */
|
|
if (inode->mode == DT_LNK) {
|
|
char *new_path;
|
|
int new_len, copied;
|
|
|
|
/* target path + NUL */
|
|
new_len = inode->size + 1;
|
|
|
|
if (next_inode_name) {
|
|
/* target path + slash + remaining + NUL */
|
|
new_len += strlen(next_inode_name) + 1;
|
|
}
|
|
|
|
if (!this_fs->fs_ops->readlink ||
|
|
/* limit checks */
|
|
--symlink_count == 0 ||
|
|
new_len > MAX_SYMLINK_BUF)
|
|
goto err_new_len;
|
|
|
|
new_path = malloc(new_len);
|
|
if (!new_path)
|
|
goto err_new_path;
|
|
|
|
copied = this_fs->fs_ops->readlink(inode, new_path);
|
|
if (copied <= 0)
|
|
goto err_copied;
|
|
new_path[copied] = '\0';
|
|
dprintf("searchdir: Symlink: %s\n", new_path);
|
|
|
|
if (next_inode_name) {
|
|
new_path[copied] = '/';
|
|
strcpy(new_path + copied + 1, next_inode_name);
|
|
dprintf("searchdir: New path: %s\n", new_path);
|
|
}
|
|
|
|
free(path);
|
|
path = next_inode_name = new_path;
|
|
|
|
/* Add a reference to the parent so we can release the child */
|
|
tmp = get_inode(inode->parent);
|
|
|
|
/* Releasing the child will drop the parent back down to 1 */
|
|
put_inode(inode);
|
|
|
|
inode = tmp;
|
|
continue;
|
|
err_copied:
|
|
free(new_path);
|
|
err_new_path:
|
|
err_new_len:
|
|
put_inode(inode);
|
|
inode = NULL;
|
|
break;
|
|
}
|
|
|
|
/* If there's more to process, this should be a directory */
|
|
if (next_inode_name && inode->mode != DT_DIR) {
|
|
dprintf("searchdir: Expected a directory\n");
|
|
put_inode(inode);
|
|
inode = NULL;
|
|
break;
|
|
}
|
|
}
|
|
err_curdir:
|
|
free(path);
|
|
err_path:
|
|
if (!inode) {
|
|
dprintf("searchdir: Not found\n");
|
|
goto err;
|
|
}
|
|
|
|
file->inode = inode;
|
|
file->offset = 0;
|
|
|
|
return file_to_handle(file);
|
|
|
|
err:
|
|
dprintf("serachdir: error seraching file %s\n", name);
|
|
_close_file(file);
|
|
err_no_close:
|
|
return -1;
|
|
}
|
|
|
|
__export int open_file(const char *name, int flags, struct com32_filedata *filedata)
|
|
{
|
|
int rv;
|
|
struct file *file;
|
|
char mangled_name[FILENAME_MAX];
|
|
|
|
dprintf("open_file %s\n", name);
|
|
|
|
mangle_name(mangled_name, name);
|
|
rv = searchdir(mangled_name, flags);
|
|
|
|
if (rv < 0)
|
|
return rv;
|
|
|
|
file = handle_to_file(rv);
|
|
|
|
if (file->inode->mode != DT_REG) {
|
|
_close_file(file);
|
|
return -1;
|
|
}
|
|
|
|
filedata->size = file->inode->size;
|
|
filedata->blocklg2 = SECTOR_SHIFT(file->fs);
|
|
filedata->handle = rv;
|
|
|
|
return rv;
|
|
}
|
|
|
|
__export void close_file(uint16_t handle)
|
|
{
|
|
struct file *file;
|
|
|
|
if (handle) {
|
|
file = handle_to_file(handle);
|
|
_close_file(file);
|
|
}
|
|
}
|
|
|
|
__export char *fs_uuid(void)
|
|
{
|
|
if (!this_fs || !this_fs->fs_ops || !this_fs->fs_ops->fs_uuid)
|
|
return NULL;
|
|
return this_fs->fs_ops->fs_uuid(this_fs);
|
|
}
|
|
|
|
/*
|
|
* it will do:
|
|
* initialize the memory management function;
|
|
* set up the vfs fs structure;
|
|
* initialize the device structure;
|
|
* invoke the fs-specific init function;
|
|
* initialize the cache if we need one;
|
|
* finally, get the current inode for relative path looking.
|
|
*
|
|
* ops is a ptr list for several fs_ops
|
|
*/
|
|
__bss16 uint16_t SectorSize, SectorShift;
|
|
|
|
void fs_init(const struct fs_ops **ops, void *priv)
|
|
{
|
|
static struct fs_info fs; /* The actual filesystem buffer */
|
|
int blk_shift = -1;
|
|
struct device *dev = NULL;
|
|
|
|
/* Default name for the root directory */
|
|
fs.cwd_name[0] = '/';
|
|
|
|
while ((blk_shift < 0) && *ops) {
|
|
/* set up the fs stucture */
|
|
fs.fs_ops = *ops;
|
|
|
|
/*
|
|
* This boldly assumes that we don't mix FS_NODEV filesystems
|
|
* with FS_DEV filesystems...
|
|
*/
|
|
if (fs.fs_ops->fs_flags & FS_NODEV) {
|
|
fs.fs_dev = NULL;
|
|
} else {
|
|
if (!dev)
|
|
dev = device_init(priv);
|
|
fs.fs_dev = dev;
|
|
}
|
|
/* invoke the fs-specific init code */
|
|
blk_shift = fs.fs_ops->fs_init(&fs);
|
|
ops++;
|
|
}
|
|
if (blk_shift < 0) {
|
|
printf("No valid file system found!\n");
|
|
while (1)
|
|
;
|
|
}
|
|
this_fs = &fs;
|
|
|
|
/* initialize the cache only if it wasn't already initialized
|
|
* by the fs driver */
|
|
if (fs.fs_dev && fs.fs_dev->cache_data && !fs.fs_dev->cache_init)
|
|
cache_init(fs.fs_dev, blk_shift);
|
|
|
|
/* start out in the root directory */
|
|
if (fs.fs_ops->iget_root) {
|
|
fs.root = fs.fs_ops->iget_root(&fs);
|
|
fs.cwd = get_inode(fs.root);
|
|
dprintf("init: root inode %p, cwd inode %p\n", fs.root, fs.cwd);
|
|
}
|
|
|
|
if (fs.fs_ops->chdir_start) {
|
|
if (fs.fs_ops->chdir_start() < 0)
|
|
printf("Failed to chdir to start directory\n");
|
|
}
|
|
|
|
SectorShift = fs.sector_shift;
|
|
SectorSize = fs.sector_size;
|
|
|
|
/* Add FSUUID=... string to cmdline */
|
|
sysappend_set_fs_uuid();
|
|
|
|
}
|